Skip to content

fix(custody): credential-presence gates and reported-failed version refusal - #232

Open
iceteaSA wants to merge 10 commits into
cortexkit:mainfrom
iceteaSA:fix/custody-cutover
Open

iceteaSA wants to merge 10 commits into
cortexkit:mainfrom
iceteaSA:fix/custody-cutover

Conversation

@iceteaSA

@iceteaSA iceteaSA commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #231, found by sweeping the class that PR's last commit belonged to.

The class

A vault-served account's local auth slot holds only the provider tombstone, so account.access === '' while the account is healthy and serving. Every check that used local credential bytes as a proxy for usability became wrong the moment custody went live. Before custody every account carried real local bytes, so the proxy was sound — this is a precondition change turning dead branches live, not a regression.

A read-only census over both packages examined 26 candidate gates: 6 live, 5 inert, 15 correct. Each inert verdict had to name the specific source-traced precondition that blocks the branch, or be downgraded to live-suspected.

Commit 1 — six presence gates

site was effect under custody
accounts.ts canUseCachedQuotaAfterRefreshError account.access && expires > now cached-quota recovery blocked after a transient vault failure
index.ts main profile hydration mainAccessToken && tier metadata never hydrates
index.ts fallback sidebar profile !account.access hydrated profile never reaches the sidebar
index.ts main sidebar profile mainAccessToken && mainState same for main
index.ts /claude-quota auth.type === 'oauth' && auth.access main skipped entirely — while the next line already reads mainServedAccessToken ?? auth.access
index.ts killswitch eager refresh Boolean(a.access) vault-served fallbacks excluded from the spend guard's pre-check refresh

All widen to local access || live vault binding. None admits an account with no credential anywhere. All six are reporting/refresh gates — no routing or serving eligibility rule is loosened, and the assertNotCustodyTombstone wire-refusal sites are untouched.

The killswitch one is the only non-cosmetic member: a spend guard that skips its own freshness step degrades silently and in the expensive direction. Scoped honestly — the background poller still keeps that quota current, so the exposure is "may evaluate without forcing a fresh read", not "evaluates on stale data".

Commit 2 — reported-failed version refusal

Cross-family drift review caught that liveMainVaultAccess (added in commit 1) hand-rolled its sibling resolveClaustrumAccess and dropped the sibling's stale-version guard. Reviewer probe: boot vault-served main → 401 → report_auth_failure at version N → /claude-quota still sends that bearer. It now delegates, inheriting both the guard and the warm schedule.

Implementing that surfaced a second, different defect: mainServedAccessToken was never cleared. A bearer whose 401 we had already reported to the vault stayed the preferred main token for the process lifetime, with no reconciliation path — local belief diverging from what the vault was told. It is now cleared on a successful report only; clearing on a suppressed or failed report would make local state believe the vault knows something it was never told.

/claude-quota also had to move from ?? to ||, or an empty served token short-circuits and the fix is inert on that path.

Verification

Per-hunk revert evidence, one proof per site — with six hunks in play a whole-diff revert only shows the batch is load-bearing. One site's first two attempts produced a test that passed with its own fix reverted; the implementer reported that rather than shipping it green, and the assertion moved from end-state to action:

refreshAllFallbacks receives ["killswitch-vault-fallback"]   with the fix
refreshAllFallbacks receives []                              hunk reverted alone

Commit 2's three proofs: warm callbacks 1 → 0, quota authorizations containing the dead bearer, and quota authorizations empty when the ??/|| chain is reverted.

Gates on the rebased tree: core 200, opencode 1889, pi 114, e2e 36, typecheck 0, biome 0 across 189 files.

Deliberate scope: get-before-report (re-fetch and retry on a vault-served 401 before reporting) is not in this PR. It is a larger behavioural change to a path a peer seat is currently measuring, and it deserves its own review.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Fixes custody vault-served accounts being skipped by credential-presence checks, stops reusing a main vault bearer after a 401 was reported to the vault, and tightens the Claustrum golden-pin checker so a drifted fixture fails CI.

Bug Fixes

  • Six presence gates now admit a live vault binding alongside local access; none admit an account with no credential anywhere.
  • Cached-quota admission rechecks the vault binding at failure time, so a binding that disappears mid-refresh isn't admitted afterward.
  • liveMainVaultAccess delegates to resolveClaustrumAccess, inheriting the stale-version refusal and warm schedule.
  • mainServedAccessToken clears only after a successful auth-failure report.
  • /claude-quota uses || instead of ?? so an empty served token doesn't short-circuit the fix.
  • Killswitch eager refresh now includes vault-served fallbacks, so spend protection evaluates on fresh quota.
  • Tripwire tests pin the vault-facing refresh TTL at 270 minutes and floor the config override at 240 minutes; lowering either lengthens the vault's rotation period and can trip the vault operator's stall alarm without advance notice.
  • The golden Claustrum fixture now tracks cortexkit/claustrum, and the checker verifies the pinned ref descends from the upstream default branch, failing when content drifts or ancestry is unchecked.

Written for commit 9f33b9d. Summary will update on new commits.

Review in cubic

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 4 files

You’re at about 91% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/core/src/accounts.ts
Comment thread packages/opencode/src/tests/index.test.ts Outdated
Comment thread packages/opencode/src/tests/index.test.ts Outdated

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 3 files (changes from recent commits).

You’re at about 91% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/opencode/src/tests/accounts.test.ts

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 2 files (changes from recent commits).

You’re at about 92% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/core/src/tests/accounts-persistence.test.ts Outdated
Six gates treated local credential bytes as a proxy for usability. A vault-served account's local slot is the provider tombstone, so quota recovery, profile hydration, /claude-quota and the killswitch's eager refresh all skipped healthy accounts once custody emptied that slot. Each site now admits a live vault binding alongside local access; none admits an account with no credential anywhere.
liveMainVaultAccess hand-rolled its sibling and dropped the sibling's stale-version guard, so main profile hydration and /claude-quota could bearer a version already reported auth-failed. It now delegates to resolveClaustrumAccess, inheriting both the guard and the warm schedule. mainServedAccessToken is cleared on a successful main report only: a suppressed or failed report tells the vault nothing, so local belief must not diverge from what the vault received.
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.

1 participant