Sign in with Bitkit (grant QR) - #101
Merged
BitcoinErrorLog merged 8 commits intoSep 24, 2026
Merged
Conversation
Bitkit accepts only pubkyauth://signin_grant. The sign-in page now offers a Bitkit grant QR (GrantAuthFlow.startDelegated, client id shop.pubky.app, the Shop capabilities) next to the unchanged Ring cookie QR. A grant session is never exported: it is saved to BrowserSessionStore at sign-in completion, inside the auth finalization lock and only if the cross-tab auth epoch is unchanged since the QR started, and reload restores it from its record id. Sign-out, warm or cold, signs the session out, then bumps the epoch and clears BrowserSessionStore under the lock, and tells other tabs to drop a live grant session. Grant sessions carry no AuthToken, so the classic Pubky Ring approvals (purchases, step-up, inventory, messaging, manual Paykit claim) show a refusal instead of a QR. Lock Server connect stays open: its page decides which QR it offers.
Grant URL arguments, delegation-gated Bitkit switch, BrowserSessionStore restore without save, expiry and transient restore outcomes, consumer mode skipping the bridge for a grant record, and the refusal on the classic purchase, step-up, inventory and messaging approvals.
…t sessions with a record id
BrowserSessionStore remove() and clearAll() failures were logged and swallowed after the record pointer had already been cleared, so logout could succeed while a delegated key stayed in IndexedDB with no pointer left to retry the delete. Removal now reads the store back and rejects while the record (or any record, for clearAll) is still listed, with two retries in the application layer. Every path removes the key before it clears the pointer: warm and cold logout run the removal after the homeserver sign-out and before local cleanup, and fail without broadcasting when it cannot finish; an expired-grant restore keeps its record and reports deferred; a second tab removes its own record before it drops its session. The unused HomeserverService.currentSessionIsGrant is removed.
BitcoinErrorLog
changed the base branch from
cursor/sdk-011-ring-a98e
to
release/shop-v0.6.8
September 23, 2026 17:39
…/bitkit-signin-grant-a98e # Conflicts: # src/components/organisms/Marketplace/MarketplaceGetPaidSettings.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bitkit sign-in through a grant QR (PR 2 of the Bitkit grant-auth wave, #48 / #49)
Targets
release/shop-v0.6.8. PR 1 (#100) merged there as3c1ac747, and release is merged into this branch atfe3bc3d5. Bitkit 2.5+ accepts onlypubkyauth://signin_grant. The sign-in page now offers a Bitkit grant QR next to the unchanged Pubky Ring cookie QR. A Bitkit identity can sign in, survive reload, and sign out. Commerce stays off for a grant sign-in until PR 3: there is no marketplace session, and the classic Ring approvals show refusal copy.Design:
/Volumes/t7/vibes-dev/.evidence/bitkit-signin-grant-48/REPORT.mdrev 2, rows R2.1–R2.11 (design review round 2: SHIP_DESIGN).Changes
GrantAuthFlow.isDelegationAvailable. It opens a Bitkit QR fromGrantAuthFlow.startDelegated(CAPABILITIES, signin, { clientId: 'shop.pubky.app', relay })and offers "Use Pubky Ring instead", which mints a fresh Ring QR. The Bitkit QR has no Ring logo.sessionExportstays null). At sign-in completion it is saved toBrowserSessionStoreinside the auth finalization lock, and only if the cross-tabauthEpochstill equals the value read when the QR started. The auth store persistsgrantSessionRecordId.session.signout()runs first. Then, under the lock, the epoch is bumped andBrowserSessionStore.clearAll()runs. Only after the store reads back empty is local state (including the record pointer) cleared, and then apubky-auth-v1broadcast goes out. On that broadcast, a tab holding a live grant session signs it out, removes its own record, and drops local state. Cookie sessions ignore it. A failed cold restore still clears.removeGrantSessionandclearGrantSessionsread the store back withlist()and reject while the record (or, forclearAll, any record) is still stored; the application retries twice. On a final failure, nothing clears the pointer:/logoutshows "We couldn't sign you out yet" with Retry;deferred;A browser without IndexedDB persistence (
isAvailable()false) holds no records, so cookie sign-out there is not blocked.GrantSessionRefusalto a grant session and never start their flow. The manual Paykit claim this PR also guarded is gone: release removed it in41287fba("drop the dead claim control"), so the merge takes release'sMarketplaceGetPaidSettings.tsxunchanged. Lock Server connect (Step 1) stays open; its page decides which QR it offers (PR 4 adds a grant QR there).Contract rows → tests
SignIn.test.tsx›bitkit qr hidden without delegationhomeserver.test.ts›bitkit qr is signin_grant with shop caps and cid;SignIn.test.tsx›switches to a Bitkit grant QR and back to a fresh Ring QR; Chromium check 1–2POST /auth/grant/session, not the cookie/session)auth.test.ts(controller) ›grant sign-in persists record id not export,save aborts after a sign-out since QR start;auth.store.test.ts›persists the grant record id and never exports a grant sessionauth.test.ts(application) ›reload restores grant session from store,restore never calls save,keeps the record on a transient restore failure; Chromium check 6auth.test.ts(application) ›reload after grant expiry shows signed-out and drops the record,expiry cleanup rejects instead of signing out when the grant key cannot be removed,expiry cleanup signs out once a retried removal succeeds;auth.test.ts(controller) ›expiry cleanup keeps the record pointer when its grant key cannot be removedauth.test.ts(controller) ›signout calls signout then clearAll under lock,warm logout removes the grant key before it clears the record pointer,warm logout fails and keeps the record pointer when the grant key cannot be removed,logout tells other tabs to let go;homeserver.test.ts›grant key removal(7 tests: read-back afterremove/clearAll, unreadable store, no-IndexedDB no-op); Chromium check 8auth.test.ts(controller) ›cold logout calls signout before clearAll,cold logout still clears grant keys when the restore fails,cold logout fails and keeps the record pointer when the grant key cannot be removedauth.test.ts(controller) ›second tab cannot save after sign-out,second tab drops live grant session on broadcast,cross-tab finalization removes this tab record before it clears the pointer,cross-tab finalization keeps the tab signed in when its grant key cannot be removed,a cookie session ignores the cross-tab broadcast; Chromium checks 9–11auth.store.test.ts›bridge never carries grant session: a grant session leaves no export to hand off;auth.test.ts(application) ›consumer mode ignores grant record id: no bridge, no cookie restoreMarketplaceSessionConnectDialog.test.tsx›grant session sees refusal not classic qr,a cookie session still starts the classic approval when opened;MarketplaceReauthDialog.test.tsx›grant session sees refusal not classic qr (step-up);MarketplaceGrantSessionRefusal.test.tsx› inventory, messaging,cookie session keeps messaging resume path (the paykit-wasm flow still starts)Fail-on-revert calibration
pr2/mutations.pyapplies each mutation and runs the named test file. It then restores the source byte-for-byte (sha256 checked) and ends with a clean run. Log:pr2/mutations.log. All 13 were caught; the clean run passes 271/271.removeGrantSessionlogs instead of rejecting when the record is still storedremove rejects while the record is still stored, even when the SDK call resolvedclearGrantSessionslogs instead of rejecting when records remainclearAll rejects while any record is still storedwarm logout removes the grant key before it clears the record pointer(and the failure test)warm logout fails and keeps the record pointer when the grant key cannot be removedcold logout fails and keeps the record pointer when the grant key cannot be removedexpiry cleanup keeps the record pointer when its grant key cannot be removedexpiry cleanup rejects instead of signing out when the grant key cannot be removedcross-tab finalization removes this tab record before it clears the pointersave aborts after a sign-out since QR start,second tab cannot save after sign-outsafeSessionExport()callssession.export()persists the grant record id and never exports a grant sessionclearAll()beforesession.signout()signout calls signout then clearAll under lock,cold logout calls signout before clearAllreload restores grant session from store,consumer mode ignores grant record idsecond tab drops live grant session on broadcastProof
next devagainst the staging homeserver and relay; staging seller test seat7oboeqnf…): 11/11 on headc0107fa7. The same run passed 11/11 onc660118b, before the fix commit that treats a missing grant record like none.signin_grantwithcaps,relay,secret,cid,cpk, the Shop capabilities, andcid=shop.pubky.app.POST /auth/grant/session(200) and no marketplace session was minted./logoutclears the record and bumps the epoch. The second tab drops its live session, stays signed out on reload, and a fresh tab is signed out.@synonymdev/pubky0.11Signer.approveAuthRequest, run after the same pre-checks Bitkit's Paykit rc55 applies (grant URL, shown caps, shown client id). That is the JS binding of the pubky 0.11PubkySigner::approve_auththat Paykit calls.pr2/chromium-pr2.log, screenshotspr2/chromium/, scriptpr2/chromium-pr2.mjs.fe3bc3d5(the same interim rule; baserelease/shop-v0.6.8@3c1ac747, the PR's 36 files): prettier/eslint/typecheck/7,078 related unit tests pass. No VRT spec is in the PR diff, so no VRT was run for the merge; CI runs the full suite. Log:pr2/gate-fe3bc3d5.log.b18fb084(interim rule while gate PR Pre-push gate, Linux-only VRT, CI path filters #97 and its VRT crash fix are open): prettier/eslint/typecheck/6,410 related unit tests for the 7 changed files pass. One listing-form test, which does not touch sign-out, failed under load (load average ~108) and passes alone, 44/44. No VRT spec renders a changed line (auth services and controller only), so no VRT was run for this commit; CI runs the full suite. Logs:pr2/gate-b18fb084.log,pr2/gate-b18fb084-listingform-rerun.log; runnerinterim-gate.sh.c0107fa7(PREPUSH_BASE= PR 1 head7724b848): prettier/eslint/typecheck/7,054 related unit tests/736 marketplace VRT pass. The marketplace VRT passed on a rerun (pr2/vrt-marketplace-rerun.log, 90/90 files) after the first run's container browser disconnected mid-suite. The non-marketplace VRT project crashes before any spec on the unchanged release head 55a084c too (pre-existing, handed to the gate owner); CI runs the full VRT. Logs:pr2/prepush.log,pr2/vrt-marketplace-rerun.log.Evidence root:
/Volumes/t7/vibes-dev/.evidence/bitkit-signin-grant-48/.Notes for review
c0107fa7. The key-removal fixb18fb084changes only the failure paths, which the unit tests and the mutation log above cover; its success path (store reads back empty) is the one the journey exercised.HomeserverService.currentSessionIsGrant()is removed. It had no caller anywhere in the workspace.GrantManager.revokeneeds a root session). The grant stays valid on the homeserver untilgrantExpiresAt. The local key is gone and cannot be saved again.BitcoinErrorLog/locks).