# Workstation AI account Gateway convergence

**Status:** DONE
**Supersedes:** the K3s/Factory portion of `2026-08-22-subrouter-authority-s6.md`
**Base:** current `origin/main` at slice start `ce4f674f372ccacab784ce65b96faeef388d55fa`

## Owner architecture decision

Overdeck is the workstation-management project. AWP owns Factory and future agentic-workspace execution.

For AI accounts on this workstation:

- **Systray AI is the sole user-facing account registry and account manager.**
- Codex (`cdx`), Claude (`cld`), and Claudex resolve the same Systray account slugs/defaults.
- Subrouter is an implementation detail for credential custody attached to a Systray account through `authority_binding`; it is not a second account directory.
- Factory/K3s does not receive or consume Subrouter account authority from Overdeck. The tailnet authority edge is not part of this workstation slice.
- Existing intentional native accounts remain native until the owner explicitly selects **Move to Gateway…**. No existing provider credential is copied into Subrouter.

## Account lifecycle

### Add and select

Existing Systray Add account and default-selection UI remains authoritative for both Codex and Claude. Claudex independently resolves its Codex and Claude selections from those same registries/defaults.

### Move to Gateway

For a native Codex or Claude account, Systray exposes **Move <alias> to Gateway…** in both the tray account-management menu and the dashboard account card.

The action opens the installed `systray-gateway migrate` command in a workstation terminal so the provider-supported login remains visible and interactive. Systray calls an authenticated administration endpoint on the already-running loopback Subrouter authority at `http://127.0.0.1:31415`; Subrouter performs the fresh authority-owned login and route/grant provisioning under its existing service identity. The runtime path contains no `deck-sudo`, `sudo`, `runuser`, or root migration helper. Systray never reads/copies/imports the native provider token file and stores only the exact route metadata plus its protected workstation grant reference.

Cutover requires:

1. fresh authority-owned login/provision succeeds;
2. the exact Gateway route reports ready;
3. release-level Codex/Claude compatibility gates for local CLI, resume/streaming, health/repair/restart, exact route, no provider credential copying, and provider-specific protocol paths are already sealed by the Subrouter candidate program.

### Gateway-managed execution

- `cdx` and `cld` execute locally on the workstation with Gateway-only homes/environment and must not enter remote/K3s offload.
- A fully Gateway-managed account fails closed if route/grant/Gateway readiness is missing; no native credential fallback.
- Claudex Codex translation uses an ephemeral private CCP config containing only the exact route grant and no provider refresh token.
- Claudex Claude/native and hybrid modes use the same Systray Claude `authority_binding`; migrated Claude requests route through the exact local Subrouter prefix without opening native Claude credentials.
- Systray health and Repair for bound accounts use the existing authority lifecycle adapter.

### Return to native

Systray exposes **Return to native…**. Rollback is transactional:

1. mark the Gateway binding quiesced and disable the exact route;
2. run Systray's existing provider-native reauthentication UI for a fresh login;
3. verify native health without a model request;
4. remove the Systray authority binding (commit point);
5. remove authority grants/route/account state and protected local grant reference.

A failed pre-commit rollback remains quiesced/authority-bound. No old provider credential is restored or copied.

## UI/state contract

Each Systray account has one custody state:

- `Native`
- `Gateway testing`
- `Gateway`
- `Gateway paused`

The tray account-management submenu exposes the state-appropriate action. Dashboard account cards expose the same actions and show a visible Gateway badge for testing/active/paused states. Locks continue to block new migration/cutover actions.

## Factory/AWP boundary

This slice removes active Factory integration from Systray AI:

- no Factory tray submenu;
- no Factory status polling from Systray;
- no Factory decision notifications from Systray;
- no Factory browser opener from Systray.

Compatibility constructor parameters may remain temporarily to avoid unrelated API churn, but they are inert. Historical Factory modules/plans are not rewritten by this slice; AWP owns the replacement execution product.

The S6 K3s authority transport is removed from Overdeck:

- no `AuthorityRemoteAccess` dispatch object;
- no K3s Subrouter route/grant Secret;
- no K3s `authority_origin` registry field;
- native/unmigrated K3s behavior is restored unchanged until AWP replaces that execution surface.

## Acceptance

Buildbox verification must prove:

1. Gateway account manager supports Codex and Claude stage/activate/cancel with Systray remaining the registry.
2. Metadata-only Gateway status does not initialize authority administration.
3. Gateway activation fails closed on non-ready health.
4. dark cancel deletes authority material and returns the Systray account to Native.
5. Gateway-bound `cdx`/`cld` cannot remote-offload and local execution receives only Gateway environment.
6. Claudex Codex and Claude paths do not read/reconcile native provider credentials when migrated.
7. Claudex hybrid preserves exact local Claude route prefix and host semantics.
8. dashboard/tray Gateway controls update across Native/Testing/Active/Paused and lock/busy constraints.
9. Factory is absent from Systray menu/poll/notification behavior.
10. K3s remote-authority additions are absent while native K3s tests remain green.
11. complete Systray suite has no new regressions beyond already reproduced clean-main baselines.
12. installed runtime has only loopback Subrouter listener; tailnet edge remains disabled.
13. real `zync` remains metadata-only native/unbound; no real account is migrated during implementation acceptance.

## Installed proof

After landing/deploy:

- verify deployed source SHA is landed main;
- verify `systray-ai.service` and `overdeck-subrouter.service` active;
- verify edge disabled and only `127.0.0.1:31415` listening;
- verify `systray-gateway status --tool codex --account zync` reports Native using registry metadata only;
- verify no provider credentials/routes/grants/unresolved attempts exist in Subrouter;
- verify installed Systray menu/dashboard source contains Gateway controls and no active Factory integration;
- verify installed `cdx`, `cld`, `claudex`, and `systray-gateway` links resolve to the canonical deploy tree.

No real account login, model call, migration, repair, or credential read/copy is authorized as part of this implementation proof.
## Pre-land verification evidence — 2026-08-22

No real account login, model call, migration, repair, provider credential read/copy, or authority binding was performed. Live authority remained 0 provider credentials / 0 routes / 0 grants / 0 unresolved attempts with Gateway ready; the tailnet edge remained disabled and only loopback `127.0.0.1:31415` listened.

Current workstation-focused verification on buildboxes:

- targeted account/dashboard/indicator suite: **310 passed**;
- authenticated candidate focused before final installer-gate tightening: **449 passed**, plus Codex+Claude synthetic migration fixture verification PASS;
- authenticated candidate static/module/full: PASS;
- installer + desktop packaging contracts after adding `systray-gateway`: **9 passed**;
- broad Systray suite after installer expectation correction: **1183 passed, 1 skipped, 1 deselected, 1 failed**. The sole failure is `test_store_rejects_malformed_or_unsafe_state`, which reproduces identically from exact current `origin/main` under the same protected buildbox parent and is therefore an inherited permissions-order baseline, not a workstation-account regression. The deselected CPU-spin supervisor test is the previously reproduced buildbox timing baseline.

The final authenticated post-commit candidate must rerun focused/static/module/full with installer/desktop packaging tests included in focused/static before landing.
## Final installed acceptance — 2026-08-22

The workstation-account convergence is landed on `main` and deployed at `84dc0e0b9a8c07d8050d312b46b4cd6e47ac85f0`. Exact landed candidate `6619652fe41721d32cae8a98bd9172bf96882a0f597fefad011c89d047ef9af5` has focused/static/module/full exit 0 on registered buildbox `debian2`.

Installed workstation proof:

- `systray-ai.service` is active/running/success.
- `overdeck-subrouter.service` is active/running; `overdeck-subrouter-edge.service` is disabled/inactive.
- the only Subrouter listener is loopback `127.0.0.1:31415`; there is no `31416` tailnet listener.
- live Subrouter reports provider credentials **0**, routes **0**, grants **0**, unresolved attempts **0**, Gateway ready.
- installed `cdx.py`, `cld.py`, `claudex.py`, `claudex`, `systray_codex_switcher.py`, `gateway_account_cli.py`, and `systray-gateway` all resolve into the canonical deploy tree.
- `systray-gateway status --tool codex --account zync` returns `state=native`; direct registry metadata inspection confirms `zync` has `authority_binding: null`. No provider credential file was opened.
- installed Systray source contains Gateway-local execution/account controls and no active Factory menu/poll/decision/browser integration.
- installed Claudex source contains both Codex and Claude authority-binding seams and routes migrated traffic through local Subrouter without provider credential reconciliation.

Final broad Systray verification from the deployed landed tree on `debian1` produced **1188 passed, 1 skipped, 1 deselected, 1 failed**. The sole failure, `test_store_rejects_malformed_or_unsafe_state`, was rerun from the exact pre-slice base `ce4f674f372ccacab784ce65b96faeef388d55fa` under the same protected buildbox conditions and failed identically because the security validator rejects the pytest-created group/world-writable file before reaching the malformed-version assertion. It is an inherited test-environment/order baseline. The deselected CPU-spin supervisor case is the previously documented buildbox timing baseline.

No real account login, model call, migration, repair, credential read/copy, or authority binding occurred during implementation or acceptance. Native accounts remain native until explicitly moved to Gateway through Systray AI. Factory/agentic execution is no longer an Overdeck Systray responsibility; that ownership belongs to AWP.

## Owner-authorized live migration / no-sudo correction — 2026-08-23

The owner explicitly authorized live Gateway migration testing and rejected the original Systray → `deck-sudo` → root helper → `runuser` → Subrouter CLI path. That privilege bridge is now treated as an implementation defect, not an architectural requirement.

Observed live failures that triggered the correction:

- `systray-gateway --wait` originally closed immediately on errors; landed/deployed `8569b509e` keeps migration failures visible.
- the live canonical Systray state uses `~/.local/state/overdeck -> ~/.overdeck`; landed/deployed `86026445a` resolves that trusted compatibility alias before protected-parent checks.
- the legacy migration helper stripped `PATH` under `env -i`; landed/deployed `9d91272f1` supplied a fixed system path and proved Codex device auth itself works under the Subrouter service identity.
- a `multideal` attempt then became an invisible orphan after its terminal disappeared while the `deck-sudo`/`systemd-run --pipe` chain remained alive. A second click collided with the first attempt's `authority-material/codex/3381fbbe7dd0` directory and reported `File exists`. The orphan was terminated and the empty stale workspace removed; `multideal` remains Native.

Replacement runtime architecture in `wt/gateway-no-sudo`:

- **Systray → authenticated loopback Subrouter admin API → existing Subrouter authority service**.
- Systray's `SubrouterMigrationAdmin` contains no `deck-sudo`, `runuser`, Subrouter service-user switching, Subrouter state-dir access, Subrouter binary invocation, or `migration-provision` invocation.
- candidate installation provisions one random 256-bit admin token into a service-owned `0600` authority token and a workstation-user-owned `0600` client token; runtime migration uses only the user-owned token over loopback.
- Subrouter performs provider login, authority account persistence, exact route creation, and workstation grant creation in-process under its existing service identity.
- the interactive HTTP request carries terminal I/O directly; closing the terminal/request cancels the provider-login context instead of leaving a privileged detached child chain.
- no second Subrouter is introduced. The intended runtime remains the single workstation service bound only to `127.0.0.1:31415`; `overdeck-vm` remains control/build infrastructure only.

Verification before final commit:

- changed Systray + installer contract tests: **30 passed** on a registered Debian worker.
- broader focused Python/installer run: **37 passed**, with only the same three inherited protected-`/tmp` Gateway-manager failures previously reproduced from clean main.
- direct pinned-upstream Go 1.24 authority tests and `go vet` on `debian1`: PASS after correcting one compile-scope error and updating old empty-admin-marker fixtures to valid synthetic admin tokens.
- official Subrouter candidate verifier, invoked correctly from `e14` as the remote-phase orchestrator: focused/static/module/full all exit **0** on registered worker `debian2`. Pre-commit sealed candidate: `78869d1cea77b1b0c8c6aaaf2ffa3ac0c33f14bec88e494c993d1b1ba7b8bad7`; binary SHA-256 `3ae29f9129b65766850d18ad969c277a557ae3be74492bbc14e6fe22a4304b6e`; patch SHA-256 `c1b4581a1b5f01d9b30d104d49708eb8f180125c1662cec455e1cace6824812a`.

Remaining steps for completion:

1. commit this exact no-sudo implementation and durable checkpoint;
2. rerun focused/static/module/full so the sealed candidate receipt binds to the final commit;
3. install the exact final candidate on workstation `e14`, deploy Systray from the same landed source, and restart only the workstation services as required;
4. prove the installed `SubrouterMigrationAdmin` has no privilege bridge and the workstation still has exactly one loopback Subrouter with no `31416` listener;
5. start a real `multideal` migration from the Systray UI, verify the device-auth terminal remains attached and no `deck-sudo`, `runuser`, `systemd-run`, or orphaned provider-login process appears; owner completes provider authorization if desired;
6. after successful authorization, verify `multideal` becomes Gateway-bound with exact route/grant and no native provider credential copying.

### No-sudo installation follow-up — 2026-08-23

After landing/deploying `c99d0853e`, installation of the sealed no-sudo Subrouter candidate correctly stopped before mutation because `validate_empty_provider_tree` classified the service store's zero-byte `claude.json.lock` as provider credential material. Live authority status simultaneously proved **0 provider credentials / 0 routes / 0 grants / 0 unresolved attempts**, so this was an installer metadata false positive rather than residual credential state.

Correction in `wt/gateway-no-sudo-install-locks`:

- retain the strict provider-tree metadata walk and no-content-read policy;
- allow only single-link mode-`0600`, zero-byte `*.json.lock` metadata at the top level of the known Codex-account and Claude provider roots;
- continue rejecting non-empty lock files as provider credential material;
- add explicit Claude empty-lock acceptance and non-empty-lock rejection coverage.

Remote service-contract verification after the correction: **23 passed**. The sealed candidate must be regenerated against the correction commit before installation because candidate provenance binds to source HEAD.

### Codex service-sandbox login follow-up — 2026-08-23

The first installed no-sudo live `multideal` probe reached the new authenticated Subrouter admin path but Codex aborted before device auth. The failure was not the HTTP boundary: the Codex child inherited Subrouter's `MemoryDenyWriteExecute=yes` systemd hardening, and Node/V8 failed at startup while trying to create executable JIT memory. The failed probe left no migration process, Systray material, provider credential, route, grant, or unresolved attempt.

A one-off diagnostic with the same `MemoryDenyWriteExecute=yes` restriction reproduced the V8 fatal without special options and succeeded with `NODE_OPTIONS=--jitless` (`codex-cli 0.146.1`). Therefore the service hardening remains unchanged; only the authority-owned Codex login child receives `NODE_OPTIONS=--jitless` together with its private `CODEX_HOME`.

Regression coverage now asserts the exact authority Codex login environment, and targeted Go authority tests plus `go vet ./cmd/subrouter` pass on `debian1`. The change must be resealed through focused/static/module/full before installation and live device-auth proof.

### Provider-login cancellation follow-up — 2026-08-23

After installing the JITless no-sudo candidate, a real `multideal` probe successfully reached the Codex device-auth URL with no V8 fatal and no legacy privilege-bridge text. Cancelling the client request, however, exposed one final orphaning defect: Node's wrapper exited but its native Codex child remained alive, reparented to PID 1. The child had inherited the Subrouter service process group, so killing that group would also have killed the authority service itself. The proof child was terminated explicitly and authority state remained **0 credentials / 0 routes / 0 grants / 0 unresolved attempts**.

Correction: `execSRCommandRunner` now starts each managed CLI command in its own process group (`Setpgid`) and overrides context cancellation to kill that child process group. This applies to managed provider/CLI subprocess trees generally rather than special-casing Codex and keeps the Subrouter service process group isolated. Regression coverage launches a shell with a background child, cancels the context, and proves the child no longer runs. Targeted `cmd/subrouter` authority/process-group tests plus `go vet ./cmd/subrouter` pass on `debian1`. The change must be resealed through focused/static/module/full and installed before the final live cancellation proof.

### Candidate scope follow-up — 2026-08-23

The process-group cancellation patch adds `cmd/subrouter/sr_command.go` and `cmd/subrouter/sr_command_test.go` to the pinned upstream patch. The S1 candidate verifier originally refused those paths before testing. Its patch-path contract is now extended narrowly: both files are explicitly allowed **and required**, preserving the closed allowlist rather than weakening scope validation. Remote `test_candidate_pipeline.py` verification: **21 passed**.

## Final no-sudo installed acceptance — 2026-08-23

The owner-requested removal of `deck-sudo` from the Systray → Subrouter migration path is complete and installed on workstation `e14`.

Final landed/deployed source:

- `d6aa39344f1d26c04f1b2ff4c8aa8b2db0c19e9c` on `origin/main` and canonical deploy;
- includes `fec7a5b3b` process-group cancellation and `2784ff660` JITless Codex child support;
- installed Systray `SubrouterMigrationAdmin` contains no `deck-sudo`, `runuser`, or `migration-provision` runtime invocation.

Final sealed Subrouter candidate:

- candidate digest `ba375c0355b9cbf6c8640e02dc247add763cd58e8ea534500f49019c624444fd`;
- source/base commit `d6aa39344f1d26c04f1b2ff4c8aa8b2db0c19e9c`;
- binary SHA-256 `8799ce21b0839302dfc171375b35a2531aadfe44b6715c2c078adb4ea77bab48`;
- patch SHA-256 `3675c083df998f3c7615b92e19639266d3ba3c448e05180ba62b47627fb9810c`;
- official focused/static/module/full phases all exit `0` on registered worker `debian1`;
- installed release is `/var/lib/overdeck/subrouter/releases/ba375c0355b9cbf6c8640e02dc247add763cd58e8ea534500f49019c624444fd`.

Installed security/runtime acceptance:

- workstation client token: mode `0600`, owned by `user:user`; service token: mode `0600`, owned by `overdeck-subrouter:overdeck-subrouter`; copies match without exposing token content;
- unauthenticated loopback admin request returns `404`; authenticated request reaches the endpoint (`409` for absent/native `multideal`);
- Subrouter hardening remains enabled, including `NoNewPrivileges=yes`, `ProtectSystem=strict`, `ProtectHome=yes`, `PrivateTmp=yes`, and `MemoryDenyWriteExecute=yes`;
- exactly one workstation Subrouter process is active and only `127.0.0.1:31415` is listening; no `31416` listener;
- `multideal` is still Native; authority status is **0 provider credentials / 0 routes / 0 grants / 0 unresolved attempts / Gateway ready**.

Live no-sudo device-auth/cancellation proof:

- a real `multideal` migration request reaches `https://auth.openai.com/codex/device`;
- no V8/JIT fatal occurs under the hardened service;
- no legacy privilege-bridge text/path is used;
- cancelling the Systray request removes both wrapper and native Codex login processes after the process-group fix;
- cancellation leaves no Systray authority material and no Subrouter credential/route/grant residue;
- the single authority service remains healthy after cancellation.

The `overdeck-vm` connector was unavailable at transport level during the final recheck, so no fresh VM service-state assertion is recorded here. Earlier live inspection showed its Subrouter and Systray services inactive. The final workstation proof independently confirms the only active authority listener on the runtime target is loopback `127.0.0.1:31415`.

Implementation acceptance is complete. A successful real account migration beyond the device-auth boundary requires the owner to complete the provider's interactive authorization; that human authorization is deliberately outside autonomous acceptance and does not require or reintroduce a privilege bridge.

## Live migration post-login handoff correction — 2026-08-23

The owner completed real Codex device authorization for `multideal`. Provider login itself succeeded, but the post-login migration initially stopped at `Gateway: migration required`. Live inspection isolated two independent handoff defects after the successful authority-owned login:

1. Systray's `authority-material/` and provider parent directories pre-existed at mode `0775`. `Path.mkdir(..., mode=0700, exist_ok=True)` does not tighten existing directories, while the protected grant reader correctly rejects group/world-writable parents. The live owner-owned parents were tightened to `0700`; source now opens existing parent directories with `O_NOFOLLOW`, verifies ownership/type, and forces exact `0700` before writing route/grant material.
2. Subrouter persisted the new authority credential successfully, but the running authority service retained its pre-login `AccountRef` snapshot. `authority-account status` therefore reported the new account `ready` while the exact route status still reported `migration-required`. Restarting the single workstation Subrouter proved the stored credential/route/grant were valid: the exact route immediately became `ready`. Source now reloads the existing in-process `AccountRef` immediately after authority credential provision/removal and advances scheduler generation; reload failure compensates the credential mutation instead of publishing a half-complete migration.

Live repair preserved the successful provider authorization. No second login and no native-token copy were performed. The staged `multideal` migration was promoted after the exact route became ready, and current live Systray status is `state=active` with binding mode `subrouter`, authority `subrouter-primary`, provider `codex`, and `quiesced=false`.

Runtime proof during repair:

- exactly one workstation Subrouter process remained active;
- only `127.0.0.1:31415` listened; no `31416` listener existed;
- `overdeck-vm` was rechecked and `overdeck-subrouter.service` remained `inactive/dead`;
- live authority-material parent modes are exact `0700`;
- workstation `systray-ai.service` and `overdeck-subrouter.service` are active.

Regression/verification evidence before final commit:

- Systray authority-material regression: **9 passed** remotely;
- combined Gateway Systray run: **16 passed, 3 failed**, where the same three failures are the previously documented protected-`/tmp` buildbox baseline and do not touch this change;
- targeted Subrouter authority/admin route-reload tests plus `go vet ./cmd/subrouter`: PASS on `debian1`;
- the new admin regression provisions through the real handler callback and immediately resolves the exact route from the refreshed in-memory snapshot as `ready`, proving no service restart is required;
- reload-failure compensation test proves account/route/grant residue is not left behind;
- official authenticated `verify-candidate` focused/static/module/full: all PASS for the pre-commit exact tree; sealed candidate `edf702501dca4037344e533ccce50f0e34a04824eb254ea30022dd58b68d9756`.

Final completion steps for this correction:

1. commit this exact source/patch/plan state;
2. rerun official focused/static/module/full so candidate provenance binds to the final commit;
3. land to `origin/main`, deploy canonical source, install the exact committed-tree Subrouter candidate, and restart only required workstation services;
4. verify `multideal` remains active after upgrade, exact route status is ready, one local Subrouter remains on `127.0.0.1:31415`, VM Subrouter remains inactive, and no second authorization is requested.

### Trusted live-authority upgrade follow-up — 2026-08-23

Final installation planning exposed one additional lifecycle rule that only becomes relevant after the first successful real migration: the original S1 installer intentionally refused any state with provider credentials. With `multideal` now legitimately Gateway-active, that bootstrap-only rule would prevent every subsequent Subrouter upgrade.

The installer now distinguishes bootstrap from trusted upgrade:

- **first install:** still requires provider credential count `0` and refuses pre-existing provider credential material;
- **trusted upgrade:** requires `current` to be an actual symlink into the release root and the referenced release to pass full immutable provenance + authority-schema validation before existing provider credentials are accepted;
- provider-tree validation is metadata-only: known Codex/Claude provider roots must contain only service-owned `0700` directories and service-owned, single-link `0600` regular files; symlinks/special files remain refused and installer code never reads credential contents;
- candidate `authority-status`, executed as the service identity in the isolated environment, must report zero unresolved attempts;
- provider credential, authority route, and proxy grant counts are captured before restart and must match exactly after candidate activation; `Gateway: ready` and the store lease must remain healthy;
- rollback still restores the provenance-valid previous runtime if activation/readiness fails.

Live pre-upgrade authority metadata is **1 provider credential / 1 route / 1 grant / 0 unresolved attempts / Gateway ready**. The provider store itself is service-owned with `0700` directories and `0600` single-link files. No credential contents were inspected while deriving this policy.

Remote candidate-pipeline + installer service-contract verification after the change: **46 passed**. Coverage includes bootstrap refusal, trusted upgrade preserving synthetic credential bytes untouched with `1/1/1` counts, trusted-upgrade symlink refusal, exact-count preservation logic, idempotency, activation rollback, provenance rejection, and authority-schema rollback constraints.

## Persistent workstation grant renewal — 2026-08-23

Post-upgrade exact-route verification found `multideal` still registry-active but the route returned HTTP `401`. This was **not** an upgrade mutation: the local `proxy.key` SHA-256 exactly matched the authority store's grant `key_hash`, route/grant counts remained `1/1`, and the stored grant had simply reached its original one-hour `expires_at` (`2026-08-23T07:08:44Z`). A persistent Gateway account therefore became unusable roughly one hour after successful migration.

Correction in `wt/gateway-grant-renewal` keeps short-lived authority grants without requiring another provider login or copying/rotating provider credentials:

- initial workstation proxy grants use a 24-hour TTL instead of one hour;
- the authority store can renew **only the exact non-revoked grant identified by route ID + SHA-256 key hash**; renewal changes expiry in-place and does not change route/grant counts;
- the authenticated loopback admin API exposes `renew-grant` and accepts only `route_id` plus a 64-hex `grant_hash`; the proxy key itself is never transmitted to the admin endpoint;
- Systray computes `SHA-256(proxy key)` from its already-protected local grant file, renews only `subrouter-primary` at exact local origin `http://127.0.0.1:31415`, and never sends the workstation admin token to another authority;
- an HTTP `401` from the exact route triggers one hash-based renewal and one status retry; a wrong/revoked key cannot be renewed because its hash does not match a non-revoked route grant;
- healthy local grants are proactively renewed when less than one hour remains, avoiding normal launch/health downtime;
- renewal keeps the same local proxy key, so no key-file rewrite race and no launcher environment/key-snapshot change is introduced.

Verification before commit:

- pinned-upstream Go renewal/store + admin endpoint tests and `go vet ./internal/authority ./cmd/subrouter`: PASS on `debian1`;
- Systray `test_authority_client.py` under the same protected buildbox test-root policy as the official verifier: **29 passed**;
- adjacent `test_command_router_authority.py` + `test_provider_services.py`: **15 passed**;
- hash-only HTTP regression proves the clear proxy key is absent from the renewal URL/body while the SHA-256 hash is present;
- a rejected non-local authority fails closed without invoking the local admin-renewal path;
- expired/rejected and near-expiry renewal regressions prove exactly one renewal followed by a successful status retry.

The currently installed pre-renewal candidate remains healthy as an authority service with `1 credential / 1 route / 1 grant / 0 unresolved attempts`, but the old grant is expired and exact route health is unavailable until this renewal-capable candidate is installed. The live provider authorization is preserved; no second provider login is required.

### Live grant-renewal acceptance — 2026-08-23

Renewal commit `0d663fa477bb3872f39861e0439ff4db8e411d6a` passed the official authenticated Subrouter candidate pipeline (`focused`, `static`, `module`, `full`, all exit `0` on `debian1`) and sealed candidate `6e360de2179ec823dc48917784a2e86c286219e594f69af89355c08060de7e3c` with patch SHA-256 `3272b7c6c3d1e35e496a43358bec7c6fbdaeaa2371e6aae7a1620e9787e17a23`. The commit was direct-landed to `origin/main`, canonical source deployed, and that exact candidate installed through the trusted-upgrade path.

Live acceptance used the already-expired `multideal` workstation grant; no synthetic replacement and no second provider login were used. Before renewal the stored grant expired at `2026-08-23T07:08:44.973790645Z`. After restarting only Systray onto the deployed client, an exact Gateway status request automatically renewed the same grant in place to `2026-08-24T07:40:58.053656818Z` and returned `state=ready`, `detail=Gateway: ready`.

The renewal preserved the same grant ID, route ID, and SHA-256 key hash. Authority counts remained exactly **1 provider credential / 1 route / 1 grant / 0 unresolved attempts** and `Gateway: ready`; there was no provider-login or migration process. `multideal` remains registry `state=active`, binding mode `subrouter`, authority `subrouter-primary`, provider `codex`, `quiesced=false`.

Final no-model-turn launcher acceptance:

- `cdx --account multideal --status --json` => `status=ok`, `detail=Gateway: ready`;
- `cdx route --account multideal` => explicit CLI chain `multideal`;
- workstation `systray-ai.service` active;
- workstation `overdeck-subrouter.service` active with exactly one `serve` process;
- only `127.0.0.1:31415` listens; no `31416` listener;
- Subrouter hardening remains `PrivateTmp=yes`, `ProtectHome=yes`, `ProtectSystem=strict`, `NoNewPrivileges=yes`, `MemoryDenyWriteExecute=yes`;
- `overdeck-vm` `overdeck-subrouter.service` rechecked `inactive/dead`.

This closes the live `multideal` Gateway migration and persistent local-grant lifecycle acceptance. The owner can migrate additional native accounts through the same Systray flow; each provider login remains an explicit interactive provider authorization, while subsequent local workstation-grant renewal is automatic and does not require reauthentication.
