Joystick consent: ask page, consent_refused, receipts, signed-in grants page with revoke #5
Loading…
Reference in a new issue
No description provided.
Delete branch "heimdall/joystick-consent-flow-design"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Builds the consent idea in
docs/joystick-consent-flow.md(rulings in §4). Updated after the review of head48207268.What it does
callbackMint === custody_only) stops at a Heimdall ask page before the provider redirect. The ask sets its own cookie; only Continue/No accept it. The launch binding is set only when Continue claims the launch, and carries the exact text shown to the callback.consent_refused(by: viewer|provider) for a decline or a provideraccess_denied.consent_receipts(append-only, SELECT+INSERT): grant rows hold scopes asked/granted, the wording text shown and its sha-256; a revocation is a second row, unique per grant.GET /v1/me/grants,POST /v1/me/grants/link,POST /v1/me/grants/:id/revoke: list every grant and receipt, one-click revoke strips the credential so the nextmanaged-credentials/resolveanswers 410. Page links keep their original 10-minute expiry.Review fixes in this push
revokes_receipt_id; revoke reports only what it wrote and audits once.Contract change (decide before deploy):
error=access_deniedis nowconsent_refusedfor every provider. Visible to apps three ways: the callback JSON/HTML result, thebackend_callbackpayload (error: "consent_refused",by), and the auth attempt'sdenialCode, whichcompleteAuthin the private command plane returns as thedeniederror to Bifrost, Repixelizer and Ghostlight. Pinned bytests/oauth-state-binding.test.tsfor Bifrost and Repixelizer (Ghostlight uses the same attempt path; it has no test of its own). Other provider errors stayprovider_error.Verification (8 GB
ulimit -v, one vitest worker): consent, drift, store and state-binding files green (66+ tests); whole suite 520 pass / 19 fail. The 19 areentrypoint-secrets(fails identically onorigin/main); 12 further files cannot load because the vendored cultnet/cultcache/cultmesh packages are not built here.Mutants, each alone with
timeout 300: first round 18/18 killed; review-fix round F1a ask sets binding, F1b no ask cookie needed, F1c shown text dropped, F2 fresh link expiry, F3 denial code reverts, F4a text rebuilt at callback, F4b labels omitted, F4c custody without text, F5a second revocation allowed, F5b revoke over-reports, F5c no unique index: 11/11 killed (F2 and F4c first survived; fixed the tests and the code, then killed).Fix run on head
f7a38a7(rulings from Eureka's review of5ce8f1c)inflateRawSyncincookieConsentTextis capped at 16 KB (maxOutputLength); a bomb cookie is refused fast.ctanswers 400consent_text_missingbefore the state is claimed.requestedScopescapped at 32 items of 512 chars; over-cap isrequested_scopes_too_largeat the ask. An ask the cookies cannot carry (wording > 16 KB or cookie > 3800 chars) is refusedconsent_ask_too_large.consent_receiptshasUNIQUE(id, account_id)and a composite FK(revokes_receipt_id, account_id)to it, so a revocation names a receipt of its own account;checkSchemacounts 17 keys; the memory store refuses the same.Addendum: the per-index predicate test now checks both partial indexes;
exerciseStorerevokes a grant twice; a live case boots withconsent_receiptsdropped;scripts/test-live-pg.shdoes its prerequisites (submodule, vendor install and dist builds, pnpm install).Mutants, each alone under the 8 GB cap and 300 s: inflate uncapped / cap enlarged, text-missing check off, scope count and char caps (off-by-one,
>=, check off), memory-store account check dropped, FK dropped, FK without account, UNIQUE(id, account_id) dropped: all killed. Not run: mutants of the two live cases (no Postgres here). Full suite 524 pass / 19 fail, the same environmental 19 as before.Gaps
initdbis not installed in this body, soscripts/test-live-pg.shwas not run. Theconsent_receiptsSQL (includingON CONFLICT DO NOTHINGagainst the partial unique index and the read-backSELECT) has only run against the fake pool. Run it before deploy. The schema is derived intocheckSchema(112 columns, 17 keys); withapplySchemaOnStartupoff, the table must exist before boot or the start refuses.systemd-runscopes unavailable to this uid; mutants ran underulimitandtimeout, notMemoryMax.FOREIGN KEY (revokes_receipt_id, account_id)toUNIQUE(id, account_id)) and the new live cases (second revocation takespostgres.tsread-back; boot withconsent_receiptsmissing refusesSCHEMA_MISSING) have not run on Postgres. Eureka runsscripts/test-live-pg.shon the new head.Not merged, not deployed.
🤖 Generated with Claude Code
WIP: Draft Joystick consent flow (design + skipped test sketch)to Joystick consent: ask page, consent_refused, receipts, signed-in grants page with revoke