Skip to content

Multi-account custody: N accounts per provider now (non-rotating kinds), custody inversion later (rotating OAuth) — the one-refresher invariant made structural #5

Description

@iceteaSA

Second of four cross-linked design filings (commons#13 carries the shared identity type; insula and astrocyte get consumption/adoption issues). This is the custody half, and it builds on three threads already on this board: #2 (multi-account ID convention), #3 (admin lockout), #4 (vault-as-mirror source-watch).

The invariant, stated as the design rather than a lesson

Exactly one refresher per credential — the custodian. Every live failure mode in our deployment traces to violating it: dual-custody rotation collision (why insula declined its own GAP-1/2), vault-mirror staleness (four manual re-seals in the last 24h alone — every opencode restart that rotates a token degrades the vault within the hour; both oauth:anthropic and chatgpt:openai hit it in one day, so the staleness class covers every rotating provider, which is #4's case strengthened), and the near-miss class where a second reader's refresh would invalidate the first's token family.

Phase 1 — N accounts per provider, immediately, for the kinds with no custody hazard

The commons#13 kind taxonomy makes this incremental: api_key, web_cookie, and oauth_static credentials don't rotate, so multiple accounts of them can be imported and served today with zero collision risk. Live inventory this covers: 2× synthetic API-key packs (second pack already under consideration — two keys = two accounts with separate request windows), alibaba/xai web-cookie accounts, deepseek keys, antigravity's 4-account set (Google refresh tokens verified non-rotating — its plugin-private rotation folds into the shared convention instead of staying a special case). Store shape: accounts keyed by AccountIdentity (commons#13), each holding 1..N CredentialSurfaces with declared kind + purpose. Only oauth_rotating surfaces (anthropic ×2, openai ×3 in our deployment) stay single-custodian-guarded and ride phase 2.

Phase 2 — custody inversion for rotating OAuth (the endgame, explicitly gated)

Claustrum becomes the sole refresher and serves an auth.json-compatible file view into the harness's auth store; the auth plugins become account-selection policy over vault-held credentials. Staleness dies by construction — there is no mirror to go stale.

Design constraints, each traced to verified fleet evidence:

  • Atomic file view or nothing. The daemon has no filesystem rails (verified at source: StorageBinding is a declaration, not an enforcement boundary; a module writes with the operator's privileges) — so write-temp-then-rename is the entire safety story for a file the harness reads unchanged, and it is wholly this module's to enforce. A torn view reads as corrupt credentials to a consumer that doesn't know a module owns the file.
  • Per-provider custody flag, hard cutover. Single writer on the view per provider at all times — never a migration window where plugin and vault both write. Failure mode is fail-loud: vault down → auth errors; never a silent plugin-side fallback refresh, which reintroduces dual custody exactly when the system is already degraded.
  • Rotation events are N-route emissions, not broadcast. subc has no pub/sub (normative: no daemon fan-out); claustrum emits per-route to each consumer holding one (insula, astrocyte). Costs land here and are accepted.
  • The trust boundary is stated, not implied. subc's identity mechanism is explicitly "not a same-user security barrier" (its own source comment) — a same-user process can read the key file and launch nonce. Sole-refresher is therefore a convention among cooperating processes on a single-operator box, and the design claims exactly that, no more. Phase 2 raises the value of impersonating claustrum; anything stronger than the stated assumption is future work someone must ask for explicitly.
  • Admin path preserved. The offline ck-auth flow (module stopped, direct store access) must survive the adapter role — it is the only re-seal path under Admin CLI vs running module: single-writer lease makes reads and mint-handle a consumer outage #3's connected-path lockout, re-verified working three times today. If a future rebase makes ck-auth daemon-dependent, phase 2 loses its recovery story.

Gate for phase 2, quantified: #4's source-watch shipped and the mirror proven stable across one full week including ≥1 natural rotation per provider and N harness restarts with zero manual re-seals. Current baseline: 4 re-seals/day, so the gate is measurable, not vibes. Policy state (sticky routing, drain order, account selection) stays plugin-side by charter — the vault exposes account enumeration + per-account health so a selector can choose cheaply, and never decides routing itself.


Cross-references: cortexkit/commons#13 (shared identity type — this issue's phase 1 store keys on it) · cortexkit/insula#7 (per-account consumption; consumes the rotation events named here) · cortexkit/astrocyte#7 (identity adoption) · harness side: iceteaSA/openai-auth#1, iceteaSA/anthropic-auth#1, iceteaSA/antigravity-auth#1 (the plugins that become policy-only under phase 2, each carrying the never-two-writers rule)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions