ok A RETIRED SUBCOMMAND OF `spt endpoint access` REFUSES BY NAME RATHER THAN FALLING THROUGH ITS OWN OPTIONAL POSITIONAL AND ANSWERING PLAUSIBLY. `access` takes an optional endpoint id positionally, so once `list` and `rules` stopped being subcommands (retired when the roster views replaced them) clap handed those tokens to the POSITIONAL, and the view reported `no access entities ruled for 'list'` at exit 0 -- a well-formed, confident report about an entity nobody has ever created. Found by flynn in the v0.49.0 field verify (releases#67). THE DEFECT IS NOT A MISSING ERROR MESSAGE, IT IS AN ANSWER: the sentence is TRUE of any name nobody has ruled, so it is indistinguishable from the real result for a real endpoint, and an operator or agent still carrying the old form reads it as a report and stops looking. The same failure shape as a stale carried-forward instruction -- the surface looks diligent and is wrong. MEASURED BEFORE THE FIX, and the measurement widened the filing: `rules` falls through IDENTICALLY, so the population is the retired-token FAMILY and not the one token the issue named. The node tier is NOT affected and that was checked rather than assumed: `spt node access` declares no positional at all, so clap refuses an extra argument natively. THE REFUSAL FAILS OPEN ONTO A REAL ENDPOINT. This guard is added to a path that previously accepted EVERY positional, so it is a narrowing, and a narrowing has an unruled arm: an endpoint genuinely named `list` must stay viewable. The known-target check therefore runs FIRST and wins -- a fix that made a real target unreachable would be a new defect wearing the old one's repair. Known means the access store rules it or a local perch carries the id; the store is already loaded at that point, so the check costs no read. The token match is case-folded, because an operator who typed the old verb in any casing typed the old verb. Gate: impl -- the retired-token table with its replacement guidance, the case-folded lookup, the refusal composer naming token then retirement then the working form, and the guard placed AFTER the known-target check inside the view; unit -- a decision table whose expectations are WRITTEN OUT AS LITERALS: each retired token refuses with a nonzero exit and names its replacement, an ordinary unruled endpoint id still gets the honest roster sentence (the silence the fix must not swallow), a real endpoint that happens to BE named `list` is viewed rather than refused, the casing variants refuse alike, and every token in the table is a token the current grammar does NOT accept as a subcommand -- walked against the real parser, so a token that comes BACK as a verb cannot sit here refusing itself. `spt api access-refresh` is MINTED THIS WAVE BUT REFUSES — the verb exists, parses and is documented, and its refusal names why: the capture-refresh is engine-room-only, and engine-room enforcement (ADR-0052) does not land until W3. Minting the refusing verb now is deliberate: it fixes the contract adapters and the engine-room brief will be built against, and it makes the wave that implements enforcement a change to ONE behavior rather than a new surface plus its gate. A refusal that merely says 'unknown command' would invite an adapter to route around it. When it does light up it updates ONLY the node's captured subnet-level fallbacks — never the node's own rules, which are the operator's, not the subnet's. Gate: doc — the CONTEXT.md capture-refresh sentence naming the verb and its engine-room-only gate; impl — the verb, parsing, and a clear refusal naming the W3 dependency; unit — invoking it refuses with the engine-room diagnostic, changes no stored state, and is not reachable as an unknown-command fallthrough. A corrupt or unreadable access store degrades CLOSED, loudly (ADR-0053 — an ADR-level flip of ADR-0009's deliberate fail-open ruling). ADR-0009 justified fail-open explicitly: the whitelist was 'a same-subnet convenience boundary layered inside subnet membership, not the system's outer wall', because every member node was the one user's own machine. The SHARED SUBNET ruling breaks that premise — member nodes now belong to different human operators and the access layer gates agents — and a boundary that evaporates when its store file corrupts is not a boundary an operator can reason about. So a store that CANNOT BE READ refuses unlisted-subject traffic on every surface rather than admitting it, and says so on the refusing node naming the store path (silence plus refusal reads as network failure and burns diagnosis time). TWO THINGS SURVIVE THE DEGRADE: the stateful-firewall reply exemption (correlation state is a separate file keyed on the endpoint's own recent outbound — a degraded node keeps its own conversations alive) and the same-node loopback Allow. THE SPLIT THAT MAKES THIS SHIPPABLE: an ABSENT store file is NOT a degrade. It is the unconfigured baseline — every fleet node today has no access.json, and reading absence as closed would refuse all unsolicited cross-node traffic fleet-wide on upgrade. The two cases split on std::io::ErrorKind::NotFound and nothing else: never-created reads as an empty store whose chain bottoms out open; any other io error or a parse failure is a degrade. Absent-is-open keeps an unconfigured fleet reachable, unreadable-is-closed makes the boundary real. Consequence accepted per ADR-0053: a personal fleet with zero configured rules now blocks unsolicited cross-node traffic if its store file CORRUPTS — the failure is loud, local, and repairable, and the alternative (silent admission on a shared subnet) is the worse lie. Existing fail-open tests are repinned to the new contract as part of this diff (a product contract change, not a test fixup). Gate: doc — ADR-0053 and the CONTEXT.md control-surface-modes entry carrying the degrade rule; impl — AccessStore::load_checked/load_checked_from returning AccessDegraded (path + cause) with the NotFound split, and the daemon gate's refuse-plus-diagnostic arm ordered AFTER the reply exemption; unit — the degrade split (absent is baseline, corrupt and unreadable both degrade, diagnostic names the path) and the gate-level proof that the same call is allowed before corruption, refused on every surface during it, still allowed as a reply throughout, and allowed again once the store is repaired without a restart. ADR-0053'S FAIL-CLOSED HAS A WRITE SIDE, AND IT IS ENFORCED AT THE SAVE SEAM RATHER THAN AT THE MUTATION CALL SITES. `load_checked` split NotFound from corrupt and the gate refused a husk correctly -- but the store is READ-MODIFY-WRITE at every path that edits it, and `AccessStore::load` is `load_checked_from(..).unwrap_or_default()`, so a degrade was swallowed into an EMPTY document. The consequence inverted the guarantee (releases#57): the gate refused unsolicited traffic, and then the first `spt endpoint access` command an operator ran to investigate the refusal saved that empty document over the damaged file -- permanent rule loss, and a posture that flipped from degraded-closed to open-with-one-rule. The boundary held exactly until someone touched the CLI, and the command most likely to be run is the one an operator reaches for BECAUSE the gate is refusing. THE SWALLOWED DEGRADE IS THEREFORE CARRIED ON THE DOCUMENT (`StoreOrigin::Degraded`, `#[serde(skip)]`, never part of the record) AND `save_to` REFUSES IT. This placement is the requirement, not an implementation detail: converting the nine production mutation call sites to `load_checked` closes the population that exists today and leaves the next read-modify-write path free to re-open the class -- the same shape of defect as REQ-SEC-REPLY-EXEMPTION-SEND-LEG's per-caller write, which shipped inert because a rule satisfied by remembering to is satisfied until someone forgets. Every write of this document funnels through `save_to`, so a refusal there binds call sites that do not exist yet, and an audit is not what holds it. The call sites ARE converted as well, for the diagnostic: a refusal at the load names the store before anything is attempted, which reads better than one raised by a save the operator already believes succeeded. FRESH IS NOT DEGRADED: a document constructed in memory -- the minted baseline, a test fixture -- stands in for nothing on disk and stays writable, so mint-on-first-absence (the split that keeps an unconfigured fleet reachable) is untouched. The refusal names the store path per ADR-0053, names the cause, and names the way out, which is real rather than rhetorical: a store the operator MOVES ASIDE is re-minted empty by the next command, so starting from empty stays available as a deliberate, loud choice -- which is precisely what silently rewriting it from empty was not. Gate: impl -- `StoreOrigin` on `AccessStore` set by the loaders (`load_from` marks the swallowed degrade, `load_checked_from` marks a parsed store `Loaded`), the `save_to` refusal in the single voice of `husk_write_refusal`, and the nine production mutation sites loading checked (cli.rs `endpoint access` allow/revoke/open + subnet-mint capture + endpoint purge, api/engineroom.rs `enforce`/node-mode/node-surface-mode, daemon pairhost.rs join-time capture); unit -- a decision table over the three origins whose expectations are WRITTEN OUT rather than derived from the code under test, asserting on-disk bytes and not merely the returned Result: a husk-derived document refuses the write and the damaged file survives BYTE-FOR-BYTE, a loaded document's read-modify-write preserves the rules it did not touch, a fresh document writes, and the refusal names the store path. `access allow`'s POSITIONAL SPELLING REACHES THE SAME MUTATION SEAM ITS FLAG SPELLING DOES -- it is a shorthand for the rule, never a second, ungated way to write one. The v1 arm matched `node: Some(..)` and swallowed every other flag in a `..`, then called `AccessStore::allow` directly, and that cost three things at once, all three RUNTIME-MEASURED before the fix (releases#185; the filing was read-derived and named only the first). (1) `--surfaces MSG` WAS DROPPED, and an empty surface list means EVERY surface -- so the store took a rule strictly WIDER than the operator typed, exit 0, no diagnostic on either stream. The direction matters and was checked rather than assumed: the `is_empty => set_all(Mode::Closed)` at the tail of `allow_surfaces` reads `acl.modes`, the POSTURE table, and has nothing to do with the surfaces argument, so this falls as an over-grant and not as a silent close. (2) `--origin user` WAS DROPPED THE SAME WAY: a rule the operator restricted to one caller class matched both. (3) CALLING `allow` DIRECTLY WALKED AROUND `apply_mutation`, AND WITH IT BOTH THE `--admit-node` ACKNOWLEDGMENT AND THE GRANT-NODES POLICY -- the gravest of the three and the one no reading of the issue predicted. Measured as a three-arm differential under ONE authority (an owner agent, which requires a real perch record and not merely the env var): the flag spelling REFUSED the unacknowledged grant, the same flag spelling with `--admit-node` was accepted, and the positional spelling then wrote the WIDER rule for that same agent at exit 0. A gate a respelling steps around is not a gate, and the respelling was two words shorter than the gated one. THE FIX IS THE SEAM, NOT THE FLAGS: the arm builds its rule through the shared `build_access_rule` -- so the two spellings cannot drift into two parsers -- and applies it through `apply_mutation` with the invocation's real authority, which is what makes the acknowledgment and the policy bind here by construction rather than by a second copy of them. WHAT STAYS v1 IS THE POSTURE: this spelling RESTRICTS the endpoint (closes its blanket mode when it sets none yet), the seam is deliberately posture-blind, so the close is re-applied explicitly through one shared body (`restrict_if_unset`) that `allow_surfaces` also calls. `iff unset` is load-bearing -- an endpoint that already names a posture has been configured, and admitting one more node must never overwrite that. Whether the FLAG spelling should close a posture too is releases#196 and is deliberately NOT decided here; preserving this arm's close is a non-regression, not a new opinion. THE CONSEQUENCE IS ACCEPTED AND DECLARED (doyle, 2026-08-18): an owner agent running the bare positional spelling is now REFUSED without `--admit-node`, which is the gate working as designed and what the flag spelling already demanded; a same-node user is untouched, since the acknowledgment binds the owner-agent authority alone. Gate: impl -- the arm destructuring its flags instead of discarding them, the shared builder, the `apply_mutation` route carrying `admit_node` and the real authority, and `restrict_if_unset` as the single body of the posture-close; unit -- a decision table whose expectations are WRITTEN OUT AS LITERALS rather than derived from the code under test: the typed surface list lands on the persisted rule and an untyped one still means every surface, an origin qualifier survives, the unacknowledged owner-agent grant is REFUSED THROUGH THIS ARM while the same-node user's is not (the bypass, pinned as a refusal rather than as an audit), the refusal leaves the store BYTE-UNCHANGED, and the posture-close still happens for the positional arm while an endpoint that already names a posture keeps it; int -- THE JOIN, which is where this defect actually lived: the real binary, a real store on disk, the operator's typed list read back off the persisted RULE, with an untyped arm beside it (the empty list is a legitimate spelling and must survive the fix) and the unacknowledged owner-agent refusal paired with the same grant acknowledged, so the flag is the only difference between the two. The granular rule list is viewable only per named ruled entity (CONTEXT.md 'access entity'): `spt endpoint access [--endpoint-rules | --node-rules | --subnet-rules ]` — mutually exclusive flags — renders exactly the rules whose subject names that entity, through the SAME ruleset-table renderer the engine-room briefing carries (REQ-ER-RULESET-TABLE), filtered on the TYPED rule subject rather than by re-parsing the rendered subject word (a rendered-string match is a second literal waiting to drift). A target positional scopes the slice to that endpoint's rules plus the node-scope rows that also decide for it; the drill's `--json` emits the rows verbatim. The one-flat-dump-of-everything view is deliberately gone — reading a posture is a comparison per entity, not a scroll. Gate: doc — the CONTEXT.md access-entity entry; impl — the typed filter seam and the flag surface; unit — the filter matches only its own entity kind (a subnet name equal to an endpoint id must not cross-match) and the flags parse mutually exclusive. Access views are roster-first (CONTEXT.md 'access entity', FF grill 2026-07-30): `spt endpoint access []` lists each ruled target's access entities — the subnets, nodes, and endpoints its own rules name — grouped by type in that order, each with its rule count; a subnet or the home node also carries its mode summary; a captured-mode subnet is listed even with zero explicit rules (a mode source is part of the posture picture); external entities with no rules for the target are omitted entirely; a ruled endpoint resolves to `@ ()` where this node's registry can see it and renders bare where it cannot (ruled-but-unresolvable must not hide the rule count). The view is node-sovereign — no remote rule-read exists; viewing another node's rules means running the CLI there. Supersedes the `access list` prose view; the `--json` surface re-homes the old shape's explicit `locked` flag onto the roster envelope. Known limit, documented: an endpoint literally named after a write verb (allow/revoke/open) parses as the subcommand and loses the positional path. Gate: doc — the CONTEXT.md access-entity entry; impl — the roster builder, renderer, endpoint directory, and the superseding CLI surface; unit — the roster grammar (grouping order, counts, mode annotations, bare-vs-resolved endpoints, explicit empty line) and the parse surface. AN ADAPTER DECLARES ITS OWN SUMMARY AND CHANGELOG IN ITS MANIFEST, AND THE NODE SERVES THE RENDERED CHANGELOG (releases#340, SEAMLESS-UPDATES W6). [adapter].summary is at most 300 characters of plain text; [adapter].changelog is an ARCHIVE-RELATIVE markdown path, so the changelog ships inside the artifact it describes and no fetch of a second source can disagree with the bytes installed. Install and update render that markdown to HTML once, at install time rather than per request, and the result is served at ...//a//changelog; an adapter declaring neither key serves nothing and is not an error, because most adapters will declare neither. BOTH KEYS ARE OPTIONAL AND ADDITIVE: a manifest written before this exists stays valid, and a declared path that is missing from the archive, unreadable, or not markdown fails the INSTALL loudly rather than installing an adapter whose changelog route answers with a broken page. The rendering dependency is pulldown-cmark, a NEW dependency whose justification belongs in the delivering PR body. The manifest SCHEMA and docs/MANIFEST.md are amended in the SAME commit as the parser: a manifest section no manifest author can find reads as absent (the REQ-SHELL-HINTS precedent), so the doc stage lands where an adapter author looks, not only in the CLI reference. F-5 (REMOTE-TRUTH triage §F-5 + doyle rulings 2026-07-05): BOTH adapter acquisition verbs (spt adapter add + spt adapter update) REFUSE when the installed spt-core is BELOW the adapter's declared [adapter].min_spt_core_version floor — with an F-1 operator refusal naming the installed core, the floor, and the next action (update spt-core first). ROOT: the floor was PARSED + required (manifest.rs) but never compared to the running core — dead enforcement; and the [update].version_check knob that gated it was DOC'D-BUT-DEAD (never read by any production path — a contract lie). RULINGS: RETIRE version_check (drop the manifest field + schema + docs + the cfg(test) literals; a pre-existing manifest still setting it deserializes fine — serde ignores the unknown key, no deny_unknown_fields, so retiring is back-compatible); SEMVER-compare NOT string-compare (the 0.9.0 < 0.25.0 lexical trap); enforce on BOTH verbs; nothing installs / registry untouched on refuse (binds both verbs, no residuals). FIX: (1) a pure spt-runtime version_meets_floor(core, floor) -> bool (numeric per-component: split '.', u64, missing→0, non-numeric→0, first-diff decides, equal-when-zero-padded ⇒ satisfied) — mirrors the CLI version_is_newer parse (same numeric model, different question: freshness=strictly-newer vs floor=at-least). (2) ADD: the gate lives INSIDE registry::register (the choke point) via a register_with_core(core_version) seam register() delegates to with env!(CARGO_PKG_VERSION) — the floor check runs right after the manifest parse, BEFORE any registry write, returning the typed RegistryError::CoreFloor{adapter,core,floor} (Display = the ONE F-1 refusal both verbs surface); nothing recorded on refuse. (3) UPDATE: a PRE-SWAP peek (staged_floor_ok) extracts the staged .spt to a THROWAWAY temp, parses its manifest floor, and refuses BEFORE apply_release_crc_swap mutates the live pointer-mode home — so a refusal (or an unverifiable floor: FAIL-CLOSED) leaves the live install BYTE-UNTOUCHED; register@8932 stays as the defense-in-depth backstop for every other entry path. THE COMPARISON BASIS IS THE CALLER'S, NOT THIS FUNCTION'S (releases#159): the bare verb judges against the running CLI, and a COMPOSITE update judges against the core that run will activate — see REQ-ADAPTER-FLOOR-VS-STAGED-CORE. The FAIL-CLOSED discipline in this clause is about an unverifiable STAGED MANIFEST and is unrelated to that input. doyle bind: the register-only gate would let the crc-swap replace the live files with a floor-violating version while the record refuses (record and reality disagree — the exact contract-lie shape this milestone kills), so the pre-swap peek is the only correct answer. Red-first: perri negative repro on ADD (fresh home + synthetic low core + high-floor manifest → CoreFloor refuse, registry untouched) + the UPDATE pre-swap refuse (live home byte-untouched) + a floor-met positive control (0.25.0-on-0.25.0 installs); + version_meets_floor table incl. the 0.9<0.25 trap. IN A COMPOSITE UPDATE, AN ADAPTER'S CORE-VERSION FLOOR IS JUDGED AGAINST THE CORE THAT RUN WILL ACTIVATE, NOT THE ONE CURRENTLY RUNNING (releases#159, doyle ruling 2026-08-21). THIS DELIVERS A CONTRACT ALREADY RULED AND NEVER SATISFIED, which is what the lane is FOR: CONTEXT.md's adapter-manifest-header entry has said since the glossary was written that `when spt-core self-updates, re-verify adapters still satisfy (coordinate core + adapter updates when needed)` — the code never did. Same shape as F-5 itself (REQ-ADAPTER-FLOOR-ENFORCE), where the floor was PARSED and REQUIRED and never compared: a dead rule, not a missing one. THE DEFECT: `spt update` plans FetchApply -> Adapters, and `spt update --restart` plans Fetch -> Adapters -> Finish; in BOTH the adapters leg runs while the NEW core is staged and the invoking CLI is still the OLD image, so a floor comparison against `env!(CARGO_PKG_VERSION)` asks a question about a core that is on its way out. An adapter whose floor the INCOMING core satisfies is therefore refused in-cycle, and the run then activates that very core and leaves the OLD adapter sitting on it — the refusal protected nothing, it deferred the pairing it was meant to enforce, ungated, into a state nobody checks again. FIX: `cmd_adapter_update` takes the comparison version as a PARAMETER instead of baking `env!`; the composite leg passes the STAGED release's `product_version`, read from the VERIFIED signed metadata via `ReleaseCache::staged_update` (`UpdateSetMetadata` / `ReleaseMetadata`). SINGLE-PASS, NOT A RE-SWEEP (doyle): a post-finish second sweep can itself fail half-rolled, so the one pass judges against the truth it already holds — and the CONTEXT.md clause above is amended to state that cadence, because a glossary sentence whose cadence a future implementer could build against would authorize the refused re-sweep with the glossary's own authority behind it. BOTH COMPARISON SITES INSIDE THAT FUNCTION TAKE THE PARAMETER (doyle confirmation 2026-08-22, dated to the confirmation and not to the lane: the correction postdates the ruled shape it corrects) — (a) the pre-swap peek `staged_floor_ok`, and (b) the post-swap re-register, which moves from `registry::register` (the `env!`-baking wrapper) to the already-public `registry::register_with_core` seam beneath it. (b) IS LOAD-BEARING, NOT TIDINESS: fixing only (a) admits the adapter at the peek, crc-swaps the new bits into the live pointer-mode home, and THEN refuses at the record against the old running core — live files new, registry record old, reported as FAILED. That is the record-and-reality disagreement REQ-ADAPTER-FLOOR-ENFORCE's own pre-swap-peek bind exists to prevent, reached from the other side, and it is strictly worse than the bug this requirement fixes (today's refusal is clean and byte-untouched). One value, two consumers, SO THE PEEK AND THE RECORD CANNOT DISAGREE ABOUT WHICH CORE THEY JUDGED AGAINST — that property is what makes this a fix rather than a second gate to keep in sync, and it is why the parameter is THREADED rather than each site reading the staged metadata for itself. THE BARE VERB IS UNCHANGED: `spt adapter update` (cli.rs AdapterCmd::Update) and its `spt update adapters` alias keep passing the running CLI's version — with no update in flight the installed core IS the right question — and both call sites are pinned as unchanged. `spt adapter add` is untouched: no staged core exists on that path. FAIL OPEN, ON THE NEW INPUT ONLY (doyle): an unreadable staged version degrades LOUDLY to the running-CLI comparison — an absent answer is not a zero, and an empty string is never compared against a floor — while `staged_floor_ok`'s deliberate FAIL-CLOSED arm for an unverifiable STAGED MANIFEST floor (REQ-ADAPTER-FLOOR-ENFORCE) is NOT touched. ONE ARM, THREE DIAGNOSTICS (doyle Q1 ruling, extended 2026-08-22 by measurement): an absent stage, an empty `product_version` (`#[serde(default)]` on both metadata types — pre-v0.3.2 releases really do parse to an empty string), and metadata that parses as NEITHER type all take the SAME degrade to the running core but must NOT share a message, because the operator's next step differs: nothing to do, an old release, and inspect your stage. The third states the FACT and points AT the stage without diagnosing it, borrowing 'malformed' from the apply leg's own vocabulary so two arms of one run never name the same corrupt artifact differently; the second offers predating-the-field as the LIKELY cause rather than the certain one, since any producer omitting the key yields the identical empty string. MERGING THE THIRD INTO THE SECOND IS SPECIFICALLY FORBIDDEN: telling an operator their release predates a field when the artifact is DAMAGED is a benign explanation offered for a corruption condition. THE THIRD ARM IS DEFENCE IN DEPTH AGAINST A STATE THE RUN SHOULD ALREADY HAVE REFUSED, and this is recorded here because the arm will otherwise look like dead code to the next reader and be deleted: in a COMPOSITE it is almost unreachable BY CONSTRUCTION — the core legs reject a malformed stage as a FAULT (RejectReason::Malformed, UPDATE_APPLY_REJECTED, exit 1 and not 3, since an integrity failure is not a policy decline) and composite_abort_on_failure is true for BOTH FetchApply and Fetch, so the run aborts before an adapters leg exists. It is reached only when a core leg exited 0 over an already-corrupt stage (a fetch with nothing new to pull) or when the stage changed after that leg ran, which is why its sentence names both possibilities and asserts neither. MEASURED REACHABILITY, one door only (relcache.rs:330-351): corruption at the SignedRelease envelope dies at `.ok()?` and yields None, indistinguishable from no stage and correctly reported as such — only inner metadata that parses as neither type, WITH an artifact on disk, falls to the `else if` and reaches the seam as Some. READING `product_version` HERE DOES NOT VIOLATE ITS OWN WARNING, stated in this title so the next reader meets the warning before they meet the use: that field's doc forbids using it to decide WHICH RELEASE IS NEWER — the monotonic `version` counter is the ordering authority and this requirement does not touch ordering — it does not forbid reading it as the semver a declared floor is compared against, provided it comes from the verified signed metadata, which is the only place this reads it from. Gate: doc — the CONTEXT.md adapter-manifest-header clause restated as the ruled cadence (the sentence this requirement delivers), the CONTEXT.md update-composite entry's floor-gate phrase, and MANIFEST.md's enforcement note, each naming WHICH core the floor is judged against; impl — the comparison-version parameter on `cmd_adapter_update` threaded to both the peek and `register_with_core`, the composite leg's staged-version read off the verified metadata, and the two-diagnostic loud degrade; unit — an adapter flooring on the FETCHED core is ADMITTED by the composite AND its record registers at the new version, the bare verb and its alias still compare against the running CLI, and the empty-`product_version` and absent-stage degrades each render their own diagnostic and fall back rather than refusing. `spt adapter translate-proof --event [--session ]` — the author-time EMIT-half proof tool for `[message-idle-translation-binary]` (ADR-0022), symmetric to `spt adapter digest-proof` (REQ-TERM-5). It spawns and feeds the adapter's declared translation binary EXACTLY as the daemon does at idle-delivery — running the REAL `spt_daemon::translation` driver VERBATIM (no protocol reimplementation): `TranslationChild::spawn` the binary, send the `{type:"init",endpoint_id,node}` line then the `{type:"event",envelope}` line, and read back the emitted `{key}`/`{text}`/`{delay_ms}`/`{commit}` keystroke-command stream — then prints it author-readable (each Key with its `key_to_bytes` rendering, Text quoted, Delay in ms, Commit marker) with counts. It fills the SAME `{id}`→option and `{session_id}`→(--session, else a placeholder) keys into the `--event` envelope the daemon fills at runtime, so an envelope that proofs here feeds faithfully live. EMIT-half ONLY: it proves the binary's spawn+feed+emit contract; it does NOT exercise the daemon's atomic PTY apply / controller-buffering (that stays covered by the W2 inject_control_wedge int gate) — `--help` says so. Exit codes mirror digest-proof: 0 ok, 1 on spawn-fail / zero commands / no-commit-or-output / unparseable, 2 when the adapter declares no `[message-idle-translation-binary]` section. The `TranslationChild` Drop does the bounded no-zombie reap. (v0.13.x) F-034 leg a (perri/hertz field finding 2026-07-09): the ADAPTER_UNRESOLVED refusal hint must print a WORKING command form. It currently says 'pass --adapter ', but --adapter is a `spt api` GROUP flag, NOT a `listen` flag — following the hint literally (`spt api listen --adapter `) produces clap `error: unexpected argument '--adapter'` (exit 2). Fix: the hint prints the group-level form, e.g. `spt api --adapter …` (a hint the operator can copy-paste and have work). Gate: the ADAPTER_UNRESOLVED message text carries a clap-VALID invocation (group-level --adapter placement) — a unit asserting the hint string parses under the api clap grammar, or at minimum places --adapter before the subcommand. Pure UX/hint-correctness fix, no behavior change. EVERY HOST-FIREWALL CHILD INVOCATION IS TIMED AND ITS OUTCOME NAMED IN THE DAEMON LOG (releases#304 W2 rider item 1, doyle ruled 2026-09-12; F-A1-1's instrument). The field leg that found F-A1-1 could only learn WHICH leg spent the subprocess budget by reading the Windows event log by hand: the reconcile's writes were timestamped ~2.5-3.2 s before the command returned, so the rule pair already existed while the product still reported the admission UNVERIFIED. Each invocation logs its LEG (verify-query, reconcile-write, cleanup, is-clean, or the Linux tool name -- never just the program, since every Windows leg runs the same powershell.exe), its wall in milliseconds, and an OUTCOME distinguishing completed, failed and killed. KILLED IS NOT FAILED: the budget expiring says nothing about whether the host changed, which is the whole content of F-A1-1, and a try_wait error that also kills the child is reported as failed rather than dressed as a timeout. A WALL WITHOUT ITS OUTCOME IS NOT A MEASUREMENT (hertz's register line, same lane): a script that fails to parse produced the fastest walls of the field night, 209 ms against a real leg's ~2000 ms, so anything ranking legs by wall alone puts a run that executed no statement first. This requirement is the instrument that answers the unpartitioned bring-up term in the field, and it ships independently of any query-shape change. Gate: impl -- the timed wrapper around every invocation with the leg label and the three-way outcome; unit -- the outcome classifier asserted on all three arms, including that a killed invocation does not read as completed or failed. THE ADMISSION VERDICT COMPARES WHAT A RULE ADMITS, NOT HOW THE HOST FIREWALL SPELLS IT -- AND EVERY OTHER DIFFERENCE STAYS REJECTED (releases#304 W2, doyle's repair contract 2026-09-12). THE ANTI-WIDENING HALF IS THE REQUIREMENT, not a caveat on it: this row exists to bound a normalization, and a normalization that admits one scope nobody chose is worse than the string comparison it replaced. Narrow to REQ-WEB-LAN-BOOTSTRAP-FIREWALL, which owns the admission pair and the scope policy this compares against; this row owns only HOW the two sides are compared. EQUIVALENCE, all three axes: an application filter that is ABSENT satisfies a spec wanting none however the host renders it (the empty string a program-less spec means, and the literal `Any` NetSecurity reads back), and NEVER satisfies a spec that wants a program; a PROFILE field compares as the SET of flag tokens it names, so one set in two renderings (`Domain, Private` against `Private,Domain`) is one set; an IPv4 NETWORK compares by value across prefix form (`192.168.1.0/24`, what the composer emits), mask form (`192.168.1.0/255.255.255.0`, what NetSecurity renders back) and a bare address, which is the /32 containing only itself. REJECTION, and each clause is a scope the policy did not choose: a genuinely NARROWER remote (a single host inside the derived prefix), a genuinely WIDER one (`Any`, `0.0.0.0/0`, a shorter prefix), a DIFFERENT network, and a profile SUBSET or SUPERSET -- `Any` is NEVER expanded into the named profiles, or an all-profile rule would satisfy the LAN half and admit the local subnet of a PUBLIC network, which is the exact widening the two halves exist to prevent. MALFORMED INPUT IS REJECTED FROM BOTH SIDES, and identical malformed text on the two sides is NOT a match: a prefix out of range, an absent or negative length, a non-contiguous mask, a truncated or out-of-range address and the empty string must answer `does not satisfy` rather than parse loosely, round to a nearby prefix, or panic -- and a SPEC whose own remotes are not networks is satisfied by NOTHING, including a rule carrying that same unparseable text, so equality between two literals can never stand in for a scope neither side can name. NORMALIZATION IS CONFINED TO THE SPEC MATCH: hygiene, source store and ActiveStore enforcement are decided at their own arms on fields these functions never see, and no canonicalization may make a rule that failed one of them satisfy a spec. BOTH SITES OF THE ONE DEFECT ARE GOVERNED: the verdict's comparison, and the lan-scope-moved log line, which on raw strings announces a move on every reconcile of an unchanged host and teaches the operator to ignore the line that exists to be noticed. Gate: impl -- the program/profile/network token normalizers, the wanted-side parse requirement, and their use at the spec match and the scope-change line; unit -- equivalence proven on each axis with each arm asserting the RAW strings differ first, so no cell can pass on a normalizer that does nothing, plus narrower, wider, different, subset, superset and malformed inputs staying unsatisfied against a positive control, and identical malformed text on both sides staying unsatisfied. releases#338 release side (#331 H2, hertz 2026-09-24): every release set carries the bundled-adapters asset `spt-bundled-adapters.tar.gz` (bundle.json naming each member name/version/asset/sha256 + one byte-identical PUBLISHED `.spt` per member; first members claude-spt from BigscreenVR/claude-spt-bs and PACER from BigscreenVR/spt-pacer-tool), its sha256 signed into UpdateSetMetadata.bundle (additive, the docs precedent: older sets parse to None, older binaries ignore the key). verify_update_set_bundle rejects a set WITHOUT the entry (Malformed) and bytes that do not match (ArtifactMismatch). Acquisition runs ONLY in the maintainer-local `xtask release-publish` with the caller gh auth (ruled doyle 2026-09-24 H2 Q1 option b: the CI half never signs so it cannot enforce, and one acquisition path for one signed asset), which REFUSES to sign a set without the entry; `xtask bundle-adapters` is the same assembly standalone, with `--local` members for W5 local int assembly. A `[patch.crates-io]` pin that FIXES A SHIPPED DEFECT is asserted still in force by `xtask check`, in BOTH of the two ways it can silently lapse. (INFRA-REGISTER IR-4 part 1, releases#125 fix-lane intake; hertz 2026-08-03.) THE PIN AT RISK: swarm-discovery 0.6.0-alpha.2's `updater::gc` answers a send to a stopped actor by scheduling another gc task 10ms later — a ~100 Hz retry loop per destroyed updater that can never reach a live receiver, so every updater recreation leaks one more loop. Latent in EVERY daemon since v0.38.1; the field cost was a kitsubito daemon burning two cores for two weeks (~9 accumulated loops). Fixed upstream in rkuhn/swarm-discovery PR #27, which is in NO crates.io release (0.6.3 predates it) and iroh pins swarm-discovery to EXACTLY 0.6.0-alpha.2, so no published version satisfies the graph — the tree carries a minimal-delta backport fork pinned by `rev`, i.e. content-addressed. TWO FAILURE MODES, WHICH IS WHY ONE ASSERTION IS NOT ENOUGH: (1) a routine `cargo update` or an iroh bump re-resolves Cargo.lock back to the registry crate while the stanza still sits in Cargo.toml looking correct — the lock disagrees with the manifest, or resolves from the registry outright; (2) the STANZA IS DELETED, after which there is nothing left for the lock to disagree with and a lock-only check reads clean over a reintroduced defect. So the manifest is asked whether the pin exists AT ALL, and only then is the lock asked whether it agrees. THE EXPECTED REV IS READ FROM THE MANIFEST, never duplicated into xtask: a third copy of the sha is one more thing to forget, and a legitimate re-pin would then red on the wrong file. What IS hardcoded is the crate plus the reason its patch is load-bearing — the judgement a lock file cannot carry and a human needs in order to decide whether dropping it is safe — and the refusal PRINTS that reason plus its own drop condition (upstream ships PR #27 AND iroh's pin reaches it), so the guard states when it should be removed rather than becoming permanent by default. Removing a crate from the guard list is therefore a reviewed edit in the same commit, not a silent lapse. Gate: impl = LOAD_BEARING_PATCHES + check_load_bearing_patch_pins wired into `xtask check` (already a golden docs-drift step on both OS legs, so this rides an existing leg rather than adding one); unit = the three failure modes each proved separately (deleted stanza, registry resolution, moved rev), the two parser traps that would make the guard answer for the wrong thing (a prefix-named crate, the trailing `[[patch.unused]]` table), and an arm that runs the real workspace so the guard cannot ship green against a tree it does not actually hold. Kin REQ-CI-WINDOWS-PHASE-A-BOUND, REQ-HEAVY-UNIT-CLASSIFICATION (same `xtask check` seam: a rule that outlived the memory it depended on). CI notifications emit an explicit diagnostic when commit attribution is missing or unparseable rather than silently treating it as an empty successful attribution (REMOTE-FRICTION #304, IR-47). Read the exact project convention Co-authored by: from raw commit bodies; preserve that space-spelled key, which git trailer tokenization does not parse. Focused selftests cover valid attribution and missing or malformed attribution. spt endpoint noun namespace: absorbs fork/suspend/wake/shutdown/rename/stop/digest + access (ported 1:1: allow|revoke|open|list, decision 21) + description (ex-resources blurb; bare=show, set=author); merged endpoint list [--local|--subnet ] grouped by subnet with SELF pinned, --detail adding the ex-resources yellow-pages blurb projection; bare spt endpoint = the list (M8 decisions 1-2, 25). SUPERSEDED (F-025 item 3): the LISTING SHAPE now lives in REQ-ENDPOINT-LIST-NODE-GROUPED (node-grouped over unique instances, not grouped-by-subnet) + REQ-ENDPOINT-LIST-REST-FILTER (suspended-hidden + --show-all); the `--local` flag was dropped by REQ-ENDPOINT-LIST-MERGE-LOCAL (the list ALWAYS merges local). This REQ owns only the endpoint noun NAMESPACE + parse surface — not the render shape. User-facing CLI output is human-readable: DIRECT-USER commands (e.g. adapter update/list/use) render friendly prose instead of raw CODE:RESULT markers — "claude-spt is up to date (0.2.0)." not "ADAPTER_UPDATE_UPTODATE:claude-spt: installed 0.2.0, latest 0.2.0". Strictly bounded to the direct-user surface: the adapter-PARSED bringup tokens (SEEDED/BOUND/READY/NO_SEED on seed/listen, which adapters grep) stay machine-parseable — humanization is additive (a human line beside the marker, or a --porcelain/--quiet split), never a silent rename of a dual-contract marker. The user-facing bringup composition belongs to the adapter (perri); this REQ owns only the direct-user CLI surface. (v0.9.0) `spt endpoint monic --help` carries a composed `Trigger kinds:` section naming every kind a trigger can watch, WHAT it watches, and WHETHER ANYTHING EVALUATES IT TODAY — the third column being the load-bearing one, because `user-input` and `agent-output` are ratified but INERT, and a section that listed them beside the live kinds without saying so would present a trigger that can never fire as one that works. That is the same honesty `monic list` already owes a stored trigger, now owed at the moment of CHOOSING one. The section is composed AT RENDER TIME from a single row table whose row carries the `TriggerKind` ITSELF, so the kind's wire spelling and its evaluated-today answer are DERIVED — from the enum and from the existing `has_live_consumer` predicate — rather than restated beside them: a kind cannot appear in the help under a spelling the `--triggers` parser would reject, nor be described as evaluated when nothing evaluates it, and a kind added to the vocabulary reaches the help by the sole act of being in the table. PLAIN TEXT, no inline Markdown, for the reason the surface section carries the identical rule: the CLI renders help through `helpfmt`, and a composer in the store crate can be read by a consumer that never passes through that renderer, so a Markdown marker reaches one audience styled and the other as literal asterisks. The words match the published docs table (kind | watches | evaluated today) rather than paraphrasing it, so an operator who read the manual and an operator who read the help learned the same vocabulary. Gate: doc — the docs-site trigger-kind table these rows speak in the same words, and the generated public CLI reference; impl — the row table beside the enum it describes, the render-time composer taking the table AS A PARAMETER, and the after-help wiring at the sited verb; unit — a fixture table carrying an INVENTED row renders that row with no edit to any display site (table-drivenness proven rather than inferred from a snapshot whose rows all agree), EVERY enum variant is present in the table (a kind added to the vocabulary cannot be silently missing from the section), a live and an inert row render their differing third column, the rendered help at the sited verb carries the composer's own text byte-for-byte rather than a second literal, and that help leaks no internal code. THE PER-MACHINE SUPERVISOR SURFACE IS SPELLED `spt node`, AND `spt daemon` REMAINS A FULL ALIAS OF IT — ONE PARSE TREE, NOT TWO. The noun operators type for their own machine was `daemon`, which names the PROCESS rather than the thing the process supervises; the surface is a node's state (daemon state, member subnets, local endpoints), so `node` is the primary spelling and `daemon` is a clap `visible_alias` on the same command. THE ALIAS IS FULL AND PER-SUBCOMMAND, NOT ROOT-ONLY: bare (the status view), run (incl. the hidden --detached auto-start-ladder flag), start, stop (incl. --force), status, access + its allow/deny/remove editing arms, refresh, and the hidden brain entry (bare and stamped) all parse identically under either spelling. A root-only equivalence proves the alias resolves and proves NOTHING about a subcommand added later under one spelling, which is why the unit asserts the parsed action as a VALUE per subcommand rather than matching variants. THE CHANGE IS CLI-SURFACE-ONLY: the spt-daemon crate, the wire, and the docs model term `spt-daemon` are untouched, and `daemon access` is mirrored as a NOUN, not duplicated as policy — one parse tree, one roster store. THE DEPRECATION NOTE IS SHARED AND ALIAS-NEUTRAL (doyle fork-D ruling): clap renders help per COMMAND, not per alias, so the note lives in the one `///` block and is worded to be TRUE under both spellings — deprecation when read on `daemon --help`, information when read on `node --help`. No argv sniffing, no stderr-only notice, no second variant. THE BRAIN SPAWN ARGV KEEPS THE `daemon` SPELLING (doyle fork-A ruling, KH-2.3): brain_child_args builds `daemon brain --generation N --start-reason R`, and the compatibility it exists for runs toward binaries that PREDATE the alias — an N-1 parser knows only the old spelling and an alias cannot rescue a parser that never had it, so migrating the spawn site is a later milestone gated on a fleet version floor, never this lane. REMOVAL OF THE ALIAS IS BLOCKED ON AN INSTALL-ARTIFACT MIGRATION (doyle fork-C ruling) AND THAT CONSTRAINT RIDES THE DOC SURFACE, not this file alone: installed OS service units and scheduled-task rungs on every deployed machine already carry `spt daemon run`, and renaming the verb does not rewrite them, so a future milestone must migrate those artifacts before scheduling removal. THE DEPRECATED SPELLING IS DOCUMENTED, NOT HIDDEN (doyle fork-E ruling): `visible_alias` rather than the hidden `--deferred`-style precedent, so the alias reaches the generated reference as a findable deprecated row — a deprecation an operator cannot find is not a deprecation. Gate: impl — the primary/alias rename with the shared note, the dispatch arm, the surface-help path table moved to the primary spelling, and the KH-2.3 spawn-argv sentence; unit — per-subcommand alias equivalence over the enumerated surface, the bare form defaulting to the status view under both spellings, and the note rendering in BOTH spellings' long help (the shape-(i) choice made visible, so moving the note to a runtime print reds here); doc — CONTEXT.md's disambiguation line separating this VERB from the retired `spt-node` separate-process deliverable, plus the reference regen carrying the deprecated row and the removal constraint. Kin REQ-CLI-2 (the noun this re-spells), REQ-HAZARD-HANDOFF-ARGV-COMPAT (the spawn argv it must not modernize), REQ-DOCS-5 (the generator whose drift gate must be green on the regen). Human-prose COMMAND OUTPUT (not just `--help`) renders the inline Markdown authored in its source strings as terminal styling, never literal markers: `` `code` `` → ANSI cyan, `**bold**` → ANSI bold, `[text](url)` → `text`, markers STRIPPED either way. REQ-CLI-HELP-MARKDOWN only hooked the clap `--help` chokepoint, so command output still printed raw Markdown (audit: `spt how-to` topic text showed `# headers`/backticks, `spt subnet`/`subnet status` hint footers showed stray backticks, the daemon-status `not running` line, the `ENDPOINT_RUN_STARTED` attach hint, and the daemon's `SUBNET_DETACHED` startup line — 13 prose surfaces). The same line-bounded pure `helpfmt::render` is applied at each emit site, color-gated by the OUTPUT STREAM's own tty (`stdout_color` for print/println, the new `stderr_color` for eprintln). HARNESS-SAFETY (binding): color is tty-gated, so an adapter (piped / non-tty / NO_COLOR) gets STRIP mode = zero ANSI + markers removed; every dual-contract MACHINE token on a rendered line (`ENDPOINT_RUN_STARTED:`, `NO_SUCH_TOPIC:`, `SUBNET_DETACHED:`) carries NO Markdown markers, so it survives strip byte-intact — the adapter parse is never perturbed. Pure-machine output (the `` envelope, bringup parse-tokens SEEDED/BOUND/READY/NO_SEED, `--json`, QR) is NEVER routed through the renderer. The one spt-daemon source string (`SUBNET_DETACHED`, the bin-local renderer is unreachable from the daemon crate) is authored marker-free instead. (v0.12.2) TEARDOWN-AUTHORITY W4 (perri adapter-surface finding 2026-07-19, doyle-grounded and RE-SCOPED; title AMENDED at gate 2026-07-19 — the original welded version to --after polling, todlando falsified it from digesthub.rs/cli.rs and doyle ruled the amendment rides the wave: fifth instance of the claim-keyed-on-the-wrong-thing class, this one in the REQ registry itself): `spt endpoint digest --json` must be self-contained on stdout. Today the digest snapshot version is NOT a field of the --json object at all — it exists ONLY in the DIGEST: version=N trailer that cmd_digest eprintln!s at cli.rs:1619 — so a JSON consumer that wants it is FORCED to parse stderr. NUMBER-SPACE TRUTH (the amendment): version is digesthub's monotonic PROJECTION counter — it bumps when the projected digest CHANGES, serves as the --follow from_version floor and a change-detection cursor, and is NOT valid --after input; --after filters on entry seq ((ledger_ordinal<<32)|line_idx), a different number space, so a version passed as --after predates the window every time. NOTE the corrected history (perri's original RCA framed this as a fleet outage and doyle falsified it; perri confirmed): the trailer has been on STDERR since it was added 2026-06-03 (16f4c8e) and no stdout trailer ever existed, so stdout-only consumers never choked — this is a CONTRACT-COMPLETENESS defect, low priority, NOT an outage. FIX: (a) emit version as a top-level integer field inside the --json object (field name pinned by the consumer — perri's adapter parses 'version' — so no second round is needed), alongside the existing after_predates_window signal; (b) gate the stderr trailer on the non-json path so --json leaves stderr clean while the human path keeps its status line. Result: --json stdout = pure self-contained JSON including the version, safe whether the consumer reads stdout-only OR merges 2>&1. Additive to the JSON shape (REQ-CLI-JSON evolution rule). The published doc must state the number-space split with the EXPLICIT NEGATIVE (version is NOT valid --after input) — the original title proves the misuse is the natural reading. Gate: doc — the json-shapes digest section carries version, the stderr-clean --json contract, the complete entry-kind enum with per-kind produced-vs-injected provenance, and the seq/version asymmetry incl the explicit negative; impl — version field on the --json snapshot path + non-json-gated trailer; unit — the --json object carries version and stderr carries no DIGEST: trailer, the non-json path still prints it (predates ordering included), and the behavior-change sweep confirms no existing test asserts the old --json shape or the trailer presence under --json. THE ECHO GATE SPLITS BY ARM INTO TWO SENTINEL FILES, AND ONLY THE WORK-DRIVEN ONE IS AGE-GATED (releases#113 fork 1, re-ruled 2026-08-30 on a corrected premise). MEASURED FIRST, AND THE MEASUREMENT INVERTED THE TICKET: the W0 report recorded the gate as armed by TRANSITION EDGES ONLY (arm_transition_echo) and concluded that an agent working for hours without a transition edge never echoes. FALSE -- cmd_state's idle arm (api/delivery.rs) writes the same sentinel UNCONDITIONALLY unless --no-gate, and that is published contract (harness-contract api.md: reporting idle also arms the echo gate). `api state idle` IS the turn-end report, so a working agent arms at EVERY turn end and the pulse loop fires within one period. spt-core was never missing the work-driven trigger; it was missing the AGE GATE. THE RULED SHAPE IS TWO FILES, NOT A CARRIER INSIDE ONE: (a) the EDGE sentinel, written by arm_transition_echo, fires UNGATED as today -- detach / attention-shift / suspend are rare and attention-correctness wants timeliness, and gating them would drop exactly the fire that matters at detachment risk; legacy supports the split, its boundary and session-edge fires were not age-gated either. (b) the IDLE sentinel, written by the idle arm, fires only at AGE >= 15 MINUTES, carrying the three legacy mechanics: a fresh sentinel answers SkipSentinelFresh{remaining} and the caller schedules a SHORT NEXT PULSE to wake near the crossing (this file only); age reads BIRTHTIME with an MTIME FALLBACK and the degradation is DECLARED rather than silent (birthtime = oldest un-fired turn end, mtime = most recent); and a metadata failure or clock skew FAILS OPEN -- it FIRES. Two files rather than one carrier is the property: an idle write can never overwrite an armed edge (no last-writer-wins downgrade), each file has one writer class, read-and-clear stays per-file, and the fire-time distinction is STRUCTURAL rather than parsed out of a byte someone has to remember to write. THE ARMING CONTRACT IS UNCHANGED and the docs sentence stays true -- reporting idle still arms; what acquires the age discipline is the FIRE. A machine-parsed diagnostic emission is handed to the OS as exactly ONE write of the complete rendered text including its terminating newline. `#` always-on address sigil: a reserved LEADING sigil marking an AlwaysOnEndpoint, extending the REQ-INST-10 grammar to `[subnet:]#id[@node]`. Mandatory + bijective — `#name` ⟺ always-on endpoint, bare `name` ⟺ agent endpoint — so the router resolves endpoint class from the address alone, before any registry lookup. Sits ABOVE REQ-HAZARD-ID-CHARSET: the address parser strips the single leading `#` before id validation, so the bare/stored id stays charset-clean and a mid-id `#` remains rejected (the charset contract is unchanged). The engine room is made aware of the surface vocabulary in the same words the CLI teaches it: the surface section is composed FROM THE SAME TABLE AND THE SAME COMPOSER the CLI help uses, WHEREVER THE ENGINE ROOM RECEIVES IT, rather than restating the vocabulary in prose. AMENDED BY REPLACEMENT in the KEYSTONE #182 W3 lane (doyle ruling on releases#179, 2026-08-19): the surface section moved from the bring-up briefing to the engine room's durable in-core role (REQ-ER-ROLE-STATIC-IMMUTABLE), so this requirement names the PROPERTY rather than one carrier — single-source is preserved across the carrier move, and role, briefing and CLI help remain unable to diverge. Before this section existed the engine room saw surfaces only as the SURFACE column of the ruleset table — a rendering of what rules already say, which tells an engine room what is configured but never what the words mean or what granting one costs. The single-source property IS the requirement: an engine room taught at seat time and an operator reading --help must be unable to receive different vocabularies, and a surface added to the table must reach BOTH renderings with no second edit — which is why the composer is table-PARAMETERIZED, and why a hardcoded surface list, in the role or anywhere else, is a defect this requirement refuses (the carrier of the role is a function and not a const for exactly this reason). The engine room must also not be sent to a verb that no longer exists: a section that teaches the vocabulary while prescribing a dead command teaches half a lie. Gate: doc — the CONTEXT.md engine-room entry naming the surface section as table-composed wherever it is served; impl — the role composer and the briefing both calling the shared composer, never a literal surface list; unit — a fixture table carrying an INVENTED row reaches the engine room exactly as it reaches the help, the section text is identical to the CLI's for the same table, and no prescribed subcommand is one the CLI does not parse. A BRING-UP THAT KNOWS THE ROOM'S ANCHOR CREATES THE ROOM'S PERCH WITH IT, so the harness bind it is about to launch inherits that anchor instead of re-resolving one it is not allowed to guess. An endpoint's anchor subnet is its DEFAULT-SCOPE subnet, so `stamp_creation_fields` refuses to invent one at creation on a node holding more than one subnet (ANCHOR_REFUSED / MULTI_SUBNET_HOME) — a deliberate explicit-choice seam that this requirement PRESERVES rather than relaxes: a caller with no anchor still cannot create a record, and the bind path still refuses exactly as before. The defect was that the engine-room bring-up HELD the answer and dropped it — `room.home_subnet` is the very value the same handler prints as ENGINE_ROOM_ADMIN_BRINGUP_EMPOWERED — so on every multi-subnet node the room's own perch was uncreatable. Measured end to end on HFENDULEAM at v0.58.0 (releases#204): no `info.json` was ever written for engine-room (the perch dir held only `spool.db` and `.has-messages`), the harness's first act `spt api bind engine-room` met the creation arm with prior=None and was refused naming all three subnets (BIGNET, SPT_MANTLE, SPT_DEV) — loudly, but only into the adapter's `hook-trace.log` — the endpoint therefore never entered any roster, `spt send engine-room` answered NO_PERCH three times across fifteen minutes, and the retained briefing row could never be delivered to a target that does not exist. The skeleton is written beside the in-flight ledger, before the launch thread fires, for the reason that entry is there: both are facts about a bring-up this broker started and both must be true before the harness exists to observe them. It is IDEMPOTENT BY REFUSAL — an existing record is left untouched, so a second bring-up cannot reset a live perch — and LOUD BUT NEVER A REFUSAL, the same denial-of-governance rule `empower_home_for_bringup` follows: a record that could not be written costs the operator the briefing, not the room, and degrades to exactly the known pre-fix path rather than inventing a new failure. SECOND SURFACE, SAME REQUIREMENT: the UNPRESENTED alarm splits, because one text was covering two events and promising what only one of them can keep. Perch-ABSENT (no record — in no roster, no inject can ever target it, the retained row waits forever) must NOT tell the operator the briefing arrives at the session's first idle; perch-SLOW (record exists, not yet a deliverable inject target) keeps today's promise, which it can honor. The absent arm fires only on a POSITIVE measurement of absence — the record file is not there — so an unreadable or unparseable record takes the promising arm (an absent answer is not a zero), and the predicate is record-EXISTENCE rather than liveness so a re-attach to a merely idle room can never false-fire it. Gate: doc — the published harness-contract sentence that the identity brief's perch claim is valid only after successful registration, and that on a refused bind the adapter emits the no-perch shape carrying the refusal (the sentence the adapter and omp-spt build against); impl — the anchored-skeleton writer in spt-store called from the bring-up path with the room's own home_subnet, plus the two-arm alarm; unit — the skeleton writer anchors a fresh record and seeds its scope, refuses to overwrite an existing one, and the alarm predicate distinguishes absent from slow with the unreadable case failing OPEN to slow; int — a REAL bring-up on a multi-subnet node leaves a rostered perch record (the pre-fix red: the record is absent and the bind is refused), which is the witness the pre-existing multi-subnet cell could never be, because its rig pre-creates the anchored skeleton that is exactly the condition the real path failed to establish. THE EVENT ENVELOPE'S SENDER-AUTHORED ATTRIBUTE SET IS OPEN, AND THE PUBLISHED CONTRACT STATES THE CLASS RULE, NOT ONLY PER-ATTR OBLIGATIONS: any consumer that re-renders deliveries (adapter pipeline, digest, relay surface) MUST pass through every envelope attribute it does not itself consume — re-emitting from a fixed allowlist of known names silently deletes every attribute added after the list was written (measured 3x in the field; 3rd recurrence was an adapter render keeping only from+body, deleting the seal citation), and the deletion is invisible at the dropping site because nothing there names what it dropped. The ONLY closed list is the receiver-composed STRIP class (trust-warning, mnemonics-json) — a strip list, never a render list. MIRROR SAFETY CLAUSE: a passed-through value stays IN ATTRIBUTE POSITION with its wire escaping intact — unescaping an attribute value into body/frame context hands a hostile sender a forgery seam (a crafted value closes the re-rendered tag and lands text at the consumer's frame level); decode only where the final consumer parses attributes. CONSUME IS DEFINED, NOT LEFT TO THE READER (perri's discriminating question, 2026-08-25): a pipeline consumes an attribute when its handling RE-EXPRESSES the value's distinction to the agent (content re-rendered into its own surface, or a dispatch whose agent-visible outcome differs per value); reading-then-emitting-shared-output is peeking, and the DROP TEST decides — if omitting the attr makes two sender-distinct deliveries indistinguishable at the agent surface, it rides. NAMES ARE TOKENS: grammar [a-z0-9_-]+, hostile-reachable (envelopes arrive from peer NODES), pass through only a grammar-matching name, refuse/drop loudly anything else in tag position. Gate: doc — the overview.md wire-contract clause stating the pass-through obligation, the consume definition + drop test, the name-token grammar rule, and the escaping-position rule. The per-job companion to REQ-FIXTURE-BIN-BUILD-EDGE, and it is a DRIFT GUARD, NOT A FINDER. `xtask perjob-map` walks every CI job step by step and asks, for each narrow cargo invocation, whether the cross-package fixture bins its tests consume were built EARLIER IN THAT JOB -- by an explicit `cargo build -p X --bin B` or by an earlier workspace-shaped build. On this tree the answer is 7 narrow invocations, 0 unguaranteed, which is what establishes that the 11 repo-wide reds are NOT CI defects. THAT ZERO IS A FACT ABOUT TODAY'S WORKFLOWS AND NOTHING MORE, and the requirement says so because the tool's value is entirely in the FUTURE case: the day somebody adds a narrow invocation to a job that does not build what it consumes. A requirement that framed this as proof the class is closed would license deleting it the moment the register entry is written. THE ONE MODELLING TRAP, already paid for once: a workspace-shaped run emits the plain binaries only if it actually builds integration-test targets -- `--workspace` filtered to `-E 'kind(lib) + kind(bin)'` compiles lib/bin harnesses and emits NO plain fixture exe on a clean pool, which is exactly what `ci.yml:102-106` documents and hand-prebuilds around. The first prototype credited it as a builder and returned a GREEN over the one known-real gap; the kind-filter test is therefore a required unit row, not an optional one. THE SCRIPT-BLIND HOLE IS PRINTED IN THE COMMAND'S OWN OUTPUT RATHER THAN FOOTNOTED IN A DOC. The model reads `run:` blocks in workflow YAML; narrow cargo invocations ALSO live inside `.github/ci/*.ps1` and `*.sh`, which those blocks merely invoke, and no `run:` parser can see inside them. Rather than leave that as a limitation somebody has to remember, the scripts are scanned with the SAME detector and every narrow invocation they contain is listed as UNMODELLED with the cross-package bins it consumes: today 2 instances (`g6-curve.ps1`, `g6-postbounce.ps1`, both `-p spt-daemon --test inject_control_wedge`), both consuming NONE, so the hole is real and its instance count is measured at zero instead of asserted. An UNMODELLED invocation that DOES consume a cross-package bin counts as a gap and reds the command, because a verdict nobody can compute must not read green. THAT SCAN NEEDED ITS OWN CORRECTION, AND THE DIRECTION MATTERS: scanning the scripts raw read `reap-census.ps1`'s comment-based-help block -- which DESCRIBES a `cargo test -p spt --test ...` failure in prose -- as a real invocation consuming three cross-package bins, i.e. a fabricated gap inside the section whose entire job is honesty about what cannot be seen. PowerShell `<# #>` blocks and whole-line `#` comments are stripped; a TRAILING `#` deliberately is not, because in sh that would eat `${var#foo}` and losing a real invocation is the worse direction here. THE YAML IS PARSED BY A DELIBERATELY SMALL SUBSET READER, AND ITS FAILURE MODE IS ACCOUNTED RATHER THAN TRUSTED: xtask carries no parser dependency, and a silent mis-parse would DROP steps, after which every job those steps belonged to would read guaranteed -- a green manufactured by not looking. So the reader counts the `run:` command keys present in the file and refuses (exit 2) unless it attributed every one of them to a job. A `run:` that opens a mapping rather than a command (`defaults.run.shell`) is not a command key on either side of that count. LIMITS THAT REMAIN OPEN AND ARE NAMED IN THE COMMAND'S OWN DOC: `uses:` steps are not followed, and guarantee is tracked per job in step order without modelling artifact reuse across jobs on a persistent self-hosted workdir -- and our golden boxes ARE persistent, so a WORKSPACE verdict is sound only within its job and a clean runner could still expose what reuse masks. Gate: impl -- `crates/xtask/src/perjob.rs` and its `perjob-map` subcommand: the consumed-bin index over integration-test targets and over the unit population (unit tests get no `CARGO_BIN_EXE_*` at all, which is why `-E 'kind(lib)+kind(bin)'` is a CONSUMER step), the step walker with its prebuild and workspace-build guarantees, the accounted YAML subset reader, the script scan with its comment stripping, and the reuse of REQ-FIXTURE-BIN-BUILD-EDGE's detector rather than a second one (mapping through a different detector would produce a per-job verdict over a different population); unit -- the parser refusing an unattributable `run:`, block and inline bodies both surviving while `uses:` and `#` lines do not, cargo invocations stopping at a shell separator and `nextest run` not being read as the shorter `test` verb, the kind-filtered workspace run classified as consumer rather than builder, flag values requiring the whole flag (`--bins` is not `--bin s`), script prose not being read as an invocation, and a job guaranteeing by prebuild, by workspace build, and failing when neither ran. THE GUARD WAS OBSERVED FIRING ON A REAL FALSE STATE, not only in tests: while this lane was in progress the checker's own test fixture named `CARGO_BIN_EXE_git_fixture` unescaped in a tracked `src/` file, which entered the unit-consumer population and made this map report a gap in `ci.yml`'s unit lane and exit 1. Kin REQ-FIXTURE-BIN-BUILD-EDGE (the repo-wide half, whose detector and helpers this reuses). REGISTRY-LIFECYCLE W4 fast-follow (hertz v0.37.0 field-verify CPU-gate FAIL RCA 2026-07-17, doyle seam-verified same day; timing-pinned ~16s burst clusters per 30s Registry pump round): recent-project gossip derives a project id AT MOST ONCE PER DISTINCT CWD per advertisement round — recent_projects_for dedups the RAW cwd string BEFORE project_id_for_dir (today seen.insert(pid) dedups AFTER derivation at registryhost.rs:1001, so every duplicate-cwd sessions.log row pays a git remote/rev-parse spawn: ~200 derivations/round on a 12-perch box, the post-self-hash-fix CPU floor). The pid-level dedup stays as the output-uniqueness second layer (distinct cwd spellings may collapse to one project); ordering semantics unchanged (first-occurrence newest-first); bounded ledger read unchanged. Gate: impl — cwd-dedup before derivation; unit — derivation-count observable == distinct cwds not ledger rows (duplicate-cwd ledger derives once), output/order parity vs the pre-fix shape on a mixed ledger. SEEDED SEPARATELY (not this REQ): gossip leg consults the ADR-0037 materialized index (kills git entirely + cross-round rederivation; staleness-semantics design change) + epoch-mint batching (26 atomic writes/round; durable-monotonic contract change). Corrupt (present-but-unparseable) info.json is NOT absent: liveness/status readers agree a destroyed record is neither alive nor Active (counter-39 #2) A daemon DETACHED-IN-FACT (no interactive console, or an inherited stderr PIPE nobody drains) that never nulled its std handles will BLOCK on stdio writes when the pipe fills, and/or pop a visible conhost window (REQ-HAZARD-WMI-DAEMON-WINDOW is a covered surface of this hazard). detach_console nulls the 3 handles only under the --detached flag; a rung that omits it (the bare line-82 elevated->deelevated respawn; a STALE installer at-logon task registered as bare `daemon run`, confirmed live field-drift on ENLYZEAM) is exposed. FIX: (load-bearing) inside `daemon run`, null the 3 std handles when stderr GetFileType==FILE_TYPE_PIPE — a pipe is the ONLY std sink that BLOCKS the daemon when it fills; catches every rung whose inherited stderr is an undrained pipe, independent of whether each caller passed --detached, while a FILE (2>run.log AND every int-test Stdio::from(file) brain-log capture), a CONSOLE (scrolls), and a NULL/absent handle (DETACHED_PROCESS rungs, already discard) all SURVIVE. DELIBERATELY NOT gated on GetConsoleWindow==NULL: a CREATE_NO_WINDOW daemon has no console window yet a drained FILE stderr — nulling it would blank the capture for ZERO safety gain (a file never blocks) and mass-red the int-test brain-log assertions. (belt) pass --detached on the bare line-82 respawn; (drift nag) parse the schtasks /Query action argv and LOUDLY nag when the at-logon task is the stale bare `daemon run` form (manual/installer re-registers; the daemon must NOT self-elevate to rewrite it). Defense-in-depth — no current spt Windows spawn path was proven to yield a BLOCKING inherited pipe (all rungs null-discard or scrolling-conhost), so this is hardening, not a confirmed incident root. ` as the ` ` entity, so no attribute value can split the line-framed EVENT. The falsifier is the composed trust-warning (releases#170), a multi-line block that rides as a `trust-warning` ATTRIBUTE: under the old escaper one delivery rendered as SEVEN lines under the default advisory (measured at the red, 2026-08-21), six of which are not envelopes. The count varies with the operator override, which is why the invariant is stated as "no attribute value can split the frame" and not as a number. Decode is amp-last-preserving: ` ` decodes BEFORE `&`, exactly as `
` does on the body side, so an attribute carrying the literal text ` ` (arriving as `&#10;`) decodes to the literal and never to a linebreak. Corollary held by the same commit: the codec has ONE escaper and therefore ONE decoder — `parse_event_from_attr` calls `event_attr_unescape` rather than hand-inlining a second rule that would make `from=` decode differently from every other attribute. Two-slice envelope parser is panic-free and tolerant (4.2) An `spt rc` session forwards the Backspace key as the VT DEL byte (0x7f), so the hosted TUI (Claude Code) deletes ONE character — never a whole word. SYMPTOM (operator dogfooding): Backspace in an rc session always behaves like ctrl+Backspace — deletes the entire last word. ROOT (doyle /diagnose, code-grounded, byte PENDING HITL confirm): rc is a RAW VERBATIM byte pump — spawn_stdin_reader (rc.rs:152) reads std::io::stdin() bytes under crossterm raw mode and forwards them unchanged (parse_stdin_chunk only intercepts the ctrl-b detach prefix); there is NO key-event encoding and NO 0x08↔0x7f normalization ANYWHERE in the tree (grep: zero SetConsoleMode / ENABLE_VIRTUAL_TERMINAL_INPUT). On Windows, crossterm enable_raw_mode does NOT set ENABLE_VIRTUAL_TERMINAL_INPUT, so the LEGACY console delivers ^H (0x08, ctrl+h) for Backspace instead of VT DEL (0x7f); Claude Code maps ^H → backward-kill-word → the observed whole-word delete. CONFIRM-FIRST (build the loop): an env-gated hexdump in spawn_stdin_reader (SPT_RC_DEBUG_KEYS) prints the forwarded byte; operator presses Backspace + ctrl+Backspace in a real rc session. FIX CANDIDATES: (a) enable ENABLE_VIRTUAL_TERMINAL_INPUT on the rc stdin console on Windows so the console emits proper VT (Backspace→0x7f, arrows/Home/End as CSI) — cleanest, fixes the whole key map not just Backspace; (b) narrow normalize bare 0x08→0x7f in the rc input path (riskier — a real ctrl+h is also 0x08). Prefer (a) unless it regresses other keys. Add a KNOWN-HAZARDS.md entry on landing. (v0.13.0) HAZARD: a node whose epoch counter has regressed below the lease on its own fleet-replicated roster row is FROZEN — it can never again change its advertised address, on any peer, and nothing in the system says so. The failure is silent by construction: the node keeps authoring self-entries at a lower lease, every peer's merge_entry returns Stale, net_up stays true, the heartbeat stays fresh, and the roster rows peers hold keep a fresh last_seen riding a dead address. Regression is near-deterministic on a hard reset because the counter is rewritten every few seconds through the non-durable atomic path and resurrects as a NUL husk that parses to absent and reloads as 0 (evidenced twice: ENLYZEAM Kernel-Power 41 seventy-two seconds after its last winning row; HFENDULEAM Jul-19 resets against a Jul-18 frozen row). Forbidden state: a node advertising a self-entry the fleet cannot admit, with no self-repair path. Twin of REQ-ROSTER-SELF-LEASE-HEAL. Gate: doc — the KNOWN-HAZARDS entry; int — the frozen-row shape is set up on a real node root and the boot heal is proved to break it (a pre-fix build strands). A shell instance's ONLINE-ness is DERIVED (recorded status AND its recorded `shell.pid` not provably dead), never the recorded `status` field alone — an abruptly-dead binary (force-kill, crash, OOM: no link-break, so `close_shell`'s offline flip never runs) must not read online forever. The shell-side twin of REQ-HAZARD-DAEMON-HOSTED-LIVENESS, which gave AGENT perches exactly this resolver and which shells never got. PROVABLY DEAD is the narrow discriminant: `shell.pid` present AND parses non-zero AND `!is_process_alive` — pid absent, unparseable, or 0 (a broker-hosted spawn whose backend exposed no pid records 0) reads ALIVE, the same interim-parity/fail-toward-alive stance `liveness.rs` already holds, so a pid-less backend is NEVER falsely declared dead. Recycled-pid caveat, accepted at mint: a reused pid reads alive, so the heal is missed, never mis-fired — the failure direction is 'stays stale', never 'kills a live instance'. SITE CLASSIFICATION IS PART OF THE REQUIREMENT (authoritative cfg(test)-excluded census at mint = 8 status reads, 3 classes — do NOT blanket-swap the predicate): (a) DERIVED — relink's already-online refusal (the gate that made recovery impossible), the `shell cmd` wake-if-offline arm (which silently spooled to a corpse), the drive drop-if-offline branch, `shelldisc::discover` (the single source of BOTH `shell list` renders, text + --json), and the activity fan-out's online filter; (b) RAW status, deliberately — `bind_shell_by_token`/`close_shell` (the WRITERS) and `cascade_owner_edge`'s suspend-close arm, where routing through the resolver would SKIP the close that is itself the cleanup, removing a heal path; (c) RAW recorded status AND the corpse-boot discriminant — the `shellwake::reconcile_once` watcher-eligibility read. AMENDED BAROMETER W2 (doyle, releases#78 comment 5156810267): this class's original text — 'RAW status, pending an operator ruling', with no spontaneous relaunch of anything — IS STRUCK, because it rested on a fact leg (a) deliberately ends. What actually held a force-killed instance out of the eligible set was the STALE `online` record itself; healing that record to the truth removes the accidental protection, so the guard had to move to a rule that STATES it. Eligibility is now recorded `offline` AND (no corpse, or the corpse's launch PREDATES THE BOOT INSTANT), reusing leg (b)'s `launch_predates_boot` and its slack constant verbatim. NO SPONTANEOUS RELAUNCH OF SAME-BOOT DEATHS: flynn's mid-deploy forcing case (an operator kills the process precisely to free the exe for overwrite — shared install dir ⇒ 'kill the process' is a routine deploy step) is preserved BY CONSTRUCTION, since a force-kill during steady state leaves a corpse launched AFTER boot, which the predicate can never accept; for those, recovery stays demand-driven (an unblocked `relink`, or a `shell cmd` that wakes). Boot-restoration of PRE-boot casualties is enabled, per #78 leg (b)'s operator-greenlit scope and this ruling (#78 thread). CORPSE IS DEFINED ON THE PARKED STAMPS, never on a bare pid re-probe: a RECYCLED pid — alive, but its native start stamp mismatching the one leg (a) parked — is corpse-equivalent because our process is dead, and the parked ABSOLUTE launch stamp is what decides pre/post-boot. Without that, the heal-then-recycle race reads 'alive ⇒ no corpse' on the next tick and walks back into the forbidden shape through a side door. Deriving is still WRONG at this site and this is NOT a swap to class (a): the census stays 8 reads / 3 classes — the site reclassifies, it does not multiply, the do-NOT-blanket-swap sentence above stands, and `shell list` still tells the truth by derivation (class (a), untouched). LOCALITY SYMMETRY (field-caught by flynn's leg-2 run, 2026-07-25): 'a shell cmd that wakes' holds for BOTH the local CLI cmd and the cross-node serve — at mint the wake arm lived in the SHELL_LINK_CMD serve handler only (module docs scoped wake-if-offline to remote cmd), so a LOCAL cmd against a corpse spooled silently and nothing woke, the exact accepted-happily/drained-by-nobody shape this req exists to kill; the arm is now the shared `linkhost::wake_if_offline_persistent`, called by both, and the derived status READ inside it is the same single census site as before (the census of 8 reads/3 classes is unchanged — the read moved, it did not multiply). ROTATION CARRIES THE DURABLE CHANNEL (flynn's frame-loss field catch, 2026-07-25, both trials): a spooled command frame is MAC-stamped under the link token current at SPOOL time, and the drain is a raw destructive passthrough — the shell verifies against its CURRENT key — so a relink's token mint orphaned every pending frame: drained to the woken binary, failed verify, correctly discarded BY THE ADAPTER, lost permanently; the wake-triggering command itself was the frame the wake lost (#23 armed the wake and never answered; #24/#19 spooled after rotation and drained in order). The fix is three-layered (todlando's A1/A2 race+crash hazards addressed at mint): (1) the token mint re-stamps all pending rows old-key→new-key (idempotent, crash-partial converges — an old-stamped remainder is converted by the next rotation or drain); (2) the rotated-out token is STASHED (`link.token.retired`, never a live credential — bind resolves only the parked file) so (3) the drain paths give any race straggler a second-chance re-stamp at delivery (`restamp_for_drain`), while frames verifying under neither key pass through untouched for the adapter to refuse exactly as before. Rows are selected by MAC verification against the threaded new key, never by token-snapshot equality (the D-2 class). MOCK CONFORMANCE IS A PROPERTY OF THE RIG, not a patch (todlando A4): mock-shell MUST verify inbound frame MACs exactly as the public contract demands of a real adapter and drop failures loudly — a mock that accepts what the field rejects is a broken rig, and that exact divergence (credulous mock) is how the frame loss passed the e2e while failing in the field. The adapter-side discard behavior is CORRECT and must never be softened to paper over the spool side. Relink additionally probes LOCALLY rather than trusting a daemon sweep, so recovery holds with the daemon down. RECOVERY PRESERVES CONSUMER STATE (the property consumers actually depend on, flynn 2026-07-25): 'same canonical id, same perch' exists so that state a consumer PERSISTED IN THE PERCH survives the recovery — an adapter's repo binding, a scanner's cursor. The teardown+spawn workaround destroyed exactly that, and its worst cost was SILENT, not the rename: alchemy's tag cursor re-baselines at the digest tip, so tags written between the kill and the re-bind are never scanned — not failed-and-retried, just never seen. A loud failure gets retried; a silent one does not. Gate: int — force-kill a bound persistent instance's process, then prove (1) `shell list` reads offline, (2) `relink` succeeds instead of SHELL_ALREADY_ONLINE and the SAME canonical id + perch survive, carrying perch-persisted consumer state with them (no teardown+spawn, no id churn, no re-baselined cursor), (3) no spontaneous relaunch occurs while the instance sits dead and undriven. FIELD VERDICT — PASS END-TO-END (flynn, alchemy-0, delivered 2026-07-26; v0.43.0, counter 77): every gate leg held in the field, on a record the pre-fix code had already poisoned. (1) `shell list` read OFFLINE while the on-disk info.json still said status=online — the daemon DERIVED offline from the corpse pid (29036 absent from the process table); the record was never corrected and did not need to be. (2) relink ADMITTED, no SHELL_ALREADY_ONLINE — verbatim SHELL_RELINKED:alchemy-0 owner=flynn pid=38644 status=offline, list online thereafter. (3) identity/state integrity: same canonical id, same owner, same perch; repo binding byte-identical (token_provenance=gh-cli); armed=true preserved; no spontaneous relaunch across the ~6.5h dead window. CURSOR SCOPING, ruled at closure (doyle 2026-07-26) — the one non-byte-identical field: the tag cursor moved 206158430541→210453397553 (gen:seq 48:333→49:49) because the recovery crossed a DAEMON restart that slid the retained digest window past the armed cursor. NOT a gap and NOT this req's property failing: spt-core raised after_predates_window and the consumer took its specced armed-cursor branch (alchemy REQ-TAG-SCANNER missed-rows leg — warn the owner, never silently re-baseline an armed cursor; alchemy src/tags.rs, two unit tests) — the FIRST FIELD FIRING of a path previously exercised only by unit tests. The 'no re-baselined cursor' gate property is hereby scoped: byte-identity holds across SHELL death within one daemon generation (the prior LEG 5 measurement, one daemon instance, uncontradicted — different scenario, generation never moved); across a daemon restart that slides the window the contract is DETECT-AND-ANNOUNCE, not preserve — corroborated independently by a second record's cursor decomposing under the same gen:seq scheme (4294967486 = 1:190). Practical exposure in this event: nil (every agent stood down through the gap; no tag traffic to miss). SECOND SPECIMEN HELD, not spent (doyle ruling 2026-07-26): liam's record — precondition verified by flynn (pid 35464 a CLEAN corpse, not recycled, so the accepted-at-mint recycled-pid caveat does not apply; same poisoned status=online shape; binding intact; cursor armed at 4294967486) — is deliberately NOT relinked while its owner is absent: the admit gate is already field-proven, a relink measurably re-baselines the cursor across a daemon-restart boundary, and liam's is the only untouched specimen with the longer (~8.5h) gap — spending it would destroy the cursor datum liam's own return produces, to buy a redundant second sample of a gate already proven to admit. It waits for liam. SPECIMEN HARVESTED (liam's own return, 2026-07-26 — operator-authorized endpoint start executed by doyle; pre-state snapshotted by doyle AND independently re-verified on disk by liam before touching anything): (1) relink ADMITTED at the ~8.5h gap — verbatim SHELL_RELINKED:alchemy-0 owner=liam pid=47608 status=offline (online at bind); the poisoned record did not refuse; info.json online is now TRUE. (2) integrity FULL PASS: id unchanged (no new mint slot), perch onlined at bind, repo binding byte-identical (token_provenance gh-cli), and the ROTATION-CARRIES-THE-DURABLE-CHANNEL machinery observed live in the field for the first time — link.token.retired present beside the fresh mint, and a post-relink round trip proven end-to-end (shell cmd spooled -> engine executed -> reply home on the owner's perch), not merely an outbound spool. (3) cursor, the sharpest datum of the harvest: BYTE-IDENTICAL through the relink itself (4294967486 armed at pid-change instant, doyle's file watch) — the relink preserves; the SCANNER moves. First scan re-baselined 1:190 -> 2:0 silently, and liam PROVED no-fire-by-precondition rather than assuming: the daemon restart bumped the generation but EVICTED NOTHING (window floor 4294967464 < cursor 4294967486, every gen-1 row past the cursor still retained), so after_predates_window is correctly false at every link of the chain (filter_after floor test -> emit-only-when-true -> unwrap_or(false)) and the missed-rows announce had no precondition to fire on. Flynn's earlier gen-bump reading is refined by this: a restart alone does not slide the window; flynn's announce fired because HIS window had genuinely slid. Scope note carried: a valid negative — the fix's announce chain verified link-by-link to contract on a specimen where it correctly stayed quiet. RESIDUE from the harvest (liam's find, alchemy lane, no core REQ): permanently-unsealable turns from a dark session are jumped by scanner cursor advance with no possible missed-rows notice (a generation bump is not a slide); ruled scanner-side announce for input-bearing null-seq jumps, pseudo-turns jump silently by design; core's per-turn input/input_seq/partial already suffice to discriminate. Command-template substitution fills argv ELEMENTS, not a re-tokenized string: spt-core currently `fill_template`s {key} values INTO the command STRING and THEN `tokenize`s the filled string (runtime.rs:94/122), so a multi-word {key} value whitespace-SPLITS into multiple argv tokens unless the adapter hand-quotes the placeholder, and a value containing a `"` (or `;`) injects/breaks tokenization (shell-injection-adjacent). A filled value MUST become exactly ONE argv element regardless of spaces/quotes in the value. Fix: tokenize the TEMPLATE into argv FIRST, then `fill_template` EACH token, so a `{key}` slot resolves to a single element and the value never participates in tokenization (no whitespace-split, no quote/semicolon injection); preserve the missing-key / empty-command errors and `{{`/`}}` non-interpretation. perri's F-009 (v0.8.1 dogfood, argv-capture-confirmed): a multi-word `{psyche_prompt}` = "PSYCHE REVIVAL time: epoch-ms:… incoming event: (none)" arrived as argv[6..12] (7 stray tokens), the harness runner strict-parsed `--prompt` against the 2nd word, exited 2 within ~1s → phantom hosted perch. Applies to EVERY [session.] template (psyche_init, extractor, notif, …); digest survives today only because its fills ({session_id}/{source}) are single-token. A VIEW is independent from the endpoint: closing the tab/window where the endpoint was brought up must detach ONLY the `spt rc` attach pump — the daemon-hosted harness keeps running and stays re-attachable via `spt rc `. ROOT (Windows, v0.12.0 real-harness defect): the daemon never breaks away from the launching terminal's Job Object. Windows Terminal / VS Code place the launched shell AND every descendant into a Job Object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; closing the tab drops the terminal's last job handle → the OS terminates every process still in that job. A child escapes only with CREATE_BREAKAWAY_FROM_JOB — used NOWHERE in the tree. Both daemon spawn paths (daemon.rs:707 detached_no_inherit = DETACHED_PROCESS|CREATE_NEW_PROCESS_GROUP|CREATE_NO_WINDOW; deelevate.rs:519 elevated = CREATE_NEW_CONSOLE|...) drop the CONSOLE but NOT job membership, so the daemon's freshly broker-spawned ConPTY harness subtree is reaped on tab-close. The ConPTY/pseudoconsole isolation itself is CORRECT (portable-pty builds the pseudoconsole in the daemon; no console signal / handle leak) — the leaking lifetime binding is the Job Object, not the console. FIX: add CREATE_BREAKAWAY_FROM_JOB to both daemon spawn paths AND pin each broker-spawned harness into a DAEMON-OWNED Job Object (mirror reap.rs/Breap) as backstop (survives even where a terminal sets SILENT_BREAKAWAY_OK=false). Unix: the daemon's own session detachment (new session, no controlling terminal) already keeps a closing terminal's SIGHUP off its children — verify, add a guard test, no code expected. FIX UPDATE (v0.12.1 L1.5, doyle re-scope operator-approved 2026-06-18): job-neutral daemon launch is now PRIMARY, breakaway DEMOTED to a fallback rung. ROOT reframed — the daemon INHERITS the terminal's Job because spawn_detached runs FROM the terminal-child CLI (DETACHED_PROCESS detaches the console, not the job); breakaway tried to claw back out but a job CAN deny it (the L1 finding). FIX: launch the cold-started daemon via a job-NEUTRAL creator so it is WmiPrvSE/Task-Scheduler-owned, OUTSIDE any terminal job from birth (why Task-Scheduler-autostarted daemons never had this bug). Launcher ladder (first-success-wins, daemon.rs spawn_detached → BOTH cold-start AND `spt daemon start`): (1) WMI Win32_Process.Create via ABSOLUTE powershell -EncodedCommand (KH 5.12 abs path; base64-UTF16LE dodges all quoting; success requires BOTH ReturnValue==0 AND a parsed ProcessId, else fall-through — never a silent launched), forwarding SPT_* env via a `cmd /c set … & start /b` wrapper because a WMI/scheduler child does NOT inherit transient shell env (verified — SPT_HOME would be lost, wrong universe); (2) schtasks one-shot (same env wrapper; best-effort fallback); (3) CREATE_BREAKAWAY_FROM_JOB (the L1 code, reordered below); (4) in-job last resort (logs DETACH_IN_JOB + tab-close caveat). detached_no_inherit (breakaway-then-in-job) is UNCHANGED for its other caller shellhost::launch_shell (a daemon-spawned shell is already job-neutral once the daemon is). The elevated deelevate path keeps its L1 breakaway for now (elevated-case WMI-reparent = FOLLOW-UP). (v0.12.1) THE INBOUND RECORD ROUND-TRIPS EVERY VERDICT VARIANT, AND THE PIN IS EXHAUSTIVE BY CONSTRUCTION RATHER THAN BY A LIST SOMEONE REMEMBERED TO EXTEND. `InboundRecord` carries `binder_pid` and `binder_path` and FLATTENS `InboundVerdict` beside them; the `PathMismatch` variant carried a `binder_path` of its OWN, so serializing it emitted a LITERAL DUPLICATE KEY, `read_current` failed the parse, and the reader fell closed to `Unknown` (releases#172; the duplicate confirmed in the on-disk record on HFENDULEAM 2026-08-06). The one verdict class the record exists to surface was STRUCTURALLY UNRENDERABLE: the file plainly said `path_mismatch` while `spt subnet status` said unknown, and the operator learned of the real mismatch only from a manual probe. IT STACKED WITH A SECOND DEFECT — the firewall rule genuinely WAS wrong — which is the shape that makes a self-erasing diagnostic expensive rather than merely untidy: the instrument went quiet in exactly the condition it was built to announce. THE COLLIDING FIELD IS RENAMED ON THE WIRE (`running_path`), NOT UN-FLATTENED: un-flattening would move every OTHER variant's shape too, for a defect that lives in one variant, and the flattened form is what every existing record on every node already carries. The variant keeps a path of its own rather than borrowing the envelope's, because `InboundVerdict::warning()` renders from the verdict ALONE and has no envelope in hand — a renderer that reached for one would be a second source for a field the record already owns. MIGRATION IS NOTHING, AND THAT IS DERIVED RATHER THAN HOPED: an old duplicate-key record already reads `Unknown`, which is the safe reading, and the next binder write replaces the file wholesale. The reader stays FAIL-CLOSED — an unparseable record is still `Unknown`, never a guess. WHY THE COVERAGE THAT EXISTED WAS GREEN THROUGHOUT: `REQ-INBOUND-VERDICT-RECORD-BINDER-PINNED`'s unit sentence says a live binder round-trips its verdict, and it was satisfied — by round-tripping the VARIANT, never the variant THROUGH THE ENVELOPE. The collision cannot exist until the two are composed, so every test that touched one half passed. The pin this requirement adds is therefore not another round-trip; it is a WILDCARD-FREE MATCH over the enum that stops the crate compiling until a newly added variant is placed in the table, plus one specimen per variant with EVERY field a distinct value (identical fillers would hide a field that round-trips to a sibling's value). Gate: doc — the published status-JSON field table and example in the inbound-reachability page, carrying the rename and the reason the old name was never emitted; impl — the renamed variant field and its producers and renderers moved with it, one name on the wire and one in the code; unit — every variant written through `write_to` and read back through `read_current_from` under a live-binder oracle, the variant tags asserted against WRITTEN-OUT LITERALS rather than counted, the failing set reported with the on-disk body so a red names which variant and what the record actually held, and the exhaustiveness carried by a match the compiler enforces. Windows inbound reachability is verified by PATH MATCH against the binder, not by rule name: the check passes only when the installer's program-scoped firewall rule names the executable of the resident daemon — the process that binds the QUIC/mDNS UDP socket — and the elevated repair rewrites the rule to THAT path. Supersedes the name-presence probe REQ-INSTALL-7 shipped, which a rule pointing at a moved or dev-build binary satisfies while inbound is blocked in fact (gap class 2). Measured on HFENDULEAM 2026-07-31: the rule names %LOCALAPPDATA%\spt-core\bin\spt.exe and the resident daemon runs that same installed binary, so this host is a MATCH, not a live mismatch — the class is proven by construction (a program-scoped rule admits exactly one path) and by unit fixture, not by a field specimen. An earlier version of this title claimed the mismatch was live here; direct measurement refuted it. The authoritative binary is never the invoking CLI's argv[0]: a repair keyed to the caller would rewrite the rule onto whichever build happened to run the command. With no resident daemon the verdict is UNKNOWN — the check reports that it cannot say rather than guessing a path. Unelevated: report loudly with the exact command, write nothing. Gate: doc — KNOWN-HAZARDS/install docs state that the rule is verified against the binder's path; impl — the netsh verbose probe, the Program-line parse, the decide seam, the elevated repair; unit — path match passes, path mismatch fails with the rule's path in the verdict, a missing Program line and a failed probe both read UNKNOWN rather than either pass or fail. A COMMUNE IS ACCOUNTED AT THE DELETION CORE ALREADY PERFORMS, CARRYING THE DROP FILE CONTENT VERBATIM (operator ruling 5). The event fires where `ingest_drops` consumes a commune drop and unlinks it — the seam core already owns — and its payload is the bytes of that file, never Psyche output, never echo-commune output, never the composed brief a resume renders from it. Wake markers (`!!wake!!`) are the harness adapter's construct and get ZERO special treatment here: core carries the text through untouched and does not parse, strip, or act on them. TWO DELETIONS EXIST AT THIS SEAM AND ONLY ONE IS A CONSUMPTION: the F-032 commit-before-delete branch (REQ-HAZARD-COMMUNE-INGEST-BLACKHOLE) deliberately PRESERVES a commune drop whose project slice could not be committed, rewriting it in place for a later resolvable ingest, and deletes only a SIGNOFF source there — a drop on that branch has not been consumed, it is coming back, and emitting for it would double-fire when the real consumption later deletes the same content. Emission is therefore keyed on the CONSUMPTION OUTCOME the ingest already reports (`preserved` false) rather than on reaching a call to remove_file, so the preserved path is excluded structurally instead of by a condition a later edit can drift past. A DELETED SIGNOFF DROP EMITS NOTHING: the vocabulary has no SIGNOFF token and one is not borrowed. Emission never alters ingest: a drop is consumed, preserved, or left to retry exactly as it is today regardless of the funnel. A SHORTFORM DISPATCH ACCUMULATES A PER-TARGET RESULT, AND THAT IS ITS ONLY CONFIRMATION CHANNEL (operator ruling 10). Each target of a parsed tag records what actually happened to it — delivered, or no perch — accumulated as the v1 DISPATCH_RESULTS category; the seal-mint outcomes and refusals of the next wave accumulate into the same category. RENDERING IS NOT THIS WAVE: the now-signal verb that surfaces these lands with #233, so this requirement delivers accumulation with no reader yet, deliberately — the same shape as the event bus shipping with one sink, and for the same reason, that the reader must cost a registration rather than a rework. No other confirmation channel may be added: an author learns what their dispatch did through the now-signal and nowhere else, so a dispatch must never grow its own bespoke echo, log line, or reply message as a substitute. ADAPTER-CONSUMABLE IO EVENTS LAND IN A PER-ENDPOINT APPEND-ONLY LOG REGISTERED AS A THIRD BUS SINK (releases#234, operator ruling 2). The funnel's claim was that a second reader costs a REGISTRATION and never a rework, and on the sink side that holds exactly as claimed — this consumer is one `bus.register` line in `default_bus`. THE READER IS FREE, THE STORE IS NOT, and this requirement is the honest half of that claim: neither existing sink writes a per-endpoint, ordered, cursorable surface — the shell-link sink spools per linked shell and the last-msg sink keeps two overwritten slots — so a delta-cursored poll needs a NEW DURABLE STORE, and that store is what this covers. EVERY ROW CARRIES ITS OWN MONOTONIC seq AS THE LINE'S KEY RATHER THAN AS A JSON FIELD: a row is `` TAB ``, so a cursor scan parses an integer prefix and never the body, and the ordering key cannot become an accident of serialization field order. THE LOG'S seq AND THE DIGEST seq ARE DIFFERENT NUMBERS AND ARE SPELLED DIFFERENTLY (`seq` versus `digest_seq`), because the digest remains the content surface that a truncated payload points at and one name for two counters is a consumer following the wrong one. THE LOG IS BOUNDED PER ENDPOINT AND TRIMMED OLDEST-FIRST so that an adapter which stops polling cannot grow it without limit; the bound is a STATED CHOICE derived from a measured event rate rather than a guessed number, and the trim is amortized against a slack so an ordinary append is not a whole-file rewrite. APPENDS ARE SERIALIZED under an exclusive advisory lock on a stable sentinel, because the daemon publishes from several edges and two racing appends must not mint a colliding seq. A SINK FAILURE IS STILL ONLY A REPORT: this store may not become the first sink whose bad day reaches the operation it observes. MID-TURN AGENT OUTPUT IS AN AGENT_OUTPUT EVENT CARRYING A mid ATTRIBUTE, NOT A NEW KIND (operator ruling, releases#238 comment 5461768445; design gated 2026-08-29). An adapter reports a span while the agent is BUSY, so the span arrives on the busy arm of api state and must be typed AGENT_OUTPUT there: state_io_kind gains a THIRD ARGUMENT and stays pure — busy+payload without the flag is USER_INPUT unchanged, busy+payload WITH it is AGENT_OUTPUT with mid set, idle+payload is AGENT_OUTPUT unchanged, and a payload-free call still emits nothing. THE FLAG AT IDLE IS A CONTRADICTION AND IS REFUSED BY NAME (STATE_MID_ON_IDLE), as is the flag with no payload; both are argument validation refused where STATE_PAYLOAD_AMBIGUOUS is refused, so the funnel's rule that an observation can never fail the operation it observes is untouched. THE ATTRIBUTE IS PRESENT-ONLY, spelled mid=1 on the frame in the house form truncated already uses, a skip-when-false row field, and a poll field emitted only when set; an enum spelling was refused because an enum invites an ORDINAL, and core cannot know a span's ordinal without modelling turn assembly, which the funnel refuses. THE ATTRIBUTE MUST BE MINTED AT EVERY SITE BECAUSE THE #234 READER DOES NOT PASS UNKNOWN ATTRIBUTES THROUGH — measured at sha 786d2381: IoLogRow carries no flatten and no deny_unknown_fields so serde ACCEPTS AND DISCARDS an unknown key, and render_json is an explicit per-field allowlist projection, so an unretained field could never reach an adapter. The attr-passthrough precedent is a fact about the EVENT wire, not about this reader. AN OLDER READER IS NOT VERSION-GATED: it reads a mid row as an ordinary end-of-turn report and therefore OVER-COUNTS TURNS, which is acceptable because nothing emits a span unless an adapter asks, a new kind is exactly what the operator ruled against, and the body an old reader gets is real agent output verbatim and in order. THAT NO-GATE ARGUMENT RESTS ENTIRELY ON UNKNOWN-FIELD TOLERANCE, so the forward-compat unit is REQUIRED rather than optional (gater rider, 2026-08-29): a stored row carrying a field this binary does not know must be READ, not skipped — unpinned, a later deny_unknown_fields would make parse_line answer None and the reader SKIP the row in silence, turning a forward-compatible record into a quiet poll. MSG_IN AND MSG_OUT ARE ACCOUNTED AT THE DELIVERY EDGES CORE ALREADY OWNS, not at a new interception point: MSG_OUT publishes where the send path commits an outbound message and MSG_IN publishes where core delivers or injects an inbound one, so the funnel observes the edges rather than growing a parallel notion of what a message is. THE INGEST-SCOPE BOUNDARY IS ESTABLISHED HERE EVEN THOUGH THE PARSERS LAND LATER (operator ruling 11): a received message body is INGEST DATA and never a parse surface — no MSG_IN body may fire a dispatch or a ceremony at the receiver, and W1 is where MSG_IN gets published in a shape that keeps the later parsers structurally unable to read it as agent output. Emission is best-effort and never alters delivery: a message is delivered, spooled, or refused exactly as it is today regardless of what the funnel does with the observation. THE `;;` SEAL-MINT SHORTFORM PARSES UNDER THE SAME SUPPRESSION GRAMMAR AS `@<…@>` (operator rulings 8 and 12-14). Pairs parse GREEDY LEFT-TO-RIGHT and each pair mints its OWN ceremony, run sequentially — two pairs in one turn are two seals over two texts, never one seal over the span between them. AN EMPTY PAIR `;;;;` IS FULLY IGNORED: no ceremony, and NO REFUSAL either, because an author who typed nothing between the markers asked for nothing and a refusal would be noise about a non-event. AN ODD TRAILING `;;` IS THE BARE CASE and seals ALL TEXT AFTER IT THROUGH END OF OUTPUT, with the text between the last completed pair and the bare marker left UNTOUCHED — the bare marker is a decision to seal the remainder, not a retroactive claim on what preceded it. Pair parsing runs FIRST and the bare case is what an odd marker becomes after pairing, which is the only order under which `;;a;; tail ;;rest` has one pair and one bare seal rather than an ambiguous three-marker soup. Detection covers AGENT OUTPUT and USER INPUT alike (CONTEXT.md): either author path mints, because the user typing the directive verbatim and the agent drafting it are the same intent. THE BARE CASE IS REACHABLE ONLY AT THE TURN-CLOSE INGEST (releases#238, design gated 2026-08-29): mid-turn, END OF OUTPUT names text the parser HAS NOT SEEN, so a per-chunk bare seal would silently seal a SHORTER region than the author asked for — a correctness failure indistinguishable from success in the output. In a mid span an odd trailing marker therefore mints NOTHING and is refused by name as SEAL_BARE_MIDTURN, routed into the shipped DISPATCH_RESULTS surface rather than a rival refusal beside it, with the refusal text naming the fix (close the pair, or place the bare marker in the turn's closing output). PAIRS are self-delimiting — both ends present in the chunk — and keep minting mid-turn exactly as ruled, which is what honours the operator's parse-all-agent-output ruling. At the idle turn-close ingest ruling 14 stands verbatim, because there end-of-output is text the parser holds in full. This narrowing costs core ZERO cross-call state, which is what keeps the no-turn-assembly boundary free of an exception. CORE PARSES `@` OVER INGESTED AGENT OUTPUT AND USER INPUT ONLY, AND DISPATCHES THROUGH THE NORMAL SEND PATH (operator rulings 10 and 11). The grammar: `@<` opens, comma-separated target ids run to the first space, the body runs to the first `@>`. THE INGEST-SCOPE BOUNDARY IS ABSOLUTE AND STRUCTURAL: parsers run over USER_INPUT and AGENT_OUTPUT ingest only and NEVER over a MSG_IN body — a message that ARRIVES carrying a live tag can never fire a dispatch or a ceremony at its receiver, because a peer's text is data about a tag, not an instruction to send one. That boundary is held by which funnel path the body traverses rather than by a kind check a later edit could widen. Dispatch reuses the delivery path core already owns — no parallel send machinery, no second notion of what delivery means — so admission, sealing, spooling and refusal behave exactly as they do for an operator-typed send. SHORTFORM IS ENABLED PER-ADAPTER, ONLY ON A MANIFEST IO-COMPLIANCE DECLARATION (operator ruling 9), AND A DECLARING ADAPTER MAY STILL OPT OUT (ruling 8). Absent the declaration core parses NOTHING for that adapter, which is what makes the migration safe: every adapter shipping its own parser today keeps parsing, alone, until its own release declares compliance and deletes the local parser in the same change — so no release exists in which both parse the same text and dispatch it twice. THE DEFAULT IS OFF AND THAT IS LOAD-BEARING: a default-on core would double-fire against every already-shipped adapter the moment core upgraded, which is precisely the window ruling 9 forbids. The manifest fields are additive and N-1-safe (absent in older manifests, omitted from serialization when unset), so an older core reading a newer manifest and a newer core reading an older one both behave as they did before. Core owns the field and the gating; the handshake with each adapter's own release is the gater's boundary, not this requirement's. ONE SUPPRESSION GRAMMAR, CORE-OWNED, SHARED BY EVERY SHORTFORM (operator ruling 8). A shortform marker inside an inline backtick span or a fenced code block is a QUOTATION and fires nothing — that is how an agent writes about a tag without dispatching one, and how a code sample containing a marker stays a code sample. The grammar lives in ONE place and is applied by every shortform reader: the `@<…@>` parser of this wave and the `;;` seal-mint of the next, so the two can never disagree about what is quoted. A second spelling of suppression is the defect this requirement exists to prevent — an author who learns the rule from one surface must not be surprised by the other. The masking is computed over the raw text before any marker scan, so a marker is either inside a suppressed region or it is live, never both, and nesting is resolved by the outer region: a backtick span inside a fenced block is simply more fenced content. DIRECTIONALITY IS SENDER-DECLARED, AND ONLY SENDER-DECLARED (ratified 2026-08-04 bag grill, releases#144 / ADR-0055). The reach-REQUESTING surfaces — `spt knock `, `spt knock send` and `spt knock redeem` — take a MANDATORY, MUTUALLY EXCLUSIVE pair, --send-only | --send-receive, with NO default: a bare invocation refuses loudly naming both, and naming both together refuses as opposite answers to one question. The declaration names what the DECLARER'S OWN SIDE does — --send-receive arms the declarer's own inbound to the counterparty, at answer-receipt for a knock and at redemption for a code, and nothing more. The RECEIVER verbs `knock approve` and `knock new-code` carry NO directionality flag at all: accepting IS the receiver's own-side act, so there is nothing further for them to declare, and the `approve --mutual` COUNTER-KNOCK IS REMOVED RATHER THAN RENAMED — a receiver who wants reverse reach knocks back, because the counter-ask is the knock verb's own job and a rider on approval duplicated it with a second grammar. VOCABULARY: send-only and send-receive are canonical; mutual and one-way are RETIRED from every user-visible surface — help text, refusals, stdout claims and the published guide alike — because 'mutual' misdescribes an act that only ever opens the declarer's side. MIGRATION IS A CLEAN BREAK WITH A SPEAKING REFUSAL: --mutual and --one-way are parse errors that NAME the flag which replaced them and the seat they were typed at, never clap's bare unexpected-argument, and there are no deprecation aliases (an alias keeps the retired mental model alive in scripts and agent habits). THE RENAME IS THE USER-VISIBLE SURFACE ONLY: store and wire field names are untouched, and PRE-AUTHORIZATIONS ARMED UNDER THE OLD FLAGS STAY HONORED — a record armed before this change still consumes through the answer-receipt path and still opens the reverse it was armed for. Gate: doc — the CONTEXT.md knock/knock-code directionality sentence and the mutual-whitelisting entry corrected by replacement so no entry states the retired seats, plus the published knocking guide; impl — the mandatory mutex at the bare form, `send` and `redeem`, the absence of any directionality argument at `approve` and `new-code`, the removal of the counter-knock path, and the retired-flag refusals; unit — a bare requesting invocation refuses naming both new flags, each flag ALONE is accepted, both together refuse, `approve` and `new-code` refuse the directionality flags outright, a retired flag's refusal NAMES its replacement at every seat that carried it, no user-visible string on the knock family carries the retired vocabulary, and — the load-bearing row — A PRE-AUTHORIZATION RECORD ARMED UNDER THE OLD FLAGS, DECODED FROM ITS SHIPPED BYTES RATHER THAN BUILT BY THE NEW CLI, STILL CONSUMES THROUGH THE RECEIPT PATH AND STILL OPENS ITS REVERSE, with the store and wire field names it names unchanged. A knock answer can impart a MONIC about whoever it admits: `spt knock approve --monic ` writes the note at the approval, and `spt knock new-code --monic ` bakes one into a code that is imparted when the code is REDEEMED. The code path is deferred by necessity rather than by design — at mint there is nobody to write a classification about yet, and the redeemer's identity only becomes stamped-and-real at redemption. THE NOTE IS ABOUT A DAEMON-STAMPED ID, never a caller-supplied one: it is written about the same stamped knocker id an approval writes its whitelist entry for, so a note can no more be planted on an innocent peer than a grant can. A knocker with no endpoint id — a bare-terminal human, stamped as their node — CANNOT be classified: the note is refused loudly and the grant stands, because writing a classification under a node id would produce a record no delivery-edge lookup can ever match while reading back as though someone had been classified. AN EXISTING CLASSIFICATION IS KEPT. Both paths route through ONE shared impart primitive whose rule is that the owner's standing judgement outranks a judgement arriving with a grant — an approval must not silently rewrite what an agent already decided about a peer, and a note typed at code-mint hours earlier certainly must not — and the caller is told which happened so it prints the replace command instead of implying the note landed. An UNREADABLE record is not a classification, so imparting replaces a husk. THE NOTE RIDES THE GRANT AND NEVER CHANGES IT: it is imparted only AFTER the grant has committed, is skipped entirely on a refusal (a classification of a peer you did not admit), and a mind that cannot be opened costs a loud diagnostic rather than a retracted approval. Whose mind holds the judgement is RECORDED, not derived: a node-target code is minted by the engine room and a node has no mind, so the imprint carries its owner endpoint from the mint, and the redemption path never guesses. The code record's imprint field is ADDITIVE — a code minted before the field existed still reads, and a code with no note serializes without the key. Gate: impl — the `--monic` flag on approve (a NEW public flag; it existed only on new-code, where it was recorded and inert), the owner-carrying imprint on the code record, the shared impart primitive, and the impart calls at the approve and redeem sites after their commits; unit — imparting writes with knock provenance, KEEPS a standing judgement and reports what is held, replaces a husk, the imprint field reads with and without a note in the record bytes, and both answer surfaces parse the flag. THE THREE CONSENTED KNOCK COURTESIES REACH THE PARTY THEY ARE OWED TO -- an approval notifies the KNOCKER, a redemption notifies the code's MINTER, and each is ADDRESSED to that endpoint rather than aimed at whoever the user was last seen at. Field origin (releases#149/#169): lia minted an invite code, it was redeemed, and lia was never told; the notification existed, landed on the right NODE, and could not surface. THREE STRUCTURAL FAULTS IN ONE SHAPE, written the same way at all four producing sites (local approve, cross-node approve, local redeem, cross-node redeem): the recipient rode the `from_id` slot because the row had NO ADDRESSEE FIELD, so the notif said the recipient issued it; the subnet was the empty string, which is not a member subnet, and BOTH surfacing paths walk member subnets only -- `resurface_at_boundary` iterates `policy.subnets` (built from real memberships) and first-fire admits candidates through `policy.visible(id, subnet)`, false for every endpoint at `""` -- so the row was unreachable by construction; and they called `NotifStore::produce_scoped` DIRECTLY, bypassing the daemon's produce-and-fire front door, so nothing was delivered live either. THE FIX IS AN ADDRESSED NOTIF, NOT A LOUDER MISS (doyle Q1, ADR-0046 amendment 2): `NotifRow` grows an optional `to_id` (additive serde, the `SubscribeReq.code` precedent -- an N-1 peer parses an addressed row clean and reads it as unaddressed), honored at BOTH surfacing paths -- first-fire targets the ADDRESSEE and the address BEATS most-recently-active, with NO fallback to the MRA winner (an absent addressee yields NoTarget and the row waits for that endpoint's own next boundary), and a boundary resurface at any OTHER endpoint SKIPS the row with its own outcome rather than a suppression, because `not you` and `not now` are different facts. Merely giving the rows a real subnet was REJECTED as a fix: delivery would stay presence-MRA, so on a 14-endpoint node the approval notice still lands on a bystander. Re-homing the three onto the message spool was rejected too -- they were deliberately put on the notif spool against inbox delivery by the ratified W4 decision, and the spool would silently retire it. ONE PRODUCER FOR ALL FOUR SITES, with a REAL member subnet: the correlation subnet the exchange rode (the member subnet whose registry has seen the peer's node) where one is derivable, else the first member subnet the ADDRESSEE IS VISIBLE IN -- visibility is the condition both surfacing paths impose, so a subnet the recipient is hidden in would reproduce the unreachable row under a nicer name. THE APPROVAL COURTESY IS LIFTED AHEAD OF THE MUTUAL ARM: the cross-node producer sat after the `ReceiptOutcome::Consumed` arm, which only a `--mutual` knocker reaches, so a plain knock's receipt returned `Ignored` and the ORDINARY knocker -- the party who asked, with no other way to learn the answer -- was structurally un-notifiable. It is now produced on every APPROVAL, proven by the outbound knock row THIS node sent (matched on correlation id, the answering endpoint and the handshake-proven origin node) rather than by the pre-authorization, so a receipt matching none of our knocks notifies nobody and a guessed correlation id cannot become a spray channel; a DENIAL still notifies no one, since the ratified set does not extend to it. A REPLAYABLE arrival carries a coalesce key naming the EXCHANGE (`spt-core:knock-answered:`) and not the kind -- two approvals are two facts and both stand, while two receipts for one knock are one fact said twice, so ADR-0046 decision 2's supersession leaves exactly one LIVE courtesy per knock instead of nagging. Gate: doc -- ADR-0046 amendment 2 stating the addressee, both honoring paths, the member-subnet rule and the lift; impl -- the `to_id` field, column and addressed produce front doors, the addressed first-fire target and the resurface skip, the shared courtesy producer and its subnet chooser, the outbound-knock proof seam, and all four producing sites moved onto it; unit -- per courtesy produce->fire->surface, an addressed row NEVER lands on a bystander (the 14-endpoints-on-a-box case), an absent addressee yields NoTarget rather than the MRA winner, a resurface elsewhere skips and the addressee's own boundary surfaces it, a plain (non-mutual) approval receipt produces the courtesy, a receipt matching no outbound knock produces nothing, and the subnet chooser refuses a subnet the addressee is hidden in. THE KNOCK PRESCRIPTION PRESCRIBES THE PAIR WHERE THE PAIR IS NEEDED: when a pending knock asks for `FORK` without `DISCOVER` and the knocker's machine would still be refused `DISCOVER` on the target, `knock list` prescribes `spt knock approve --surfaces FORK,DISCOVER` beside the form line, naming why -- the knocker would be granted an operation it cannot reach. Prescribed ONLY where the binary would otherwise refuse (doyle Q3), from the SAME predicate REQ-ACL-FORK-WITHOUT-DISCOVER-CONSEQUENCE answers, so the two statements about one question cannot drift; the rule an as-requested approval WOULD write is BUILT and handed to that predicate rather than described, which is the shape `approval_form` already established after a hand-written second copy of an approval classifier went stale in the field. IT RIDES BESIDE THE FORM LINE, NEVER REPLACING IT: the form line says what the SEAT must pass for the approval to be honored, this says what the approval must GRANT to be usable, and an operator who pasted the form line and stopped would write the exact grant that filed the request. `--approve-requested` IS NOT TAUGHT TO WIDEN: it means exactly what was asked for, and quietly granting a surface nobody requested would make that flag a liar at every other site that uses it -- so the prescription is an explicit `--surfaces` invocation the operator can read before pasting. Silent when the request already names `DISCOVER`, when it is blanket (which covers every surface), when it names no `FORK`, or when the knocker's machine can already resolve the target. Gate: doc -- the CONTEXT.md fork-pair entry, prescription half; impl -- the prescription composer over the built rule and the shared verdict, and its `knock list` call site below the form line; unit -- a FORK-only knock from a machine that cannot resolve gets the paired invocation with DISCOVER appended, the same knock from a machine that CAN resolve gets nothing, a FORK+DISCOVER request gets nothing, a blanket request gets nothing, and the composed command parses as the approve verb's own `--surfaces` form. GATEWAY-LIVENESS (flynn field bug 2026-07-09, RCA reader-divergence root): `spt endpoint list` (human) and `endpoint list --json` MUST report an IDENTICAL status for a locally-hosted endpoint — especially a pid-alive, status-ABSENT gateway (no psyche_init). ROOT (todlando RCA STEP-1, doyle-verified): the --json builder (crates/spt/src/cli.rs cmd_endpoint_list) emits each subnet row's status straight from resource_projection (spt-net registry.rs:566, passes instance.status through verbatim :592 — the persisted WAN snapshot, a lagged gossip that can carry a stale/crash-time Suspended) and NEVER applies the self-owned reconcile the human/picker path applies (reconcile_self_owned, crates/spt/src/picker/data.rs:160 via gather_endpoints :112). So a pid-alive self-owned gateway reads Suspended on --json but ONLINE on human (roster::enumerate spt/src/roster.rs:38 -> is_perch_alive pid-fallback spt-store/liveness.rs:136); the adapter suspend-poll (parse_endpoint_status over endpoint list --json --show-all) reads the divergent --json status -> self-suspends a pid-alive gateway. Candidates REFUTED: resource_projection does NOT re-derive liveness (copies instance.status, only skips !routable :582); render is a faithful {:?}. advertised_status (registryhost.rs:822) DOES compute Active via pid-fallback (live advertise fine) — the bug is the READER showing the un-reconciled snapshot. FIX (doyle-ruled): apply the self-owned reconcile in the cli --json builder (SAME spt/picker consumer layer as gather_endpoints, NOT lifted into resource_projection which inverts the dep spt-net->perch), reading LOCAL perch truth (is_perch_alive/unbound -> Active/Offline, mirroring the picker's local_rows). Do NOT force status=online (DEFECT B latent — seed-#5 orphan-listener false-ONLINE risk). Gate: a pid-alive locally-hosted gateway (status-absent, no psyche_init) reads the SAME non-Suspended status on human AND --json. Field-verify flynn (mobile-gw). Kin REQ-PICKER-3 + REQ-PRESENCE-LIVENESS-TRUTH + seed-#5 orphan-listener false-ONLINE. Adapter profiles — sparse leaf-replace overlays (shipped + local), composite : addressing, shadow-refusal, tighten-only consent floors Adapter-declared shortcut basename (M12-W2 follow-on): an optional `[adapter] shortcut_basename` manifest field names the basename the picker bakes into the generated `-` launcher shortcut (REQ-RUN-SHORTCUT). Absent ⇒ the harness-agnostic default `spt` (→ `spt-`); an adapter sets it to brand its shortcuts (claude-spt → `cc` → `cc-`), so the Claude-Code-ness lives in the PUBLISHED adapter manifest, never hardcoded in spt-core. The picker reads it from the RESOLVED manifest of the selected adapter (registry::resolve_option), falling back to `spt` when absent/empty/unresolvable. Additive + N-1-safe (serde-default Option, omitted from serialization when absent; old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). Documented in docs/MANIFEST.md `[adapter]` section + the claude-spt worked example — the adapter-author contract perri builds spt-claude-code against. [adapter] host_binaries declares the harness executable basenames a kind="harness" adapter hosts agents inside (e.g. host_binaries = ["claude"]); bind-time pid→exe-basename match (case-insensitive, .exe-stripped) over the seed's parent_pid selects the candidate adapter set; zero matches → a friendly error naming the binary + the --adapter escape hatch. Additive + N-1-safe: optional Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] (omitted-serialized like shortcut_basename, old manifests parse clean); manifest.schema.json regenerated from the derive (ADR-0001, CI drift-gated). The match-key for ADR-0021 adapter-agnostic bind-time resolution. (v0.9.0) AN UNKNOWN MANIFEST KEY IS NAMED ON STDERR AND REGISTRATION PROCEEDS (WEBSERVE W0 rider, doyle-ruled 2026-09-06 18:15Z from hertz W3 RED, releases#272/#265). manifest.rs carries NO deny_unknown_fields anywhere BY DESIGN (forward-compat for adapters built blind against a newer core), so refusal is out -- and that makes a misspelled OPTIONAL key visible only by its ABSENCE: web_short_pth registers with no alias and no error, docs_dir follows in W3. The tolerant branch that KEEPS the invariant: at adapter add (the register choke point, which adapter update also passes) every key not in its table's known set emits ONE stderr line `manifest: unknown key []. (ignored)` and registration PROCEEDS. Shape: a generic walk of the parsed toml::Value against the schemars-derived schema (the same derives the parser reads, so the known set cannot drift): $refs resolve through $defs, anyOf/oneOf/allOf branches union their properties, [[arrays]] descend through items, a map table (additionalProperties = a schema, e.g. [env.]) admits every key and sweeps each value, a free-form table (additionalProperties true -- [profiles.*], [strings]) is the adapter's own vocabulary and is not judged, an unparseable document yields nothing because the parse error already speaks. Gate: doc -- one paragraph in harness-contract/manifest.md (unknown keys are ignored; the diagnostic names them); impl -- Manifest::unknown_keys + the register-site emission; unit -- a web_short_pth typo parses AND registers AND is named table-qualified, a known key emits nothing, the full fixtures sweep clean (no false positive across refs, options, maps, arrays), depth naming ([adaptor], [session].comune_dir, [env.HOME].direktion, [hints].keyword, a root scalar), free-form tables silent, unparseable silent. The monics a receiving agent holds that MATCH an arriving message RIDE THAT MESSAGE'S ENVELOPE as a `mnemonics-json` attribute carrying a JSON array of the matched records, so the agent reads its own standing judgement about the sender in the same breath as the message rather than having to go and look it up. THE MATCH IS EVALUATED AT THE ENVELOPE RENDERERS, NOT AT THE WAN EDGE (ruled 2026-07-31): a classification that fires only when the peer happens to be REMOTE is one the agent cannot rely on, and the trust warning's edge is the wrong home for it because that edge exists to consume the ACCESS GATE's verdict — the two are different concerns that happen to read the same register. THERE IS EXACTLY ONE EVALUATION PRIMITIVE and every delivery surface calls it: a match rule copied per surface is a classification that fires on one channel and not another, which is worse than none because the agent cannot tell which it is looking at. The renderers therefore take the RECIPIENT's id as well as the structural sender/body pair, and NO OWNER-LESS RENDERER REMAINS — a renderer that did not need the recipient is the shape a future delivery surface reaches for by habit, arriving attr-less with nothing failing. THE ATTR IS PRESENT IF AND ONLY IF SOMETHING MATCHED: an empty array would say 'evaluated, no match' where absence says 'not evaluated', a distinction no consumer has a use for and a second shape every consumer would have to handle. THE TRIGGER VOCABULARY IS RATIFIED (operator ruling 2026-08-03, superseding W5's sender-match-only limit) AND IT BINDS BOTH CONSUMERS — this edge and the now-signal MONICS surface — precisely so that the second consumer adds a CONSUMER and never a record migration. Triggers are a JSON array of matchers over five kinds: `sender` (identity match on the PROVEN sender id, which is the trust warning's classification question and the only kind that answers it), `content` (the incoming message body), `json` (a custom payload), `user_input` and `agent_output`. MATCHING COPIES THE KEYWORD-HINTS RULE VERBATIM — a literal CASE-INSENSITIVE SUBSTRING by default, a REGEX when `regex` is set, and an INVALID REGEX NEVER MATCHES (a bad pattern silences its own trigger and never panics) — and it copies it BY CALLING IT: the rule lives in one place with two consumers, because a rule copied verbatim into a second place is two rules that merely agree today. Literal-by-default is what lets a trigger carry arbitrary symbols with no escaping burden. WHICH KINDS HAVE A LIVE CONSUMER IS A SEPARATE QUESTION FROM WHICH ARE RATIFIED: `sender`, `content` and `json` evaluate at this edge, which exists; `user_input` and `agent_output` are RATIFIED BUT INERT until the now-signal / IO-parser funnel is built, and a record carrying one is not malformed — it is waiting for its consumer. Reading 'ratified' as 'has a live consumer' is the error this clause exists to prevent, and any surface that lists a trigger must say which it is looking at. THE TWO EDGE QUESTIONS ARE DELIBERATELY ASYMMETRIC AFTER THE RE-KEY: a content-triggered monic rides a message it matched WITHOUT classifying its sender, because classification is a `sender` question — so the delivery attr can be present where the trust warning still treats the peer as a stranger. The direction that must hold is the one that protects the agent: anything that classifies the sender ALSO matches the delivery, so an agent is never warned about a stranger whose monic it was handed in the same envelope. The ARRAY shape is not premature generality but the ratified envelope contract, and it is what lets a trigger kind matching several records ride with no envelope change. THE READ IS STORE-FREE AND PURE-PATH, off the tracked root, for the reason the trust warning's is: it happens on the inbound message path, where the store's ensure-worktree spawns git per message. A RECIPIENT WITH NO MIND — a shell link, an agent that has classified nobody — READS NOTHING AND GETS NO ATTR, and that is the primitive telling the truth rather than a case to special-case; it costs one sentence in the module doc, not a branch. An UNREADABLE record rides nothing, the same fail-safe reading the trust warning takes, and the two consumers are asserted to agree over every state a record can be in — an agent must never be warned about a stranger whose monic it was handed in the same envelope. The attr KEY is a compile-time constant and its VALUE is attr-escaped by the one composer, so monic text carrying quotes or angle brackets cannot forge an attribute or terminate the envelope. STATED LIMIT, NAMING ITS ONE PEER-TRAFFIC CASE: a body that is ALREADY a typed envelope passes through verbatim and carries no attr — splicing an attribute into a finished envelope means re-hand-rolling the grammar this codebase consolidated into one composer, and typed envelopes are otherwise machinery (notify, file-drop, echo-commune) that is not peer chatter. THE ONE CASE WHERE IT BITES PEER TRAFFIC IS `restamp_wan_user_msg` IN THE WAN EDGE, named here exactly so a later wave can lift it without re-deriving which path is meant: a FORGED `user-msg` — one whose sender the daemon could not prove — is re-stamped into a typed plain-`msg` envelope, which then rides as a typed body and gets no attr. What is lost there is the MONIC RIDE, never the caution: the trust warning is composed at that same edge from the gate's verdict and is untouched by this leg, and an unprovable sender is warned about unsuppressibly in any case. Lifting the limit means re-composing the restamp through the owner-aware renderer, which requires threading the RECIPIENT into `restamp_*` — real work on adversarial-path code, deliberately not taken in this wave, and to be requested through the board rather than bled into a neighbouring change. A SENDER-SUPPLIED TYPED ENVELOPE CANNOT CLAIM THE RECEIVER'S JUDGEMENT: every inbound `mnemonics-json` attribute is stripped at `receive_wan` before delivery, while the peer's body remains unchanged; the forged value is inert. Gate: impl — the one match primitive over the tracked root, the msg composer's extra-attribute form, the owner-aware renderers on both delivery edges, and every production render site converted to hand over its recipient; unit — a sender-matched monic rides as a JSON array carrying the record verbatim with `from` still first and BOTH edges composing identically, an unclassified sender and a mindless recipient get no attr at all, a husk rides nothing AND reads identically to the warning predicate over every state, a typed body carries no attr even when a monic matches, monic text full of envelope metacharacters cannot break out of the attr, and the attr survives the listener's chunk/reassemble round trip. A PRESENT-BUT-UNREADABLE monic record — a HUSK — COUNTS AS PRESENT when `add` and `update` classify what they found (ratified 2026-08-04, bag grill; this REVERSES the reading that shipped in W5 and was deliberately preserved through the releases#70 re-key). The question the two verbs ask is whether the monic id is already SPOKEN FOR, and a file filed under that id speaks for it whether or not anything can parse the bytes. So `add` REFUSES a husk, naming it as unreadable and pointing at `update` and `remove`, and `update` PROCEEDS over one, announcing the replacement out loud. The superseded polarity was not dishonest — both arms said what they saw — but it made `add`, the verb whose entire job is refusing to clobber what is already filed, the one that destroyed content, and `update`, the verb whose job IS replacement, the one that refused to perform it; it then instructed the caller to reach for the destructive verb in order to replace a record, teaching precisely the reflex the add/update split exists to prevent, in the ONE case where the operator cannot read the old content to judge what the replacement costs. THE ESCAPE HATCH IS VERIFIED, NOT BUILT: making `add` refuse a husk would strand a caller only if nothing else could clear a corrupt record, and `remove` decides on FILE EXISTENCE rather than readability, so it already withdraws a husk by construction — no `--force` flag is minted, and that shipped property is asserted by a test rather than trusted. THE POLARITY IS ONE FACT AND MUST BE SPELLED THE SAME EVERYWHERE IT IS ENCODED: the CLI's classify call, the store's own add/update primitive, and every printed line that names a verb for an unreadable record — a listing hint still pointing at `add` would be a surface instructing the caller to run the arm that now refuses. UNCHANGED, deliberately: the DELIVERY EDGE still reads a husk as never-classified (it fails safe — it warns more, never less), and the LISTING still shows a husk under its own id marked unreadable. Gate: doc — the CONTEXT.md mnemonics entry states the ratified polarity, the reason the previous reading was reversed, the verified escape hatch, and what stayed unchanged; impl — the classification input counts a husk as present at BOTH encoding sites (the CLI verb path and the store's write primitive, which classifies on the FILE question rather than on a successful parse), the `add` arm refuses with the unreadable diagnosis naming `update` and `remove`, the `update` arm proceeds and keeps the announced-replacement line, and the listing's advice names the verb that now replaces; unit — a husk REFUSES `add` as Exists AND SURVIVES THE REFUSAL UNREAD (the refusal is not a partial write, which is the whole point of refusing over content nobody can read), a husk is REPAIRED IN PLACE by `update` (the caller has no other route to repair), and `remove` withdraws a husk and stays idempotent over the cleared id, proving the escape hatch that made the `add` refusal safe to ship. An agent edits its own monics through `spt endpoint monic [--owner ]` and copies another endpoint's through `spt endpoint monic clone |--all --from [--to ] [--overwrite]`. ADD, UPDATE AND REMOVE REQUIRE `--target `; ADD AND UPDATE REQUIRE `--triggers `; AND THE BODY ARRIVES ON STDIN, because a monic body is prose carrying newlines, quotes and shell metacharacters, which an argv-borne body hands to whatever shell is in the middle to mangle. ONE STDIN PAYLOAD MAY CARRY SEVERAL MONICS: the multi-record form takes whole records from stdin as a JSON array, each with its own id, triggers and body, and it is a DISTINCT INPUT SHAPE from the single-record form rather than a reinterpretation of it — the flags name the record and stdin is its body, so a payload that also had to carry ids would be two sources for one fact, and the surface refuses the two spellings together so neither is ever ambiguous. A multi-record write applies RECORD BY RECORD and does not roll back what already landed, because each record is an independent fact and a partial application that names exactly which ids landed is more recoverable than an all-or-nothing failure the operator must then diagnose; the exit code is non-zero if ANY record was refused, so a script cannot read a partial application as success. NO MONIC VERB IS EVER ELEVATION-GATED (ratified): a monic is the agent's own judgement, written by the agent, and an agent holds no OS privilege to prove — gating one would make an agent's own mind editable only by the human standing at the machine, which is the opposite of what the register is for. That invariant is a CHECKED PROPERTY, not a comment: the verbs' one decision point TAKES the process's real elevation and ignores it, so a gate added later in either direction fails a test rather than passing unnoticed as a filled-in absence. ADD AND UPDATE ARE SEPARATE VERBS because they refuse opposite states — add refuses to replace a monic that already exists, update refuses to invent one that does not — so a typo'd id or a re-run script cannot quietly change a record; the refusal names the other verb. THE LISTING SHOWS UNREADABLE RECORDS AS UNREADABLE. A record that is present and unparseable reads as never having matched at the delivery edge, which is correct there because it fails safe (it warns more, never less) — but a husk that also vanishes from the listing is a file the agent can neither act on nor discover, the erased-distinction class. So the list names it UNDER ITS OWN MONIC ID, marks it unreadable, and points at the verb that rewrites it. The listing also states a trigger kind that is RATIFIED BUT INERT as such, since a trigger that cannot fire today must not be presented as working. CLONE COPIES THROUGH THE FORK'S COPY SEAM (REQ-FORK-MIND-COMPLETE), not a second copy path, and copies the source's mind AT ITS TIP; a record the destination already holds under that id is KEPT and reported rather than silently replaced (--overwrite is the deliberate replacement), and every copied record is re-stamped as inherited so the destination can tell its own monics from the ones it was handed. A husk in the source travels as a husk — visible in the destination's listing, never dropped and never invented. A clone that copies nothing mints nothing: no branch, no worktree, no empty commit. Gate: impl — the five verbs over the monic store with owner defaulting to the session's own endpoint, the stdin body read, the trigger-set parse with an unparseable set REFUSED rather than defaulted to empty, the multi-record payload form, the elevation-taking decision function, the add/update preconditions, the unreadable-record row in the listing, and the clone routed through the shared copy seam; unit — the elevation table (every verb x record-present/absent yields the SAME outcome under elevated, not-elevated and undeterminable), the add/update split including the husk reading ratified 2026-08-04 (a husk is PRESENT for these verbs, so add refuses it with the unreadable diagnosis and update replaces it while announcing that it did — see REQ-MONIC-HUSK-PRESENT, which owns that polarity and its escape hatch), the clap surface (bare `monic` lists, --target and --triggers required without the multi-record flag and refused with it, clone takes exactly one selector), the two-part husk row (delivery edge says never-matched, list path names it unreadable under its id), and the clone rows (existing destination record kept and reported, --overwrite replaces, copies stamped inherited, source untouched, self-clone refused, nothing-to-copy mints no mind). Listener stream stdout emits EVENT envelope lines (sister-format, ADR-0001): parse the __REPLY_TO__ frame, pass pre-formed typed envelopes through verbatim (no double-wrap), compose otherwise, chunk oversized lines into EVENT-PART Activity-gated inbound delivery + per-message send control as THREE ORTHOGONAL AXES plus opaque metadata (ADR-0028; grilled w/ operator 2026-06-23). SUBSTRATE (the legacy-SPT parity gap, scaffolded-but-unwired today: `delivery::is_idle` + `resolve_inject_methods` exist but the result is discarded `let _methods`, and `broker::dispatch_endpoint_input` injects unconditionally — its comment calls activity-gating 'a deferred follow wave'): an inbound message has an ACTIVE window (endpoint active → spool for the receiver's hook-poll, non-disruptive) and an IDLE window (idle/idle-transition → deliver immediately: translation binary spt-hosted → relay-poll either topology → spool, in fallback order). AXES (each composes; each defaults to its unrestricted value): (1) DELIVERY WINDOW — default (both, first-to-fire) | `--idle-only` (idle window; immediate if already idle) | `--active-only` (active window only, never wakes; the RENAMED `--deferred` — `deferred=1` spool column + `api poll --include-deferred` keep their names). (2) CHANNEL RESTRICTION — unrestricted | `--prefer-native` (translation binary if running else fall back) | `--force-native` (binary ONLY, no fallback/no spool-to-other-method). Native flags do NOT respect the binary's idle-gating: the WINDOW says when, the native flag says through-what (so `--force-native --active-only` = binary injects during the active window, mid-turn-safe via the existing InjectFloor). (3) PERSISTENCE — durable (default; spool until delivered or TTL) | `--ephemeral` (drop if undeliverable in the accepted window — at window-open with no live carrier, or at TTL, whichever first). METADATA (orthogonal): `--json-payload ''` → a single attr-escaped `json="…"` envelope attr ALONGSIDE (not replacing) the body, pure verbatim passthrough across spool/TCP/WAN/EVENT-PART, parsed only by the receiving adapter; collision-proof by construction (structured data lives INSIDE the one `json` value, can never forge `from`/`type`); available to ANY sender (confers no spt-core authority). HAZARD: `--ephemeral` is the ONLY path permitted to drop silently — the sender-opted-in carve-out to REQ-HAZARD-IDLE-SILENT-NONDELIVERY (that hazard gains a '…unless --ephemeral' clause in v0.15.0). (v0.15.0) The body envelope (spt-proto::event, the ADR-0001 grammar) is the SOLE canonical arriving-message format at EVERY harness arriving-message surface on an AGENT perch — api listen AND api poll/worker-poll, byte-identical (reverses REQ-MSG-4's 'hook drains keep the raw frame by contract'). SCOPE CARVE-OUT: the shell-command relay (api poll --link, cmd_poll_shell) is a distinct internal transport carrying RAW MAC'd stamped frames the shell child consumes verbatim — NOT an arriving-message surface, deliberately EXEMPT from composition (notify_shell_e2e guards this boundary). __REPLY_TO__ — mis-elevated during the clean-room port to a fake ADR-0001 'stable wire format' (spt-msg/wire.rs, lib.rs) — is REMOVED entirely (spool format_row, the spt-msg TCP frame, emit parse_frame); (from, body) carried structurally, composed once at the delivery boundary. No legacy sister-interop (spt-core never required it). Reply-correlation rebinds onto the structural from / attribute (ADR-0009 access-gate + ADR-0012 Psyche/spt-live reply-target). Self-delimiting by construction → finding F-002 (non-self-delimiting multi-message poll) dissolves. ADR-0020. Notif scope is a per-row producer-chosen attribute node|subnet: node-scoped rows (update/consent/rollback kinds) live and die on their node and NEVER enter a replication feed; subnet-scoped rows keep the full ADR-0007 machinery (replication, cross-node dismiss, semilattice join); scope determines the first-fire candidate set (node-scoped targets the most-recently-active endpoint ON that node). New row fields additive serde-defaulted (host_binaries pattern) — old peers parse clean, schema regen drift-gated An update-available notif row minted by a node running PRE-0.40.0 spt is retired on the version the running node has ALREADY reached — because the keyed catch-up dismissal is structurally blind to it. (DAEMON-LIFECYCLE W2 RIDER, operator-ordered; doyle root-caused end to end 2026-07-22 on the live box.) FIELD CHAIN, verified: GRAVITY-NVDA-PC (f15d837b, BIGNET) runs pre-0.40.0 spt, whose legacy producer mints the update notice SUBNET-scoped with NO coalesce key (the scoped+keyed producer shipped in 0.40.0). The row replicated fleet-wide. The modern catch-up dismissal (REQ-NOTIF-SEAM-DISMISS, pump/update.rs dismiss_staged_notif_if_caught_up) dismisses ONLY by coalesce key, so a keyless row can never be retired by it: a FULLY-UPDATED node holds a live 'v0.41.0 available' row forever, surfacing once per endpoint at every boundary (observed: todlando ~19:54 + Librarian/Athenaeum-Library; store showed seen=2, undismissed, the only undismissed update row in the whole history). FIX: a version-grounded retirement sweep at the EXISTING catch-up site, same per-tick per-subnet cadence, running ALONGSIDE the key path (which stays PRIMARY — belt-and-braces, not a replacement): dismiss any UNDISMISSED row whose from_id == 'spt-update' AND whose body advertises a version that PARSES AND is <= the running image's own env!(CARGO_PKG_VERSION) (the same running-image self-truth the existing out-of-band leg reads). CONSERVATISM INHERITED VERBATIM from version_ge: an unparseable advertised version NEVER dismisses (the ancient 'update-available 5' counter-shape stays untouched), and advertised > running STAYS LIVE (a genuinely pending notice must survive the sweep). MUST NOT KEY ON kind ALONE: kind=consent is SHARED with the grants act-gate asks (grants.rs mints consent-kind through the plain front door with from_id = the asking agent), so from_id == 'spt-update' PLUS the body version-parse is the discriminator. The relevance predicate stays DAEMON-side (ADR-0046: the notif primitive evaluates no relevance predicate) — the store keeps only its existing undismissed()/dismiss() primitives. KNOWN LIMIT, stated not hidden: the ORIGIN node's one-time cross-node wake at mint is a plain live send from old code and is not preventable from our side; retiring/updating the legacy node is the operator's lever. This fix kills the row's AFTERLIFE (undismissable + boundary-resurfaces at every endpoint forever), which is the repeating harm. Gate: impl — the sweep + the pure advertised-version extractor at the catch-up site; unit — the extractor cells (dotted token found / v-prefixed / counter-shape and garbage refused) and the retire predicate; int — RED-first four legs: (1) legacy-shaped row (subnet scope, NULL key, from_id spt-update, advertised <= running) SURVIVES pre-fix ticks and is DISMISSED post-fix; (2) sibling advertising > running stays live; (3) sibling with a garbage/counter-shape body stays live; (4) regression: a modern Node-scoped KEYED row still dismisses via the key path; plus an asserted (not assumed) replication leg — a sweep-minted dismissal merges as dismissed through notifsync, since the fleet-wide kill is the point. A-4a (REMOTE-TRUTH triage §A + ADR-0034 Decision 1 + Amendments 1 & 2): the broker effect journal's dedup key gains a minter dimension so ops minted by independent counters can never collide. ROOT (high, ground-truthed vs HEAD): the journaled-op producers key into ONE journal namespace (NET_EFFECT_SESSION|shell_sid, op) at broker.rs (EffectKey=(u64,u64)); a CLI wake op colliding with an already-journaled daemon op reproduces the typed 'already applied … retry with a fresh op_id' with NO broker restart (field-hit: spt endpoint wake id@node WOKE_FAIL). Same latent class: nethost dial_ops/stream_ops HashMap ('Shares the one net op-id namespace') would re-clobber even after the journal separates them; AND shellchan::deliver_stdin_pending journals (shell_sid, row_id) so an rc operator's ops on the same shell_sid collide with spool row ids (dropped keystroke OR dropped spool row). Amendment 2 corrected the minter set: the REAL journal minters are {cli, pump, rc, shell} + legacy — psyche/epoch are the EpochSource notif/lease counter domain, NEVER submit to apply_once, DROPPED from the journal enum (a tag with no stamp site = doc'd-but-dead knob). FIX (Decision 1 + Amdt 2): ONE canonical Minter enum {Legacy, Cli, Pump, Rc, Shell, Wake} — Legacy reserved for pre-upgrade lines + untagged wire, monotonically shrinks; enum is the single source for the TEXTUAL journal-line token (self-describing during recovery). EffectKey becomes (effect-class, minter, op); recover() DUAL-PARSES (old shorter line → minter=Legacy, new longer line → parsed tag) so old journals need no migration and old-shape keys can never equal new-shape (migration-free). A MintedOp{minter, seq} newtype REPLACES bare op_id:u64 through the brain/daemon THREADING paths so forgot-to-stamp is UNCOMPILABLE (row_id stays the shell seq — never re-minted, the durable spool exactly-once identity). Wire keeps an additive optional minter field (serde default absent ⇒ Legacy materialized at broker decode; serde_json no deny_unknown_fields ⇒ NO wire version bump); the newtype is NOT forced into wire structs. nethost op-maps re-key by (minter, op). Red-first: mint an rc op == a journaled shell/pump/daemon op int on the same session → pre-fix the second dedups/clobbers (WOKE_FAIL class); post-fix both are distinct keys, both Applied. A perch directory is RESIDUE only on a POSITIVE record-absence predicate, and the store that answers it is the owlery tree itself (3.4 extension; releases#109, doyle-ruled 2026-08-04). There is NO independent endpoint-existence store on a node: `spt::roster::enumerate`, the daemon's `projwriter::enumerate_perches` and `perch::list_self_perch_ids_checked` all derive the endpoint set from `read_dir(owlery)`, and a dir with no `info.json` is not a perch — so THE PERCH DIRECTORY PLUS ITS PARSEABLE RECORD IS THE ENDPOINT RECORD. The local registry is NOT that store and must never be asked: KNOWN-HAZARDS 4.3 has `clean_stale_entries` DELETE dead-pid rows and `unregister_address` fire on an ordinary stop, so registry-absence is the normal steady state of every OFFLINE endpoint (measured HFENDULEAM 2026-08-04: 6 of 14). PREDICATE: residue iff no `info.json` is present on ANY read attempt. Positive, present-tense, one read, and NEVER an age/mtime heuristic — a dormant endpoint, a suspended session and a long-idle live agent are indistinguishable from 'old' by mtime. Absent is the ONLY answer that authorizes deletion, so the read keeps NotFound distinct from every other I/O error: unreadable ⇒ occupied, corrupt (present, unparseable) ⇒ occupied (a destroyed record belongs to an endpoint that exists). REAP POPULATION IS NARROWER THAN RESIDUE: recordless AND spool-less AND non-empty AND no record-write in flight (`.info.lock` held ⇒ a bringup mid-create) AND every nested/shells child independently reapable (deepest-first; a recordless parent holding ANY non-reaped child is itself refused). A REFUSED DIRECTORY SHIELDS ITS WHOLE SUBTREE (doyle, ruled 2026-08-04): a descendant that would qualify on its own is reported SHIELDED-BY-REFUSED-PARENT and left alone, because refusing a directory must mean not modifying its CONTENTS either — carving a child out destroys part of what the refusal report just asked the operator to look at, and can move the parent's own classification on the next pass, and a sweep whose refusals are unstable across its own runs is untrustworthy. A recordless dir carrying a SPOOL is refused permanently — undeliverable-by-construction settles deliverability, not VALUE, and that spool is the only surviving copy of what was queued. An EMPTY dir is refused permanently — the mid-create race 3.4 protects exists here identically and no positive discriminator for 'not mid-create' exists for an empty dir. A reparse point anywhere in the subtree is refused. Store unavailable (`read_dir` on the root fails) ⇒ DO NOTHING and say so, reported distinctly from an empty owlery. AN ASSEMBLY PICK MUST BE AUDITED FOR FIDELITY ON ITS CHANGED-LINE SET, AND THE COUNT AND DIGEST ARMS MUST BE REPORTED AS SEPARATE VERDICTS BECAUSE THEY FAIL IN OPPOSITE DIRECTIONS. MEASURED on the TURNKEY #212 assembly (IR-57): a cherry-pick's conflict resolution silently dropped exactly two lines and the assembled head did not compile, while every instrument in the assembly path reported success -- the pick completed with no conflict remaining, traceable-reqs check returned 797/797 exit 0 because it parses tags and never invokes the compiler, and the lanes themselves were green and stayed provably clean. Lane-green plus conflict-free is not a claim about the assembled head. The audit compares ONLY the + and - lines of git show --format= for the lane source and for its pick, excluding the +++ and --- file headers: hunk headers and context lines legitimately drift once the head's copy of a file has moved, so a whole-diff comparison returns a confident wrong answer. Of 24 picks on that chain two did not match and they were DIFFERENT CLASSES -- one differed in COUNT (302 lane against 300 pick by the prescribed header-excluding pipeline; the register originally cited 310/308, which is the raw count including the 8 file-header lines of its 4 files, and the delta is 2 either way), the real defect; the other had the SAME count and a different digest, a paragraph the head had already amended for another lane whose merged result correctly carries both lanes' sentences. A count check MISSES the first class entirely and a digest check FLAGS the second as if it were a defect, so neither alone classifies a pick: the verb reports both arms per pick, fails the run on a count mismatch only, and prints the digest-only class loudly as a human read rather than reddening on it, because a check that reds on the measured benign class gets disabled and takes the count arm with it. A pick the audit cannot pair to a lane source is UNAUDITED and exits non-zero in its own class rather than reading as a pass, since a silent skip is the same failure the entry documents wearing a green. Both sides of every comparison are read at ONE layer -- git's own diff output for two commits in one repository -- because a comparison taking one side from the working tree and the other from git's normalized view lies toward MISMATCH in a checkout that carries CRLF against LF blobs, which this one does. BUILD-CACHE POOLS ARE RECLAIMABLE ON EVIDENCE, NOT ON AGE OR NAME. Cargo never GCs a target directory and pools OUTLIVE the lanes that made them: this box carried gate-target-h1 at 17.5 GB with ZERO links pointing at it -- unreachable, unowned, and un-reclaimable by any mechanism that existed, because nothing recorded whose it was. The ownership stamp supplies the missing facts, so a sweep can classify rather than guess: IN USE (a lane holder live by the process-table-plus-birth-stamp predicate) is never touched; OWNED (owner tree still on disk, no live lane) is somebody's WARM CACHE and is reclaimed only when explicitly asked; ORPHANED (owner tree gone, or unstamped with nothing linking to it) is the safe row. WHAT MAKES A DIRECTORY A POOL IS CARGO'S OWN CACHEDIR.TAG SIGNATURE, never its name -- a sweep that decided by the word target could delete a source directory somebody happened to call that, and the point of this leg is a reclaim an operator can run without auditing it first. UNSTAMPED DOES NOT MEAN UNWANTED: an unstamped pool with inbound links is a pre-guard pool still in use, and getting that backwards would reclaim a live lane's cache on the very first sweep, since the pools predating the stamp are exactly the ones most likely to be somebody's. UNKNOWN LIVENESS COUNTS AS IN USE here, the opposite polarity from the guard's proceed-with-warning, because the consequences are asymmetric: an unresolvable holder that blocks a reclaim costs disk, while one that permits it costs a lane its cache. THE INBOUND SWEEP COMPLETES BEFORE ANY VERDICT -- a link found after the verdict is a link that did not save the pool -- and path comparison is normalized, since Windows records a junction target with a verbatim device prefix and arbitrary case and a raw compare would report zero inbound links for a pool half the box points at, orphaning every one of them. DRY RUN IS THE DEFAULT: the failure mode of a wrong reclaim is silent, nobody notices a deleted warm cache until the next build takes twenty minutes. Deletion re-asserts real-directory-not-link at the point of removal, because a reparse point removed as a tree takes its TARGET with it, and reports size and free space so the reclaim is a number rather than a claim. A PSYCHE OPERATION'S OUTCOME IS CLASSIFIED AT ITS SOURCE AND CARRIED AS A TYPE, NEVER RECOVERED BY READING ITS PROSE. The soft-timeout budget and the hard-failure budget can only differ if the two are told apart, and today they cannot be: `RuntimeError::Timeout { command, after }` exists at the source (spt-runtime runtime.rs) and `EchoError::Runtime` wraps it, but `fire_echo` ends `.map_err(|e| e.to_string())?` and `note_turn_outcome` takes `&Result<(), String>` -- so by the time the budget sees an outcome the fact is prose. RECOVERING IT BY MATCHING THE MESSAGE IS FORBIDDEN, and not as a matter of taste: this codebase already ruled that class out for the sibling discriminator, where the reserved exit code 95 is the SOLE session-not-found signal and the comment at its site states that the human-readable stderr line is never parsed. A harness whose summarizer prints the words `timed out` inside an ordinary non-zero failure must not thereby buy the softer budget. THE CLASSIFICATION IS THREE-VALUED (Ok / Timeout / Hard) and both legs -- the per-event turn and the commune-sync echo -- produce it, so the budget owner never re-derives what the spawn already knew. Gate: impl -- a classified outcome type produced at the runtime boundary from `RuntimeError::Timeout` and threaded unflattened through `fire_echo` and the turn leg into `note_turn_outcome`, which consumes the type and never the string; unit -- a HARD failure whose stderr text CONTAINS `timed out` still classifies Hard (the anti-parse row: a message-matching implementation passes every other row and fails this one), a real bound kill classifies Timeout, and a clean run classifies Ok. On Windows, `spt rc` must FORWARD scroll-wheel events to the harness when the harness has mouse reporting on. ROOT (operator HITL): P1's RawGuard EnableMouseCapture (added for right-click paste, REQ-RC-WIN-PASTE) makes Windows Terminal forward ALL mouse — including the scroll wheel — to rc instead of scrolling its own buffer, but the rc mouse handler dropped everything except right-button-down → scroll DIED (and WT's native scrollback is stolen by the capture). Operator ruling: keep mouse capture + right-click bracketed paste AND forward scroll to the harness. FIX (doyle design, cfg(windows), folds into the rc mouse handler): TRACK the harness's mouse-reporting mode by scanning its OUTPUT stream for the DECSET set/reset — ESC[?1000h/1002h/1003h (mouse on) + ESC[?1006h (SGR ext) and their ...l (off) — into a shared MouseMode{enabled,sgr} (pump writes from output, stdin reader reads); the scan survives a sequence SPLIT across output chunks (a bounded carry buffer). The mouse handler: right-button-DOWN -> bracketed clipboard paste (unchanged, REQ-RC-WIN-PASTE); ScrollUp/Down -> translate to an xterm SGR mouse report (ESC[<64;col+1;row+1M up / ESC[<65;..M down, 0-based crossterm -> 1-based xterm) and forward ONLY when enabled && sgr (else DROP — a legacy X10 report the harness may not parse is garbage); Moved/drag/left/middle -> DROP (scroll is the operator's need; click-forward risks garbage, no click-to-position). Unix UNCHANGED (no capture; the terminal scrolls natively). (v0.13.0) RC-RENDER-TRUTH W1 (ADR-0042 decision 4, hertz elevated-endpoints RCA core leg 2, doyle seam-confirmed rc.rs establish_attach): the resolver's canonical BARE endpoint id is carried separately from the user-facing qualified target — AttachRequest.endpoint_id is always the bare id (today rc passes the ORIGINAL qualified string; the target's resolve_local_session compares verbatim vs the bare HostedSession.endpoint, so `spt rc id@node`/`subnet:id` dials the RIGHT node then gets a false no-live-session refusal). N-1-additive: bare-form callers are unchanged. Gate: impl — canonical-id carry through establish_attach; unit — Address::parse qualified forms yield bare wire id, user-facing copy keeps the qualified spelling; int — bare + id@node + subnet:id ALL attach against a remote broker-hosted target, wire always carries the canonical bare id; doc — ADR-0042. A resize transition is ATOMIC from ScreenGrid's perspective: every byte is parsed at the geometry it was EMITTED under. (hertz v0.39.0 field RCA 2026-07-21, doyle re-grounded at source the same hour; P0 — this falsifies a SHIPPED v0.39.0 release claim, so it is a regression, not new scope.) SYMPTOM: right-margin fragments and left-shifted rows on a node where CLI and broker are BOTH 0.39.0 — i.e. exactly the defect the v0.39.0 notes claim fixed. SOURCE (statically provable, no repro box required): broker.rs dispatch_resize calls session.resize(SurfaceSize::new(rows, cols)) (~4557-4559) and only THEN recover_log(&log).set_size_and_notify(rows, cols) (~4560), which reaches self.grid.resize(rows, cols) (~1492) — TWO SEPARATE recover_log acquisitions, so the reader thread's append/parse interleaves between them BY CONSTRUCTION. On ConPTY the resize ITSELF emits a full repaint asynchronously; the reader parses that repaint into the grid at the OLD geometry; the subsequent top-left-preserving grid.resize then faithfully preserves an ALREADY-MISWRAPPED model; the cold attach repaint emits the shift. WHY THE EXISTING TESTS PASS: the spt-term width oracle (REQ-SCREENGRID-WIDTH's int stage) is green 5/5 while the field is red — it never crosses the ConPTY-emission -> grid-geometry -> attach-repaint seam, and the surface mock structurally cannot. The W3 width model is therefore NOT falsified; it is correct work running under a broken geometry transition. FIX — and the record must carry WHY the obvious fix is wrong: merely REORDERING (set grid geometry before invoking the ConPTY resize) is INSUFFICIENT. It is safe on a GROW and unsafe on a SHRINK, because bytes already in flight at the old WIDER geometry are then parsed at the new narrow width and wrap early. Neither pure ordering is correct because the defect is not the ORDER — it is that the grid has no notion of 'these bytes were emitted under the old geometry'. Required shape: quiesce/gate the drain across the ordered surface+grid transition AND carry an explicit geometry EPOCH so parse always reckons at the emitting geometry, with rollback/refetch of the grid geometry if the surface resize fails. Gate: doc — the epoch/barrier contract stated where the resize path is documented, including the shrink counter-example so the insufficient reorder is not re-proposed; impl — barrier + geometry epoch + failure rollback in the dispatch_resize/set_size_and_notify path; unit — a resize whose repaint bytes arrive mid-transition is parsed at the emitting geometry (both grow AND shrink), and a failed surface resize leaves the grid geometry unchanged; int — Windows REAL ConPTY (not the surface mock): resize emits a dense frame, then the synthesized attach repaint is compared against an INDEPENDENT terminal authority, never ScreenGrid against itself. FIXTURE AMENDMENT (hertz field supplement 2026-07-21 second capture, doyle-accepted, carried by todlando at build): the dense frame is ordinary readable PROSE with REPEATED WORDS and punctuation, not sparse synthetic markers — the field captures are semantic corruption of rendered content (`ReQ2`, `ROADMAPnsayh'credertigls`, `Recommend:gdirect REST`, `thii reqw st`, `resdluuion`: clobber, merge, substitution and displacement of width-1 cells MID-ROW), and repeated words are what make a displaced fragment land somewhere that still looks plausible — the shape that let this read as cosmetic. The leg must fail on DELETION, INSERTION, SUBSTITUTION, MERGE or DISPLACEMENT of any width-1 cell, with NO normalization and NO presence-only assertions (full-row untrimmed string equality plus equal row counts satisfies this by construction). CONSTRAINT, binding: the fixture stays inside the region where the authority emulator and ConPTY AGREE — the screen at the resize instant carries no line reaching either margin and loses no row, so reflow-vs-truncate is a no-op on it, and the wrap-crossing prose is emitted AFTER the transition opens, at the new geometry. A fixture crossing the margin at the resize instant would red on emulator POLICY DIVERGENCE rather than on this defect; pinning the wrap policy between authority and ConPTY is separate work and must not be smuggled in here. The comparison is EVERY CELL, including cells expected to be BLANK — not row starts, right margins, or text presence. The dense frame must include erased interior spans and repeated spaces, and those cells must still be blank after the synthesized repaint. (hertz field supplement 2026-07-21, doyle-accepted: the operator confirmed a THIRD corruption shape on the same fully-0.39.0 node — stale characters surviving INSIDE nominal whitespace between words, e.g. `isnsettled`, `fulllyacaptured`. A mis-widthed repaint places glyphs into wrong interior cells; later differential output and EL operations address the CORRECT geometry and so do not necessarily clear them; the synthesized cold repaint then faithfully re-emits the contaminated interior. Accepted as further evidence for THIS requirement, not a new root, on a DISCRIMINATION rather than the prediction: the captured debris is pure ASCII, every glyph width-1, so no width-table or wide-half defect can produce it — and the erase paths are already width-normalized on both halves at screen.rs clear_cell_keep_pen ~416/~420, the span helper ~440-443, and delete/insert_chars ~617-620, with erase_across_a_wide_half_leaves_no_orphan ~1391 passing. Word-scale `isn`+`settled` collapsing across a space is the same shifted-by-N placement as the whole-row left shift, observed at word scale. OPEN DISCRIMINATOR: interior debris in a session where NO resize occurred at any point would mean a SECOND root that this fix will not clear.) BINDING CONSTRAINT on satisfying the blank-cell assertions: NO whitespace special-casing and NO extra clears — the barrier must restore ONE geometry authority for glyph placement AND erasure. A fix that passes by clearing harder satisfies the test and leaves the defect. KNOWN ACCEPTED RESIDUAL (doyle ruling 2026-07-21): a cold attach landing DURING a transition renders the PRE-TRANSITION screen — the held bytes are not parsed yet. Accepted because it is a strict improvement over the shipped behavior on the same input (pre-fix: miswrapped/corrupt; post-fix: stale but coherent). The window is bounded in TIME (settle + one surface call) AND — since REQ-RC-RESIZE-PRESENTATION-BARRIER (built 2026-07-21) — in VISIBILITY: the commit/abort-time sync frame delivers every attached sink, transition-era attachers included, the fresh repaint the moment the barrier tears down, quiet child or not (the successor REQ's commit-time sync frame IS the follow-up seed this residual carried, absorbed by construction). The attach itself still renders the pre-transition screen for the width of the window. REFUSED, so nobody builds it later without a fresh argument: making the attach repaint WAIT on an in-flight transition — that puts a user-facing attach behind the settle cap / serialize wait. During a resize transition, LIVE DELIVERY is barriered along with the grid parse: no attached sink (controller or viewer) ever receives mixed-geometry raw bytes; commit substitutes ONE synthesized sync frame at the target geometry. (hertz post-fix field RCA 2026-07-21 on a fully-0.39.3 node, doyle-accepted with every cite verified at source; SUCCEEDS REQ-RC-RESIZE-GEOMETRY-EPOCH, which shipped correct but scoped to the wrong surface — broker.rs's own ResizeTransition doc states append 'still rings + fans out every chunk exactly as before' during a transition, and OutputLog::append gates ONLY grid.advance. A live attached terminal therefore receives old+new-geometry differentials across an already-resized viewport; the cold-attach repaint model is definitionally blind to that path, which is why the shipped gate was green while the field was red. REQUIREMENT-SCOPE defect: the gate held against documented design.) CONTRACT (ADR-0031 Amendment II): (1) append still assigns seq and rings every chunk, but controller handoff and viewer fan-out are SUPPRESSED during the transition — a skip, never a block or sleep (KH 7.12 intact), and the suppressed window does not count toward the controller Full-eviction deadline; (2) commit replays held segments at their emitting geometry (the geometry-epoch machinery, unchanged), lands the grid at target geometry, then UNDER THE SAME OutputLog LOCK ACQUISITION pushes viewers the size frame FIRST and then one synthesized sync frame (render_repaint at target geometry ++ deferred non-grid bytes) to EVERY attached sink, controller included, before raw fan-out resumes — no raw frame may interleave; (3) the controller sync frame carries watermark seq = highest suppressed seq, so the cursor-of-record advances past the whole suppressed range as-if-written (repaint supersedes; the repaint_initial watermark shape); WIRE-FLAG AMENDMENT (doyle ruling 2026-07-21, from the build's real-wire int finding — the watermark jump would otherwise fatal every STRICT consumer as an output gap and ladder into ControllerIrrecoverablyBehind = marked truncation on every resize under a live controller): OutputEvent gains additive `sync: bool` (serde default false, never serialized when false — the resume_seq D4-1 additive shape), set by the broker on the commit AND abort sync frames AND on every repaint_initial batch; a consumer accepts a FLAGGED FORWARD jump and baselines on it on BOTH dedup paths (the supersession is explicit and broker-authored — B2-sound, nothing silently skipped); FORWARD-ONLY, binding: a flagged frame at/below the cursor dedup-drops exactly as today, never a backward baseline; an UNFLAGGED jump keeps strict reject-gap byte-for-byte; baseline_next_output STAYS as the old-broker x new-client cold-attach compat path (retirement is an N-2 seed at most); N-1 matrix published: new broker x old client = a resize in the mixed-version window ends the attach with a marked truncation (honest termination, strictly better than the pre-fix silent corruption; release notes carry 'update CLI and broker together'), old broker x new client = no flag arrives, strict paths byte-identical (unit-pinned as the N-1 leg); REJECTED shapes recorded in ADR-0031 Amendment II so none is re-proposed: unconditional baseline-on-jump, empty-frame delivery through the window, seq rebase, cold-reattach-on-second-gap; (4) abort takes the same uniform path minus the size frame, at the old geometry; (5) the log records the presentation floor (seq after last commit/abort) and a resume-from-floor asking BELOW it is served the cold-attach shape (sync repaint + skip to live), never a raw replay across the boundary — at-least-once preserved in effect because the repaint supersedes the skipped range. NON-GRID BYTE DISPOSITION (enumerated at triage, every class ruled): tracked-by-grid state (title OSC 0/2, alt screen 47/1047/1049, DECTCEM 25, DECSTBM, pen, cursor) = SUPERSEDED by the repaint by construction, unit-pinned per class; untracked STATEFUL sequences (bracketed paste 2004, mouse 9/1000/1002/1003 + encodings 1005/1006/1015, focus 1004, DECCKM 1, DECAWM 7, keypad ESC=/ESC>, DECSCUSR, OSC color 4/10/11/12/104/110/111/112, charset designation), ONE-SHOT events (BEL, OSC 52 clipboard, OSC 9/777 notifications) and child->client QUERIES (DA1/DA2, DECRQM, XTWINOPS, OSC 10/11 '?') = DEFERRED verbatim in emission order, flushed inside the sync frame (a dropped toggle is indefinite divergence, a dropped query can hang a waiting child; a deferred one is bounded-window latency); cell-scoped decorations the grid does not carry (OSC 8 hyperlinks, DCS graphics) = DROPPED as a balanced class (content superseded by the repaint; an unbalanced deferred open would decorate unrelated post-repaint text; matches the repaint's existing fidelity boundary). DSR is in NO class: the drain strips + answers it pre-append (REQ-DSR-SINGLE-CPR); its mid-transition CPR reports the pre-transition cursor — accepted bounded residual, recorded not built-around. MECHANISM CONSTRAINT, binding: classification runs in the ONE parser authority — ScreenGrid's vte::Perform in a capture mode during held-byte replay, re-encoding unconsumed sequences from callback parameters; NEVER a second scanner over raw bytes (two parsers = two truths about sequence boundaries). SIDE EFFECT, must be recorded when built: the commit-time sync frame IS the seeded 'push a fresh repaint to transition-era attachers' follow-up — the cold-attach-during-transition residual's 'not bounded in VISIBILITY' caveat dies (the stale window now ends at commit); update the KNOWN ACCEPTED RESIDUAL text at repaint_initial and in REQ-RC-RESIZE-GEOMETRY-EPOCH accordingly. RESIZE_HOLD_CAP overflow now also means suppressed-never-delivered bytes: the sync repaint after a partial parse leaves client and grid sharing the same self-healing partial view — strictly more consistent than shipped; keep the overflow loud. BINDING CONSTRAINTS CARRIED FORWARD from the predecessor: NO whitespace special-casing, NO extra clears (one geometry authority for placement AND erasure — a fix that passes by clearing harder fails gate); fixture rules verbatim (readable prose, repeated words, every cell incl. expected-blank interiors, full-row untrimmed equality, no reflow-crossing at the resize instant). FENCED SEPARATE, do not build in this wave: the heuristic epoch-split strengthening (mark_resize_issued precedes session.resize; the 20ms-quiet/250ms-cap split is a heuristic, RESIZE_SETTLE_CAP admits old-geometry bytes may land in the new epoch) — only if the field stays red after the fanout repair. Gate: doc — ADR-0031 Amendment II states the presentation contract incl. the byte-class disposition table; impl — suppression + commit-time sync frame + watermark cursor advance + presentation floor in OutputLog/append/commit_resize/abort_resize; unit — per-class disposition pins (a title change, cursor-visibility toggle, DECSTBM change, alt-screen switch, AND a bracketed-paste/mouse toggle inside the suppressed window each surface in or after the sync frame; a hyperlink pair does not), the watermark advance (a resumed controller never re-receives a suppressed seq), the eviction-deadline exemption, the abort path, AND the wire-flag matrix (flagged-forward accepted + baselined on both dedup paths; flagged-backward dedup-dropped unchanged; unflagged-forward strict reject-gap byte-for-byte; old-broker wire without the key defaults false and a cold attach still baselines via baseline_next_output); int — the REAL live controller writer driven across a transition: an attached controller whose socket feed is parsed by an independent terminal authority at the CLIENT TARGET geometry, full untrimmed row equality every row after the sync frame, every cell incl. blanks; a viewer leg asserting size-frame-before-sync-frame and zero raw frames between commit and sync; a resume-across-resize leg (controller detaches pre-resize, resumes post-commit, receives zero mixed-geometry raw bytes and lands exact at target geometry). The cold-attach oracle is insufficient by construction and does not satisfy the int stage. #281: the daemon's RegistryHost is WRITE-ONLY against identity/registry/.json -- new_at seats an empty map and nothing in the tree ever reads the snapshot dir back, so a restarted daemon forgets every peer endpoint until gossip re-teaches it (one advertisement cadence), and inside that window the WAN user-msg origin resolver cannot find a cross-node sender Gateway-typed in the receiver's registry (REQ-MSG-6), so it silently re-stamps every cross-node user-msg down to a plain msg and the file-access helper's remote arm is dead. Fix: hydrate the in-memory registries from the on-disk snapshots AT CONSTRUCTION, through the same parse the out-of-process readers already use (presence::load_registry_snapshots), behind one named breadcrumb that says what was seated and from where. Hydration is ROWS ONLY, never the heard map: an unhydrated heard map leaves every peer unheard-this-process, so post-restart liveness must be re-earned by a probe instead of inherited from a stamp written before the restart -- the conservative direction, and the one that keeps the online claim earned. Own-node rows cannot shadow the node's own fresh advertisement: advertise_local stamps a bumped epoch from the durable counter every round and the per-(endpoint,node) lease supersedes on strictly-newer, so a hydrated own row is Updated, never a Stale-drop of the live one. Gate: impl -- hydrate at construction + the breadcrumb; unit -- a pre-seeded snapshot dir is visible through rows/instances_of before any feed is applied, an absent dir and an unparseable snapshot both hydrate to empty WITHOUT failing construction, the heard map stays empty across hydration, and a fresh local advertisement supersedes a hydrated own row for the same (endpoint,node). `spt ring --timeout` IS DENOMINATED IN MINUTES, DEFAULT 30, WITH AN EXPLICIT SUFFIX STILL ACCEPTED (releases#59, CHANGE; ruled 2026-08-02). A ring is a question put to another agent, and an agent answers on agent time -- a 60-SECOND default expires while the peer is mid-turn, so the verb that exists to WAIT for a reply routinely reported a timeout against a peer that was about to answer. A bare number therefore reads as MINUTES, and the parser ALSO accepts an explicit s or m suffix (90s, 2m) so sub-minute waits remain expressible. Ruled and accepted with the change: callers written against the old second-intent get a longer wait, which is low risk because the failure mode of over-waiting is patience and the failure mode of under-waiting was a false negative about a peer. THE UNIT MUST FOLLOW THE VALUE ALL THE WAY TO THE SURFACES THAT REPORT IT: both no-reply arms -- the timeout arm and the spooled-but-uncollected arm -- state what was ACTUALLY waited, suffix-aware, rather than a hardcoded s, or the same wait gets reported in two units by two outcomes of one command. THE PARSE LIVES IN EXACTLY ONE PLACE, taking the argument from a bare integer to a parsed duration value, because a second parse site is how two spellings of one rule are born; garbage is REFUSED rather than silently defaulted, since a value the caller typed and the tool ignored is a wait nobody chose. IN-REPO CALLERS AND DOCUMENTED EXAMPLES REPIN WITH THE SEMANTICS, and this is required text because the defect it prevents is invisible in review: an existing bare 30 written as a 30-second ceiling becomes a 30-MINUTE one, so the ring end-to-end test loses the very bound it exists to enforce and a red arm parks instead of failing. The help sentence, the argument doc, the how-to example and the generated CLI reference all state the new unit and default; the public-help gate applies to the new wording. Gate: impl -- the single suffix-aware parse, the minutes default, the duration threaded to the wait, and both no-reply arms printing the true wait; unit -- a bare number is minutes, an s suffix is seconds, an m suffix is minutes, garbage is refused, and the no-reply arms render the wait they actually took in the unit the caller expressed. `spt api seal describe ` RENDERS THE SEAL RECORD'S FIELDS — token, content hash, fully-qualified minter, mint timestamp, ceremony kind — as the read surface for citing a seal WITHOUT re-presenting its content (the verify verb's complement: describe answers 'what does this token attest', verify answers 'is it THIS content'). Unknown token refuses `SEAL_UNKNOWN_TOKEN`; malformed token refuses at the format seam, distinct (REQ-SEAL-TOKEN-FORMAT). Read-only, no auth gate, answers on any member node of the binding subnet via the replicated store (REQ-SEAL-STORE-REPLICATES-SUBNET-SCOPED). Field rendering is LINE-ORIENTED KEY:VALUE on stdout so an agent citing a seal can quote it verbatim; the describe surface NEVER renders anything that is not in the record — in particular it cannot leak subnet secret material, because the record carries none (the minter address names a subnet; naming is not revealing). Gate: doc — the CONTEXT.md entry's describe clause; impl — the api verb + rendering; unit — field rendering exact over a known record, unknown-vs-malformed refusal distinction, exit codes. #219 (operator, FIELD-SEAL W2): DESCRIBE IS THE HUMAN SURFACE AND ITS BLOCK IS NOT A PARSE TARGET. The MINTER renders with its node half NAMED where this node can name it, in the house `LABEL (prefix…)` convention (REQ-PICKER-4's never-bare-hex rule) — `SPT_DEV:lia@HFENDULEAM (14efb80c…)`; MINTED_AT renders `YYYY-MM-DD HH:MM TZ` in the HOST NODE'S OWN timezone. Both renders are DERIVED — from this node's roster and gossiped registry, and from this node's clock — so TWO NODES MAY LEGITIMATELY RENDER ONE RECORD DIFFERENTLY and that difference is NOT a defect: the machine-readable answer is the record itself, never this text, and nothing derived here reaches verification (the fido2 tuple recomposes from the STORED spellings verbatim, REQ-SEAL-FIDO2-SIG-VERIFY's exactness rider, untouched). THE NAME RESOLVE IS A PREFIX MATCH AND ANSWERS ONLY WHEN UNAMBIGUOUS: a record stores its minter's node as an 8-CHAR KEY PREFIX (`registry::key_prefix` at the mint) while both label directories key on the FULL key, so the candidates are a SET, never a first match (roster_node_hex_resolver's doctrine) — EXACTLY ONE candidate key renders its name; zero (nothing known) and two-or-more (the prefix does not identify a node) BOTH degrade to the stored hex, because a name guessed from an ambiguous prefix attributes a decision seal to a machine the operator never named. An instant the local calendar cannot place degrades to the raw milliseconds rather than inventing a time. THE LABEL DIRECTORY IS AN INJECTED SEAM (the probe/decide split): the verb loads the union of the two existing tables at its edge and hands it to a pure renderer, so every arm of the naming rule unit-tests against a fixture table instead of against whatever the build box gossiped. Gate adds: unit — the naming rule's three arms (unique names, ambiguous degrades, unknown degrades), a minter that does not parse rendered untouched, the stamp's shape and its degrade-to-raw arm. `spt api seal verify` EXTENDS TO THE SIGNATURE FOR fido2 RECORDS, ON ANY MEMBER NODE (#217 anchor 6): when the record's ceremony_kind is `fido2`, BOUND requires the content hash to match AND the signature to verify — the verifier RECOMPOSES the canonical tuple FROM THE RECORD'S OWN STORED FIELD SPELLINGS VERBATIM (the content_hash hex as stored, the fully-qualified minter as stored, minted_at as stored — no re-derivation, no respelling; ANY divergence is NOT-BOUND, never a second canonical form: doyle's exactness rider) and checks signature_hex against the pubkey looked up STRICTLY BY (node, subnet) — the record minter's node half x its binding subnet — in the replicated enrollment store, REFUSING BY NAME when no enrollment record exists there (no fallback to some record with this pubkey elsewhere: the replication scope IS the verification audience, the SEAL_NO_SHARED_SUBNET rationale — doyle ruling 3, 2026-08-24, unit-pinned). ALGORITHM DISPATCH RIDES THE ENROLLMENT RECORD'S backend_kind TOKEN: `hello-kcm-rs256` = RSASSA-PKCS1-v1_5 over SHA-256 of the tuple bytes, the pubkey parsed from the stored DER SubjectPublicKeyInfo, verified by the pure-Rust rsa crate — VERIFY-ONLY IN CORE (signature creation stays behind the authenticator seam; RUSTSEC-2023-0071 is private-key-op timing and cannot reach a verify-only consumer — the dep comment states exactly that with the advisory id, per doyle ruling 1) — and a FUTURE BACKEND ADDS A TOKEN AND A VERIFIER ARM, NEVER A RECORD MIGRATION; an unknown token refuses by name. `hello-rs256` IS RETIRED UNMINTED (#222 ruling, 2026-08-25) and gets NO verifier arm: its mint path was structurally unreachable on every box, so no record can carry it by construction (census: trust/seals.json holds totp records only, no enrollment record exists anywhere) — a record claiming that token is fabricated, and the unknown-token refusal is the honest answer to it. A fido2 record with a MISSING signature is NOT-BOUND (a self-declared fido2 mint that cannot be checked is not evidence); non-fido2 records verify exactly as before — the signature path is additive. `describe` renders the ceremony kind and the SIGNATURE PRESENCE (the fingerprint discipline anywhere a human reads key material). Exit contract unchanged: 0 IFF BOUND. Gate: doc — the published verify clause; impl — the signature arm + tuple recomposition + strict lookup + rsa verify + describe rendering; unit — valid signature BOUND, wrong signature NOT-BOUND, tuple recomposition pinned to stored spellings (a respelled minted_at diverges to NOT-BOUND), missing-enrollment refusal by name distinct from NOT-BOUND, unknown backend token refusal, fido2-with-missing-signature NOT-BOUND, non-fido2 records unaffected. A WAX SEAL IS A DURABLE RECORD BINDING CONTENT TO A MINTER: `{token, content_hash, minter, minted_at, ceremony_kind}` — the token (REQ-SEAL-TOKEN-FORMAT), the lowercase-hex SHA-256 over the EXACT BYTES presented at the ceremony (the artifact-digest convention, `sha256_hex` house style: no prefix, no truncation), the FULLY-QUALIFIED minter address `subnet:endpoint@node` (the subnet component IS the binding subnet — the record carries no separate subnet field, and the store derives scoping by parsing the minter through the spt-proto address grammar, one truth not two), the mint timestamp in epoch ms, and the ceremony kind as an open string (`totp` arrives W2; FIDO2 is fast-follow; test-seam mints name themselves honestly and never wear a ceremony they did not run). NO EXPIRY AND NO REVOCATION IN V1 (operator ruling 6, 2026-08-23): the timestamp is in the record and a reversed decision is a NEWER seal, so records are IMMUTABLE ONCE MINTED — which is what makes the replicated store a join-semilattice for free (REQ-SEAL-STORE-REPLICATES-SUBNET-SCOPED). A seal is EVIDENCE, never an authorization subject by itself (the KNOWN-HAZARDS 7.5 discipline transported): nothing grants on token possession; only a BOUND verify verdict (REQ-SEAL-VERIFY-CONTENT-BOUND) is citable. W1 lands the mint machinery behind an INTERNAL SEAM ONLY — no public mint verb, no ceremony; W2 wires the TOTP ceremony to that seam. The seam refuses EMPTY content: a seal over zero bytes proves nothing and the verify surface treats empty stdin as absent content, so admitting the mint would create a record whose verification is indistinguishable from the no-content refusal. Gate: doc — the ratified CONTEXT.md wax-seal entry carries the record shape; impl — the record struct + mint seam in spt-store; unit — roundtrip persistence, minter-parse-derived binding subnet, empty-content mint refusal. SEAL RECORDS LIVE IN SUBNET SECURITY MATERIAL AND REPLICATE SUBNET-SCOPED, so `spt api seal verify|describe` answers on ANY member node of the binding subnet. The store is `identity/trust/seals.json` beside the grant store and whitelist (one JSON document, atomic write, the grants.rs template; a new path fn in perch.rs per REQ-HAZARD-SINGLE-PATH-SOURCE). PREMISE CORRECTION, MEASURED AT INTAKE (0fcbc086): the grant store and whitelist are NODE-LOCAL AND DO NOT REPLICATE — their 'propagation seam' is aspirational prose with no wire family behind it — so 'same plumbing' is honored by riding the ONE shipped subnet-scoped replication pattern, the notif store's four-part shape: (1) MONOTONE JOIN — the apply point is insert-if-absent by token, so records commute, a replayed feed no-ops, and immutability (REQ-SEAL-RECORD) makes the store a join-semilattice with no delta bookkeeping; a same-token-different-content arrival is DROPPED WITH THE EXISTING RECORD KEPT and counted as an anomaly, never merged, because tokens are minted once and a collision is either astronomical or adversarial, and the adversarial copy is already harmless (verify recomputes the hash). (2) KIND-TAGGED WIRE RECORD — NDJSON with kind = 'seal' (the knockmsg N-1 lesson: an untagged record lands in an old daemon's WanMessage arm as a chat message; a tagged one drops cleanly). (3) STREAMFAMILY REGISTRATION — a `Seal` family arm in classify_first_line + serve dispatch, widening the census array the bijection test asserts. (4) APPLY GATE + FULL-RE-PRESENTATION EMIT + PUMP WORKER — the gate admits a row only when THIS node is a member of the record's binding subnet AND the handshake-proven origin node is on that subnet's roster (fail-closed: non-member subnet drops, untrusted origin drops — the REQ-NOTIF-1/REQ-MESH-5 posture verbatim); the emitter re-presents every record of the subnet (duplicates are free under the join); the pump worker fans per member subnet x roster peer through the existing fan_targets discipline. DEGRADE POLARITY IS OPEN-TO-EMPTY (the grants polarity, chosen deliberately): a husk or corrupt store loads empty, so verify answers unknown-token — the store failing can only LOSE the ability to prove, never mint false evidence, which is the safe direction for an evidence primitive. Binding-subnet scoping is derived from the minter address at the ONE parse helper on the record — the emitter and the gate share it, so the scope a record replicates under and the scope it is admitted under cannot diverge. Gate: doc — the CONTEXT.md entry's subnet-scoped replication clause; impl — store + wire record + family arm + gate/emit/worker; unit — join idempotence + collision-drop, gate's verdict arms (admitted, non-member-subnet dropped, untrusted-origin dropped, replay no-op), emit/decode roundtrip, husk-loads-empty. int at gate: a record minted on one member node answering describe on another. WAN-TRUTH #41 leg 2: this node OWN dialable address -- id, direct paths, and the relay it is actually homed on -- is reported by spt daemon status and its --json twin. The home relay reached no surface at all before this: the fleet homed onto iroh-canary relays while daemon.json still declared N0Default, and nothing an operator could run would contradict the config file. The value is therefore read off the LIVE endpoint (the broker net-status reply, which already carried it unconsumed), never echoed from configuration -- a line that restates config would have agreed with the wrong answer. No relay advertised is a reported state, not an absence of one, and an unparseable or unknown address shape degrades to that rather than failing the status read. Distinguish from REQ-PUMP-STAGE-TRUTH, which reports whether we can reach OTHER nodes; this reports what we advertise about OURSELVES. Gate: impl -- the relay accessor over the opaque EndpointAddr JSON, the status --json fields (self_addr, home_relay, node_id_hex, all additive), and the human status line; unit -- the accessor is proved against a REAL serialized EndpointAddr rather than a hand-written fixture (this repo existing opaque-addr fixtures spell paths as bare strings, which iroh does not emit, so a fixture-verified accessor would return no relay for every node in the field), a relay-less address reports no relay, and junk degrades without panicking. Remove the `--reply-to` TARGET FALLBACK and its REPLIED label from `spt send` — a nicety that confuses agents and has NO WIRE EFFECT (ADR-0020 already made messages structural (from,body), no __REPLY_TO__). Hard-remove (no deprecation shim): the target fallback (a bare `spt send --reply-to ` delivering with no positional), the is_reply/REPLIED label branch (always SENT/QUEUED), the `send` how-to example, and the mention in REQ-DOCS-6's send topic. Reply-correlation stays on the structural `from` attribute; `target` is a REQUIRED positional. (v0.16.0) — AMENDED WEBSERVE W2 (2026-09-07): this requirement is about the SEMANTICS, not the spelling. ADR-0061 re-uses `--reply-to` for a DIFFERENT thing — a parent message short-ID carried as an additive envelope attribute (REQ-MSG-SHORT-ID) — which leaves both killed halves dead: the flag still cannot stand in for the target, and it still stamps no label and changes no delivery. The unit cell is repinned to assert those two properties against the live flag rather than asserting the string no longer parses. The shell relay frame vocabulary is a PUBLISHED contract, not a reverse-engineered one. (flynn spt-alchemy clean-room audit 2026-07-21, doyle code-verified, seed pair item 1.) TODAY: shellchan.rs composes the exact frames a shell binary must parse — shell_command / shell_text / shell_file (+ shell_close, sensory, drive) — but the published export (docs-site/src/shells/) carries ZERO occurrences of those type names; the docs say only 'the shell child parses its own vocabulary', so every adapter author (notify-shell, alchemy) reverse-engineers the frame shapes compatibly from source. FIX: publish, in the shells section of the docs-site export, the frame type names + their attrs (op, xfer-id, path) + body encodings — a shell_command body is a JSON object of named args (positionals zipped against the manifest's declared arg names), a shell_text body is the raw text, a shell_file body is the original filename with the perch-relative landed path in the path attr, a shell_close body is the manifest's pre_close instruction (NOT vocabulary-checked — the vocabulary gates agent commands, the manifest is its own authority over its own binary). Fold in the quoted-composite-tail sharp edge: the vocabulary check REFUSES extra positionals (an op given more args than its declared arity errs, so a quoted composite tail must be ONE argv element). Docs-drift-gated like every published page. Gate: doc — the shells export carries the full frame vocabulary (type names, attrs, body encodings, shell_close) + the files/ landing contract (published together with REQ-SHELL-PERCH-DIR's key so the path attr is resolvable the day it is documented) + the arity sharp edge. A SHELL ADAPTER'S [[hints]] ARE LIVE TEXT, READ THROUGH THE NOW-SIGNAL HINTS CATEGORY UNDER A PER-SOURCE CAP (releases#133, operator-authored and operator-GREENLIT into NOW-SIGNAL #23; the cap shape ruled by the gater 2026-08-30). THE SCHEMA WAS NEVER THE GAP: hints is a top-level manifest field with no kind gate, so a kind=shell manifest has always parsed, validated, registered and profile-merged its [[hints]] -- what was missing is a READER, because the only reader resolves the ENDPOINT-BOUND manifest and therefore never looked at any other adapter. THE ONE-HINT-PER-MESSAGE CLAUSE (CONTEXT.md keyword hints, ratified 2026-06-12) IS AMENDED BY REPLACEMENT TO A PER-SOURCE CAP: the harness contributes at most one line and EACH shell adapter contributes at most one line, each still once per session. The authority chain is explicit: the clause was ratified when exactly ONE manifest could speak, its ground was flood protection from a single adapter's chatter, and #133 cannot be delivered under a global cap at all -- so the amendment implements the newer operator decision while preserving the original ground per source, where a global cap would convert every second adapter into a structurally mute one that the ground never asked for. TWO ARMS, DECIDED BY INSTANTIATION AND NEVER BY LINK STATE: an adapter this owner holds an instance of surfaces the FULL hint text regardless of whether that instance is online or offline, and an adapter with no instance surfaces only a teaser naming the trigger keyword and the command that shows the text. THE TEASER'S COMMAND MUST EXIST AT THE HEAD THAT SHIPS IT -- the same class as the SEAL_BRIEF phantom verb, and the shipped surface is spt adapter hints , verified against the built binary rather than remembered. THE SEEN-SET KEY GAINS ADAPTER IDENTITY, because the existing key hashes only keywords, text and the regex flag, so two adapters shipping identical hint text would otherwise share one entry and the first to fire would silence the other invisibly for the session. THE HARNESS KEY SPELLING IS UNCHANGED: qualification is additive and shell-side only, so no session's seen-set forgets what it has already been told when this lands. The shell key also carries its ARM, so a shell that is instantiated after its teaser fired still delivers the full text once -- a teaser tells the agent the hint exists and acting on it must not be punished by silence. ABSENCE IS SILENCE: no shells, no matching keyword, an unreadable or deregistered adapter, or an endpoint bound to no harness manifest at all each contribute NOTHING, and in particular the shell arm is NOT gated on the harness manifest being present, because an endpoint with no bound manifest can still own shells. THE DOC STAGE LANDS WHERE A SHELL-ADAPTER AUTHOR LOOKS (docs-site shells) rather than only in the ratification text, because a manifest section no author can find is a section that reads as absent. A RECORDED PID IS NOT KILL AUTHORITY ON THE PRODUCT PATH EITHER: no shell-lifecycle kill fires until the pid+birth PAIR proves the process at that pid is still the one we launched. BAROMETER W2 authenticated the DERIVATION path (`shellinfo::shell_pid_provably_dead` runs the pair test) and left the KILL path believing a bare pid, so the same recycled-pid state that the derived read correctly calls a corpse still routes to `taskkill /PID /T /F` (unix `kill -9`) against whatever unrelated process now wears that number -- and `/T` takes its whole subtree with it, on the user's machine. Proven reachable by probe, not inferred: a live stranger's pid parked beside a foreign birth stamp is killed by `kill_shell_at` while `shell_pid_provably_dead` calls the same pair a corpse. SITE SHAPE IS TWO CHOKEPOINTS, NOT THREE CALL SITES: `shellhost::kill_shell_at` (reached by `close_shell` and the `spt shell teardown` force path) and `shellwake::kill_waker_at` (reached by `launch_shell`, `close_shell`, `stop_watcher`, and the reconciler), both funnelling into the single `kill_shell_pid` taskkill; the gate therefore lands in the two chokepoints and NO caller changes. THE WAKER HALF NEEDED A WRITE-SIDE MINT BEFORE ANY GATE COULD MEAN ANYTHING: `waker.pid` was written bare, and `relay_liveness(live_pid, None)` is HELD by design (pre-stamp records fail toward alive), so a read-side gate there would have returned Held for every record that exists today and every one written tomorrow -- a VACUOUS gate that proves green. The pair is therefore minted in the waker's single existing write as a two-line `waker.pid` (`\n`): ONE file, ONE write, so the pair is atomic -- a second file has a torn window in which the pid is on disk and the stamp is not, and that window reads Held, which is the vacuous arm reintroduced as a race. Write and every reader move through ONE shared record/read function pair, mirroring `record_shell_launch`/`read_shell_launch`. FAIL-TOWARD-ALIVE IS THE PARSE ARM TOO: an unparseable record reads None and REFUSES the kill while LEAVING the record, because kill-nothing-and-retire-the-pid-file-anyway orphans a live waker while presenting success -- worse than the defect being fixed. UNPROVEN REFUSES: the destructive arm treats unknown as do-not-fire (consequence asymmetry -- a mis-fire force-kills a stranger's tree, a missed kill leaves our own binary running and observable), which FALSIFIES the shipped unconditional promise that a link-break's force-close always kills and "the binary never survives", so `close_shell`'s step 3, the REQ-SHELL-2 language, the CONTEXT lifecycle line and KNOWN-HAZARDS 7.58 are amended together with the code rather than one comment patched. THE REFUSAL IS LOUD: every declined kill names the reason and the pid on the channel the close path already speaks, and the bounded confirm loop reports a binary that outlived it -- a quiet wrong state is the un-RCA-able one, and the harm-asymmetry argument has to hold in the log and not only in principle. THIS NARROWS THE MIS-FIRE, IT DOES NOT ELIMINATE IT, AND THE TEXT SAYS SO: on Linux `process_started_at` reads /proc//stat field 22, which counts CLK_TCK=100 jiffies, so two processes whose starts fall in the SAME 10ms tick carry an identical stamp and the pair cannot separate them. The window closes from ANY recycled pid to A PID RECYCLED INTO THE SAME 10ms TICK AS THE RECORDED START -- which requires our process to die AND the OS to hand its number to a new process inside one tick, a precondition that is its own mitigation. Windows is unaffected (FILETIME, far finer resolution). Measured by hertz on kitsubito; recorded verbatim so nobody re-derives it. THE ASYMMETRY IS WHY THIS IS THE KILL PATH'S SENTENCE TO WRITE: the same predicate with the same imprecision is SAFE on the derivation side and UNSAFE here -- Held means 'stays stale' for a derived read and 'fire the kill' for a destructive one, so only the kill side carries the residual harm and only the kill side has to declare it. Image-path corroboration (which would unify this rule with the rig-side technique in KNOWN-HAZARDS 7.58) is filed past as releases#120, deliberately not taken in a closing milestone. MIGRATION WINDOW, NAMED NOT SILENT: pre-W2 shell records and pre-fix waker records carry no stamp, read Held on existence alone, and keep today's bare-pid kill until their next launch writes a pair. DOWNGRADE DIRECTION, NAMED: an OLD binary reading the NEW two-line `waker.pid` parse-fails to None, which reads as no-pid and refuses the kill -- missed-kill, not mis-fire, the safe polarity. Gate: impl -- the pair gate in both chokepoints, the shared waker record/read pair with the two-line format, every waker.pid reader moved onto it, the loud refusal arms, and the confirm-loop survivor report; unit -- a recycled pid (live, birth mismatch) is NOT killed where the shipped code kills it, a genuine own-process pair (Held) IS still killed so the gate is not vacuous, an absent stamp keeps the pre-stamp kill, Unproven refuses, an unparseable waker record refuses AND leaves the record in place (asserted together, since retire-anyway passes a kill-count assertion alone), and the refusal is observable rather than silent; int -- hertz's probe inverted: a live stranger parked beside a foreign stamp SURVIVES `kill_shell_at` while `shell_pid_provably_dead` still calls the pair a corpse (the control), with the victim checked through `try_wait` on the held handle rather than a pid-existence probe, which reads alive for an already-killed process while the handle is open. Per-machine re-pair trust overwrite: registry rows carry a hashed stable machine identifier (OS machine id /etc/machine-id|MachineGuid, domain-separated SHA-256 before gossip, spt-minted persisted UUID fallback; additive serde-default field — old rows parse clean); a COMPLETED pairing ceremony presenting the same node label AND machine id as an existing trusted row evicts the superseded identity's trust + registry rows on the seed-holder and replicates the eviction; a gossiped claim alone never evicts trust (M8 decisions 13, 22) THE ENDPOINT LIFECYCLE READS AS VERBS, AND `endpoint run` RETIRES WITHOUT A SHIM. One overloaded verb carried the whole lifecycle: `spt endpoint run` minted an endpoint, started a new session on an existing one, resumed a prior session, opened the interactive picker, and (with `--save`) set a startup default — the invocation's MEANING sat in which of nine flags were present, so the CLI could not be read and the picker's doors could not be named. The ratified surface (releases#5 bag grill, 2026-08-04) is: `endpoint create [--subnet S] [--adapter A] [--cwd DIR]` = the ONLY mint, subnet immutable after it; `endpoint start [--adapter A] [--cwd DIR]` = a NEW session on the endpoint's most-recent adapter in its most-recent project folder, NEVER the env cwd, and an UNKNOWN id REFUSES pointing at `create` so a typo cannot mint a phantom endpoint; `endpoint resume ` = its LATEST session; `endpoint auto-start [--off]` = the standalone lever that was `run --save`; and top-level `go ` = the operator's take-me-to-this-endpoint ladder (online+uncontrolled -> rc; online+controlled -> interactive kick confirm over `rc --take`; suspended -> wake then rc; offline WITH sessions -> resume then rc; offline WITHOUT sessions -> mint the first session then rc; engine-room -> defers to the gated `rc engine-room` path). `go`'s offline discriminant reads the SESSION LEDGER (a non-provisional row) and NOT the record's `session_id`, which persists by design after a clean stop as the CAS identity anchor and is never a liveness claim. RETIREMENT IS A CLEAN BREAK: `endpoint run` is removed through a RAW-ARGV PRE-SCAN ahead of `Cli::command()` so its nine args never enter the derive tree, and it answers with a parse error naming the replacement verb — no hidden variant, no silent alias. TWO CAPABILITY NARROWINGS ARE DELIBERATE AND MUST NOT BE RE-ADDED QUIETLY: specific-session resume (`run --resume `) retires with the verb and has no replacement, `resume` being latest-only; and the picker's two argv prefill quadrants (`--adapter`-only pre-select, `--id`-only create-new prefill) retire with their verb, the id-only case's replacement being `start `'s refusal. `create` MINTS ONLY — skeleton perch, permanent home, recorded adapter/cwd defaults — and does NOT start a session; the first session is `start` (or `go`, which also attaches), so a fresh bringup is TWO commands where `run --start` was one. That is a shape change to every bringup script rather than a lost capability, and it rides the release notes beside the resume narrowing. It was decided rather than read off the table (todlando, ratified by doyle 2026-08-04) on the ladder's own evidence: a first-class offline-WITHOUT-sessions rung is near-unreachable unless zero-session endpoints are routine, so create cannot be starting one. The picker's own door is BARE `spt` on an interactive terminal and this requirement does not touch it, nor does it add a `pick` verb. Generated launcher shortcuts bake the retired argv, so the generated body becomes `spt go ` (the launcher's intent IS `go`'s ladder, and it opens a console so the kick confirm has its TTY), the shortcut SENTINEL is bumped so a stale launcher is detectable rather than merely broken, and the refusal text names BOTH the new verb and shortcut regeneration. REGISTRY SUPERSESSION: this requirement is the authority on the bringup SPELLING, and it supersedes every earlier title in this file that names `endpoint run`. Those were swept where they state a CURRENT contract or a CURRENT gate's invocation; where they narrate a PAST failure or a past field observation they are left verbatim, because a failure record rewritten into a spelling that did not exist when it happened stops being a record. Arg budget: the ratified surface is the MIN spelling at 11 leaf args against the retired 9, a net +2 that is only affordable because the pre-scan keeps the retired args out of the tree — and it rides on REQ-CLI-STACK-HEADROOM having deleted the ceiling rather than budgeted under it. AN ATTACHMENT IS A SNAPSHOT REGISTERED FOR SERVING AND THE MESSAGE CARRIES ONLY ITS URL (ADR-0058, releases#272/#246). `spt send --attachment ` COPIES THE BYTES AT SEND TIME into `$SPT_HOME/serve/snapshots/` and registers an entry of kind attachment, because a message's attachment is as immutable as the message -- this is the ONE place the serving registry does NOT resolve at request time, and it is exactly what distinguishes an attachment from a `serve add` file entry: an edit after the send must NOT change what the receiver pulls, and deleting the source must NOT turn the link into a 404. NOTHING IS PUSHED TO THE RECEIVER. The envelope grows an ADDITIVE `attachments` key -- a list of {name, url, bytes} whose url is the node-prefixed `//f/` (ADR-0056) and whose bytes is the snapshot size, so a receiver can decide BEFORE it pulls. The key is additive because a receiver on an older core must ignore it and deliver the message anyway; nothing on the delivery path may deny unknown fields, and that tolerance is itself under test. LIFETIME IS A TTL, DEFAULT 30 DAYS (operator-set), overridable per send with `--ttl `: the entry records its expiry and the daemon's EXISTING pulse reaps what has expired, deleting the snapshot file AND its registry entry TOGETHER so a reaped attachment answers not-found rather than serving a hole, and LOGGING THE REAP COUNT, because a silent reaper is indistinguishable from a reaper that never ran. The served name obeys REQ-WEB-SERVING-REGISTRY's disambiguation rule UNCHANGED, so two sends of the same basename get stable distinct URLs and neither link ever changes meaning. Several `--attachment` flags on one send are allowed and each mints its own entry. A MISSING OR UNREADABLE PATH REFUSES THE SEND BY NAME rather than delivering a message whose link is dead on arrival. Gate: doc -- the serving attachments page; impl -- the snapshot store, the attachment entry kind, the send-path registration, the envelope key, the pulse reaper; unit -- snapshot immutability against a post-send edit and against a post-send delete, the additive key round-tripping with an unknown-key receiver tolerating it, the ttl default and the `--ttl` parse, the reaper removing file and entry together and counting what it removed, the refusal on a missing path; int -- a real send whose attachment is fetched back byte-equal through the server. `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. The `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state; whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9) and renders the same description-carrying SELF pin. HISTORY: originally minted whoami as a thin ALIAS of `spt endpoint list` — that alias premise is SUPERSEDED by REQ-WHOAMI-IDENTITY-ONLY (PROJECT-INDEX W1, 2026-07-15): the alias inherited the list's O(perches x branches) git fanout onto hook paths (the 2026-07-15 message-delivery incident), so whoami is now identity-only over the shared render_self_pin. The pin render + parse evidence here stands; the full-roster surface lives solely on `endpoint list`. RULED DESIGN, delivery unowned (doyle 2026-07-26): when a caller hands identity resolution an EXPLICIT non-empty $OWL_SESSION_ID that resolves to NO perch, core must REFUSE identity (unresolved, exit 1, loud distinct diagnostic) rather than fall through to an ambient/inherited one — today `detect_self_id` (roster.rs, legs a→b→b2→c) treats sid-UNMATCHED identically to sid-ABSENT, so the fallback chain re-adopts precisely the identity a sharper claim just failed to prove. MEASURED (perri, this node, 2026-07-26, three read-only whoami calls from a genuine descendant of the perri host process): (1) all SPT_*/OWL_* scrubbed → id null, exit 1 — ancestry resolved nothing (caveat honored from the probe: the perch's recorded pid was not in the caller's chain, so this run refutes lineage-as-the-mechanism for probe v1 without disproving a lineage path in general); (2) inherited SPT_ENDPOINT_ID=perri + explicit OWL_SESSION_ID matching no perch → perri, exit 0 — the mismatch datum was IN HAND (core had already scanned and failed to match the explicit sid) and the ambient id won anyway; (3) real OWL_SESSION_ID with endpoint id scrubbed → correct self — the healthy path any fix must leave untouched. SCOPE OF THE REFUSAL, ruled: only sid-PRESENT-AND-UNMATCHED poisons the fallback, and it poisons ALL weaker legs (b SPT_AGENT_ID, b2 SPT_ENDPOINT_ID, c pid-ancestry) — an explicit failed claim outranks every ambient claim below it; sid-ABSENT/empty keeps today's full chain unchanged, because the leg-b2 field root (live-repro'd 2026-07-10: the adapter surfaces OWL_SESSION_ID to the session shell as an UNEXPORTED var, so the child process legitimately carries endpoint id without sid) is exactly the flow the guard must not break — that flow is sid-absent, never sid-mismatched. WHY CORE AND NOT ONLY THE ADAPTER: the measured entry path is closed adapter-side (perri's REQ-HAZARD-INHERITED-IDENTITY-ADOPTION @ their a7558aa + claude-spt KNOWN-HAZARDS 7.4, shipped: whoami child calls scrub SPT_ENDPOINT_ID/SPT_AGENT_ID; rig discipline now detached AND env-scrubbed — the scrub is the operative half), but the inconsistency being fixed is CORE'S: the adapter fastpath's verified_env_id REFUSES an inherited SPT_ENDPOINT_ID on carrier-proof mismatch (carrier sid != payload sid) and core's fallback then RE-GRANTS what that layer just refused — one layer's refusal must not be another layer's grant, and every other harness/adapter gets the defense only if core holds it. SEVERITY UPGRADE recorded at mint: unlike the KH 7.1–7.3 shapes (lost reads), this adoption was WRITE-CAPABLE in the field — the adopting descendant re-pointed the ANCESTOR's session pin, so the ancestor went dark while the descendant looked healthy (perri's own pin, probe v1). KIN, same review same milestone: leg (b) SPT_AGENT_ID returns UNCONDITIONALLY today — not even perch-checked, weaker than leg b2's bound-perch gate; align it when this lands. MEASURED on the live node, not just code-read (perri, same probe run, reported 2026-07-26): SPT_AGENT_ID=nobody-xyz with a bogus OWL_SESSION_ID returned {'id':'nobody-xyz','ready':false,'alive':true,'unbound':false}, exit 0 — a phantom identity for an endpoint that does not exist, beating both the sid leg and ancestry; the returned shape has no state key and ready:false but a populated id, and the adapter parser takes .id first, so a whoami-trusting adapter writes state under the phantom — the same write-capable class as the inherited-adoption case, sourced from a made-up name instead of a real ancestor. perri's adapter scrub covers SPT_AGENT_ID as well as SPT_ENDPOINT_ID for exactly this reason. POSTURE UNCHANGED: whoami legs remain from-label/routing only, never authentication (KH 7.3/7.5, F-024 stays parked) — refusal tightens label discipline, it promotes nothing to auth. Gate at activation: unit — sid-unmatched + ambient endpoint id present → refusal with the distinct diagnostic (probe shape 2 goes loud); sid-absent + ambient endpoint id on a bound perch → still resolves (leg-b2 field root preserved); sid-matched → unchanged (probe shape 3). [must] missing_stage REQ-DELIVERY-WINDOW-IS-ACTIVITY-AXIS-ONLY stage=int — Requirement is missing integration test evidence [must] missing_stage REQ-SEND-BUSY-ONLY-FLAG stage=doc — Requirement is missing documentation evidence note: 5 tag-carrying file(s) not placement-judged (no grammar for the language, or the parse recovered from errors) TREQS=1