Skip to content

feat: share pubky keys with pubky ring - #1329

Open
Jasonvdb wants to merge 27 commits into
masterfrom
feat/shared-pubky-keys
Open

Jasonvdb wants to merge 27 commits into
masterfrom
feat/shared-pubky-keys

Conversation

@Jasonvdb

@Jasonvdb Jasonvdb commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

This PR:

  1. Shares Bitkit's pubky with Pubky Ring through a read-only content provider
  2. Lets the user pick one of their Pubky Ring pubkys on Pubky Choice
  3. Clears a picked pubky when Pubky Ring deletes it
  4. Removes the old deep-link flow that asked Pubky Ring to sign for Bitkit

Description

  • Adds a read-only content provider so Pubky Ring can use the pubky Bitkit created; only apps signed with Bitkit's certificate can read it.
  • Only reads from a Pubky Ring app whose signing certificate matches, so a look-alike app is never trusted.
  • Lists the user's Pubky Ring pubkys on Pubky Choice; "Create profile with Bitkit" only shows when there are none.
  • Uses a picked Pubky Ring pubky by reading its key only when signing, so Bitkit never stores or backs up a key it does not own.
  • Reopens Create Profile after a relaunch when the picked pubky has no profile yet, the same as iOS.
  • Shows an error toast when a picked pubky's follows fail to load, then continues to Pay Contacts, so a failed import isn't mistaken for having no follows.
  • Re-signs in with the picked pubky's key from Pubky Ring when its saved session is rejected at launch, the same as iOS. It only signs in, never signs up, and keeps the session if Pubky Ring can't be read.
  • Clears a leftover pick (from backing out or a crash mid-pick) when a signup link creates Bitkit's own pubky, so the new pubky keeps its key and is backed up.
  • Asks the user to use Pubky Ring, instead of creating an identity, when Bitkit can't read a picked pubky's key.
  • Checks the picked pubky at launch and whenever Bitkit returns to the foreground. When Pubky Ring has deleted it, Bitkit clears it with a toast and returns to Pubky Choice; a failed read, or Pubky Ring being unavailable, does not count as deleted.
  • Removes the deep-link relay flow, which is no longer needed now that the apps read each other's keys directly.

Release gate

Out of Scope

  • Remote cleanup: when Pubky Ring deletes a picked pubky, Bitkit only clears local state.
  • Backups: a picked Pubky Ring pubky is never backed up, so it has to be picked again after a restore.

Reviewer Notes

  • Who can reach this: the profile, Pubky Choice and Paykit screens are all behind the experimental Paykit UI toggle, which is off by default and only reachable from the hidden Dev Settings. Bitkit only has a pubky key after going through those screens, so for regular users of released builds nothing changes: Bitkit publishes no key and Pubky Ring shows no Bitkit pubky. The notes below only affect people who turned that toggle on.
  • Paykit migration: Paykit is still under active development in Bitkit, so this PR doesn't handle Paykit-specific migration. Existing Paykit sessions and links are left as they are, and links made with a picked Pubky Ring pubky stop working if Pubky Ring deletes it. This will be handled once Paykit settles.
  • Old relay backups: backups from the removed relay flow (session kind externalSession, written since v2.3.0 behind the same toggle) still decode, but restore without a Pubky identity. They only held a homeserver session, never the key: the key stays in Pubky Ring and can be picked again on Pubky Choice with a Pubky Ring build that includes feat: share pubky keys with Bitkit pubky/pubky-ring#377. Covered by restoreSessionBackupState should restore no identity for legacy external session backups in PubkyRepoTest.kt.
  • Existing profiles: when the profile lookup fails for a signed-in pubky, or for one whose key Bitkit already stores, Create Profile doesn't publish until the lookup succeeds, so a network error can't replace an existing profile with an empty one. A brand-new key stays lenient: with no PKARR record yet, its lookup fails the same way a network error does.
  • Signing up a picked pubky: sign-up rewrites a pubky's homeserver record, so after a failed sign-in Bitkit signs a picked Pubky Ring pubky up on Homegate only when the network confirms it has no PKARR record. Any other failure shows the sign-in error. Known limit: pkarr gives up on a relay after 2 s, but relays take about 3–4 s to answer 404 for a missing key, so confirming "no record" relies on the DHT. Where the DHT is unreachable (as on our QA emulator), a never-signed-up pubky can't be picked until it is signed up in Pubky Ring. A longer relay timeout in the SDK would close this; that is a follow-up.
  • Follows import failure: the toast has no retry, so the import is skipped for that pick. iOS continues to Pay Contacts without a toast, as it already did before this PR for every sign-in; adding the toast there is a follow-up.
  • Pubky Ring unavailable: if Pubky Ring is uninstalled, disabled or archived, Bitkit keeps the picked pubky and only clears it once a trusted Pubky Ring listing leaves it out, the same as iOS.
  • Trust boundary: the signing certificate. Every Bitkit Android app is signed with the release certificate, and so is every Pubky Ring Android build from feat: share pubky keys with Bitkit pubky/pubky-ring#377 on. Any app signed with it can read the shared key; an app signed with any other certificate is rejected (manual test 13). The client also reads only from app.pubkyring, and only when its certificate matches. Debug and e2e builds of both apps share the committed debug keystore, so any locally built app can read a dev-flavor wallet's pubky key; release builds aren't affected.

Design

Pubky Choice follows the "Profile use existing" and "Profile Create" mockups; the matching Figma frames aren't confirmed, so no link is added.

Preview

Pubky Choice listing Pubky Ring pubkys

Pubky Choice with no Ring pubkys (Create only)

Profile using the adopted Ring pubky

Back on Pubky Choice after Ring removed the adopted pubky

pubkyauth approval with the adopted pubky

QA Notes

Journeys

N/A — no journeys yet; see Manual Tests. Most flows need Pubky Ring running alongside Bitkit, which the Capabilities table doesn't list; the few that don't are marked "no journey yet".

Manual Tests

  • 1. Ring with pubky A (signed up) and pubky B (never signed up) → Bitkit Profile → Pubky Choice → both listed with truncated-key caption, lock icon and avatar, and no Create card — Pubky Ring alongside Bitkit not in Capabilities
  • 2. Pubky Choice → tap A → Create Profile pre-filled with A's pubky, the profile saves, and the keychain holds no local secret, only the Ring source reference — Pubky Ring alongside Bitkit not in Capabilities
  • 3. Pubky Choice → tap B (never signed up) → sign-in fails, Bitkit confirms B has no record, signs it up through Homegate and continues into Create Profile; needs a reachable DHT, see Reviewer Notes — Pubky Ring alongside Bitkit not in Capabilities
  • 4. Network off → Pubky Choice → tap a signed-up Ring pubky → the error toast shows, Bitkit doesn't sign up and stays on Pubky Choice with no source reference stored; with the network back, the pubky is picked through sign-in — Pubky Ring alongside Bitkit and a network cut not in Capabilities
  • 5. Force-stop both apps → relaunch Bitkit → Profile still shows the picked pubky, the session is restored with no local secret, and a pubkyauth grant is approved with the Ring key read just in time — Pubky Ring alongside Bitkit not in Capabilities
  • 6. Pubky Choice → tap a Ring pubky that already has a profile → Bitkit continues to Contact Import when it has follows, otherwise Pay Contacts, then Profile — Pubky Ring alongside Bitkit not in Capabilities
  • 7. Pick a Ring pubky with no profile → force-stop Bitkit on Create Profile → relaunch → Create Profile reopens once the session is restored; after saving, a relaunch stays on Home — Pubky Ring alongside Bitkit not in Capabilities
  • 8. Ring → delete the picked pubky → relaunch Bitkit → the identity is cleared and Bitkit returns to Pubky Choice, which still lists the remaining Ring pubky — Pubky Ring alongside Bitkit not in Capabilities
  • 9. Ring → delete the picked pubky while Bitkit is on Create Profile or Home → switch back to Bitkit without relaunching → the toast shows, Pubky Choice opens without that pubky, and Back goes to Home rather than the old Create Profile — Pubky Ring alongside Bitkit not in Capabilities
  • 10. Disable Pubky Ring → foreground and cold-start Bitkit → no toast and the profile stays; after re-enabling Ring it is still there — Pubky Ring alongside Bitkit not in Capabilities
  • 11. Pick a Ring pubky → disable Pubky Ring → scan a pubkyauth request → the "Use Pubky Ring" toast shows instead of "Pubky Identity Required" — Pubky Ring alongside Bitkit not in Capabilities
  • 12. Pubky Ring with 14 pubkys → Pubky Choice → the list scrolls and the last pubky can be picked — Pubky Ring alongside Bitkit not in Capabilities
  • 13. regression: wrong certificate → re-sign the Ring APK with a throwaway keystore and install it → neither signature permission is granted, Ring shows no Bitkit card, and Bitkit lists no Ring pubkys — Pubky Ring alongside Bitkit not in Capabilities
  • 14. regression: install order → install Bitkit first, then Ring → both signature permissions show as granted and Bitkit lists the Ring pubky, so the grant is retro-applied — Pubky Ring alongside Bitkit not in Capabilities
  • 15. Pubky Choice → Create profile with Bitkit → the key is Bitkit-owned (local secret stored, no source reference) and Ring offers it as the dashed "pubky #N (Bitkit)" card — Pubky Ring alongside Bitkit not in Capabilities
  • 16. Ring → Use in Pubky Ring → in-sheet confirm → the card becomes a solid pubky with the Bitkit pill and Authorize, the detail hides Backup and the homeserver fields, and rename works — Pubky Ring alongside Bitkit not in Capabilities
  • 17. Ring → authorize a pubkyauth request with the Bitkit pubky → Ring signs the grant with the secret read from Bitkit's provider and reports success — Pubky Ring alongside Bitkit not in Capabilities
  • 18. Ring → Bitkit pubky detail → Delete → the confirm copy says it remains in Bitkit, the dashed offer returns immediately, and Bitkit keeps its identity and secret — Pubky Ring alongside Bitkit not in Capabilities
  • 19. Bitkit Profile → Edit → Delete Profile → foreground Ring → Ring shows the removal toast and drops the adopted card, leaving Ring-owned pubkys untouched — Pubky Ring alongside Bitkit not in Capabilities
  • 20. Ring Settings → Wipe Pubky Ring → relaunch Bitkit → Pubky Choice shows only the Create card, and Bitkit's own provider still serves a newly created Bitkit profile back to Ring — Pubky Ring alongside Bitkit not in Capabilities
  • 21. No Pubky Ring pubkys → Bitkit Profile → Pubky Choice → only the "Create profile with Bitkit" card, captioned "New pubky"; Create → the profile publishes, then Pay Contacts, then Profile — no journey yet
  • 22. regression: pubkyauth deep link → Bitkit approval sheet → requester, capabilities and active identity render and Authorize succeeds, with no relay UI left anywhere in the flow — no journey yet

Automated Checks

  • added SharedPubkyContractTest.kt — a shared secret must be valid hex that derives the requested pubky
  • added SharedPubkyProviderTest.kt — an untrusted caller is rejected, and the credential row is served only for the stored pubky
  • added SharedPubkyClientTest.kt — a trusted Pubky Ring is listed, and a missing Ring or one with another certificate fails without its provider being queried
  • updated PubkyRepoTest.kt — adoption, the credential mismatch rejection, sign-up only when the pubky has no record, the pending profile setup, the launch and foreground re-checks, the null backup snapshot for a picked pubky, a signup link clearing a leftover Ring reference, and re-signing in with the Ring key when a saved session is rejected
  • updated PaykitSdkServiceTest.kt — a picked pubky's secret is read just in time from the Pubky Ring provider
  • updated PubkyChoiceViewModelTest.kt — Ring pubkys are listed with their profile, and picking one continues to Create Profile, Contact Import or Pay Contacts, with an error toast when adoption or the follows lookup fails
  • updated CreateProfileViewModelTest.kt — a failed lookup blocks saving for a signed-in or stored pubky, while a new pubky can still be created
  • updated AppViewModelSendFlowTest.kt — a pubkyauth request shows the "Use Pubky Ring" toast when a picked pubky's key can't be read
  • removed PubkyRingAuthCallbackTest.kt — the relay callback it covered no longer exists
  • removed PubkyRepoTest.kt — relay auth start, completion, cancel and callback nonce cases, and the external-session backup cases, because the relay flow and that session kind no longer exist
  • removed PubkyChoiceViewModelTest.kt — the Ring launch and relay approval cases, because Pubky Choice no longer opens Pubky Ring
  • removed AppViewModelSendFlowTest.kt — the Pubky Ring callback deep link case, because the callback no longer exists

@Jasonvdb
Jasonvdb marked this pull request as ready for review September 23, 2026 19:14
@Jasonvdb
Jasonvdb requested review from ben-kaufman, jvsena42 and ovitrif and removed request for ben-kaufman and jvsena42 September 23, 2026 19:14
@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Regtest APK

Built from 3ff19f5 (run).

Download bitkit-dev-debug universal APK (expires in 30 days).

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: ⛔️ Request Changes


Review: diff 25 files.
Counterpart synonymdev/bitkit-ios#774 diverges — see the parity finding.

Findings:
8 inline (2 blocking)

Audit:
Awaits QA.

Coverage:
QA: journeys and manual tests await all reviewers to approve, author can run it now via comment: @ovi-reviewer test


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner)

Comment thread app/src/main/java/to/bitkit/data/sharedpubky/SharedPubkyClient.kt Outdated
Comment thread app/src/main/java/to/bitkit/repositories/PubkyRepo.kt
Comment thread app/src/main/java/to/bitkit/data/sharedpubky/SharedPubkyClient.kt Outdated
Comment thread app/src/main/java/to/bitkit/data/sharedpubky/SharedPubkyContract.kt Outdated
Comment thread app/src/main/java/to/bitkit/ui/screens/profile/PubkyChoiceViewModel.kt Outdated
Comment thread app/src/main/java/to/bitkit/ui/screens/profile/CreateProfileViewModel.kt Outdated
Comment thread app/src/main/java/to/bitkit/data/sharedpubky/SharedPubkyContract.kt
Logger.warn("Ignoring Pubky Ring auth error callback with missing or invalid nonce", context = TAG)
PubkyRingAuthCallbackHandlingResult.Ignored
}
_publicKey.update { publicKey.ensurePubkyPrefix() }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user adopts a Ring pubky with no profile and closes Bitkit before saving, the next launch restores the session but opens the empty Profile screen. Retry only reloads the missing profile, so they have to disconnect and pick the key again. Could we persist the pending profile setup state here so the existing resume flow takes them back to Create Profile?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6dca12c: adoptRingIdentity now sets the pending profile setup flag when the picked pubky has no profile, and clears it when it has one, the same as iOS, so the existing resume flow reopens Create Profile after a relaunch. Saving, signing out or losing the Ring source clears it. Tests added in PubkyRepoTest.kt.

modifier = Modifier.testTag("PubkyChoiceImport")
)

else -> uiState.identities.forEachIndexed { index, identity ->

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Ring identities are all rendered in a fixed, non-scrollable Column. When there are more cards than fit on screen, the later pubkys cannot be reached or selected. Could we make this list scrollable so every identity remains accessible?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 42bdc1b: the Pubky Choice list now scrolls, so every Ring pubky can be reached.

ovi-reviewer[bot]

This comment was marked as resolved.

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two gated findings and one copy regression, all inline, all checked by a second pass. Nothing blocking: every profile route is behind PaykitRouteGuard and the flag defaults off.

Checked and clean:

  • Provider surface. Exported but double-gated: a signature-level readPermission plus a per-call Binder.getCallingUid() check that requires every package of the uid to match signatures, so shell and unknown uids fail closed. insert/update/delete throw, call and openFile are the defaults, and getType returns only MIME.
  • The credential row is emitted only when the requested pubky matches the key derived from the stored secret.
  • Squatting. The package manager refuses a second app declaring the same authority or the same permission under another signer, and the client pins the Ring package and checks signatures before any query.
  • What crosses. Ring's secret is read just in time, never written to PUBKY_SECRET_KEY, never in a backup payload (sessionSecret is gone from PubkySessionBackupV1), and only redacted pubkys reach the logs.
  • Identity binding. readCredential requires a single row, a matching row pubky and a matching derived key; adoption re-derives and requires equality; managedSecretKeyFor compares against the active public key before signing.
  • Lifecycle. Keychain.wipe() clears the reference too, sign-out and SDK teardown delete it, and Ring being uninstalled fails the listing rather than emptying it.
  • Migration from the v2.5.0 relay flow: those sessions still import until expiry, and legacy externalSession backups decode and restore to no identity.

Two caveats worth knowing, not findings: debug and e2e builds of both apps share the committed debug keystore, so a locally built app can read a dev-flavor wallet's pubky secret; and the seed-derived pubky secret cannot be rotated, so a leak on Ring's side is permanent for that wallet.


throw PubkyAuthAttemptInactive()
}
keychain.upsertString(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Back during adoption orphans the reference, and a later signup link then throws away the seed-derived secret. MEDIUM, gated behind the Paykit opt-in.

The reference is upserted here, before the network signIn, and the only cleanup is the .onFailure branch. runSuspendCatching rethrows CancellationException, so on cancel there is no Result and that branch never runs. Back is live while the adoption spins — the screen only swaps the list for a spinner, there is no BackHandler, and popBackStack destroys the entry-scoped ViewModel and cancels its scope. Process death mid-adoption leaves the same orphan, and a restart keeps it: checkAdoptedSourcePresent returns early because Ring still lists the pubky.

Later a pubkyauth:// signup link finds hasIdentity() false and proceeds. approveSignupAuth never clears the reference (unlike createLocalIdentitySession), so persistSessionAccess sees an adopted pubky and deletes PUBKY_SECRET_KEY. After that managedSecretKeyFor returns null, so every auth request is refused with "Pubky Identity Required", snapshotSessionBackupState returns null so the identity is never backed up, and the next cold start hands the SDK Ring's credential for a different pubky. If the user later deletes that Ring key, they are told an identity that never came from Ring is "no longer available in Pubky Ring".

Fix: delete SHARED_PUBKY_SOURCE before registerIdentity in approveSignupAuth, mirroring createLocalIdentitySession — that alone closes the consequence. Optionally also clear the reference under NonCancellable on cancellation here, or run the adoption on appScope.

iOS is not exposed to this: synonymdev/bitkit-ios#774 runs adoption in an unstructured Task and clears the reference on cancellation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 0dc4e77: approveSignupAuth in PubkyRepo.kt now deletes SHARED_PUBKY_SOURCE before registering, like createLocalIdentitySession, with tests in PubkyRepoTest.kt. I left adoption cancellable: clearing the reference on cancel would let an abandoned pick that fails later delete the reference of a retried pick.

}

private suspend fun activeSecretKeyHex(): String? {
val publicKey = _publicKey.value ?: return keychain.loadString(Keychain.Key.PUBKY_SECRET_KEY.name)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An adopted identity whose stored session is rejected is never re-signed in, although the credential is right there. LOW-MEDIUM, gated.

initialize() reads only PAYKIT_SESSION and PUBKY_SECRET_KEY, and for an adopted identity the latter is always null by design. So when importSession fails, resolveSignedInSession gets a null secret and returns RestorationFailed without ever calling sharedPubkyClient.readCredential(adoptedPubky) — which managedSecretKeyFor already knows how to do. The session is kept, so every cold start repeats the "session expired" toast with no identity, while hasIdentity() stays true and a signup link is refused as already signed in. activeSecretKeyHex() short-circuits on the null _publicKey, so refreshSessionIfPossible cannot help either.

The exit is re-picking the key on Pubky Choice, which the toast does point at, so this is recoverable rather than a lockout. It is mainly a parity gap: synonymdev/bitkit-ios#774 passes the adopted secret into its re-sign-in and recovers.

Fix: fall back to the Ring credential for storedSecretKeyHex in initialize(), and use the same fallback in activeSecretKeyHex() when _publicKey is null.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 420fef9: when the saved session is rejected and there's no own key, resolveSessionInitialization in PubkyRepo.kt now re-signs in with the Pubky Ring credential. It only signs in, and keeps the session as before when Pubky Ring can't be read; tests in PubkyRepoTest.kt. I left activeSecretKeyHex() as is, since managedSecretKeyFor already reads the Ring key once _publicKey is set.

if (isSignup && rejectPubkySignupForExistingIdentity()) return

if (!isSignup && pubkyRepo.publicKey.value == null) {
if (!isSignup && (pubkyRepo.publicKey.value == null || !pubkyRepo.hasSecretKey())) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The !hasSecretKey() case was folded into the publicKey == null branch when profile__auth_approval_ring_only was removed, so an adopted identity whose key is momentarily unreadable — Ring's provider unavailable, or the orphan state above — now reads "Pubky Identity Required / Create a Pubky identity in your profile" although the profile is right there. Worth keeping two branches, with a "key unavailable, open Pubky Ring" message for the second.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 94940c1: handlePubkyAuth in AppViewModel.kt has two branches again. With a public key but no readable key it now shows "Use Pubky Ring" with "Bitkit can't read the key for this pubky from Pubky Ring. Open Pubky Ring to approve this request." iOS had the same gap and gets the same check in 55abbe3a (#774).

@jvsena42 jvsena42 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delta since aa6db6384 (0dc4e7701, 420fef976, 94940c13c): no findings. All three are addressed.

  • approveSignupAuth deletes SHARED_PUBKY_SOURCE before registerIdentity, so an orphaned reference can no longer make persistSessionAccess drop the seed-derived secret.
  • The re-sign-in falls back to adoptedSecretKeyHex(), which reads the Ring credential through the same trusted client, so an adopted identity with a rejected session recovers instead of looping. The failure to read is logged and returns null rather than throwing.
  • The auth toast is split again: a missing identity and an unreadable key now say different things, and the new copy points at Pubky Ring.

Small leftover, not worth a change on its own: activeSecretKeyHex() still returns only PUBKY_SECRET_KEY when _publicKey is null, so refreshSessionIfPossible() cannot help an adopted identity before initialize() has run. The startup path is the one that mattered and it is fixed.

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advice: ✅ Approve


Reaudit: diff 10 files.
No new findings; the rest is in the review.

Coverage:
QA: waits for the other reviewers' approval, or @ovi-reviewer test


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)

@ovi-reviewer
ovi-reviewer Bot dismissed their stale review September 24, 2026 16:43

addressed - reaudit confirmed

@ovitrif ovitrif added this to the 2.6.0 milestone Sep 24, 2026

@ovi-reviewer ovi-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Advice: ✅ Approve


Reaudit: diff 2 files.
No new findings; the rest is in the review.

Coverage:
QA: waits for the other reviewers' approval, or @ovi-reviewer test


Reviewed by gpt-6-sol-xhigh via gh-pr-review-loop skill
Commands: @ovi-reviewer review · test · retest · audit (author or owner) · wrong <why> (owner)

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants