# Self-hosted Subrouter authority S0 — request

**Status:** COMPLETED — S0 landed, installed on loopback, and proved credential-empty; S1 continues in `2026-08-18-subrouter-authority-s1.md`.

**Primary design:** `docs/specs/2026-08-17-subrouter-authority-design.md`

**Goal:** Install a credential-empty self-hosted Subrouter dark candidate whose rotating-refresh transaction remains single-writer and recoverable across concurrency, caller cancellation, persistence failure, and process death, while mechanically preventing access to native owner credentials.

## Context

The incident in `docs/reports/2026-08-17-zync2-codex-token-invalidated.md` proved that copied rotating grants are not safe. `docs/plans/2026-08-17-codex-grant-single-writer.md` correctly requires one refresh-token owner, but its custom-authority and copy-reconciliation slices are replaced by Subrouter. Its already useful registry, lifecycle, and redacted-transition work remains follow-on input to S1.

The pinned self-hosted Subrouter revision has a safe lock-through-refresh Codex path, but its Claude store releases the credential lock during provider I/O. Neither provider has a durable pre-send marker that survives process death after a rotating token may have been consumed. Upstream also discovers native local state by default. No provider credential may enter the candidate in this request.

This request implements only design slice S0. S1 exact routes/dark Systray bindings, S2 live expendable canary, S3 authenticated tailnet edge, S4 remote seat grants, S5 account migration, and S6 retirement remain out of scope and must not start before S0 is landed, deployed, and installed-proven.

## Files

- Create `modules/subrouter/upstream.json` — machine-readable upstream URL, release, revision, canonical archive command, archive SHA-256, patch path, and required Go toolchain.
- Create `modules/subrouter/patches/0001-overdeck-authority-safety.patch` — bounded patch against the exact pinned Subrouter source; provider protocol and forwarding remain upstream-owned.
- Create `modules/subrouter/bin/verify-candidate` — fail-closed focused/static/full verifier and remote-build-compatible candidate producer.
- Create `modules/subrouter/bin/install-candidate` — root-only immutable installer invoked through `deck-sudo`; validates a completed exact-revision receipt before changing runtime state.
- Create `modules/subrouter/systemd/overdeck-subrouter.service` — root-owned system service running as the dedicated non-login identity.
- Create `modules/subrouter/tests/conftest.py` — record the successful external-temp-root module contract phase for the exact-tree receipt without rerunning it in the broad phase.
- Create `modules/subrouter/tests/test_candidate_pipeline.py` — manifest, archive, patch, receipt, immutable-artifact, and failure-path contract tests.
- Create `modules/subrouter/tests/test_service_contract.py` — unit/install/service-isolation/status contract tests without root or live credentials.
- Create `modules/subrouter/README.md` — operator contract, exact verification/install/status commands, rollback, and explicit no-credential/no-backup rules.

The patch modifies only these upstream paths; any additional upstream path is a scope blocker that must return to this request contract before code changes:

- `internal/accounts/refresh_attempt.go` and `internal/accounts/refresh_attempt_test.go` — shared durable attempt record and recovery classification.
- `internal/accounts/codex_auth.go` and `internal/accounts/codex_auth_test.go` — Codex mutation transaction integration.
- `internal/agents/claude/store.go` and `internal/agents/claude/store_test.go` — Claude lock-through-I/O transaction integration.
- `internal/accounts/store_lease.go` and `internal/accounts/store_lease_test.go` — lifetime exclusive store ownership.
- `cmd/subrouter/authority_status.go`, `cmd/subrouter/authority_status_test.go`, and `cmd/subrouter/main.go` — `--authority-mode` and sanitized installed status.

## Pinned source contract

`modules/subrouter/upstream.json` must encode these literals:

```text
repository: https://github.com/manaflow-ai/subrouter.git
release: v0.1.81
revision: 29c7ebb306ac54739206f4752449e437047dd150
archive_command: git archive --format=tar 29c7ebb306ac54739206f4752449e437047dd150
archive_sha256: 74ab8da37d467a7d36bb73b11d82d90fd5ea14f3b75c535026adb0e45d9529f6
go: 1.24.0
patch: modules/subrouter/patches/0001-overdeck-authority-safety.patch
```

The verifier must clone/fetch into a run-owned path under `${XDG_CACHE_HOME:-$HOME/.cache}/overdeck/tests/subrouter/<run-id>`, archive exactly the pinned revision, verify the tar-stream digest before extraction, apply the patch with zero fuzz/offset, and refuse a dirty or mismatched source. Mutable test state never lives under the repository or another worktree.

Remote execution may return only the final content-addressed binary, manifest, logs, and atomic receipt under `.local/subrouter-artifacts/<candidate-digest>/`; no mutable build tree, Go cache, or test state is pulled into the checkout. The artifact directory is immutable after receipt completion and is never committed.

## Refresh transaction contract

The patch adds the following data seam without token bytes or provider response bodies:

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

Every Claude and Codex refresh entry point must obey one transaction:

1. acquire the cross-process account mutation lock;
2. preflight credential and parent-directory writability plus required free space;
3. reread the durable credential and decide refresh is still required;
4. atomically write and fsync a `prepared` marker for that exact generation/fingerprint, then fsync its parent directory;
5. once provider send may begin, run under an authority-owned bounded context that is not canceled when callers stop waiting;
6. atomically persist and fsync the successful credential at generation + 1 before any caller can use it;
7. remove the marker and fsync the parent directory only after durable success.

Concurrent callers wait, reread, and receive the first transaction's result; they never make a second provider call for the same generation.

Startup recovery under the same mutation lock must:

- freeze an account when an unresolved marker names the still-current generation;
- clear a marker only when the durable credential generation has advanced;
- refuse automatic retry of terminal, transport, malformed-success, authority-timeout-after-send, process-death, or persistence-ambiguous outcomes;
- clear a block only after a fresh supported login has durably installed a new generation.

The patch must preserve Codex's existing lock-through-refresh guarantee and strengthen Claude to the same contract. It must not add any OAuth refresh POST to Overdeck code.

## Authority-mode and singleton contract

`subrouter serve --authority-mode` must:

- require an explicit state directory;
- acquire one lifetime exclusive store lease before reading account state and hold it until process exit;
- fail readiness and exit nonzero when another process owns the store;
- disable legacy migration, native credential synchronization, periodic Codex/OpenCode/pi switching, account import, transcripts, and any implicit owner-home discovery;
- expose only existing unauthenticated liveness/readiness on loopback;
- provide `sr authority-status` with sanitized fields: upstream revision, source digest, patch digest, service identity, store-lease state, provider credential count, unresolved attempt count, and literal owner state `Gateway: ready` or `Gateway: unavailable`;
- never print a token, token fingerprint with reversible input, Authorization value, OAuth body, full credential path, or native-account label.

No exact-route, proxy-grant, tenant mutation, remote listener, live login, or provider account behavior is added in S0.

## Installer and service contract

The only accepted runtime layout is:

```text
service user: overdeck-subrouter (non-login)
release root: /var/lib/overdeck/subrouter/releases/<candidate-digest>
active link: /var/lib/overdeck/subrouter/current
state: /var/lib/overdeck/subrouter/state
unit: /etc/systemd/system/overdeck-subrouter.service
listener: 127.0.0.1:31415
```

`install-candidate` must verify the candidate digest, upstream/source/patch fields, toolchain, focused/static/full commands and exits, exact dirty-tree diff hash, worker identity, isolated temp root, logs, elapsed times, and atomic receipt completion before installation. It creates a root-owned read-only release, atomically updates `current`, preserves the prior link for rollback, creates only the dedicated state ownership, installs the repository unit, and restarts through systemd. Re-running the same candidate is idempotent. A failure before the link swap leaves the current runtime untouched; a failed readiness after swap restores the prior link and service.

The unit must set an isolated HOME/XDG tree inside the dedicated state path and include at least:

```text
User=overdeck-subrouter
Group=overdeck-subrouter
ProtectHome=yes
ProtectSystem=strict
NoNewPrivileges=yes
PrivateTmp=yes
ReadOnlyPaths=/var/lib/overdeck/subrouter/releases
ReadWritePaths=/var/lib/overdeck/subrouter/state
```

It starts only `current/bin/subrouter serve --authority-mode` on loopback with switching disabled. The service user cannot read the invoking owner's Claude, Codex, Claudex, Systray account, or SSH homes. Provider credential state is excluded from backup/snapshot automation; disaster recovery is fresh supported login in a later slice.

## Receipt contract

`verify-candidate` exposes phases `focused`, `static`, and `full`, plus a read-only `path` query. `path` prints exactly one absolute path to the valid content-addressed artifact for the current tree/manifest/patch or exits nonzero; it never chooses an artifact for different bytes. Focused runs the named refresh/store-lease tests. Static verifies JSON, patch applicability, Go formatting/vet for touched packages, shell syntax, systemd unit syntax, and `git diff --check`. The external module-contract pytest run records its successful command, toolchain, timing, worker, log, exit, and external `--basetemp` against the same input digest. Full runs the pinned source's complete `go test ./...`, builds the binary, and writes the receipt only after focused + static + module-contract + full all passed for identical bytes.

The atomic receipt records exact source revision/archive/patch/diff hashes, commands, start/end/elapsed, Go/tool versions, dependency state, worker identity, temp root, logs, exit codes, and final binary digest. A dirty Overdeck tree is keyed by base commit plus deterministic diff hash. Any byte change invalidates it. Deployment reuses a valid exact receipt and does not rerun the broad suite; installed proof still runs.

Fixture tests use local fake OAuth servers and synthetic tokens only. They must prove:

- N simultaneous refresh callers cause one provider request and one generation advance;
- cancellation of every waiting caller after send does not cancel the authority-owned transaction;
- a second process cannot acquire the same store lease;
- process death before marker, after marker/before send, after send/before response, after response/before credential fsync, and after credential fsync/before marker removal produces the specified restart state;
- an old generation plus unresolved marker is blocked with zero retry calls;
- an advanced generation plus old marker recovers without a provider call;
- terminal, lost-response, malformed-success, and persistence failures never retry the old chain;
- no fixture secret appears in stdout, stderr, logs, receipt, status, or repository files.

Process/deadline tests use injected clocks and fault hooks. Keep one bounded real subprocess test for signal/death/restart wiring; never shorten production timeouts or sleep to make tests pass.

## Owner-obtainable evidence

Before installation, record bounded fingerprints and metadata—not contents—for every native Claude/Codex credential file the service isolation test targets. After installation:

- `systemctl status overdeck-subrouter.service --no-pager` reports active under `overdeck-subrouter`;
- installed `sr authority-status` reports the exact revision/digests, singleton lease, `0 provider credentials`, `0 unresolved attempts`, and `Gateway: ready`;
- a second daemon against the installed state refuses;
- the exact-revision receipt reports all fault/concurrency cases green;
- bounded after-fingerprints match the before-fingerprints exactly;
- the service has no read access to owner credential homes.

This is enabling-only S0. It unlocks safe server-owned onboarding and exact routes in S1/S2; it does not claim a live provider canary.

## Out of scope

- Provider login, import, migration, refresh forcing, or model calls.
- Reading, copying, reconciling, backing up, restoring, or deleting any live provider credential.
- Exact routes, proxy grants, Systray binding changes, buildbox delivery, tailnet exposure, or Claudex copy retirement.
- Cloudflare mode, Sub2API, active-active service, public Internet listener, transcript recording, or application-layer credential encryption.
- Publishing a Subrouter fork or pull request.
- Refactoring `remote-build.mjs`, the Factory, account registry, command router, health UI, or seat credential machinery.

## Acceptance

Run in this order, with unique run-owned temp roots outside `/home/user/Projects`:

1. Focused: `modules/subrouter/bin/verify-candidate focused`
   - PASS: all named refresh journal, concurrency, cancellation, crash-recovery, and store-lease tests pass against the pinned patched source.
2. Static: `modules/subrouter/bin/verify-candidate static`
   - PASS: manifest/digest/patch, Go fmt/vet, shell, unit, JSON, and diff checks are clean.
3. Module contract: `RUN_ID="$(date +%s)-$$"; TMP_ROOT="${XDG_CACHE_HOME:-$HOME/.cache}/overdeck/tests/subrouter-pytest/$RUN_ID"; mkdir -p "$TMP_ROOT"; python3 -m pytest modules/subrouter/tests/ -q --basetemp "$TMP_ROOT"`
   - PASS: installer, receipt, isolation, rollback, and status contracts pass without root/live credentials.
4. Broad/build: `modules/subrouter/bin/verify-candidate full`
   - PASS: complete pinned patched `go test ./...`, binary build, artifact digest, and atomic exact-revision receipt succeed once for identical bytes on a registered buildbox.
5. Installed candidate: `CANDIDATE="$(modules/subrouter/bin/verify-candidate path)"; deck-sudo modules/subrouter/bin/install-candidate "$CANDIDATE"`
   - PASS: installation and readiness complete from the exact receipted artifact.
6. Installed evidence: `deck-sudo /usr/sbin/runuser --user overdeck-subrouter -- /usr/bin/env -i OVERDECK_SUBROUTER_SERVICE_IDENTITY=overdeck-subrouter /var/lib/overdeck/subrouter/current/bin/subrouter authority-status --state-dir /var/lib/overdeck/subrouter/state`
   - PASS: the owner-obtainable status/isolation/singleton evidence matches the contract and native credential fingerprints are unchanged.

No live provider probe is part of this request.

## Receipt location

The current exact-tree result is authoritative only when `modules/subrouter/bin/verify-candidate path` succeeds. Build and installed evidence are not edited into this request because changing this file intentionally invalidates the candidate digest.
