EXIT=0 AN OWNED SHELL OBSERVES ITS OWNER'S ATTACHMENT STATE AS AN ATTACHMENT FRAME ON THE EXISTING SHELL-LINK EVENT STREAM (releases#44). The SIBLING of REQ-ACTIVITY-LINK-PUSH in every structural respect, and deliberately so: link-scoped (the owner is implied by the link token, no new verb and no addressing surface), drive-class semantics -- ephemeral, latest-wins, CURRENT-STATE-CARRYING, so a redundant same-state resend is a harmless no-op the consumer derives edges from -- NEVER spooled and NEVER replayed, because a stale attachment is actively wrong rather than merely old. Current state is emitted on EVERY link establishment and re-link, which is what makes restart resync free. Bounded observation, sub-second class, never hard-real-time. IT GETS ITS OWN DriveHub SLOT CLASS: latest-wins is only correct WITHIN a class that supersedes itself, so an attachment push must never be able to eat an undelivered drive command or an activity frame, nor they it. WHAT THE FRAME CARRIES: whether a controller is attached and from which NODE, how many read-only viewers are attached and from which NODES, and which node CHANGED on this edge. THE VIEWER NODE LIST IS RETAINED IN-MEMORY ON THE BROKER'S ViewerSink FOR FRAME COMPOSITION ONLY, and that bound is part of the requirement rather than an implementation note: THE DURABLE CONTROL TRIPLE (driven_by / controlled / viewer_count) IS UNCHANGED, and durable viewer identity, if it is ever wanted, is ITS OWN REQUEST (kin: the register's IR-72 custody-surface gap -- noted in a comment, never built toward). The measurement that justifies retaining it at all: the viewer's origin node ALREADY REACHES the broker on the attach path (attach.rs attach_as_viewer_snap -> brain.rs subscribe_with `by`) and is DROPPED on the floor by a ViewerSink that keeps only tx/send/evicted/writer -- reachable-and-discarded on the correct write path, which is materially different from a source that was never built in the arm at all (releases#11b) and is why this is a retention rather than a board request. Gate: int -- a live shell link sees an attachment frame on controller attach and detach and on viewer attach and detach, naming the changed node; a re-link re-emits current state; and an attachment change during link-down is represented by the re-link's current-state emit, never by a replayed backlog. [OK] REQ-BROKER-EXIT-EVERY-REAP required: [impl, int] stages: -doc +impl -unit +int PORTER (releases#201, todlando mechanism pass 2026-08-21): the broker exit waiter emits a terminal KIND_EXIT for EVERY reap it performs, INCLUDING the reap whose PtySession::wait() yields Err. This requirement states EXISTENCE; REQ-BROKER-OUTPUT-BEFORE-EXIT states ORDERING — the two are separate seams over the same frame, and any Exit this requirement adds is bound by that one's exit-behind-output enqueue discipline. TODAY broker.rs's `if let Ok(status) = status` gates the WHOLE Exit emission block, and only that block: the row removal, the best-effort kill, the drain bytes_forwarded quiesce and the reap-time unlatch (stamp_reaped, REQ-HAZARD-CONTROL-STAMP-LIFETIME) all sit OUTSIDE it and all still run on the Err arm (measured at b88fab2a: block spans broker.rs 6430-6457, stamp_reaped at 6467). So the session is genuinely, completely gone from the table AND the perch is unlatched, while NO Exit frame ever reaches the controller or viewer sinks — the attached rc has no terminal event to act on and burns its full 30s reconnect window over a session that can never come back. The Err arm is reachable in the field, not only under injection: PtySession::wait (spt-term/src/pty.rs:203) is `self.child.lock().unwrap().wait().map_err(SurfaceError::Io)`, so ANY Io error on the child handle takes it — the kill-vs-natural reap race included, where our own kill() and the child's natural exit contend for the same handle. AN ABSENT EXIT CODE IS NOT A ZERO AND IT IS NOT A NO-RECORD (the absent-is-not-zero discipline, releases#197): ExitEvent.code is ALREADY Option, so the Err arm can name the death with the code ABSENT rather than fabricating a 0 or suppressing the frame — a suppressed frame is indistinguishable to the viewer from a session still running. Gate: impl — Exit enqueued on BOTH arms of the wait outcome, code absent-but-named on Err, still behind the drain quiesce; unit — the event built for an Err reap carries code: None and is not skipped; int — a NARROW fault valve makes wait() yield Err while row removal, drain quiesce and stamp_reaped ALL still run (the existing SPT_TEST_EXIT_WAITER_SUPPRESSED is precedent for the shape but suppresses the ENTIRE waiter, so it does NOT reproduce this face) => the attached controller and viewer OBSERVE an Exit frame, with the valve-off control arm proving the same panel observes the healthy Exit (non-vacuity: a panel no assertion depends on is untested by construction); doc — KNOWN-HAZARDS. [OK] REQ-BROKER-OUTPUT-BEFORE-EXIT required: [doc, impl, unit, int] stages: +doc +impl +unit +int RC-RENDER-TRUTH W3 (ADR-0043 decision 1, hertz stale-glyphs RCA leg 1 P0): the PTY drain/output writer is the SOLE FIFO sequencer for terminal Output + Exit per attach sink — Exit is enqueued BEHIND all prior output (drain EOF/completion first, then Exit); the exit waiter never direct-writes KIND_EXIT around the queued output path (a mutex serializes bytes, not producer order). Kills the stranded-final-frame race (final EL/SGR-reset/cursor-show/?1049l lost when Exit overtakes Output — already admitted and compensated in the broker test suite, never fixed in production rc). Gate: impl — single sequencer, exit-behind-output enqueue; unit — ordering invariant on the writer queue (exit never precedes queued output for a sink); int — short-lived child emits 'XXXX ESC[2K ESC[?25h ESC[?1049l' then exits => that exact Output precedes Exit through the PRODUCTION broker->attach->rc path; doc — ADR-0043. CI (operator-ruled 2026-07-16, from PR #8 review; registry-only extension operator-approved 2026-07-27; superseded for main pushes by ADR-0050 on 2026-07-29): a PR whose ENTIRE diff is Markdown OUTSIDE docs-site/ and/or the declarative `traceable-reqs.toml` registry runs THIN CI — the heavy build/test jobs are skipped via a changed-files classifier job. `traceable-reqs.toml` has no Rust/product consumer; its load-bearing correctness gate is traceability. Invariants: the traceability gate ALWAYS runs (doc/registry tags and activated-stage coverage are load-bearing evidence, and a run with zero checks is unmergeable); any docs-site/ change runs the PR lint/unit/traceability lane; any other non-Markdown path runs that same lane. Main pushes also run thin CI only; the full evidence for main's exact SHA is the uncancelled golden run that produced it, and main advances only by fast-forward to that tested SHA. The classifier is plain git diff over the PR merge commit (HEAD^1..HEAD, fetch-depth 2) — no third-party changed-files action on the self-hosted runners. Skipped-required-check note: GitHub treats an if-skipped job as satisfying required status checks, and the classifier + traceability always report, so thin PRs stay mergeable. Every golden run PRINTS the toolchain that judged it, on BOTH OS legs, from inside the job. (INFRA-REGISTER IR-4 part 3, carrying IR-9; hertz 2026-08-03.) WHY A PRINT IS EVIDENCE AND A BOX IS NOT: both golden boxes own rustup-managed toolchains that drift independently, so 'did CI resolve this the way I did?' — the question the acaaa4f lock comparison needed answered — had NO answer in any run log. MEASURED on the two boxes' INTERACTIVE accounts 2026-08-03: hfenduleam (Windows leg) cargo/rustc 1.93.0, clippy 0.1.93; kitsubito (Linux leg) cargo/rustc 1.96.0, clippy 0.1.96 — three minor versions apart, which is IR-9's class with numbers on it. That measurement is a PRIOR, not the fact: a runner service account's toolchain is what judges a lane and an interactive shell on the same box does not prove it, which is precisely why the step prints from inside the job. Direction of the skew is deliberately NOT asserted — clippy renames and removes lints as well as adding them, so 'newer is stricter' is unmeasured. IR-9's docs half (align the boxes, or declare one authoritative clippy leg) is HELD by doyle's ruling of the same day: rule it once this step reports runner-account versions, rather than on the prior. STEP SHAPE: one `TOOLCHAIN ` line prefix on every line of both legs so a run's toolchain is one grep; cargo, rustc, and clippy versions are the load-bearing three and their absence reds the step; the rustup active-toolchain line is a convenience and its absence must NOT red a step whose whole job is to print — which needs a different construction per shell, and both were exercised before shipping rather than reasoned about. Windows: under GitHub's pwsh wrapper (`$ErrorActionPreference = 'stop'` + a trailing `$LASTEXITCODE` check) a MISSING COMMAND is a terminating error, so rustup's absence is TESTED with Get-Command rather than caught, and a present rustup's non-zero exit is cleared before the wrapper reads it. Linux: no `| head` anywhere — under `set -o pipefail` a producer SIGPIPEd by an early-exiting head surfaces as exit 141 and reds the print, so first-line trimming is parameter expansion. Gate: impl = the two `Toolchain versions` steps in golden.yml's `test` job (the job that builds, tests, and lints, so the one whose resolver and lint set a reader is asking about). Kin REQ-LOCK-TOUCHING-LANE-PROCEDURE (the procedure that sends an author here), REQ-CI-LOAD-BEARING-PATCH-PIN. THE CLI MUST HAVE ROOM TO BUILD ITS OWN ARGUMENT TREE, WITH MEASURED HEADROOM. clap's derive-built `Command` tree is constructed recursively at the top of every invocation and its cost scales with the argument population; `main` ran `cli::run()` on the MAIN thread, whose stack on Windows is the PE-header default (1 MiB, not settable at runtime), while a Rust-spawned thread's is explicit. IR-33 measured the tree within roughly SIX net new arguments of exhausting it: U1's eight hidden bool args made EVERY invocation die with 'thread main has overflowed its stack', exit -1073741571 (0xC00000FD), INCLUDING `--version` — before any of our own code ran. The failure face is why this is a requirement and not a tuning note: it presents as a totally broken binary that names no stack, and through REQ-DOCS-5's generator (which enumerates the tree by PARSING the binary's own root help) it presents instead as a silently gutted docs page that the drift check then compares to itself and blesses. FIX: run the CLI on an explicitly-sized thread, so the argument population stops being a budget the next verb-surface lane has to fit under. THE WRAPPER MUST BE FAITHFUL, WHICH IS THE WHOLE RISK OF THE CHANGE: a panic inside the CLI must still leave the process with the SAME face it had when the panic unwound out of main — the same message through the same hook, and the same nonzero exit code (101) — and an ordinary return must exit with the INNER run's code, not the join's. A spawn failure falls back to running inline and SAYS SO, because a resource blip must not turn into a dead CLI, and a silent fallback would restore the old ceiling invisibly. Gate: impl — the sized-thread wrapper in main.rs with its fidelity mapping and its loud fallback; unit — the wrapper driven with a returning closure and a PANICKING one, asserting the code passes through and the panic maps to 101 (the prod call site passes `cli::run` to the same function, so this is the real wrapper and not a parallel spelling); int — a smoke that the DEBUG-built binary answers `--help` with non-empty stdout at exit 0, which is the only assertion that would have caught IR-33 in its own build profile and also backstops the empty-emit generator above. W3 (LIFECYCLE-TRUTH, observability): broker + brain stderr tee to a rotating size-capped file under SPT_HOME (e.g. 2x5MB), stamped per generation. ROOT: detached daemon nulls stdio -> the 2026-07-06/07 incident window left ZERO logs (both RCAs ran blind; rigs had to recreate everything). KNOWN-HAZARDS note: never inherit handles (REQ-HAZARD-DETACHED-DAEMON-STDIO) — open the file in-process, don't pipe. The digest SNAPSHOT PULL crosses nodes: `spt endpoint digest <[subnet:]id@node>` (including `--after ` incremental polling) resolves and serves under the EXISTING address gate — the CROSS_NODE_M4 qualified-address refusal lifts for the pull verb only. The structured-delta stream stays node-local until a real cross-node subscriber exists (ADR-0048 decision 5, defer-until-consumer; operator-ruled 2026-07-24). Access policy is NOT new — fetch was ratified address-gated long ago; this is routing. Composition note: REQ-DIGEST-SEAL-ON-IDLE's stable seqs are what make remote --after polling trustworthy. Forcing customer: spt-mobile 'view live digest of ANY endpoint' (host proxies transparently once the refusal lifts). Gate: int — two-node rig: a qualified digest pull from the non-hosting node returns the hosting node's buffer; --after returns only rows past the cursor; the address gate still refuses a non-resolvable caller. A CLEANLY-OFFLINE ENGINE ROOM IS STILL BRINGABLE THROUGH ITS OWN CEREMONY: for the engine-room endpoint class with a bring-up code in hand, the rc offline fast-fail must not preempt the bring-up dispatch, because cleanly-offline is precisely the state whose ratified entry path IS a bring-up. Field-reported (releases#142, operator): after a harness exit the operator's third `spt rc engine-room` answered 'offline — nothing to attach to' for the same verb that had brought the room up on the first attempt, leaving the seat unrevivable through the only door it has. THE EXIT PATH IS CORRECT AND IS NOT WHAT CHANGES — a harness exit leaves the endpoint cleanly offline exactly as the ratified lifecycle entry requires (`mark_offline` → `terminal_normalize`); what is wrong is the reader's precedence, and the fix is a NARROWING of one gate rather than a change to any writer. REQ-HAZARD-RC-ATTACH-FAILFAST SURVIVES VERBATIM FOR THE GENERAL POPULATION and that survival is part of this requirement rather than a side note: the guard stops a real historical infinite-blank hang for every endpoint whose broker dance would end on a dead resolve, and the engine room is the one class whose dance does NOT end there — it would bring the seat up — so the guard's population was simply never narrowed to exclude the endpoint that has a revival path. Deleting the guard, or widening the exemption past (engine-room class AND a code presented), rebuilds that hang. THE ROW VALUE IS THE DISCRIMINATOR EVERY TEST MUST CARRY EXPLICITLY (hertz's one-field falsifier, ruled binding): a fixture whose `status` row happens to be ABSENT passes against this defect and proves nothing, so both arms — row PRESENT and offline, and the same fixture with the row cleared and nothing else changed — must be asserted, and they must reach the same outcome. RIDER, SAME LANE: every refusal arm reachable by the engine-room class names the ceremony surface (`spt rc `, which prompts for a member-or-admin code) as its remediation, never `spt endpoint run` — that verb is not the engine room's entry surface and the reserved-id refusal already turns it away, so the printed line sends the operator (or a reasoning agent, which will invent a route from a bare no) at the wrong door. The zombie arm carries the same class-correctness bug and is swept with it. Gate: doc — the CONTEXT.md engine-room lifecycle sentence (a harness exit leaves the endpoint cleanly offline and re-bringable through the same TOTP gate), ratified 2026-08-04 and tagged rather than re-authored; impl — the narrowed gate and the two class-correct refusal remediations; unit — the gate table carrying the row value EXPLICITLY in both arms (engine room + code reaches the bring-up dispatch with the offline row present, and again with it cleared), the general population still fast-failing on an offline row (a non-engine-room endpoint, and the engine room WITHOUT a code — the code-less reconnect re-drive must not ratchet the attempt ledger), and each swept refusal naming the ceremony surface rather than `endpoint run`. A daemon-hosted Psyche that spawns then EXITS IMMEDIATELY is a host failure, surfaced like a spawn failure (closes the v0.8.1 residual masking): the REQ-HAZARD-LIVEHOST-BOOT-RACE signal stamps `psyche_host_error` only when `spawn_psyche` returns Err, NOT when the detached spawn() returns Ok but the child dies within moments (e.g. a bad-argv child exiting 2 — the F-009 case). That leaves the residual 'online + no Psyche + no cause' gap: the nested `{id}-psyche` info.json is written status=online with a real-but-DEAD pid and the PARENT perch carries NO psyche_host_error (perri's F-010: tasklist showed 0 host procs across the window while info.json read online). The host MUST confirm RESIDENCY — a hosted child not alive (or whose `{id}-psyche` perch never re-registers / has a dead pid) within N seconds of spawn is treated as a host failure: stamp the parent perch `psyche_host_error{reason:"host not resident within s (psyche perch missing/dead pid)"}` (and do not leave a phantom online nested perch). Closes the last masking gap the v0.8.1 fix left open. perri's F-010 (v0.8.1 dogfood). Sibling of REQ-HAZARD-LIVEHOST-BOOT-RACE. THE SEND PATH RETURNS A NAMED OUTCOME, NOT AN EXIT CODE, AND THAT IS WHAT POPULATES NO_PERCH (booked as the W5 prerequisite, IO-PARSER #22). Before this, cmd_send_with_body answered 1 for a missing perch, a refused admission gate, an ambiguous target, an unconfirmed WAN hop and an empty body alike, so the DISPATCH_RESULTS accumulator could only record the coarse transitional NOT_DELIVERED arm and the ruled delivered/no-perch split was NOT DERIVABLE AT ALL — a report this lane handed the gater by name rather than approximating. The send path therefore names its own outcomes: DELIVERED covers SENT and QUEUED alike (both mean the message is the target's problem now), NO_PERCH covers every absence arm across all three delivery cascades — the local perch that is not listening, the WAN leg answering no-perch-remote, and the WAN registry answering not-found — and REFUSED carries the exit code for every outcome that is a refusal rather than an absence. THE EXIT CODE IS DERIVED FROM THE VERDICT AND NEVER THE OTHER WAY AROUND: the CLI wrapper maps the verdict to its process code so that the operator-facing contract is unchanged to the byte, while the in-process caller reads the name. AN ABSENCE IS NOT A REFUSAL, AND THE OPPOSITE READING IS THE ONE THIS EXISTS TO KILL: an author whose peer simply is not listening must not be told their message was refused, because those two facts call for different next actions. NO ROW ALREADY WRITTEN IS MIGRATED — the store is append-only, so previously-written NOT_DELIVERED rows stay exactly as written and the arm merely narrows for rows written from here on. A2 (F028, operator #2): the picker confirm-panel 'controlled locally' line renders for OFFLINE endpoints. view.rs:425-436 builds control_line from ep.controlled with NO status gate; an offline endpoint with a stale controlled stamp shows 'controlled locally' (operator screenshot: hall-a offline + controlled locally). RENDER HALF (this REQ): control_line MUST be empty when status != Online. The upstream STICKY-stamp half (stamp survives client SIGKILL >=5min) is B3/REQ-PRESENCE-CONTROL-REAP-ON-EXIT. FIX: gate the render. See triage A2(a). THE RECORDED PID MUST CARRY WHAT IT MEANS. `info.pid` has two incompatible meanings decided by write path, and every reader has been guessing: the `api listen` path records a process that GENUINELY HOLDS the endpoint (its death IS the endpoint's relay death), while the `api bind` path records the announcing CLI, which is EXPECTED TO EXIT within seconds and whose death means NOTHING — the hosting life it announced (a broker PTY session) is not recorded anywhere in the row. LIVE MEASUREMENT (todlando, HFENDULEAM 2026-07-27, the finding that forced this mint): todlando pid 22588 DEAD, doyle pid 45160 DEAD, deployah pid 29176 DEAD — all controllable=true, state=live_agent, status=online, and all three GENUINELY ALIVE AND WORKING (the measurement was taken by one of them, messaging another) — against hertz pid 11216 ALIVE and mobile-gw pid 46152 ALIVE, which are real `api listen` relays. The split is by adapter integration pattern (claude-spt binds and exits; omp-spt/mobile hold a listener), NOT by anything a reader can see in the record. CONSEQUENCE ALREADY PAID: two proposed fixes for the emphasys convergence gap were BOTH falsified pre-build on this fact — oracle-first convergence, and carry-forward scoped to 'earning pid alive' — each would have converged three live agents node-wide. Both were keyed on a pid whose meaning they could not read. FIX: stamp the role at the seam that writes the pid — `relay` on the listen path, `binder` on every bind path — never inferred at read time. Absent ⇒ legacy row ⇒ NO KNOWLEDGE, and every consumer fails toward alive (inheritance stands, convergence never fires); such rows heal at their next re-bind. Consumers re-key on it: relay-death convergence fires only on `relay`, and the controllable carry-forward drops a stamp only when a prior `relay` pid is provably Gone. CLASS: a claim keyed on the wrong thing (kin: `is_perch_alive` reading a status FIELD as hosting topology; the inherited capability stamp routing a liveness proof) — the cure is to make the record SAY the thing rather than have readers infer it. AUDIT RIDER (doyle, required in the doc stage since convergence re-keys on this field): enumerate which row classes reach the convergence branch under the new key — fresh NonAgent/None rows, shell instances, gateway listens — and pin the answer structurally, not just for tonight's node. Gate: doc — the record-shape doc carries the field, the two meanings it ends, the legacy/no-knowledge rule, and the audit-rider enumeration; impl — the field on InfoJson stamped from the entry path at the bind seam, plus both consumer re-keys; unit — the role-stamp table over the three hosting authorities, the carry-forward table incl. the claude-spt-shaped NEGATIVE (binder-role dead pid + Some(true) + listener re-bind ⇒ inheritance STANDS, row never routes to convergence), and the convergence role gate incl. the legacy-absent row; int — the synthetic emphasys template (relay-role + dead pid/parent + valid birth stamp + listener-only re-bind ⇒ derives fresh ⇒ routes to convergence ⇒ oracle Gone ⇒ converged) with a BrokerPty sibling that re-asserts fresh and is untouched. [OK] REQ-PRESENCE-CONTROL-REAP-ON-EXIT required: [impl, unit, int] stages: -doc +impl +unit +int PROJECT-INDEX W2 (ADR-0037): the daemon is the SOLE single-flight project-index writer: load persisted index at startup; ready WITHOUT warm (cold start = daemon ready + CLI fast before background completes); background reconcile with BATCHED complexity O(P+B+F+C) — enumerate branches ONCE, <=1 tree scan per changed branch, ONE derivation per distinct normalized cwd (in-process BranchStore traversal or fixed plumbing calls; backgrounding the existing 100+ process loop is REJECTED); atomic replace; last-known-good preserved on any failure; warm start with unchanged generation performs NO scan. Observability surface: generated time, source generation, pending refresh, last duration/error, endpoint/project/cwd counts, cache hits/misses, stale reads, repair count — index presence alone is not health. Gate: impl — writer + observability; unit — single-flight, atomic-replace, last-known-good, no-scan-on-unchanged-generation; int — cold+warm start legs against a real store; COMPLEXITY COUNTERS are the CI gate (wall-clock = manual acceptance ONLY, shared-runner flake class); doc — daemon docs writer-duty section. Kin REQ-PROJECT-INDEX-STORE, REQ-PROJECT-INDEX-INVALIDATION. [OK] REQ-PSYCHE-ACCOUNT-REFUSAL-EXIT required: [doc] stages: +doc -impl -unit -int RESERVED EXIT 96 — account/credential refusal from a psyche_resume turn: the inner tool refused for account-level reasons (spend/usage cap, expired/revoked credential, org quota) — session healthy, code healthy, retry correct-but-pointless until a HUMAN acts. Core discriminates on the EXIT CODE ALONE (text-blind, the exit-95 layering exactly: adapters own text matching because their inner tool's wording is theirs to track; core's contract survives any rewording). SEMANTICS ruled 2026-07-26: (1) OWN PACING, fully separate from the C3(b) strike budget — an account refusal fails FAST (refused before a billed turn), so ten near-instant cycles could exhaust the defect budget in seconds and kill the psyche host as a thrashing component while nothing thrashes; the strike budget is a DEFECT budget and an outage must not be able to spend it (fold-with-higher-threshold REFUSED at ruling: it keeps the bug in a quieter form). Slow capped exponential ~60s doubling to ~15m cap, held INDEFINITELY (no give-up: a cap clears on human action or a calendar boundary — unpredictable but CERTAIN — and a permanently-given-up psyche is invisible), reset on first success, no state to unwind. (2) DISTINCT SURFACE: never the defect-shaped PSYCHE_TURN_FAIL prefix — renders as its own class (adapter-side PSYCHE_TURN_REFUSED; core-side an additive class discriminator beside psyche_host_error, never a fold into it), NAMES the actor and action (a human, the inner tool's account — not the endpoint/adapter/session), carries the inner tool's own words via the dual-stream tail (REQ-PSYCHE-TURN-STREAM-EVIDENCE), and states HOW LONG it has been refused. (3) NEVER reseeds — custody is fine; a reseed would destroy a healthy transcript for nothing. (4) Never counted as a crashloop defect. Adapter contract half: claude-spt docs/design/RESERVED-EXIT-ACCOUNT-REFUSAL.md (appendix of observed CC strings is informative-not-contract, populated ONLY from observed bytes — the invented-string chain of 2026-07-26 is the anti-pattern it refuses). Gate at activation: unit — exit 96 classifies refused (no strike increment, no reseed, own backoff schedule engaged); 95/generic/None unchanged; surface renders the distinct label + duration. P-2 (WORKER-TRUTH triage addendum, perri-filed field finding 2026-07-06): the per-event psyche_resume spawn threads the perch record's CAPTURED read_env stamps into the spawn ENVIRONMENT — the F-027 Half-B env-parity contract (BINDING, design-frozen: read_env captured at creation + stamped on the record + threaded IDENTICALLY to every session spawn; the spawn never reads its own process env for a stamped var) extended to the psyche role the design predates. Field driver: flynn (claude-spt:ccs) — the ccs wrapper relocates the account root via CLAUDE_CONFIG_DIR at PARENT launch and the perch record correctly captured it, but the daemon spawns psyche_resume with bare env → default ~/.claude root → headless 'Not logged in' exit-1 → strike loop; psyche + parent land in DIFFERENT account roots (auth AND root-scoped continuity both break). Core stays harness-agnostic (threads whatever [env] direction=read captured — knows nothing of CLAUDE_CONFIG_DIR). Scope note: this is the URGENT psyche leg of F-027 Half B; the full pre_spawn seam + endpoint-session env threading stays design-parked (F-027-ENDPOINT-SPAWN-FAIL-DESIGN.md) unless operator pulls it forward. PUMP-TRUTH W1 (RE-SCOPED post round-2 empirical lock — the DIAL is EXONERATED, healthy ~100ms): a pump worker-leg PEER-REPLY read to a connect-then-silent / half-alive peer must drop THAT peer as an ORDINARY per-peer failure (peer_outcome's non-TimedOut arm -> PUMP_PEER_FAIL -> drop conn + redial, round CONTINUES, heartbeat advances), NEVER burn the brain's 30s PUMP_PEER_IO_TIMEOUT carrier deadline into a whole-round TimedOut POISON -> supervise_pump doubling-backoff restart. ROOT (deployah leg-instrumented capture, enlyzeam, 3 identical rounds): DIAL_EXIT 96ms ok, LEG i=3 update ms=30025 err[TimedOut] = the wedge. request_update (propagate.rs:373-375) opens the update stream + sends UpdRecord::Query (all bounded, all land), then BLOCKS read_event_until(deadline=call_deadline()=30s) on the peer's Offer/UpToDate reply; a peer that accepts the stream but never answers burns the full 30s -> TimedOut -> peer_outcome (pump/mod.rs:601) POISON -> whole-round abort + restart (= the field PEER_PUMP_FAIL: brain IPC read deadline, always-zero PUMP_PEER_FAIL). request_sync (sync.rs:374-376) is the LATENT TWIN (SKIPS the reply-read only when the want-set is empty; bites the moment it is non-empty against a silent peer). FIX (both legs): (a) reclassify the reply-read no-progress timeout OUT of TimedOut to a non-poison kind (Brain::read_peer_reply_until) so peer_outcome drops ONLY that peer -- poison RESERVED strictly for a genuine broker-IPC-CARRIER desync (the carrier ops net_open_stream/subscribe/send keep raw TimedOut); the abandoned peer stream is safe (exactly-once seq cursor stays contiguous, a late reply matches no live stream id). (b) budget-decouple the reply-read below 30s (Brain::reply_read_deadline = now + min(io_timeout, 10s)) so a silent peer drops promptly even in the still-sequential pre-W2 pump and can never race the carrier deadline. Files: propagate.rs (request_update) + sync.rs (request_sync) + brain.rs (reply_read_deadline + read_peer_reply_until) + pump/mod.rs (peer_outcome poison reserved for carrier-desync). Gate: a connect-then-silent peer at fan#0 -> the update leg drops it ordinarily within the reply-read budget, round continues + heartbeat advances, NO PEER_PUMP_RESTART; happy path (live peer) unchanged; + the sync-non-empty-want-set latent case. Kin REQ-PUMP-PEER-ISOLATION (W2 concurrency, VALIDATED by this root) + REQ-HAZARD-PUMP-IPC-DEADLINE (the poison it must stop mis-firing on a peer). ResidentService substrate (ADR-0049, design ratified 2026-07-26): a daemon-supervised binary an adapter declares via a `[service]` manifest section — core-owned from birth, NO perch/identity/address. SPAWN: the daemon launches it job-neutrally (detached_no_inherit + the cold-start ladder posture), so it is never a shell's child (`/T` tree-kill cannot reach it; the shell-descendant hazard class of REQ-SHELL-ADAPTER-OWNED-DETACHED-SERVICE never arises) and never inside a launching terminal's Job Object (the REQ-SHELL-CLI-SPAWN-JOB-EXPOSURE service half closes by construction). START TRIGGER declared in the manifest: start = 'boot' or start = 'bind'; supervised identically once running, with the wake-watcher scaffolding (backoff, give-up latch, one-per-instance lock, orphan-kill, brain-side reconcile) minus the offline-only flip. 'boot' is DESIRED-STATE-RUNNING, not an event: the supervisor reconciles a boot service toward running at EVERY opportunity — daemon boot, ADAPTER REGISTRATION while the daemon is live (operator addition 2026-07-26: installing or registering an adapter whose manifest declares a boot service starts it THEN — spt itself is never restarted to bring a new adapter's service up), update-hold release, and first shell bind as the defensive ensure. 'bind' starts only at the adapter's first shell bind. CARDINALITY: one supervised instance per registered adapter-option `[:profile]` (consumer-confirmed as COHERENT TARGET SHAPE — flynn's precision, 2026-07-26: not exercisable by the first consumer until per-option config dirs exist; alchemy today has one config dir and daemon.toml carries exactly one guild_id); the adapter may keep its own kernel file lock as a private double-start guard — core neither reads nor depends on it. PER-OPTION IDENTITY IS THREADED (flynn's gap, accepted): the supervisor passes the adapter-option name and the per-option runtime dir into the service's spawn environment, so an adapter can scope its private guard AND its config per option — the mechanism that makes two-options-two-services deliverable rather than merely permitted. Without it, two options resolving one adapter config dir produce the silent flap flynn derived: instance two exits immediately on the kernel lock, core sees only start-then-die, and crash-relaunch backoff is CORRECT behavior against that observation — two correct components disagreeing about the unit. UPDATE IS A FIRST-CLASS SUPERVISOR OPERATION WITH AN EXPLICIT HOLD: quiesce -> hold (stopped and NEVER relaunched while held) -> bits swap -> start new bits -> release; adapter update-apply performs this ordered operation; crash-relaunch with backoff applies ONLY when not held. The forcing case is structural, not advisory (flynn, argued against their own convenience): an eager relaunch during a swap re-pins the OLD exe mid-deploy (Windows exe lock), converting a diagnosable os-error-5 into an unwinnable race — if delivery must sequence, the hold ships FIRST and a dead service stays dead until told otherwise (the STALE-ONLINE no-spontaneous-relaunch ruling, same reason, one layer down). FAST-EXIT IS A CONFIGURATION FAULT, NOT A CRASH: consecutive immediate exits (exit within a startup threshold, N in a row) trip the give-up latch EARLY with a distinct loud STARTUP_FAULT diagnostic carrying the captured early stderr — a double-start lock conflict then reads as the configuration fault it is, never as a silently flapping service ground through backoff. QUIESCE IS COOPERATIVE EXIT + DEADLINE: the supervisor places a stop-request marker (a file in the service's runtime dir — polling services observe it on their next cycle; no inbox exists or is added); the service exits WHEN SAFE and the kernel-observed exit IS the ack — 'not ready' is expressed by not-yet-exiting, so no busy record exists to go stale in either direction; a manifest-declared grace deadline (default ~30s) bounds the wait, then force-kill. Delay possible, veto never. An OPTIONAL advisory status line may surface in service status display — never consulted for decisions. LIVENESS IS DERIVED, NEVER RECORDED: the supervisor is the parent and holds the child handle (exit is kernel-observed); no supervisor-maintained running-record exists (the v0.43.0 stale-online lesson applied one layer down — flynn's condition, structural here). Any status/version identity surface keeps the locked-file split lesson: never require reading a file the service holds an exclusive OS lock on (Windows). CLI INVOCATION CAPABILITY (consumer-blocking, non-negotiable per flynn): the supervisor threads the environment so the service can invoke the spt CLI (`spt send` et al., identityless cli@node from-label, durable spooling per ADR-0002) — if a supervised Hub cannot shell out to spt send, node-wide Watch delivery dies silently. ADDRESSING: none — a service needing a two-way agent-facing surface has one at its adapter's endpoint/shell layer (the alchemy layering argument that re-scoped ADR-0023's faceless-service rejection); AlwaysOnEndpoint (REQ-EP-8) = this substrate + the addressable front. Gate at activation (all legs against a MOCK service adapter — the gate never depends on the first consumer being the boot specimen): int — a manifest [service start='boot'] binary rises with the daemon job-neutrally, a registration of that manifest against an ALREADY-LIVE daemon starts the service without any restart, survives a shell teardown of the same adapter (tree-kill does not reach it), a held update swaps bits with zero relaunch races (hold observed under a concurrent crash), quiesce marker -> cooperative exit within grace, deadline -> force-kill on a wedged mock, a mock that exits instantly N consecutive times surfaces STARTUP_FAULT (not a backoff flap), and the service successfully invokes spt send from its supervised environment. ACTIVATED FOR W1 2026-07-26 WITH THE FOLLOWING BUILD RULINGS FOLDED IN (doyle; constraints live in the artifact, not the dispatch thread). VERB SURFACE: the operator-facing verbs are `spt adapter service list` (all registered options + derived state) and `spt adapter service status `, nested under the ADAPTER group — NOT a bare `spt service`. Reason: 'service' is ALREADY public surface carrying an unrelated meaning — the platform daemon-service abstraction (REQ-DAEMON-6/-8, `crates/spt-daemon/src/service.rs`: the systemd user unit vs the Windows at-logon task) surfaces in `spt daemon` help as 'registered OS service' / 'managed service' / 'the managing service label'. Ownership-scoping separates the two meanings permanently: the OS-service is the DAEMON's and lives under `spt daemon`; the resident service is the ADAPTER's and lives under `spt adapter`. ADR-0049's 'service status display' means `spt adapter service status`; the W1 PR carries a one-line ADR errata note. GIVE-UP LATCH SCOPE: the latch suppresses relaunch grinding until something plausibly changed — it is NOT a durable verdict. Cleared by exactly three events: (1) DAEMON BOOT — desired-state-running enumerates boot as a reconcile opportunity with NO latch exception, and the re-trip is bounded (N fast exits) and LOUD (STARTUP_FAULT re-fires each boot); an in-memory per-daemon-lifetime latch is an acceptable implementation, and if the durable-marker scaffolding is reused then boot clears the marker; (2) ADAPTER RE-REGISTRATION — declared intent that manifest/config changed, reconciling immediately; (3) UPDATE-HOLD RELEASE — new bits invalidate the fault evidence. NOT cleared by the first-shell-bind ensure: a bind changes nothing about the service's config, so the bind-time reconcile SKIPS latched services — otherwise ordinary shell use converts the latch into the very flap it exists to stop. The REJECTED alternative is recorded deliberately: a durable latch with explicit-clear-only leaves an operator's already-fixed config fault sitting behind a service that stays dead and quiet forever — loud-bounded beats quiet-permanent (the heal-assigns-owner-never-closes shape one layer down). SANCTIONED BUT NOT W1-REQUIRED: `spt adapter service restart ` as the explicit manual clear+reconcile — take it into W1 only if cheap once the verb group exists; the three automatic clears ARE the requirement. REGISTRATION-TIME START IS A WIRE OP: `registry::register` runs in the CLI PROCESS (`crates/spt/src/cli.rs`, the adapter-add and adapter-update call sites), so it cannot itself start anything in the daemon — registration-starts-the-service structurally requires ONE new adapter-scoped daemon IPC op, semantically `AdapterServiceReconcile { adapter }` (exact spelling matched to house op style where it lands), which the CLI calls after a SUCCESSFUL register on BOTH paths when the daemon is reachable. The daemon handler runs THE SAME reconcile code path as boot / hold-release / bind — ONE reconcile function taking an opportunity discriminant, never a second start authority. The response is a per-option outcome list (started / already-running / held / latched / bind-deferred / startup-fault) so the CLI prints honest per-option text. Daemon NOT reachable: registration STILL SUCCEEDS — never a refusal — and the CLI prints a REQUIRED notice that the service is declared, the daemon is not running, and it will come up at the next daemon boot. That notice is CONTRACT, not courtesy. RUNTIME-DIR ENCODING: cardinality is per adapter-option, so the option name becomes a PATH component and `:` is illegal in a Windows path. Core NEVER uses the raw option string as a path component — every construction site goes through ONE shared encoder, and that encoding MUST BE INJECTIVE (a lossless escape, never a strip/replace that can collide). Two distinct options mapping to one runtime dir means two services sharing a quiesce-marker namespace: the silent flap one layer down. The unit gate MUST include a collision-adversarial pair (e.g. if `:` maps to `_`, then `a:b` and `a_b` must remain distinct). MODULE PLACEMENT: the supervisor lands as `servicehost.rs` (the established `*host` convention — shellhost, harnesshost, linkhost, applyhost); `service.rs` is untouched and BOTH module headers cross-reference the other meaning of 'service'. INSTALL-DIR RESOLUTION RIDES W1 (operator-requested via flynn, ruled in after code verification): the `[service]` spawn resolves its binary through the EXISTING REQ-INSTALL-11 helper — the same primitive, NO parallel resolution path — and the same wiring lands at the two shell-family fill sites, which today resolve NEITHER the install-dir program token NOR `{adapter_dir}`. Without it a `--release`-installed shell adapter registers but cannot spawn (bare token → os error 2; `{adapter_dir}/x` → 'no value for substitution key'), released shell adapters need a hand-maintained per-node manifest, `spt adapter update` on them is a permanent no-op, and THIS req's hold/swap/start ceremony would be exercisable by MOCK ONLY — never by the named first consumer. Site census discharged BEFORE build (authoritative grep, cfg(test) excluded, accepted by doyle): the production template-fill sites are `shellhost.rs::fill_spawn_command` and `shellwake.rs::fill_wake_command` (both targets — threading not shape, since the wake caller already holds `AdapterRecord.source_dir`, which IS the install dir), plus `harnesshost.rs` session `role.command`, which is OUT OF SCOPE and already resolves correctly through `resolve_program_in_dir`. Scope guard: shell spawn/wake + `[service]` ONLY — this does NOT expand into the `[session.self]`/`[history]` follow-on sites. FAULT COUNTERS ARE TWO, NOT ONE (ruled 2026-07-26 after the builder surfaced the reading): (1) the FAST-EXIT counter increments ONLY on exits whose uptime is UNDER the startup threshold, and RESETS the moment any run EXCEEDS that threshold. Without the reset the latch mislabels slow-crash decay as a configuration fault — the latch lying about cause, which is worse than no latch. STARTUP_FAULT is reserved for THIS path alone. (2) The ORDINARY consecutive-crash give-up (the shell wake-watcher scaffolding's `give_up_after` = 6, deliberately UPTIME-BLIND) sits BEHIND it UNCHANGED in W1 and keeps its existing diagnostic label — no silent behavior fork from the shell watcher. (3) BOTH counters reset on the latch-clear events above (daemon boot, adapter re-registration, update-hold release): a clear that left either counter primed would relatch on the first post-clear crash and thereby defeat the clear. (4) NOTED-OPEN, deliberately NOT W1 and NOT a promise: the ordinary counter's uptime-blindness means a service that crashes once a day gives up after six days and then stays down until a clear event. Whether that decay behavior is right for SERVICES (as opposed to the shell watchers it was designed for) is a future ruling; it is recorded here as open so the next builder inherits the question rather than rediscovering it in the field. ORPHAN ADOPTION IS IMAGE-VERIFIED, AND ITS PLATFORM GAP IS RECORDED-OPEN (ruled 2026-07-26 after the builder surfaced the trade): a fresh daemon kills a dead daemon's parked orphan ONLY by path-verified identity — a live pid whose image path cannot be READ classifies `Unverifiable` and BLOCKS adoption, i.e. the start refuses loudly rather than proceeding. Loud-blocked over quietly-double-started is this design's whole posture (a bare-pid kill is the recycled-pid class, and a double-start is the silent flap the cardinality rule exists to prevent). RECORDED-OPEN consequence, NOT debt owed by W1: the image oracle is `/proc` on unix, so a unix WITHOUT `/proc` (macOS/BSD) would block on every live orphan until that pid dies. This is theoretical for every platform we ship — win, linux-gnu, musl — and no macOS/BSD asset exists; a future builder adding one inherits the question here rather than rediscovering it in the field. SUPERVISOR PLACEMENT IS BROKER-SIDE (ruled 2026-07-26): the supervised set, its boot sweep and the reconcile control socket live in the BROKER process, beside the digest/drive/tunnel hubs — NOT in the restartable brain child that hosts shellwake. Two reasons, both structural. (a) A supervisor owns LIVE CHILD HANDLES and, from the update ceremony on, an in-memory HOLD: that is a daemon-lifetime continuity resource, which is the ADR-0018 Q2/Q5 broker-side test; the Q5 exception that put shellwake in the brain reads 'a pure disk-reconciler' and this is not one. (b) A brain restart is the ROUTINE UPDATE PATH (StartReason::Update exists precisely for it), so brain-hosting would bounce every resident service through the orphan-adoption path with no quiesce, no grace and no hold — the ungoverned bounce this req's ordered update operation exists to replace — and would lose the hold mid-swap. Broker-hosting is also what makes the wire op possible at all: all control sockets are broker-served because a CLI cannot reach brain memory (stated in drivehub.rs's module header and obeyed by every hub). NO PERIODIC SWEEP — CHOSEN, NOT OMITTED (ruled 2026-07-26): the supervisor host runs the boot sweep and then parks; there is deliberately no timer re-sweeping on a cadence. The four ruled opportunities are all EVENTS, each with a caller that enters the one reconcile function directly, so a timer would be a FIFTH start authority nobody ruled in — and its only distinctive work would be silently healing a failed registration nudge, converting a diagnosable defect into invisible behavior. The REQUIRED daemon-unreachable notice is the honest answer to that case; machinery that papers over its own failure class is refused (the same instrument-soundness razor as the rest of this design). A SUPERVISOR WHOSE DECLARATION DISAPPEARS RECONCILES TOWARD STOPPED (ruled into W1 2026-07-26): every sweep runs a STOP side before its start side — an option whose adapter is soft-deregistered, hard-removed, or whose manifest no longer declares a [service] is torn down through the handle that names its child. This is the symmetric half of desired-state-running, not an addition to it: without it a deregistered adapter's binary outlives its own registration until the daemon dies, which is exactly the ungoverned-lifetime shape this req abolishes. TREE TEARDOWN ON EVERY SUPERVISOR-INITIATED KILL (ruled 2026-07-26): the force-kill deadline is where the unconditional-kill promise is WRITTEN, but it is not the boundary of the problem — a supervised service's descendants are torn down on every kill the supervisor initiates, including daemon-shutdown stop_all and the stop-side sweep teardown. Descendants outliving THOSE paths are strictly worse off than ones outliving a force-kill, because the next daemon's orphan sweep is structurally blind to them: it knows one parked pid and holds no handle to anything below it. Windows reaches the tree through a SUPERVISOR-OWNED Job Object assigned at birth (CREATE_SUSPENDED -> AssignProcessToJobObject -> resume, so no descendant is ever spawned outside the job; KILL_ON_JOB_CLOSE deliberately OFF, so a dying supervisor is never an unannounced service outage); unix through the process group setsid already establishes. Job-NEUTRALITY is not contradicted: that invariant governs OTHER people's jobs reaching our processes, which CREATE_BREAKAWAY_FROM_JOB still handles at birth. A job the OS refuses is a LOUD DEGRADE, never a refusal to start: the process still dies on demand and only its descendants become unreachable, which is exactly where this path stood before the job existed — refusing the spawn would convert a bounded descendant leak into a total outage over a failure in an OS facility rather than in anything the adapter declared. THE TREE PROMISE STATES ITS EDGE (ruled 2026-08-03, IR-16): a teardown whose TREE reach FAILS — as distinct from a job never acquired — does not silently wear the unconditional promise. It announces `SERVICE_TREE_KILL_INCOMPLETE`, naming the LOSS (the tree claim was not honored; descendants may still be running and nothing holds a handle to them) rather than the syscall, alongside the pid, the failed reach and the OS error; the process kill proceeds regardless, so this is a NAMED EXCEPTION to tree-teardown-on-every-kill and never a refusal to kill. It is the kill-time twin of `SERVICE_JOB_UNAVAILABLE` — ONE loss, one vocabulary, both ends of the lifetime, each code naming the other so grepping either finds both. THE SPAWN-SIDE DEGRADE RULING ABOVE MUST NOT BE CITED FOR THIS ARM: it rests on refusal costing a total outage, which is true at spawn and false at kill, where the process kill proceeds either way and refusing the tree claim costs the caller nothing — the two share a noun and not an argument. THE QUIET ARMS ARE A WHITELIST, so an unforeseen failure announces rather than being swallowed: on unix `ESRCH` ALONE is quiet (MEASURED 2026-08-03, Linux 6.8.0-124: `kill(-pgid, SIGKILL)` returns 0 for a live group AND for a group whose only member is an unreaped zombie, and `ESRCH` only once the group is gone and reaped — and since this path never reaps first, `ESRCH` means something else already did, which is the path where the supervisor took its exited branch and never called the teardown at all); on Windows the DIRECT-PROCESS return is not checked at ALL (MEASURED 2026-08-03, Win 11 Pro 10.0.26200: `TerminateProcess` against a handle to an already-exited process returns 0 with `GetLastError` 5 `ERROR_ACCESS_DENIED`, so a check there would fire on the ordinary path), because that arm already carries the strictly stronger downstream liveness confirm behind `SERVICE_KILL_UNCONFIRMED` and a second weaker rule beside a working one is refused. A FALLBACK PROCESS-TABLE TREE-WALK WAS CONSIDERED AND REFUSED as the degraded arm — the ceiling is chosen, not missed: live `(pid, ppid)` descent breaks at the middle hop the teardown itself kills, returning an empty set in exactly the failure case it would exist to detect (a silent loss upgraded to a false clean), and it would kill by bare pid, reintroducing product-side the recycled-pid class. Growing this into an image-authenticated population sweep stays open as its own instrument. DISCRIMINATION: a teardown failure WITH this code is this arm; WITHOUT it, a survivor is a victim of a bare-pid mis-kill — the two must not be blurred by wording. THE STATUS SURFACE IS DAEMON-ANSWERED OR IT SAYS NOTHING (ruled 2026-07-26, the leg-D companion of the tree-teardown ruling): the CLI NEVER derives service state from the pid file. Either the daemon answers `spt adapter service list|status` over the control socket, or the CLI prints that the daemon is not running — verbatim, and with no fallback read. The pid file is a KILL HANDLE for the NEXT daemon, and reading it as liveness in a display surface is the v0.43.0 STALE-ONLINE class one layer down: a record answering a question the record cannot know. Three properties follow and are requirements, not implementation taste. (a) The status op is a PROJECTION over the live supervised set — the supervision threads, the hold flags and the stand-down records — plus the registry; it starts, stops, holds and kills nothing, because a diagnostic that converges the thing it measures is a start authority wearing a diagnostic's clothes. (b) A row reports its EVIDENCE: a latch surfaces with the captured startup output behind it, since a fault reported without its cause is the instrument failing at its one job. (c) An option SUPERVISED WITHOUT A DECLARATION behind it is reported as exactly that rather than hidden — with no periodic sweep, a deregistered adapter's supervisor lives until the next opportunity's stop side reaches it, and that window is precisely when an operator asks what is running. THE ADVISORY STATUS LINE IS DISPLAY-ONLY AND BOUNDED: the service may write one line into its runtime dir (named in docs/MANIFEST.md beside the stop-request marker, so it is implementable); core reads the FIRST line under a byte cap, treats an unreadable file as simply no advisory (the locked-file split rule holds), and consults it for NO decision — core deciding on it would put a least-trusted binary's self-report in the control path, and a service that stopped updating it would silently become whatever it last claimed. WIRE LENIENCY IS PLACED, NOT SPRINKLED: fields a reader BRANCHES on stay typed with a `#[serde(other)]` unknown arm (KH-2.3), while a field only ever ECHOED carries the daemon's label verbatim — re-deriving a lenient copy of the manifest's validated `start` vocabulary for the wire would weaken the one place strictness matters (registration). An op an older daemon predates HANGS UP rather than reading the request and answering nothing: a server that silently ignores an unknown kind leaves the caller blocked on a reply that never comes, so an older daemon would WEDGE a newer CLI instead of failing it. `spt seal enroll-authenticator [--subnet ]` ENROLLS THIS NODE'S PLATFORM AUTHENTICATOR INTO A SUBNET'S SECURITY MATERIAL, GATED BY THE SHIPPED TOTP CEREMONY (#217 anchor 1). ORDERED SEAM CHECKS, EACH REFUSING BY NAME BEFORE ANY CEREMONY OPENS (no human should present a code for an enrollment that cannot complete): (1) the binding subnet resolves by the house rule's applicable arms — explicit `--subnet` wins but a subnet this endpoint is not a member of refuses fast by name, otherwise the endpoint's anchor subnet, and no anchor refuses by name (REQ-SEAL-SUBNET-BINDING-DEFAULT arms 1-2; enrollment is node x subnet — no destination, so the shared-subnet arm does not exist here); (2) seam availability (REQ-SEAL-AUTH-BACKEND-SEAM) — including the Linux named stub — refuses verbatim; (3) ALREADY-ENROLLED (the node x subnet key holds a record) refuses by name, NAMING THE EXISTING RECORD'S PUBKEY AND ITS IMMUTABILITY: records are immutable v1 (intake ruling 6 transported), re-enrollment/revocation is STOP-AND-REFER, never a lane decision, so the verb neither overwrites nor duplicates. THEN ensure-keypair runs (the backend's own user-verification prompt), and the verb requests the daemon's TOTP ceremony carrying FACTS — the pubkey hex and backend kind — plus, at the code arm, the PRESENTED CODE, never a verdict (write-through-daemon verbatim: verification, throttle and the record write all daemon-side). The ceremony rides the SHIPPED machinery as an additive purpose (the attach-stream additive-variant discipline of REQ-SEAL-CEREMONY-RC-CLIENT), and the overlay shows an ENROLLMENT BRIEF composed DAEMON-SIDE from the request's facts — naming the enrolling node's key short hex, the binding subnet, the backend kind and the enrolled pubkey's SHA-256 FINGERPRINT (64 hex via the one hash helper; the FULL pubkey hex lives in the record — an RSA public blob is ~588 hex chars (294-byte DER SPKI), measured, which no human can compare, and a fingerprint is what consent can actually read) — shown verbatim as ceremony content (the content-shown discipline: the human consents to THIS key for THIS node on THIS subnet), with the brief's own text carrying the ENROLL framing so even a chrome that says seal cannot misrepresent what is consented. THE THROTTLE IS THE EXISTING trust/seal-ceremony-gate.json LEDGER — enrollment mints NO new ledger (the denial-of-governance separation holds; a ruling is the only path to one) — throttle-before-verify, member-or-admin of the binding subnet, both seeds combined bitwise, cancel spends nothing: all inherited by riding the one ceremony-code path. ON ADMIT the daemon writes exactly one enrollment record (REQ-SEAL-ENROLL-RECORD-SUBNET-MATERIAL) under the same apply serialization as seal records and replies the record's fields; the verb prints them line-oriented. EXIT 0 IFF ENROLLED; every non-admit outcome (no-surface, throttled, refused, cancelled, ledger-unwritable) is nonzero carrying the daemon's own token and detail VERBATIM — the CLI never re-words a refusal it did not decide. Gate: doc — the published verb page; impl — the SealVerb variant + verb fn, the daemon enrollment-ceremony purpose arm + record write; unit — subnet-resolution arms (override wins, non-member override refuses by name, anchor default, no-anchor refuses), seam-unavailable refuses before ceremony, already-enrolled refuses before ceremony naming the record, admit writes exactly one record and exits 0, each non-admit outcome nonzero with the daemon detail verbatim, the daemon-composed brief carries node short hex + subnet + backend kind + pubkey hex and the ENROLL framing. `spt seal mint` IS THE DECISION-SEAL ENTRY POINT: the text to seal arrives on STDIN, the verb requests the daemon's TOTP ceremony over exactly those bytes through the ONE production mint seam (Brain::seal_ceremony -> KIND_SEAL_CEREMONY; the CLI process never verifies, never ledgers, never mints - REQ-SEAL-CEREMONY-TOTP's write-through-daemon posture), and on admit prints the MINTED TOKEN to stdout as the whole result. STDIN IS TRIMMED EXACTLY AS `spt send` TRIMS ITS BODY (the REQ-HAZARD-ENVELOPE-CR-LINESAFE parity: a Windows `echo |` pipes a trailing CRLF, and a seal minted over an invisible trailing newline is a citation whose verify mysteriously reads NOT-BOUND) - so the buffer `spt seal mint` seals for a given text is byte-identical to the buffer a sealed send delivers for that same text; EMPTY content after the trim refuses fast CLI-side by name, before any ceremony opens (a ceremony over nothing asks the human to consent to nothing). REFUSALS RIDE THROUGH VERBATIM: no-surface, content cap, non-UTF-8, throttle, wrong-code and cancellation each surface the daemon's own outcome token and detail sentence - the CLI never re-words a refusal it did not decide (the W2 reply contract: every field is the broker's answer and none of it is the client's to summarize). The binding subnet resolves through REQ-SEAL-SUBNET-BINDING-DEFAULT (minter endpoint's anchor; `--subnet` overrides). EXIT CONTRACT, mirroring verify's 0-iff-BOUND: exit 0 IF AND ONLY IF the ceremony ADMITTED and the token printed; refused / throttled / cancelled / transport-error are each nonzero with the named reason on stderr. Gate: doc - the CONTEXT.md wax-seal entry's mint-verb sentence; impl - the CLI verb and its outcome mapping; unit - admit prints the bare token and exits 0, each non-admit outcome exits nonzero carrying the daemon's own detail verbatim, stdin trim parity with send, empty-content fast refusal by name. `spt api seal verify ` IS CONTENT-BOUND: the content arrives on stdin, the verb recomputes the lowercase-hex SHA-256 over the exact bytes received and compares it to the record's `content_hash`, and the verdict is BOUND or NOT-BOUND with the record's fields printed alongside — a verify that does not read content is not a verify, so BARE VERIFY WITH NO CONTENT (empty or absent stdin) REFUSES BY NAME, pointing the caller at `describe` (operator ruling 7: `SEAL_VERIFY_NO_CONTENT`, the refusal names the verb that answers the question actually asked). EXIT CONTRACT, PINNED SO A SCRIPTED CALLER CAN NEVER MISREAD A NON-VERDICT AS EVIDENCE: exit 0 IF AND ONLY IF BOUND. NOT-BOUND prints its verdict line (with the expected-vs-presented hashes) on stdout and exits nonzero; an unknown token refuses `SEAL_UNKNOWN_TOKEN` on stderr; a malformed token refuses at the format seam (REQ-SEAL-TOKEN-FORMAT) — three distinct texts, none of them exit 0, because ONLY A BOUND VERDICT IS EVIDENCE (KH 7.5: the token, and any envelope attribute carrying it, is never an authorization subject by itself; forged or wrong tokens are harmless by construction precisely because this verb recomputes the hash). The verbs are READ-ONLY and take no auth gate (the EndpointInfo precedent): the store they read replicates subnet-scoped, so membership already bounds who can answer, and a verify mutates nothing. Gate: doc — the CONTEXT.md entry's verify clause; impl — the api verb, hash recomputation at the store's one helper, the no-content and unknown-token refusals; unit — BOUND on exact bytes, NOT-BOUND on a one-byte delta, empty-stdin refusal pointing at describe, unknown-token refusal distinct from malformed-token refusal, exit-0-iff-BOUND across all five arms. #220 (operator, FIELD-SEAL W2): TWO CHANGES, NEITHER OF WHICH WEAKENS THE BINDING. (1) THE MINT'S TRIM IS MIRRORED AS A SECOND CANDIDATE: `seal mint` seals `raw.trim()` (both ends, the CRLF rationale of REQ-HAZARD-ENVELOPE-CR-LINESAFE), so presented bytes carrying a shell's trailing newline — every `echo |` on the fleet — could never bind the seal minted from that same text. Verify hashes the EXACT bytes FIRST and, ONLY ON A MISS, hashes the mint-trimmed candidate: additive by construction, so nothing that binds today stops binding, including content deliberately sealed WITH its whitespace. The trim MIRRORS THE MINT rather than the ticket's narrower 'trailing' — `str::trim` for UTF-8 content (the identical function the mint calls), ASCII whitespace for bytes the mint verb could never have produced but the store can hold. (2) THE VERDICT'S RECORD BLOCK IS THE NARROW ONE: `token`, `content_hash` and `ceremony_kind` DO NOT surface from verify — the verdict line already carries the token (`SEAL_BOUND:`) and, on a mismatch, both hashes in prose, and a field repeated beside its own verdict reads as a second, weaker answer; `describe` keeps all three. Verify's minter rides VERBATIM (the human render is describe's alone). Gate adds: unit — exact bytes bind, trailing-newline/CRLF/both-ends-padded bind through the fallback, a real one-byte delta and interior-whitespace differences still NOT-BOUND, a seal minted over trailing whitespace still binds its exact bytes, the non-UTF-8 arm, and the three dropped fields pinned as an ABSENCE. [OK] REQ-UPDATE-COMPOSITE-EXIT-PRECEDENCE required: [doc, impl, unit] stages: +doc +impl +unit -int THE COMPOSITE UPDATE'S EXIT IS FOLDED BY PRECEDENCE, NEVER BY WRITE ORDER (releases#216): `cmd_update_composite` aggregated leg exits with `worst = code` — the LAST nonzero, not the worst — so under `--restart` (the only plan where a leg runs AFTER the isolated adapters leg) an Adapters FAILURE of 1 followed by a Finish REFUSAL of 3 reported 3, and a refusal MASKED a failure. That inverts REQ-UPDATE-REFUSAL-EXIT-DISTINCT's contract, whose 3 means refusal present AND no failure — the same class of misread as the releases#153 field report where a refused fleet roll recorded itself as rolled. THE TOTAL ORDER IS 0 < 3 < EVERY OTHER NONZERO, classed BY VALUE and never by which constant minted it (the tree holds TWO distinct 3s — `EXIT_NOT_ELEVATED` and `EXIT_REFUSED_NO_WORK` — and a fold that cared which one it saw would grow a second vocabulary to drift from). An UNKNOWN nonzero is FAILURE, not refusal (doyle ruling 1): mapping an unminted code to the polite class would re-create this very defect for a code nobody has designed yet, and clap's usage `2` therefore sits in the failure class too, which is what makes the fold TOTAL. WITHIN A CLASS THE FIRST-SEEN CODE SURVIVES (doyle ruling 2) — the earliest failure is the one whose diagnostics the operator has already read, and later-overwrites-earlier is the defect mechanism itself. THE AGGREGATION CONTRACT IS STATED WHERE BOTH AUDIENCES READ IT: in the function's own doc (it stated the abort-vs-isolate failure policy and said NOTHING about aggregation, which is how the face stayed invisible to a source reader) AND on the published self-update page, because callers SCRIPT on these codes and a consumer misreading this surface is the documented history. SCOPE DECLARED, NOT ASSUMED (doyle ruling 3): this requirement covers the FOLD and the LEG ORDER that makes the defect reachable. That the composite PLUMBING routes each real leg's exit into the fold against the real binary stays UNPROVEN by this lane — an end-to-end arm would have to manufacture an adapters failure and a finish refusal, and what it would add beyond the units is that two legs still return codes their own suites already pin; the gate reads the single fold call-site as the review-arm for it instead. Gate: doc — the published self-update clause naming the precedence and both classes; impl — the pure fold + its ONE call site in the composite loop; unit — BOTH DIRECTIONS asserted together (failure-then-refusal folds to the FAILURE and refusal-then-failure folds to the same failure, since a fold that merely reversed the write order passes either arm alone), an unknown nonzero outranking a refusal, first-seen surviving within the failure class, success never outranking anything, and the leg plan pinning that Adapters PRECEDES Finish under `--restart` (the ordering the defect needs — a later plan change would silently un-reach it and the fold's arms would keep passing). [OK] REQ-UPDATE-REFUSAL-EXIT-DISTINCT required: [doc, impl, unit] stages: +doc +impl +unit -int A REFUSAL AND A FAILURE ARE DIFFERENT ANSWERS AND MUST NOT SHARE AN EXIT CODE OR A SUMMARY WORD (releases#153, doyle ruling 2026-08-21). A guard that DECLINED and did no work leaves the box exactly as it was; a failure means something broke and may have left work half-done. A caller gating on exit status therefore needs THREE answers: applied (0), refused-with-nothing-done (3), failed (1). CODIFICATION, NOT A NEW CONTRACT — the tree already answered refusals with 3 at three sites (EXIT_NOT_ELEVATED, DAEMON_STOP_REFUSED, UPDATE_FINISH_REFUSED) while cli.rs's own module doc called 1 a 'runtime refusal'; there is no migration to look for, and a fourth code would fork a convention that already reads consistently. WHAT THIS REQUIREMENT COVERS is the places the distinction was genuinely MISSING, which is not where the issue filed it: the adapter sweep's outcome type gains a REFUSED variant carrying the guard's own reason, so a declined release stops rendering FAILED and stops exiting 0 (a sweep that installed nothing must not report success — the filed field shape was a fleet roll recording itself as rolled on the majority of nodes); and the core apply's refused-class arm answers 3 rather than 1. PRECEDENCE: a sweep carrying both a failure and a refusal exits 1 — something broke, and a held rule must not mask it. THE DISCRIMINANT IS DECLINE VERSUS FAULT, NOT WHETHER BYTES MOVED (doyle ruling 2026-08-21, stated here because the tempting wrong rule will be re-litigated by the next reader of ApplyStagedOutcome's doc comments): 3 means policy declined, the system is healthy, change your intent or context and ask again. Rejected (at-rest re-verification — tamper, expiry, key trust) stays 1 even though its own doc says nothing was swapped, because that sentence is a statement about BLAST RADIUS and not about outcome class: what is on disk cannot be trusted, which is a fault. The asymmetry settles it independently of the taxonomy — an &&-gated caller reading 3 carries on, and carrying on is the one reading a tampered artifact must never receive, so filing a security-relevant integrity condition under the benign code would make this contract actively harmful at the site where being loud matters most. Quarantined stays 1 as well: a version rolled back after failing readiness had work happen. The UPDATE_FINISH_REFUSED site is explicitly OUT of scope: it already returns 3 (since 559632e0, shipped in v0.50.0) and is already pinned non-zero from an endpoint context by broker_stop_endpoint_deny_e2e, so the filed face was measured fixed before this lane opened. Gate: doc — the cli.rs exit-code contract sentence and the CONTEXT.md update-composite entry naming what a refusal exits; impl — the REFUSED outcome variant, the sweep's exit aggregation with its precedence, and the refused-class arm; unit — a refusal renders REFUSED with its reason and never FAILED, the sweep exits 3 for refused / 1 for failed / 0 for applied, the mixed sweep exits 1, and the refused apply class answers 3. `spt fetch [dest]` IS THE ONE-COMMAND PULL AND ITS EXIT CODES SEPARATE REFUSED FROM FAILED (ADR-0058, releases#272/#246). It accepts a full node-prefixed URL or the bare `/f/` shorthand, writes the bytes to `dest` (default: the URL's basename in the current directory), REFUSES TO CLOBBER an existing dest without `--force`, and PRINTS THE WRITTEN PATH on success so a caller can pipe it. THE THREE OUTCOMES ARE DISTINCT AND THAT IS THE WHOLE POINT: exit 0 wrote the file; exit 3 the owner REFUSED (403 -- an access decision, not an error, and a caller that retries it is wrong); exit 1 is everything else (unreachable owner, not-found, a deadline, a local write error), each naming its cause on stderr. This verb's spelling is a PUBLISHED CONTRACT and not an implementation detail, because the FILE_ACCESS_HELPER now-signal emits the line verbatim for an agent to run. It rides the SAME cross-node path a browser does (REQ-WEB-CROSS-NODE-PROXY) rather than minting a second client, and a local-node URL goes through the same loopback server, so there is exactly ONE code path to be wrong about. THE BODY IS STREAMED TO A TEMPORARY FILE AND RENAMED INTO PLACE, so an interrupted or refused fetch never leaves a truncated file sitting at the destination path where a later reader would trust it. Gate: doc -- the attachments page's fetch section; impl -- the verb, the URL and shorthand parse, the dest and clobber rules, the temp-then-rename write, the exit-code mapping; unit -- URL and shorthand parse, default dest from basename, clobber refusal and `--force`, each exit code derived from its status, and an interrupted write leaving no partial at dest; int -- a fetch across two real daemons landing byte-equal, plus a denied fetch exiting 3. note: 5 tag-carrying file(s) not placement-judged (no grammar for the language, or the parse recovered from errors) EXIT=0