# Self-hosted Subrouter authority

status: ACTIVE — owner selected self-hosted first on 2026-08-17
plan slug: `subrouter-authority`
audience: implementers and reviewers
supersedes for the target architecture: `docs/plans/2026-08-17-codex-grant-single-writer.md` S1/S2/S5

## Outcome

Overdeck runs one self-hosted Subrouter daemon as the sole refresh-token owner for Claude and Codex subscription accounts. Workstation clients, buildboxes, Factory seats, Claudex, and health probes authenticate with account-scoped proxy grants and non-secret exact route IDs; they never receive provider refresh tokens or the authority's admin credential.

The first deployment is a reversible credential-empty dark candidate on the workstation. Existing native credential routing remains available until each real owner flow has passed against a fresh server-owned canary account. No existing provider grant is copied, uploaded, or manually tested during migration.

## Owner-visible evidence

Each slice must end with evidence the owner can obtain without reading source code:

1. Systray shows `Gateway: ready`, `Gateway: unavailable`, or `Gateway: migration required` for a gateway-managed account, using owner language and no internal identifiers.
2. The Subrouter status command reports the selected account, provider, refresh generation, and last safe refresh outcome without token material.
3. A routed Claude or Codex session identifies the requested account in sanitized Subrouter session/status output.
4. Buildbox dispatch receipts prove that no provider credential was transferred for a gateway-managed run.

A green unit test or installed file alone is supporting evidence, not completion evidence.

## Scope

### Included

- A pinned, reproducible self-hosted Subrouter runtime owned and installed by Overdeck.
- One authority process and one writable provider-credential store.
- Cross-process and in-process single-flight refresh for Claude and Codex, covering proxy, health, background, login, and repair mutation paths.
- Fail-closed handling of terminal and ambiguous refresh outcomes.
- Protected, server-owned account onboarding using Subrouter commands; never credential-file copying.
- Exact account selection for Claude and Codex calls.
- Dark per-account migration through the existing account registry and command router.
- Gateway-aware health and lifecycle state in Systray.
- Removal of provider credentials from buildbox/seat inputs for migrated accounts.
- Bounded, redacted transition records and rollback.

### Deferred

- Cloudflare Worker/Durable Object deployment.
- Active-active authority instances.
- Sub2API deployment.
- Migrating every account in one change.
- Transcript recording.
- Application-layer encryption of the live credential store. The trusted-host boundary is the dedicated non-login service identity plus mode-0600 state and systemd filesystem isolation. Provider credential state is deliberately excluded from backup; disaster recovery is fresh supported login, never restore of a possibly stale rotating chain.
- Publishing an upstream Subrouter pull request. The source-controlled hardening patch remains local until separately authorized for public submission.

## Non-negotiable credential rules

- Never move a provider credential file between homes or hosts.
- Never print, paste, log, diff, or commit a provider token or gateway key.
- Never POST a refresh token from Overdeck code.
- Account onboarding invokes Subrouter's supported server-owned login flow. A fresh login establishes authority custody; an existing rotating refresh token is not imported.
- Model probes use only the repository's approved model-test fixture policy. Owner directive 2026-08-20 replaces the retired Claude canary: the only live canary/migration account authorized in this program is Codex `zync2`, and the only live model is `gpt-5.3-codex-spark`. No other live account or model may be exercised without a later owner directive. Claude remains fixture-only until separately authorized.
- Native credential paths stay untouched while an account is dark. Cutover changes routing first; retirement happens only after installed owner-flow proof.

## Architecture

```mermaid
flowchart LR
  C[Claude Code / Codex / Claudex] -->|proxy grant + route ID| E[Overdeck routing adapter]
  B[Factory and buildbox seats] -->|run-scoped grant only| E
  S[Systray lifecycle and health] -->|sanitized control API| A[Self-hosted Subrouter]
  E -->|HTTP, SSE, WebSocket| A
  A -->|access token only on upstream request| P[Anthropic / OpenAI]
  A --> V[(0600 authority store)]
  A --> R[bounded redacted refresh journal]
```

The workstation is the first authority host because it already owns account lifecycle, Systray, and dispatch. The listener begins on loopback. The daemon runs as the dedicated non-login `overdeck-subrouter` service identity with an isolated HOME, XDG tree, and state directory; `ProtectHome=yes` and explicit read/write paths prevent discovery of native owner credentials. Legacy migration, native credential synchronization, account import by data-plane callers, and periodic Codex/OpenCode/pi switching are disabled before first start.

Remote exposure is a later deployed slice. It uses an authenticated tailnet-only listener, tailnet ACLs, and the binding's proxy credential; raw Internet exposure is forbidden. Loopback trust applies only inside the service's local control plane.

There is exactly one writable authority store and one active daemon. A lifetime exclusive store lease is acquired before reading credentials and held until process exit; a second process fails readiness. Upgrade drains and fully stops the old daemon before a replacement may acquire the lease. Provider credential state is excluded from backups and snapshots. Recovery uses a fresh supported login.

## Components and seams

### 1. Runtime package — `modules/subrouter/`

Owns the external runtime without vendoring the Subrouter repository.

Contract:

```text
SubrouterRuntimeSpec
  upstream_repository: https://github.com/manaflow-ai/subrouter.git
  upstream_revision: 29c7ebb306ac54739206f4752449e437047dd150
  upstream_release: v0.1.81
  source_archive_sha256: 74ab8da37d467a7d36bb73b11d82d90fd5ea14f3b75c535026adb0e45d9529f6
  patch: modules/subrouter/patches/0001-overdeck-authority-safety.patch
  unit_source: modules/subrouter/systemd/overdeck-subrouter.service
  service_user: overdeck-subrouter
  release_root: /var/lib/overdeck/subrouter/releases
  active_link: /var/lib/overdeck/subrouter/current
  state_dir: /var/lib/overdeck/subrouter/state
  listen_mode: loopback | tailnet
```

The canonical source bytes are the tar stream produced from a clean clone by `git archive --format=tar 29c7ebb306ac54739206f4752449e437047dd150`; SHA-256 is computed over that stream. The patch digest is generated from the committed patch at install time, recorded in the immutable release manifest, and checked by both startup and `sr authority-status`. A release is addressed by the combined upstream revision, source digest, patch digest, and build receipt; changing any input creates a new directory.

Responsibilities:

- Fetch or build only the pinned upstream revision through the project build/offload path.
- Apply a source-controlled minimal hardening patch and fail if it no longer applies exactly.
- Install immutable runtime content under the Overdeck release tree and atomically repoint the active runtime.
- Install the root-owned system unit from the pinned repository path and create the non-login service identity/state paths through `deck-sudo`.
- Start with isolated HOME/XDG variables, `ProtectHome=yes`, no owner-home read path, no legacy migration, no native sync/auto-switch, and no transcripts.
- Keep secrets in the protected state directory or systemd credential files, never unit text, process arguments, base URLs persisted in logs, repository files, or deployment receipts.
- Expose unauthenticated liveness/readiness only; sanitized account status, drain, tenant mutation, and version evidence use a local-only admin credential.

The runtime package does not own provider-specific refresh logic; the upstream Subrouter code does. Its patch only closes verified authority-safety gaps.

### 2. Refresh safety patch

The self-hosted upstream must satisfy this seam before receiving any provider refresh token:

```text
Refresh(account_id, reason) -> Refreshed | StillFresh | Blocked(failure_class)

RefreshAttempt
  attempt_id: random non-secret identifier
  account_fingerprint: bounded non-secret fingerprint
  credential_generation: monotonic local generation
  refresh_fingerprint_before: one-way bounded fingerprint
  state: prepared
```

Required behavior:

- One in-flight refresh per provider account across every request path in one daemon.
- A cross-process account mutation lock remains held from durable reread through provider I/O, atomic persistence, and publication. Login/repair replacement uses the same lock and generation discipline; remote account import is disabled.
- Before provider I/O, the authority acquires the mutation lock, preflights directory/file writability and available space, rereads the durable credential, decides refresh is still required, then atomically writes `RefreshAttempt` for that exact generation/fingerprint and fsyncs the marker and parent directory.
- Once provider send may have begun, an authority-owned bounded context completes refresh independent of caller cancellation. Callers may stop waiting but cannot cancel the rotation.
- A second caller waits for the first result, then rereads; it never spends the same refresh generation.
- Successful rotation atomically writes and fsyncs the credential with generation + 1 before publishing it. Only then does it remove the attempt marker and fsync the parent directory.
- On startup, a marker whose prior generation is still current freezes the account because provider consumption is unknowable. A marker with a durably advanced generation is stale completion evidence and may be cleared under the mutation lock.
- Terminal rejection records a sanitized failure and removes the account from scheduling.
- A transport failure, authority timeout after send, malformed success response, process death, or persistence ambiguity freezes the account. It is never automatically retried with the old refresh token.
- Repair requires a fresh supported login, atomically replaces the chain under the mutation lock, advances generation, and clears the block/attempt marker only after durable replacement.
- Refresh state records generation/fingerprint metadata only, never token bytes or provider response bodies.

Codex's existing lock-through-refresh path must remain covered. Claude's current self-hosted path, which releases its credential lock during provider I/O, does not meet this contract and must be corrected before canary onboarding.

### 3. Authority configuration and account binding

The existing account registry remains the owner-facing account catalogue. Gateway bindings are metadata, not credentials.

```text
AuthorityBinding
  mode: native | subrouter-dark | subrouter
  authority_name: configured endpoint reference
  route_id: opaque non-secret Subrouter route identifier
  provider: claude | codex
  proxy_grant_ref: protected workstation grant file reference

AuthorityRoute
  route_id: opaque non-secret identifier
  tenant_id: opaque internal tenant identifier
  provider: claude | codex
  account_id: exact server-side account identifier
  enabled: boolean

ProxyGrant
  grant_id: opaque non-secret identifier
  route_id: exact route scope
  key_hash: SHA-256 hash of bearer key
  audience: workstation | buildbox-run
  issued_at: timestamp
  expires_at: timestamp
  revoked_at: timestamp | null
```

Overdeck authority mode adds an exact route in front of Subrouter's tenant pool. Every proxy request resolves the non-secret route ID, authenticates the proxy key, verifies the bound provider/account still exists and is enabled, and forces that account server-side. The route never enters pooled scheduling and ignores/rejects a conflicting client account selector.

Credential separation is mandatory:

- The local-only admin credential may create/repair accounts and routes but cannot be distributed to clients.
- A route's data-plane proxy grant may call only that route's provider proxy and its bounded sanitized self-status endpoint. It cannot list pools, create/import/repair/delete accounts, mutate tenants/routes/grants, drain, or read admin status.
- Grant creation supports `--proxy-key-file <protected-path>` and writes the new bearer key directly with mode 0600 without printing it. Only its hash and lifecycle metadata are stored by the authority.
- The data-plane base URL contains `route_id`, never the key: Claude uses `/r/<route-id>` and Codex `/r/<route-id>/v1`. The client sends the key in its normal auth header, which Subrouter replaces before upstream forwarding.
- Workstation grants are revocable and explicitly rotated without overlap: install the new grant, update protected client state, then revoke the old grant. Already-open HTTP streams/WebSockets continue without reauthentication; the old key cannot open a new connection.
- Every buildbox run receives a unique short-lived `audience=buildbox-run` grant whose expiry bounds the run and which is revoked at teardown. It is never reused across runs or hosts.

Rules:

- No hardcoded account slug, route, tenant, or remote account identifier in source.
- `native` preserves current behavior.
- `subrouter-dark` resolves and health-checks the authority but launches natively.
- `subrouter` launches only through the authority. Missing endpoint, unexpired proxy grant, route, or exact account fails visibly; it never falls back to native provider credentials.
- Every request and every control-plane mutation revalidates route/account cardinality under the account mutation lock. Adding/replacing an account cannot leave an enabled route with zero or multiple targets; the transaction either preserves exactly one target or disables the route atomically.
- Tenant-key authorization for account import is disabled in authority mode. Fresh login/repair executes locally under the admin credential and same mutation lock.
- Registry migration is additive and round-trippable. Older entries remain valid as `native`.

Workstation grant files live in protected Overdeck client state and are excluded from logs/backups. Run-scoped grant files exist only in the run's protected credential staging area and are deleted after revocation. APIs and receipts expose only route/grant/key fingerprints and lifecycle timestamps.

### 4. Client routing adapter — `modules/systray/command_router.py`

The command router is the single launch seam for interactive and dispatched clients.

```text
build_authority_launch(account, tool, argv) -> executable + argv + environment
```

Behavior:

- Codex uses `http://127.0.0.1:<port>/r/<route-id>/v1` (or the authenticated tailnet origin) through Subrouter's custom-provider/wrapper path; Responses HTTP, realtime WebSocket, resume, and app-server flows retain the suffix/path behavior documented by the pinned upstream.
- Claude uses the same origin at `/r/<route-id>` through Subrouter's proxy path.
- Both clients receive only the route's revocable proxy key in their normal API-auth environment. They receive neither provider credentials nor the local admin credential.
- The server-side route forces the exact provider account on every request, so selection does not depend on a client being able to add `X-Subrouter-Account-ID`.
- Every launched session gets a stable session identifier and provider namespace for sticky routing.
- The adapter removes inherited native provider credential variables and never passes a native credential home. When a client requires a config home, it receives a dedicated gateway-only directory whose manifest forbids provider auth files.
- A forced account that is missing, blocked, or provider-incompatible fails. It never silently chooses another account.
- Info-only/version commands that make no provider request remain local and credential-free.

Exact selection is therefore a server-enforced route invariant, not an optional client header or a pool convention. Any route/account mismatch blocks the request and disables an invalid route rather than scheduling another account.

### 5. Lifecycle and health adapter

`modules/systray/provider_services.py` gains a gateway-backed provider implementation selected by `AuthorityBinding.mode`.

Lifecycle contract:

```text
add_or_repair(binding, provider) -> interactive Subrouter login session
```

Health contract:

```text
fetch_gateway_health(binding) -> AccountSnapshot
```

Behavior:

- Add/Repair launches Subrouter's server-owned OAuth flow with the real resolved binary, preserving the existing visible prompt/error-dialog behavior.
- It does not inspect or reconcile provider token files.
- Health reads sanitized Subrouter account/usage status and distinguishes unavailable authority, missing binding, blocked refresh chain, quota exhaustion, and healthy state.
- Status transitions feed the existing health store and owner-visible account cards.
- A gateway outage reports unavailable and preserves the last verified snapshot as stale; it never opens native credential fallback.

### 6. Seat and buildbox credential boundary

For a gateway-managed account, `seat-creds.mjs`, `seat-run.sh`, K3s job creation, and remote dispatch exchange only gateway material required for the scoped run.

Receipt shape:

```text
CredentialDeliveryReceipt
  provider: claude | codex
  authority_mode: subrouter
  authority_name: non-secret identity
  route_id_fingerprint: bounded fingerprint
  grant_id_fingerprint: bounded fingerprint
  grant_expires_at: timestamp
  grant_revoked_at: timestamp
  provider_refresh_token_present: false
  provider_credential_file_present: false
  authority_admin_credential_present: false
  remote_grant_file_removed: true
```

The three credential-absence booleans are asserted from the materialized input manifest, not self-reported by the worker. A migrated run is refused if any is true. The teardown controller revokes the grant first, removes every staged/remote copy, then atomically completes the receipt with revocation/removal evidence. Existing native accounts retain the old path until their own cutover slice.

### 7. Observability and audit

Every authority transition records:

- time;
- authority version and exact installed revision;
- provider and opaque account fingerprint;
- refresh generation before/after;
- reason and structured outcome;
- caller class (`proxy`, `health`, `background`, `repair`);
- host/service identity;
- bounded sanitized error class.

No token, Authorization header, raw OAuth response, request body, or full credential path is recorded. Logs are bounded by size/retention. Systray surfaces the current owner-relevant state; detailed internal evidence remains available for incident review.

## Data flows

### Normal model request

1. The command router resolves an account and its binding.
2. A gateway-managed launch receives the authority origin, non-secret route ID, scoped proxy-key reference, and stable session key.
3. Subrouter authenticates the proxy key, resolves its exact route, revalidates the bound provider/account, and bypasses pooled scheduling.
4. If the provider credential is still fresh, it forwards with the provider access token confined to the authority's protected store and process.
5. If refresh is needed, the single-flight refresh contract completes before forwarding.
6. Subrouter strips all internal routing headers before the provider request.
7. Sanitized session/account evidence is available to health and receipt collectors.

### Add or repair

1. Systray starts the supported Subrouter login command for the configured authority.
2. The provider login creates a fresh server-owned chain directly in the authority store.
3. The authority validates and durably records the new generation without a model request.
4. Health confirms the sanitized account identity and unblocked state.
5. The binding remains dark until an explicit canary owner flow passes.

### Refresh failure

1. The refresh coordinator classifies the failure.
2. A definitely terminal or ambiguous result atomically blocks the account.
3. Waiting callers receive the same blocked result; no caller retries the old generation.
4. Because each route binds one exact account, the request fails when that account is blocked; it never selects a substitute.
5. Systray offers Repair and records the bounded transition.

## Rollout slices

Each slice lands, deploys, and produces its named evidence before the next begins.

### S0 — Hardened credential-empty dark candidate

Patch and fault-test the pinned source before installation. The suite covers simultaneous calls, rotating-token success, terminal failure, lost response, malformed response, caller cancellation, persistence failure, and subprocess death/restart at every marker/send/persist boundary. Only after it passes, install the credential-empty singleton on loopback under the isolated service identity with the lifetime store lease, immutable revision evidence, readiness, drain, and bounded logs. Exact routes and proxy grants are not implemented in this slice.

Evidence: the installed `sr authority-status` command reports the exact upstream revision, source digest, patch digest, service identity, singleton lease, `0 provider credentials`, and `Gateway: ready`; the repository's exact-revision atomic receipt reports one provider call per generation plus restart freeze/recovery outcomes; bounded before/after fingerprints prove native Claude/Codex credential files were unchanged.

### S1 — Exact dark route and Overdeck binding

Add server-enforced exact routes, scoped proxy grants, local-admin separation, silent grant-file generation, registry binding, gateway-only client config roots, runtime resolution, gateway health adapter, and dark command-router resolution without changing live provider launches. Install the next immutable candidate and create a credentialless fixture route/grant. Reconcile the useful lifecycle/observability work from `wt/codex-grant-slice1`; custom-authority code superseded by this design is not carried forward.

Evidence: the installed fixture proves the proxy grant can reach only its exact route/self-status and cannot call any mutation/admin route; missing, expired, revoked, wrong-route, and conflicting-selector requests fail; one configured dark Systray fixture renders `Gateway: ready` or `Gateway: migration required` while native owner flows remain routed as before and gateway-only config manifests contain no provider auth file.

### S2 — Credentialless installed canary

Owner update 2026-08-21: no recoverable disposable live provider account is available. `zync2` is unavailable and must not be recreated. `zync` may be used only for a direct model verification attempt with `gpt-5.3-codex-spark`; it must not be reauthenticated, migrated, authority-bound, repaired, copied to a seat/K3s secret, or otherwise mutated. Because sanctioned remote model execution would distribute the native credential, no live provider call is required for S2 acceptance.

S2 therefore proves the installed authority credentiallessly: the real HTTP authority creates a temporary fixture account, exact route and real scoped grants, proves status/missing/malformed/wrong-route/conflicting-selector/admin-isolation/provider-unavailable/revoked/expired behavior, removes all fixture state, restarts, and returns to zero provider credentials/routes/grants. Provider refresh concurrency/crash semantics remain fixture evidence from S0. A later live account can be onboarded only by a new owner directive.

Evidence: installed `authority-proof` passes all exact-route/grant/admin-isolation assertions with provider credentials remaining zero before and after; restart returns healthy with zero fixture state. `zync` live-model inspection pins `--profile=zync` and `gpt-5.3-codex-spark`, but no confirmed model call is claimed.

### S3 — Authenticated remote edge

Expose the same singleton through a separate transport edge bound only to the workstation's Tailscale CGNAT address. Subrouter itself remains loopback-only and keeps the lifetime store lease. The edge resolves approved machine identities from local `tailscale status --json`, rejects any non-approved tailnet source with HTTP 403 before forwarding bytes, and raw-proxies approved connections to `127.0.0.1:31415`; route-scoped proxy grants remain the data-plane credential. Admin/list/import/repair/drain stay local CLI operations and are not introduced on the remote listener. Tailscale supplies WireGuard encryption. Central tailnet ACL policy remains defense in depth outside this repository; repository acceptance does not depend on mutating the owner's tailnet policy.

S3 starts with only `debian1` approved. `debian2` is the negative peer and must receive 403 before route authentication. This gives a machine-level deny proof without touching non-buildbox hosts. The edge logs only its tailnet bind and approved-peer count, never route URLs, headers, grant values, or provider material.

Evidence: `debian1` reaches an exact credentialless route status through a short-lived `buildbox-run` grant; missing key and wrong-route key are denied by the authority; route/global control-plane paths remain unavailable; `debian2` receives edge 403; listener evidence shows an exact Tailscale address with no wildcard/LAN/public bind; authority remains provider-credential-empty.

### S4 — Remote seat without provider credentials

Route one buildbox/Factory canary through the authenticated edge and enforce no-provider-credential manifest assertions. Mint a unique short-lived run grant directly into protected staging, deliver only that grant, revoke it at teardown, and delete every staged/remote copy.

Evidence: owner-visible dispatch result plus the complete `CredentialDeliveryReceipt`: all three forbidden-credential booleans false, route/grant fingerprints, bounded expiry, revocation timestamp, and remote grant removal true.

### S5 — Incremental account migration

Migrate one account/provider at a time using fresh supported authority login. Under the 2026-08-21 owner directive, no live account is authorized for migration in this program. `zync2` is unavailable; `zync` is test-only and must remain native/unmodified. Generic Claude/Codex migration machinery is implemented and fixture-tested only. No live account may be authenticated, authority-bound, repaired, or migrated without a later owner directive.

Evidence: per-account receipt linked from the plan index. Failed accounts remain dark or execute the rollback reauthentication transaction; no token reconciliation or copying occurs.

### S6 — Retire distributed refresh capability

Only after all target flows are authority-backed: stop distributing provider credentials to buildboxes/seats, remove Claudex Codex proxy copies and reconciliation, and make native fallback impossible for migrated entries.

Evidence: repository/runtime scan plus real dispatch prove migrated clients have no provider refresh token; Systray Repair still works through the authority.

## Error handling

- Authority unavailable: visible unavailable state; no native fallback.
- Missing gateway credential: launch refusal naming configuration repair, never credential value/path.
- Exact account absent or blocked: fail the forced request; no scheduler substitution.
- Provider 401 on a fresh access token: classify and block/request repair according to upstream semantics; do not blindly refresh repeatedly.
- Refresh ambiguity: freeze and repair.
- Durable-store write failure after provider success: freeze the account and stop the daemon from serving that generation.
- Tailnet authentication failure: 401/403; never open mode.
- Version/patch mismatch: installation and startup fail before credential access.
- Status API failure: preserve last verified status as stale with reason.

## Testing strategy

### Static and fixture tests

- Registry schema/default compatibility and secret redaction.
- Native/dark/gateway launch environment snapshots.
- Exact route authentication, server-side account forcing, conflicting-selector rejection, and data-plane/control-plane scope separation.
- Single-flight and generation semantics under goroutine and subprocess concurrency.
- Caller cancellation after send does not cancel the authority-owned refresh.
- Terminal versus ambiguous refresh classification.
- Fsynced attempt marker, atomic persistence, and restart recovery after process death at every marker/send/persist boundary.
- Silent proxy-grant file creation, rotation/revocation, expiry, URL/header/log redaction, and run teardown.
- No-provider-credential seat manifests and receipts.
- Service identity, HOME/XDG isolation, native migration/sync disablement, lifetime store lease, immutable revision, and patch digest checks.

### Integration tests

- Local fake OAuth endpoints simulate rotation, delayed responses, lost responses, malformed success, and persistence failure without any live grant.
- Real Subrouter HTTP, SSE, Responses WebSocket, realtime WebSocket, resume, and app-server paths run against fixtures through an exact route.
- Systray lifecycle and health consume sanitized fixture responses.
- Service isolation test places sentinel native credential files in otherwise conventional homes and proves the daemon cannot discover or alter them.
- Remote-edge tests prove tailnet allow/deny identity, proxy-key scope, no public bind, and gateway-only materialization.

### Live canary

Any live model call uses `model-test-fixture` and an explicit owner-authorized account only. Current owner policy permits only a test attempt with native Codex `zync` and the fixture-pinned `gpt-5.3-codex-spark`; it does not permit credential distribution, reauthentication, migration, authority binding, repair, or fallback. If the sanctioned execution path would copy the native credential, the live call is skipped rather than weakening isolation. Account onboarding requires a future explicit owner directive and the authority's local supported login path. No manual curl with provider credentials, no token inspection, and no validation from inside inherited Claude session auth. Live refresh is observed only when it occurs naturally; forced refresh, crash, and ambiguity tests use fake providers.

Required installed proofs:

- exact route selected;
- installed authority reports its durable generation and no unresolved attempt;
- restart preserves the current generation;
- fixture receipt proves a blocked ambiguous chain cannot route;
- Repair replaces it through supported login;
- buildbox run contains no provider credential.

## Rollback

Rollback is a reauthentication transaction, never an immediate mode flip:

1. quiesce new gateway launches and drain sessions for the binding;
2. disable the exact route and freeze the authority account under the mutation lock;
3. perform a fresh supported native login, creating a new chain directly in the native owner;
4. verify native readiness without a model request;
5. atomically switch the binding to `native`;
6. delete the frozen authority chain through its supported local admin path only after native launch evidence.

Failure before step 5 leaves routing quiesced and the binding unchanged. No credential is copied or restored. Runtime binary rollback may select a previous immutable release only when its manifest declares the state schema compatible and the old process has drained/released the lifetime store lease.

## Architecture decisions

- **Self-hosted first:** owner decision, 2026-08-17. Cloudflare mode is deferred.
- **Workstation first authority host:** minimizes the initial custody move and uses existing lifecycle ownership; remote access remains dark until authenticated tailnet proof.
- **Singleton, not active-active:** one writer is the product requirement. Availability does not outrank grant integrity.
- **Carry one bounded authority-safety patch rather than build provider OAuth:** refresh journaling, exact-route enforcement, data/control credential separation, service status, and isolation close verified self-hosted gaps; provider protocol and forwarding remain Subrouter-owned.
- **Keep the account registry:** deleting it would scatter owner labels, routing policy, and health bindings across clients; it remains a deep owner-facing boundary.
- **Keep one command-router seam:** deleting it would duplicate credential stripping and route resolution across every launcher.
- **Keep data/control credentials inside runtime/configuration ownership:** two credential roles are required, but a generic secrets abstraction would still have one implementation and fail the single-adapter test.
- **Use server-enforced exact routes, not one-account tenant convention:** tenant keys currently authorize mutation and identify pools; a non-secret route ID plus proxy-only key binds one provider/account on every request and cannot mutate custody.
- **No header-injecting compatibility edge:** exact route URLs work through both clients' base-URL seam, so another Fewtok/Claudex proxy is decorative and excluded.
- **Keep seat enforcement separate:** it removes a whole remote refresh-token distribution class and is independently testable.
- **Merge patch and first install:** installing a known-unsafe binary would be a consecutive enabling slice; S0 tests first and installs only the hardened credential-empty artifact.
- **Exclude rotating grants from backup:** a stale snapshot can destroy the live chain; recovery and rollback use fresh supported login.
- **Dedicated service identity:** filesystem isolation mechanically prevents accidental native-account adoption before any credential enters custody.
- **Rollback reauthenticates before routing:** a metadata flip could revive a stale native chain, so rollback is a quiesce/freeze/login/verify/switch transaction.
- **No big-bang cutover:** every account binding is a reversible two-way door until distributed credentials are retired, but reversal always creates a fresh chain.

## Acceptance criteria

- [ ] Exactly one installed writable authority store exists and a lifetime lease rejects a second process.
- [ ] Every refresh path for both providers uses single-flight, a fsynced pre-send attempt marker, durable generation reread, and authority-owned completion after caller cancellation.
- [ ] Process death or ambiguous refresh outcomes freeze an unresolved old generation rather than retry.
- [ ] The daemon service cannot read native owner credential homes and native sync/migration is disabled.
- [ ] No Overdeck code sends a provider refresh request.
- [ ] Data-plane proxy keys cannot invoke any account/tenant/route/import/repair/admin mutation.
- [ ] Gateway-managed Claude and Codex launches contain no provider refresh token or local admin credential.
- [ ] Exact-route requests either use the server-bound account or fail; pooled scheduling is unreachable.
- [ ] Claude HTTP/SSE and Codex HTTP/Responses WebSocket/realtime/resume/app-server flows pass through the authority.
- [ ] Systray Add, Repair, and health operate through sanitized Subrouter interfaces.
- [ ] The tailnet edge denies unapproved identities and remote control-plane calls before any remote canary.
- [ ] A remote canary dispatch proves provider credential files absent, revokes its run-scoped grant, and removes every grant copy.
- [ ] Installed runtime version, source digest, patch digest, service identity/status, store lease, and readiness are owner-obtainable.
- [ ] Provider credential state is excluded from backups; restore/rollback documentation requires fresh login.
- [ ] Each migrated account has a live-flow receipt before the next migration.
- [ ] Existing native flows and credential fingerprints remain unchanged while dark and no credential is manually moved.
