# ACCESS-CONTROL-JIT — milestone A (releases#20)

Board: releases#20 (GREENLIT→WIP) ← #15 (ACM core), #9 (trust warning), #10 (knocking), #13 (monic store half).
Design of record: CONTEXT.md glossary (shared subnet, control surface, modes, member/admin key, engine-room, empower, knock, trust warning, mnemonics) + ADR-0051/0052/0053 — all on main @ffc7e9e.
**Golden CI pilot milestone (ADR-0050):** builder PRs run the thin lane only (lint, unit, traceability); the full suite runs once on the gater-assembled golden branch; main advances ff-only to the tested sha. Old per-PR merge choreography retires on this milestone's first green fast-forward.

## Wave map (JIT: W1 concrete, later waves sketched, re-planned at each gate)

### W0 — golden-CI lane bundle (hertz, parallel lane, no W-dependency)
The ADR-0050 rollout vehicle: thin-lane workflow split, **flake registry** (checked-in, test-infra-owned, reviewed like code) seeded with the known nondeterministic set — candidates from the 2026-07-29 main red (`zombie_claim_refuses_never_attaches`, `activity_frames_reach_a_linked_shell_through_the_real_daemon`) IF the at-sha rerun greens them (evidence + retire condition per entry), plus hertz's previously-seeded concurrency-exemption + workflow_dispatch fixes (absorbed per the 2026-07-27 ruling).

### W1 — ACL core rewrite (todlando, CONCRETE — dispatch now)
REQ mints ride the W1 build PR (registry-mints-ride-build-PRs): `REQ-ACL-SURFACE-VOCAB`, `REQ-ACL-SUBJECT-CHAIN`, `REQ-ACL-FAIL-CLOSED`, `REQ-ACL-RC-VIEW-SPLIT`, `REQ-ACL-DISCOVER-GATE` — doc/impl/unit stages active W1; int activates at gate.

- **T1 surface vocabulary.** CONSTANT_CASE open string ids. `access_check(endpoint, origin, class)` grows a `surface` parameter; the six existing call-site families tag themselves: `MSG` (wan.rs), `RC_ATTACH` (attach.rs), `XFER` (xfer.rs), `WAKE`/`SUSPEND` (resthost.rs — split by rest event), `SHELL_LINK` (linkhost.rs), `DIGEST` (digestlink.rs). Unknown surface strings are legal in rules (later waves mint ids).
- **T2 store schema v2** (`spt-store::access`). Target-endpoint-scope entries with subject kinds {sender-endpoint, node, subnet-wildcard}; **node-scope entries** {node, subnet-wildcard}; optional per-endpoint + per-node **mode** records (open|closed per surface). Migration: existing `AccessEntry.nodes` rows → per-endpoint node entries preserving semantics; the inert `users` field drops (no-user-identity ruling). Captured-subnet-mode table = schema stub only (populated by W2's join capture; absent ⇒ chain bottoms out **open**, preserving current fleet behavior).
- **T3 resolution chain.** First-match: per-E sender-endpoint → per-E node → per-E subnet-wildcard → per-node node → per-node subnet-wildcard → E mode → node mode → captured subnet mode → (absent) open. Reply exemption and same-node loopback Allow PRECEDE the chain, unchanged.
  **Tier-1 ruled INERT in W1 (doyle 2026-07-29, todlando finding):** `from` is caller-supplied reply metadata (hazard 7.5 / REQ-HAZARD-WAN-ORIGIN-AUTH — survives verbatim, untouched) and no authenticated sender stamp exists; 5 of 6 surface families carry no sender endpoint on the wire at all. Tier-1 stays schema-real, fed `None`, never matches — same treatment as the captured-subnet-mode stub. W1 adds: (i) a unit test pinning inertness (entry with sender-endpoint subject + inbound with matching forged `from` ⇒ no tier-1 match, chain falls through — the anti-wire-`from`-in-later guard), (ii) CONTEXT.md §endpoint-access-whitelist premise correction (the "sender identity is daemon-stamped" sentence is false today — reword to: node identity handshake-proven ⇒ node-tier sound now; sender-endpoint tier awaits the authenticated sender stamp, own wave). Rides the W1 PR (same-change doc discipline).
- **T4 fail-closed degrade (ADR-0053).** Corrupt/missing store → refuse unsolicited + loud per-refusal diagnostic naming the store path. Reply exemption survives (correlation state, separate file). Existing fail-open tests repinned to the new contract (product change, todlando — the repin is part of this diff, not a hertz fixup).
  **MINT-ON-FIRST-LOAD ruled (doyle 2026-07-29, resolves todlando's upgrade-brick flag):** load path on NotFound WRITES an empty v2 store (atomic temp+rename) and proceeds unconfigured-open per chain bottom; any later NotFound (post-mint vanish) degrades CLOSED per ADR letter. ADR-0053 amendment paragraph rides the W1 PR. W1-accepted limits: (a) newly-DENIED row is withheld from next advertisement, not retracted (matches visibility-hiding precedent; real retraction = one later wave fixing both — seed banked); (b) DISCOVER-denied + surface-allowed = unresolvable-but-reachable, intended (one-sentence CONTEXT.md resource-advertisement note in the PR).
- **T5 RC_VIEW split + DISCOVER gate.** attach.rs keys the surface on `AttachIntent` (Viewer → `RC_VIEW`, Control/Take → `RC_ATTACH`). `DISCOVER` gates registry resolve/advertise viewer-side + filters the `resources` blurb per viewer (the ADR-0009 named-but-unbuilt leg).
- **Gate (doyle):** isolated worktree + throwaway target; access/visibility/grants units; ALL six family seam suites (shared-seam rule); `endpoint_lifecycle`, `digest_cross_node`, `activity_link_push_e2e` int via nextest; clippy --workspace preflight; `traceable-reqs check`. Cross-platform leg on kitsubito.

### W2 — two-key subnet + mode capture (sketch)
ADR-0051: dual-seed mint at `subnet create` (prompt open/closed, no preselection, `--open`/`--closed`), admin-code join path (SPAKE2 responder computes either code), both-seed replication at join, NO admin reveal verb anywhere. Subnet-mode record + **join-time capture** into W1's stub table; advisory gossip on mode change (notif producer); `api access-refresh` verb (ER-only enforcement arrives W3; until then the verb refuses).

### W2b — authenticated sender stamp (sketch; NEW 2026-07-29, from todlando's W1 tier-1 finding)
Daemon-stamped authenticated sender-endpoint identity on the wire: a NEW additive serde field (ADR-0001-safe), NEVER repurposing `from` (hazard 7.5 invariant survives; adapters legitimately pass `--from`). Stamp source = session-proven self id (REQ-MSG-5 `classify_local_origin` precedent — perch info.json, never `--from`). Feeds chain tier-1 (flips it live), MSG surface first; other surfaces mint sender fields only when their records grow one. Mints own REQ (`REQ-WAN-SENDER-STAMP` or similar) + likely a short ADR ruling the separate-field shape. Sequencing vs W2 decided at W1 gate re-plan; wire/identity change ⇒ own gate, never rides an ACL diff.

### W3 — engine-room + empower (DISPATCHED 2026-07-29, doyle ruled (a)–(f); worktree `.worktrees/engine-room`, branch `build/engine-room` off `build/two-key-subnet` @ecbf582)

**Rulings in force (doyle 2026-07-29) — these override the recon recommendations below where they differ:**
- **(a) Creation = the reserved-id path, no new create verb.** One reserved per-node id, structural not policed. **Creation IS the reset ceremony run against an empty record** — one code path, unit-provable. Create/reset gated by **OS elevation** (it sets home subnet + adapter = node-scope, and it closes the bootstrap hole where no subnet material exists to TOTP against). Bring-up stays TOTP (§2) — two gates, different questions, neither substitutes. Adapter bound by explicit argument at the ceremony, stored in the ER record; **bring-up fails closed if the bound adapter is missing** (ADR-0053 spirit).
- **(b) `empower` = `spt api` verb, `Proof`-gated + ER-identity check.** The gate is not "no agent" but "no agent EXCEPT the reserved ER" (the ER has a mind and is the invoker). Admin TOTP stays the authority proof; ER-binding is what gives the grant a controller lifecycle to die with. **ONE shared auth fn, two call sites** (`empower` + `access-refresh`), unit it once.
- **(c) §3a amendment adopted** (draft in scratchpad `adr-0052-amendment-draft.md`) with two edits: keep loud displacement (`displaced_envelope`) as a **second** named ground — an incumbent human cannot be silently unseated; and append to the consequence block: the bring-up gate's local attempt bound applies identically to a take's gate pass — **a take attempt is a gate attempt**.
- **(d) Local attempt bound = ONE shared failure ledger + exponential backoff + loudness.** A single persisted counter covers BOTH secrets, so two acceptable secrets no longer double the budget (the budget is the ledger's, not the keyring's). Backoff 1s doubling to a 1h cap; **attempts during backoff are refused WITHOUT counting** (an agent must not be able to ratchet the human into lockout — denial-of-governance); reset on success; **persisted across daemon restarts** (a bounce must not clear it — the W-SPEC per-process-mint defect class). **Loud:** notif on every failure past the 3rd. The ADR states this local bound is ledger+backoff, explicitly distinct from ADR-0051 §1a's network rate-limit argument, which does not apply on the local path. Pure `GateOutcome` classifier carries it.
- **(e) Briefing = system-authored message spooled at bring-up.** Role is durable identity, posture is per-session state — role-text mutation would race the role editor and pollute the Psyche. Content: capabilities + responsibilities + exact current posture + pending advisory-gossip deltas; ruleset tables ride the same message.
- **(f) Knock exemption hook lands in W3, dormant** — it is part of the inbound lock's shape; the seam owner lands the seam once. W3's hook default-refuses + a test pins that everything except the reply exemption is refused today; W4 fills it.
- **REQ mints:** all 12 approved, doc/impl/unit active at W3, int at gate, minted in the build PR.
- **Gate:** as written below, plus the standing stop-condition — any timing singleton in the heavy class during the battery, stop after the phase and report before rerun.

#### Recon (2026-07-29 todlando — what the rulings above were ruled on)
ADR-0052: reserved per-node endpoint, TOTP bring-up gate (member-or-admin), structural locks (inbound reply-only + knocks, controller-attached-only lifecycle, no rc view, local take = restart + revoke), purge-reset under elevation, `empower` session grants, session-start briefing message, ruleset-table rendering, Esc-cancel on every ceremony overlay (releases#19 pattern lands with the seal overlay in milestone B; the ER TOTP prompt gets it here).

**Base:** stacks on `build/two-key-subnet` (W2 replicated both seeds; W2b's proven sender stamp is the local-caller-identity precedent). Thin lane only.

#### Recon findings that change the wave (read before planning tasks)

1. **ADR-0052 §3 "online only while a controller is attached — detach drops it offline" collides with the attach-lifecycle hazard** (`docs/KNOWN-HAZARDS.md` §7.10 job-object entry: "the `rc` pump detaching should end ONLY the viewport; the harness must keep running + stay re-attachable") and with `harnesshost.rs`'s doc-invariant ("the harness survives a `spt rc` detach — PTY ownership stays with the broker, no termination on detach … satisfied by construction"). The reconcilable reading — and the one W3 must build — is **access-posture offline**: detach refuses all inbound + drops every empowerment + de-advertises; it does NOT tear the harness process down. Building the literal reading re-breaks a paid hazard.
2. **"local `rc --take` … forces a harness restart and revokes all empowerments" is false against current code.** `Take` is a *broker lease displacement* (`broker.rs` `resolve_subscribe` → `SubscribeOutcome::TookControl`, loud `displaced_envelope`); no harness restart anywhere. Revocation must be an **explicit** empowerment-drop on take/detach, never assumed from a restart. Same family as W1's tier-1 finding (ADR premise vs code) — needs an ADR-0052 amendment paragraph riding the W3 PR.
3. **`empower` as a bare CLI verb is agent-invocable** — the exact thing ADR-0052's context section rules out. It needs the same gate as the ER-only api verb: caller authenticated as the reserved ER endpoint. Recommended shape: `empower` is an **`spt api` verb** (auth by `api::auth::Proof` = perch capability token + session id matching the perch's info.json), not a `Cmd::` verb. Doyle rules.
4. **Member-or-admin acceptance at the bring-up gate re-instantiates ADR-0051 §1a's two-acceptable-secrets shape** — a 2× online-guess budget, this time against a **local CLI** attacker (a confused/adversarial agent on the box), where the pairing rate limiter (`spt-net::net::pairing::ratelimit`) is not in the path. W3 must state its own attempt bound; carrying §1a's "rate-limited, not count-limited" argument across unexamined would be unsound (local attacker, no network round trip).
5. **No engine-room / empower REQs exist yet** — `traceable-reqs.toml` has zero `ENGINE-ROOM`/`EMPOWER` matches. W3 mints its own set in the build PR (registry-mints-ride-build-PRs).

#### Task breakdown (seams named)

- **T1 reserved ER identity + purge-reset.** Reserved per-node endpoint id + record (`spt-store::perch`, `info.rs`); creation/reset the only way to set home subnet + harness adapter. `cmd_endpoint_purge` (`cli.rs` ~13820: `PurgeOutcome`/`PurgeReport`) grows an ER arm: **requires elevation** (`spt::elevation`) and yields `ResetEngineRoom`, never `Purged`. Unit: purge decision table incl. non-elevated refusal.
- **T2 bring-up TOTP gate.** Same-node CLI call + member-or-admin TOTP verified locally against `SubnetStore::seed_bytes` / `admin_seed_bytes` (both replicated at join, W2 T3/T4) via `totp::code_at` + `accepts_step` window. Pure classifier fn (`GateOutcome`) separate from I/O so the invariant is unit-provable. Carries finding 4's attempt bound. No OS elevation in this path (deliberate — ADR-0052 §2).
- **T3 controller-bound posture + rc intent locks.** `attach.rs::attach_surface` already splits `RC_VIEW`/`RC_ATTACH` (W1 T5) — ER denies `RC_VIEW` **even same-node** (the loopback Allow precedes the chain today, so this is an explicit pre-chain ER carve-out, not a rule row), denies remote attach, allows local `Take`. Empowerment state = session-scoped (`grants.rs` precedent), dropped on detach AND on take (finding 2). Posture-offline on detach per finding 1.
- **T4 inbound lock.** Refuse all inbound except replies to own outbound; the stateful-firewall reply exemption already precedes the resolution chain (W1 T3), so the lock rides that seam in `wan.rs`. Knock/knock-code acceptance is W4's surface — W3 lands the exemption **hook** and a test pinning that today's lock refuses everything else.
- **T5 advertisement lock.** `registryhost.rs::advertise_local` + `spt-store::visibility` — ER not advertised by default; whitelist-only advertisement. Rides W1's `DISCOVER` gate.
- **T6 `empower <subnet-id> --admin-code <totp>`.** ER-authenticated (finding 3), verifies the admin TOTP locally, grants session-scoped authority over that subnet's control-surface modes. Refuses for a subnet the node is not a member of.
- **T7 node-mode set + `access-refresh` flip.** `spt-store::access` mode records (W1 T2 schema) become writable through the ER only; `api/mod.rs::cmd_access_refresh` (W2 T6, currently `EXIT_REFUSED` / `ACCESS_REFRESH_UNAVAILABLE`) becomes real — ER-authenticated, updates **only** captured subnet-level fallbacks, never the node's own rules. Repin the W2 refusal test to the new contract in the same diff.
- **T8 briefing + ruleset tables.** Session-start briefing message stating capabilities/responsibilities + the exact current posture (incl. pending advisory-gossip deltas from W2 T5's notif producer). ⚠ mechanism is a **boundary question**: core never caters to a harness (BINDING), so the briefing must be core-side data an adapter renders — candidates: reserved role text (`cli.rs::cmd_role` / `live_role_path`) vs a system-authored message spooled at bring-up. Recommend the spooled message (posture changes per session; role is durable identity). Ruleset table rendering ships with it. Esc-cancel on the TOTP overlay (releases#19).
- **Gate (doyle):** isolated worktree + throwaway target (**disposal leg last**); units per task; int — `endpoint_lifecycle`, registry advertisement, api auth; cross-platform leg on kitsubito; `clippy --workspace` preflight; `xtask check` (CLI docs drift — new verbs ⇒ public `--help` text, and internal ADR/REQ codes must stay out of `///`); `traceable-reqs check`.

#### REQ mints (W3 build PR)
`REQ-ER-RESERVED-ENDPOINT`, `REQ-ER-PURGE-RESETS`, `REQ-ER-BRINGUP-TOTP-GATE`, `REQ-ER-CONTROLLER-BOUND-POSTURE`, `REQ-ER-RC-INTENT-LOCKS`, `REQ-ER-INBOUND-LOCK`, `REQ-ER-NOT-ADVERTISED`, `REQ-SUBNET-EMPOWER-VERB`, `REQ-ACL-NODE-MODE-SET`, `REQ-ACL-ACCESS-REFRESH-ER-ONLY`, `REQ-ER-SESSION-BRIEFING`, `REQ-ER-RULESET-TABLE`. doc/impl/unit active at W3; int at gate.

#### Evidence for two of the rulings (todlando, from code)
- **(a) reset targets already exist as record fields.** `spt-store::info::PerchInfo` carries `home_subnet: Option<String>` and `adapter: Option<String>` — exactly the two things ADR-0052 §4 says only a reset may change. Same record carries `controlled: bool` / `driven_by` / `viewer_count`, which is the controller-attached predicate T3's posture keys on. So the ER is a perch record with reserved-id semantics + a locked write path, not a new storage shape.
- **(e) a briefing needs no new mechanism and no harness catering.** `spt-store::spool::spool_message_at(perch_path, from_id, body)` spools a message from any author id; the adapter drains it at session start like any other inbound (`spool_message_deferred_at` exists if the briefing should wait for the session-start drain specifically). A reserved system `from_id` gives a briefing that every adapter renders for free — core-side data, adapter-agnostic. This is why I prefer it over reserved role text.

#### Doyle rulings needed before dispatch
(a) ER creation surface — new `endpoint` action vs reserved-id path, and how the harness adapter is bound at reset. (b) `empower` as `spt api` verb vs `Cmd::` verb (finding 3). (c) posture-offline reading of §3 + explicit revoke-on-take (findings 1–2) ⇒ ADR-0052 amendment wording. (d) local attempt bound for the two-secret bring-up gate (finding 4). (e) briefing delivery mechanism (T8). (f) whether the knock exemption hook lands in W3 or waits for W4.

#### W3 T5 — DONE @e15ee3a (2026-07-29, todlando)
Advertisement filter wired at both halves: `advertise_local` withholds the ER row from the whole
round (peers AND the local registry) unless posture Online + non-empty whitelist; the per-viewer
filter sits in `DiscoverGate::discloses` AHEAD of the loopback allow. Pure rule =
`classify_engine_room_advertisement(posture, whitelisted)`, posture beats whitelist, withhold-only
(a `Disclosed` pair falls through to the ordinary chain). A withheld ER drops out of the scan's
`live_ids` so ghost-heal takes its already-published LOCAL row Offline — own rows never decay, so
without that the posture drop is cosmetic on the node the ER governs. ADR §3b amendment: the
whitelist is NODE-keyed (a row pushed to a node is readable by every endpoint on it; no endpoint
identity rides a feed push). Off-node retraction deliberately not built — retracting to a peer means
disclosing to the viewer being refused; same convergence gap per-subnet visibility hiding has.

#### W3 T2 — DONE @1221b9c · W3 T8 — DONE @8f4bf95 (2026-07-29, todlando) · PR #125
**T2 call site.** `spt rc --code <CODE>` -> `AttachRecord::Request.code` (additive, skip-when-none)
-> `serve_attach`/`handle_attach_request` -> `Brain::attach_as_coded` -> `SubscribeReq.code` ->
`Session::bringup_refusal` behind `engine_room_refusal`'s `Pass` arm. Adapter checked first with the
ledger STILL UNREAD (a misconfiguration must not spend the human's budget); both seeds tried with a
bitwise `|` so the timing cannot name which key admitted; one shared persisted ledger; refusals ride
`BusyControlled`. Loud-past-third alert fires OFF the log lock in a detached thread.
⭐ NEW RULE, not in the original design: `engineroom::bringup_required(seated_conn, attaching_conn)`
makes the gate per-CONNECTION, so a controller's gap-resume re-subscribe keeps the seat it paid for.
The CONN is the discriminator, never the attach generation — a generation is a wall-ms number an
agent can guess, a broker conn id belongs to another process's socket. Without this the gap-resume
would have thrown a live human out of the controls.

**T8 briefing.** `spt-store::briefing` — `ruleset_rows` + `render_ruleset_table` (sorted, explicit
empty table) + `compose_briefing` (capabilities / responsibilities / exact posture / pending
`declared_seen` deltas / the table) + `spool_briefing_at`. Spooled at the admit moment under
reserved author `spt-engine-room`; posture is passed IN because the perch `controlled` stamp lands
AFTER the seat decision (deriving it would brief the human at the controls that nobody is there).
`spt endpoint access rules` serves the same renderer on demand, ungated (it renders what `access
list` already prints in prose).

**Gate at 8f4bf95:** units 309/721/469 green; clippy --workspace --all-targets clean; xtask check OK
(reference.md regenerated twice — `rc --code`, `access rules`); traceable-reqs exit 0 (all three W3
findings closed); int green across the subscribe/attach seam + the W2 pairing legs (list in PR #125).
⚠ RIG FACT (new): a nextest int run needs `cargo build -p mock-adapter --bins` first or
`attach_resize_capture` fast-fails 0.09s on a missing `capture-player.exe` — same class as the
translate-proof fixture rule for `-p spt --bins`.

**INTEGRATION RULING (todlando in doyle's seat, 2026-07-29):** do NOT rebase W3 onto
`build/sender-stamp` — tried, conflicts at T4, 16 commits replayed across a sibling for no gain.
Order instead: merge #119 -> #120 -> #121 -> #123 into main (ff-only, bottom-up), THEN rebase
`build/engine-room` onto main ONCE, re-gate at that sha (tested sha == merged sha), retarget PR #125
to main, then main baseline -> bump-in-PR v0.46.0 c81 -> deployah publishes.

**CORRECTED (todlando, same day, before executing it): that order is not executable, and ADR-0050
already rules the right one.** Two facts killed it. (i) ff-only main can absorb at most ONE lane
unrebased: `ci/golden-lane-w0` and `build/acl-core` are both children of `ffc7e9e`, so the first ff
strands the other — the "bottom-up ff" sequence would have forced a rebase + re-gate of EVERY
remaining lane, not one. (ii) ADR-0050 is this milestone's pilot and says main never advances except
by ff to the tested tip of a **gater-assembled `golden/<milestone>` merge chain**, full suite run
once on that branch. Merge commits INSIDE the golden chain are fine — main only has to be an
ancestor of its tip — which is exactly what dissolves the sibling problem.

**Executed instead (golden route):**
1. Merged `origin/build/sender-stamp` INTO `build/engine-room` @cf0bbeb (not a rebase): the
   resolution stays reviewable in PR #125 and the 16 commits keep their tested identity. One real
   conflict — W2b split `access_check` into a wrapper + `access_check_with_sender`, W3 had put the ER
   inbound lock at step 0 of the old body. Lock now lives in the **with-sender body**, ahead of the
   same-node allow: `wan.rs` is the sole caller passing a proven sender and MSG is precisely what the
   inbound lock exists to refuse, so a lock left in the wrapper would have been DEAD on the only
   family T4 targets. (Same class as the W1 tier-1 and ADR-0052 findings: an integration that looks
   textual has a live security seam inside it.)
2. Assembled `golden/access-control` in `.worktrees/golden-a`: main + `#119` (W0) + engine-room
   chain (carries #120/#121/#123/#125) + the v0.46.0 bump. **Candidate = `fe68f98`.** No conflicts
   beyond the one above; `traceable-reqs.toml` auto-merged W0's 8 lines with W3's 133.
3. Bump rides the batch as `release/v0.46.0` @df944e8 (PR-shaped, per the bump-in-PR standard) so
   the tested sha IS the shipped sha — CHANGELOG + `Cargo.toml` 0.46.0 + `Cargo.lock` (verified
   version-only: exactly 12 first-party line pairs, zero other diff lines). Counter 81.
4. Local pre-batch battery rig = scratchpad `golden-battery.ps1` (legs: build, clippy, units, xtask,
   reqs, intprep, int, **dispose last**). `intprep` encodes the new rig fact (mock-adapter + spt
   `--bins` before nextest). Golden run covers BOTH boxes, so the kitsubito cross-platform leg is
   inside the golden run — no separate ssh leg needed.
5. Push `golden/access-control` (pushing `golden/**` starts the full suite; golden runs are never
   cancelled) -> on green, ff main to the candidate sha, which auto-closes #119/#120/#121/#123/#125
   as merged -> deployah publishes counter 81.

Not in the batch: #122 (TLA+ pilot, parks on the operator's `TRACEABLE_REQS_TOKEN` — its red would
red the batch) and #124 — hertz took the seat conditionally, then his cleared-target rerun came back
RED, so it is **excluded** (his ruling: proceed without waiting for his repair).

**Candidate after the reachability test = `2b08287`** (`fe68f98` is its parent; the test commit
@47dc012 landed on `build/engine-room` after the first assembly).

⚠ **Carried into the golden run — a pre-existing broker-seam red, already exonerated of this batch.**
hertz's #124 rerun (branch `test/probe-cap-count-not-clock`, carrying NONE of this batch) failed
`daemon_e2e::daemon_hosts_lifecycle_and_survives_brain_restart` at `daemon_e2e.rs:334` — "child exit
must reap the session from broker table" — with 181/182 otherwise green, and his original `E0463` did
NOT recur on the clean target (so that one was transient artifact state, not a dep/build-order
defect). That test sits in the broker-session seam W3 moves, so the seam-touched-red rule forbids
calling it a flake on one sighting; the useful half is that the sighting is on a branch WITHOUT W3,
which is advance evidence the batch did not cause it. It is measured in the local battery
(`-Leg int` includes `daemon_e2e`) before the golden run can be surprised by it. It is not in the
flake registry, so if it reds in golden it BLOCKS the batch per the red protocol — expect to either
fix it as its own lane or hold the batch, never to wave it through.

**Provenance note:** the bump commit body named `3836f95` as the candidate; amended to name the range
without a tip sha (bump = `e2e564f`), and the golden chain was rebuilt over it. Tree verified
BYTE-IDENTICAL to the battery-tested `2b08287` (`git diff --stat 2b08287 HEAD` empty), so the battery
evidence carries.

#### PUSHED — golden run in flight (2026-07-29)

**Candidate `af65ac0` = `golden/access-control`**, pushed; **golden run 30496546229** (workflow
`golden`, event push, uncancelled by design). `build/engine-room` pushed to `47dc012` so #125 carries
the merge + the reachability test as a reviewable diff; its `ci` run 30496521053 was cancelled
immediately so it could not contend with golden for the runner (the CI-KITSUBITO "failure" message
for it is the informant echoing my own cancel — not a red).

**Battery verdict at the candidate (all green):** build 231s · clippy `-D warnings` 77s · units
469+722+70+38+309 (spt-daemon lib 721 -> 722 = the new test, so the leg is proven to have run it) ·
`xtask check` 113s, no CLI-reference drift from the version bump · `traceable-reqs check` exit 0 ·
int 66/66 across 15 suites · `flake-registry.py` OK. Disposal leg reclaimed **47.4GB**, free
114.8GB against golden's 32GB floor — deliberately run BEFORE the push, since the CI workspace target
shares the drive.

⭐ **The units red resolved AWAY from the batch, with a mechanism instead of a load story.**
`spt-store daemon_inhibit::tests::inhibit_stands_until_an_intent_verb_clears_it` failed the first
pass; those tests write the PROCESS-GLOBAL `SPT_HOME` (3 unguarded sites in that file; `perch.rs`'s
env tests already share an `ENV_LOCK`) and cargo runs a crate's units on parallel threads in ONE
process, so a sibling retargets the home between the write and the read. File untouched by the batch;
3 reruns green (2 parallel + 1 `--test-threads=1`). Reported to hertz, who ruled the fix:
`*_at(home)` private seams with production wrappers resolving `spt_home()` once, tests passing
`TempDir::path` — explicitly NOT `serial_test`. Deliberately NOT flake-registered: the registry is for
nondeterminism you cannot explain, and parking a fixable defect behind an ignore list is the failure
mode the protocol warns about.

⚠ `daemon_e2e::daemon_hosts_lifecycle_and_survives_brain_restart` PASSED at the candidate (7.8s), so
hertz's #124 sighting is not this diff. It can still surface in the golden run — it is unregistered,
so it would BLOCK the batch.

**Remaining legs:** watch 30496546229 -> on green, ff `main` to `af65ac0` (closes #119/#120/#121/#123/
#125 as merged) -> let main's THIN run settle green with nothing pushing behind it (under ADR-0050 the
separate sacred-baseline full run is one of the procedures that retires — golden is the full evidence
for the sha) -> GO to deployah (draft ready in scratchpad `to-deployah-GO.txt`, counter 81, standing
#117 riders listed) -> hand hertz the box for the `daemon_inhibit` fix + his #124 repair.

#### W3 T2 — bring-up TOTP gate CALL SITE (design of record, LANDED)
Chunk 1 built `GateLedger` + `classify_attempt` + `apply` (spt-store::engineroom) and the empower
verb's SEPARATE ledger (§2b). What is missing is the bring-up call site itself.

**Where the rule goes: the broker's seat resolver, not the CLI.** Same reasoning as T3's rc locks —
an agent on the box can speak the broker's IPC and skip `serve_attach` entirely, so a client-side
check in `rc.rs` is decorative. Extend `Session::engine_room_refusal` (broker.rs ~1441), which
already runs FIRST in `resolve_subscribe` (~1911) and already rides the `BusyControlled` refusal
carrier — no new wire variant, N-1 tolerant.

**Threading the code.** `SubscribeReq` (spt-daemon/src/msg.rs:434) grows
`#[serde(default, skip_serializing_if = "Option::is_none")] pub code: Option<String>` — the exact
shape `by` already uses, so an N-1 brain that never sends it deserializes fine (and lands on the
refusal, which is the fail-closed direction). Thread: `spt rc <id> --code <totp>` → `serve_attach`
→ `Brain::subscribe` (brain.rs ~2021) → `SubscribeReq`. Only the LOCAL path needs it — the ER
already refuses off-node attach (`NotFromOffNode`), and that lock is evaluated before this one.

**The rule, pure** (spt-store::engineroom, beside `classify_posture` / the advertisement filter):
`classify_bringup(adapter_present: bool, verified: bool, attempt: GateOutcome) -> BringUp`
with variants `Admitted` / `NoAdapter` / `Throttled{retry_in_ms}` / `Refused{failures, loud}`.
Order is load-bearing: **adapter first** (fails closed when the bound harness adapter is missing,
ADR-0052 §2 + ADR-0053 spirit — and it is not a guessing surface, so it must not spend ledger
budget), then the throttle (evaluated BEFORE verification so a shut gate is not an oracle, §2a),
then the code.

**Verification.** Member-or-admin TOTP of the ER record's `home_subnet`, verified locally against
`SubnetRecord::seed_bytes` / `admin_seed_bytes` (both replicated at join, W2 T3/T4) through the
±1 ceremony window. Reuse `api::engineroom::code_matches` — it is already constant-time over the
whole window; it must move to a crate both call sites can reach (spt-store::engineroom, or a
shared helper) rather than being reimplemented in the daemon. ONE shared ledger for both seeds
(`engine_room_gate_file()`), so a second acceptable secret buys nothing.

**Adapter presence** = the ER record's `adapter` resolving in `spt_runtime::registry::registered`.

**Units** (REQ-ER-BRINGUP-TOTP-GATE): a member code admits; an admin code admits; a wrong code
refuses; elevation never substitutes (there is no elevation input in the rule at all — assert the
signature-level absence by testing that only the three inputs decide); a missing bound adapter
refuses AND leaves the ledger untouched; plus the seat-resolver leg — an attach with no code is
refused for the engine room and unaffected for every other endpoint.

### W3-tail — admin-seed rotation (PREP-ONLY, operator cut-to-B veto window open)
`REQ-SUBNET-ADMIN-SEED-ROTATION` (minted INACTIVE, gate spec in its title). Defect: eviction rotates only the member seed (`SubnetStore::rotate_seed`), so an evicted node keeps the admin seed forever — and an admin key IS a membership key (`REQ-SUBNET-ADMIN-CODE-JOIN`), so it can **rejoin**. Leg plan: (1) `rotate_seed` rotates BOTH seeds and bumps the epoch once; (2) both redistribute over the join path's replication machinery (`add_joined` / `adopt_rotation` — the admin seed has no re-provisioning surface, so replication is its only answer); (3) one-deep prior-generation grace stays member-seed-only (`prev_seed_bytes`); (4) ADR-0051 amended with the rotation rule (doc stage); (5) units — rotated subnet's admin seed changes, and a node holding only the pre-rotation admin seed neither verifies an admin op nor rejoins. Interacts with `REQ-HAZARD-PAIR-SEED-ROTATION` and the revoke/coalescing-window REQ (line 1238) — check overlap at activation. **DO NOT BUILD until doyle confirms the veto window closed.**

### W4 — knocking (RULED 2026-07-31, knock grill — CONTEXT.md @5af470f is the design of record; this section is the build digest. Milestone releases#53, todlando)

**Glossary entries binding on this wave:** *knock initiation & code verbs*, *knock grant authority (target-tier split)*, *sender attributability (per-surface)*, *ENDPOINTS_CAN_GRANT_NODES*, *origin qualifier*, *mutual whitelisting & knock notifications*, *access rule mutation & revocation*, the monic never-elevation-gated clause, plus the amended *engine-room* + *knock/knock-code* entries.

- **Verb family** (namespace `spt knock …` — supersedes releases#24's `spt endpoint knock` shapes): `spt knock <target> [--surfaces …] [--for <local-endpoint>] [--mutual]` · `spt knock list|approve [--surfaces …|--approve-requested] [--admit-node] [--mutual]|deny` · `spt knock new-code [--surfaces …] [--for-node] [--monic <msg>]` (`--for-node` ER-only) · `spt knock redeem <code> [--mutual]`. Every form's stdout states what was created/granted/activated, well-formatted (operator-ruled).
- **BINDING AT MINT: the knock record's knocker field is daemon-stamped** (session-proven, W2b discipline, never caller-supplied) — an approval writes a whitelist entry for that id, so a forgeable knocker = grant-to-attacker. KNOCK is attributable by construction.
- **Bare-user knock** (no perch): requests `subject <invoking-node>, origin user` — the narrow default; `--for` verified against local perches at send.
- **Authority split:** node-target grants (node-tier rules/modes/permissions) answer + mint ER-only, land only in the ER inbox. Endpoint-target stays self-sovereign; approving/baking a non-attributable surface = node-subject entry ⇒ requires `--admit-node` + gated by `ENDPOINTS_CAN_GRANT_NODES` (unset ⇒ derived per-decision from the requested surface's effective posture; explicit ER set overrides). Policy-false mixed knock: partial approve of the attributable subset works; refused remainder stays pending with stdout naming the ER.
- **Mutual + notifications:** `--mutual` = persisted self-side pre-authorization, consumed exactly once at answer-receipt, keyed to knock correlation id + node-proven answer. `approve --mutual` = approve + counter-knock into the knocker's inbox. Three consent-based notifications (counter-knock arrival → knocker; approval → knocker; redemption → code minter) — ride the notif spool; everything else stays inbox-only.
- **Flood/abuse:** one pending knock per (knocker, target), re-knock updates in place; **TTL 1d** (operator-ruled; stdout states expiry); per-node inbound knock rate limit (~10/h default, knob); redemption attempts per-node rate-limited; single-use redemption atomic; code entropy sized to close guessing under TTL+rate. Codes carry their target ⇒ no blind enumeration via the DISCOVER bypass (docs state it).
- **Inbox is daemon-owned:** knocks land and persist regardless of agent attach state; ER posture-offline loses nothing.
- **Grant provenance:** entries record origin (knock-approve / code-redeem / manual); FF-T3 views render it and print the exact tuple-shaped `remove` command per rule.
- **Rule mutation CLI** (rides this wave or splits at re-plan): `spt endpoint access allow|deny|remove` grows `--origin user|agent` + `--admit-node` + the `--any-of <subnet>` subject flag; widening node-subject mutations gated in BOTH polarities; `spt daemon access allow|deny|remove` ER-authenticated (empower-pattern), and the ER may edit **endpoint-scope** entries node-wide (admin command center — supersedes "owns modes only" for editing; knock-answer routing unchanged).
- **Origin qualifier enforcement:** entry matching per the glossary (unknown/absent ⇒ agent); classification at the sending daemon over the existing classify_local_origin/ancestry seams; MSG + rc/attach first, same staging as W2b. `user-proven` is forward-design only — do NOT build.
- **Monic hook:** `--monic` on accept/new-code imparts knocker monic (storage dep = W5); monic verbs never elevation-gated (ruled — drop the #24 parity check).
- **Riders in milestone #53:** releases#1 (broker-stopping commands deny endpoint callers) + releases#39 (`spt ring` doesn't block) — operator-moved 2026-07-31, separate small lanes, not knock-coupled.
- REQ mints ride the build PR (N1-N6 at mint, scope-at-mint into groups); doyle gates per standing gate shape + gate-teardown assert-gone step.
**releases#24 rides here (operator-attached 2026-07-29) — prototypical knock-monic CLI shape:** `spt endpoint knock accept|new-code [--monic <message>]` (preload a monic into a knock-code / impart at accept; the knocker's endpoint id gets the monic) + `spt endpoint knock-monic [--id <ID>] [--overwrite]` (default monic applied to all accepted knockers). Monic *storage* dep = W5's monic data model; imprint hook lands W4, `--monic` flags go live when W5's store exists — split ruled at W4 re-plan. Note: default-knock-monic is agent-behavior instruction like trust-warning override — check elevation-gating parity (trust-warning verb is elevation-gated) at re-plan.

### W5 — trust warning + monic store (RECON DONE + RULED 2026-07-31; todlando)
Monic data model in `tracked/agents/<id>/` (mind substrate, replicates) + CRUD CLI + `monic clone`; delivery-edge trigger eval + `mnemonics-json` envelope attr; trust-warning composer (closed ∧ passed-gate ∧ no-monic; system-authored block at delivery edge) + elevation-gated `endpoint trust-warning` verb; knocker-monic wiring from W4. Monic *surfacing* (now-signal) stays milestone D.

#### Recon findings (todlando 2026-07-31, before any code — what the rulings below were ruled on)

1. **`--monic` is narrower than W4's close-out said.** It exists ONLY on `knock new-code`; `knock approve` has no such flag (args: id / `--surfaces` / `--approve-requested` / `--admit-node` / `--mutual`). So W5 **adds a public CLI flag**, it does not wire a dormant one. #53's close-out line needs correcting to say dormant-on-new-code-only.
2. **The mind substrate exists; the fork seam ripples.** `spt-store/src/contextstore.rs` owns `tracked/agents/<id>/` (`live_role_file`, one `a-<id>` worktree+branch, `commit_live`/`rename_endpoint`/`fork_endpoint`/`remove_endpoint`). `rename_endpoint` **moves the whole worktree dir** (a new file rides free); `fork_endpoint` **copies by explicit filename** (`LIVE_CONTEXT_FILE` only) — so it already drops `live-role.md` today, and would silently drop monics.
3. **No dangling seeds.** Zero requirements in `traceable-reqs.toml` mention monic / mnemonic / trust-warning — W5 mints fresh.
4. **The gate discards what the composer needs.** `wan.rs receive_wan` (~348) is the right edge (whole message in hand, gate right there), but `access_check_with_sender` returns a bare `AccessDecision`; the `MatchedTier` it computes is thrown away except for an eprintln. So the edge can see *that* it passed, not *why* — posture-open vs explicit-entry vs same-node vs Reply are indistinguishable. Re-opening the store in the composer is the stale-snapshot class and is not planned.
5. **The envelope attr has a precedent.** `compose_typed_event(type, attrs, body)` takes an arbitrary attr list, and `cli.rs:7563` already composes `("from", …), ("json", j)` — `mnemonics-json` follows that shape with no grammar change. Note: `compose_msg_event` **hand-rolls** the same grammar instead of delegating to `compose_typed_event` (which `compose_user_msg_event` does) — an attr added to one and not the other diverges silently. Attr *keys* are written unescaped (values are escaped), so keys stay constants.

#### Doyle's rulings (2026-07-31, all three + the leg-2 fork)

- **(a)** `--monic` on `knock approve` is W5 scope — the ratified surface arriving, not scope growth. Plan clap surface + guide + reference regen. Correct #53's close-out line (replacement discipline).
- **(b1)** Monics **follow a fork** — releases#33 (GREENLIT) mandates it and the ratified fork entry already implies it. (#33 stays homed elsewhere; delivered-by recorded as a link, milestone-attach refused — one parent per sub-issue.)
- **(b2)** The `live-role.md` fork omission is a **latent gap, not intent**. Ruled fix, own REQ: `fork_endpoint` copies the agent's mind tier **as a whole directory at tip**, never by filename enumeration — closes live-role.md now, monics next, and every future mind file structurally. Pin with a fixture file the enumeration never named.
- **(c)** Storage shape: **directory of per-peer records**, `tracked/agents/<id>/monics/<peer-id>`. Deciding argument is the merge substrate: the mind store's driver resolves conflicts **per file**, so one `monics.json` makes every pair of concurrent writes — even about *different* peers — a whole-file conflict, while per-peer files merge cleanly and confine true conflicts to the peer both instances touched. `monic clone` **reuses the context-store copy seam** (with (b2)'s whole-dir fix, `clone --all` IS that seam pointed at `monics/`); no parallel copy path.
- **(leg 2)** **Widen the shared return** — `AccessDecision` grows into a verdict carrying decision + pass-reason/matched-tier (posture-open vs explicit-entry vs same-node vs reply-exemption), enough for the composer with **no second store read**. Give the seven `== Deny` sites an `.is_deny()`-style helper so the ripple is rename-shaped, not seven partial destructures. Gate the whole ripple; pin touched fixtures **by intent**. **Same-node and Reply never warn** (confirmed): same-node is inside the node trust unit, and a reply is traffic the agent itself invited — warning there teaches agents to ignore warnings. State both as non-warning cases in the guide.

#### Task breakdown (seams named; REQ mints in the task that delivers them)

- **T1 — whole-mind fork.** `fork_endpoint` copies the agent tier as a directory at tip, dropping the `LIVE_CONTEXT_FILE`-by-name read. Mint `REQ-FORK-MIND-COMPLETE`. Unit: a fixture file the enumeration never named survives a fork; `live-role.md` survives (the gap this closes); rename/remove stay green. **Blocks T2's "monics follow the mind" claim.**
- **T2 — monic store.** `tracked/agents/<id>/monics/<peer-id>` records + CRUD in spt-store beside `contextstore`. Mint `REQ-MONIC-STORE` (record shape, per-peer file rationale, replicates with the mind). Unit: concurrent writes about *different* peers do not conflict — the merge-substrate argument is the requirement, so it needs the test that would fail under one-file storage.
- **T2 TAIL — corrupt-record visibility (ruled 2026-07-31 at T2's acceptance, doyle).** `list_monics` skips an unreadable/unparseable record today. The #52 husk lesson applies: unreadable-reads-as-not-classified is CORRECT for the warning path (it fails safe — warns more, never less), but a corrupt record must stay VISIBLE somewhere, so `list_monics` surfaces it AS unreadable rather than dropping it from every read path (a husk that silently vanishes from every read is the erased-distinction class). Two-part test: warning path says not-classified; list path names it unreadable. Lands with T3.
- **T3 — monic CLI.** `spt endpoint monic <list|add|update|remove> [--owner <id>]` + `monic clone <id>|--all --from <src> [--to <dst>]` over the T1 copy seam. Mint `REQ-MONIC-VERBS` — **never elevation-gated** (ratified), which is a unit assertion, not a comment.
- **T4 — knocker monic.** Add `--monic` to `knock approve` (new clap surface); wire `new-code`'s recorded-but-inert value into the T2 store; both impart a knocker monic on the stamped id. Mint `REQ-KNOCK-MONIC-IMPART`. Correct the #53 close-out statement in the same change.
- **T5 — verdict widening.** `AccessDecision` → verdict with pass-reason; `.is_deny()` helper; ripple the 8 production sites (attach ×2, xfer ×2, digestlink, linkhost, resthost, wan). Mint `REQ-ACCESS-VERDICT-REASON`. Gate the whole ripple; fixtures pinned by intent.
- **T6 — trust-warning composer.** At the `wan.rs` delivery edge, on pass: closed ∧ passed-gate-by-explicit-entry ∧ no-monic ⇒ system-authored block. Mint `REQ-TRUST-WARNING` with the non-warning cases (same-node, Reply, posture-open) written into the req text at mint.
- **T7 — override text.** Elevation-gated `endpoint trust-warning` verb (custom warning text is agent-behavior instruction). Mint `REQ-TRUST-WARNING-OVERRIDE` — elevation lives **here and nowhere else** in the monic family.
- **T8 — delivery-edge trigger + envelope attr.** Monic trigger eval at delivery; matched monics ride as `mnemonics-json="…"` following the `json=` precedent. Mint `REQ-MONIC-DELIVERY-TRIGGER`. Resolve the `compose_msg_event` hand-rolled-grammar divergence rather than adding the attr twice.
- **T9 — docs leg.** CONTEXT.md doc tags, knock-guide update (incl. the non-warning cases), `xtask check` reference regen, KNOWN-HAZARDS entry if T5 or T8 turns one up.
- **T10 — int leg.** Only if a rung earns it on real evidence; cross-node coverage stays one rung by the W4 precedent.

Order: T1 → T2 → T3 → T4 (store chain) · T5 → T6 → T7 (warning chain) · T8 after T2 · T9 continuous, T10 last.

### W-SPEC — TLA+ access model (GATED: java-on-kitsubito)
Spec-pilot discipline (SOURCES.txt drift gate + weakened fidelity variants): the resolution chain (first-match totality, fail-closed degrade) + mode capture/refresh (no remote posture flip). Unblock java first; lands before golden.

## FF — fast-follow publish before A-2 (grilled 2026-07-30, operator + doyle; flynn's request)

Two user-facing gaps closed: (1) the engine-room ceremony had NO invocable verb (`provision_at` had
zero production call sites — a user could not set one up); (2) access rules were viewable only as a
flat node-local table that omitted the mode tiers. Rulings of record (ADR-0052 §2 entry-UX note +
§4a; CONTEXT.md *access entity*):

### FF-T1 — ceremony verb (todlando)
`spt endpoint engine-room <subnet> --adapter <id>` → `provision_at` (one code path, Created/Reset).
- **Create (absent record): NO elevation** — bootstrap friction ruled to outweigh pre-emption; the
  window closes at first run; docs say "run the ceremony early".
- **Reset (present record): elevation required** (ADR-0052 §4 unchanged).
- **Both arms refuse invocation by an SPT agent**: deny hinges on env markers AND process identity
  (caller ancestry vs broker-hosted session PIDs) — defense-in-depth, not a gate substitute.
- Refuse an unjoined subnet (no seeds to verify bring-up TOTP later) and an unregistered adapter
  (bring-up fails closed on it anyway — fail at bind time instead).
- REQ mint: `REQ-ER-CEREMONY-VERB` (doc stage already tagged in ADR-0052 §4a).

### FF-T2 — bring-up TOTP prompt (todlando)
Bare `spt rc engine-room` opens an interactive TOTP prompt (Esc-cancel overlay, releases#19
pattern); `--code <totp>` stays, argv-exposure caveat documented.

### FF-T3 — access view redesign (todlando; supersedes `access list` + `access rules` CLI verbs)
Roster-first per CONTEXT.md *access entity* (operator-specified shapes, illustrative syntax):
- `spt endpoint access [<endpoint-id>]` — ruled access entities grouped by type:
  - endpoint item: `<endpoint-id>@<node> (<shared-subnet-1>, …<shared-subnet-n>) - x access rules`
  - subnet item: `<subnet> - mode: [open|closed] [- x access rules]`
  - home node: `<home-node> - mode: [open|closed]` (color matches home-node color in `spt endpoint list`)
  - external node: `<external-node> - x access rules` (color matches external-node color)
  - unruled external nodes/endpoints omitted.
- Drill-down (granular rules only per named ruled entity):
  `spt endpoint access [<endpoint-id>] [--endpoint-rules <id>|--node-rules <node>|--subnet-rules <subnet>]`
- `spt daemon access` — the node-tier roster, same shapes.
- NO `spt subnet access` (subnet = subject tier + mode source, never a rule target); declared vs
  captured mode + pending `declared_seen` delta land on `spt subnet show <subnet>`.
- `render_ruleset_table` survives inside the briefing (REQ-ER-RULESET-TABLE untouched); the
  superseded verbs' `--json` shape re-homes onto the new view.
- REQ mints ride the build PR: `REQ-ACL-VIEW-ROSTER`, `REQ-ACL-VIEW-DRILLDOWN`,
  `REQ-ACL-NODE-VIEW`, `REQ-SUBNET-SHOW-MODES` (names final at mint).

### FF docs leg
Public docs: engine-room first-time setup guide (ceremony → bring-up → briefing) + access-viewing
reference + the explicit **no-remote-rule-read** statement (node-sovereign; run the CLI there) —
the flynn/adapter-builder docs-gap class closed at the contract, not in chat. Briefing contract
documented as **push-only** (ruled 2026-07-30): spooled at the admit moment under reserved author
`spt-engine-room`, no re-request verb — mid-session deltas ride advisory notifs, every seat
re-briefs, ad-hoc state via `subnet status` + the access views. Admin-key surfacing doctrine
(ADR-0051 §2a/§2b) + co-admin out-of-band note ride this leg too.

### FF gate (doyle)
Isolated worktree + throwaway target; ceremony decision-table units (create-unelevated /
reset-elevation / agent-deny / unjoined-subnet / unregistered-adapter refusals); prompt leg;
view snapshot tests; `xtask check` (new public verbs ⇒ reference.md, no internal codes in help);
`traceable-reqs check`; kitsubito leg inside golden per ADR-0050.

### FF-T4 — admin-seed rotation (W3-tail, operator-authorized; AMENDED by the 2026-07-30
admin-key grill — ADR-0051 §2a/§2b @d4f5acf are the rulings of record). Build per the W3-tail
leg plan above PLUS:
- `spt subnet revoke` (both forms incl `--force-rotate-seed`) requires the **admin TOTP**
  (member code insufficient; elevation stays on top).
- Rotation re-surfaces the NEW admin key exactly once, to the human who proved the OLD code:
  QR/otpauth WITHOUT its current TOTP → typed current-code capture proof → screen cleared.
  Member key never re-surfaces at revoke (`show-code` covers it).
- Wire shape (todlando finding 2, doyle-approved): admin seed rides as a second well-formed
  seedxfer item under reserved name `admin:<subnet>` (old decoders fail-soft via adopt_seed
  NotFound); `adopt_admin_rotation` accepts only at the matching epoch, no prev retained
  (member-seed-only grace preserved). Same diff: `subnet create` refuses `:` in new names
  (reserved-delimiter rule extended); census existing names first.
- **Parked-pending-key choreography (todlando shape, doyle-approved 2026-07-30):** the revoke
  ceremony mints + surfaces + capture-proves the pending admin seed UP FRONT (human present,
  old-admin already proven), PARKS it persisted beside the rotation schedule — park ordered
  BEFORE the tombstone/schedule write (schedule-without-parked-seed = corruption, never a race);
  window-close rotates the member seed and ADOPTS the parked admin seed (cleared on adoption).
  Later revokes in the same window re-prove old-admin and re-display the SAME pending key with
  their own capture proof; one epoch bump at close; `--force` = park + rotate-now, same
  choreography. DEGRADE at close on absent/corrupt parked seed: rotate member seed anyway, keep
  old admin seed, LOUD "admin rotation incomplete" notif naming `revoke --force-rotate-seed` as
  recovery — never silent-skip, never brick the close.
- Activate `REQ-SUBNET-ADMIN-SEED-ROTATION`; units incl. admin-gated-revoke decision table +
  capture-proof choreography + the parked-seed order/degrade table.

### FF-T5 — create-ceremony capture proof (NEW, ADR-0051 §2b)
`subnet create` reorder: (1) admin QR/otpauth FIRST, its current TOTP NOT displayed; (2) human
types a current admin code back (proof of authenticator capture — unproducible from the screen);
(3) admin material cleared; (4) member QR/otpauth at the very end. Create does not complete
without the proof. Modes prompt unchanged. Co-admin out-of-band doctrine rides the docs leg.

### FF-T6 — subnet status modes (renamed from the T3 bullet; todlando mismatch 1, doyle-ruled)
Declared-vs-captured mode + pending `declared_seen` land on `spt subnet status` (NOT a new
`show` verb — near-synonym verbs are a UX defect). Mint `REQ-SUBNET-STATUS-MODES` (title names
`subnet status` as the surface).

## Golden + release
Gater assembles the golden branch from W0–W5 lanes → one full-suite golden run → ff main to tested sha → deployah publishes (counter from published ledger; bump-in-PR standard). **At publish, standing #117 riders fire:** ping perri (ring reinstate + release number), emphasys (drop CI.md ring warning + self-ban), mobile-gw per standing directive; fleet ring ban lifts.

#### W5 T3 — DONE @7f652f2 (2026-07-31, todlando) · carries the T2 TAIL
`spt endpoint monic <list|add|update|remove> [--owner]` + `monic clone <peer>|--all
--from <src> [--to <dst>] [--overwrite]`. `REQ-MONIC-VERBS` minted (impl+unit active;
doc deferred to T9's guide).

**never-elevation-gated, as a checked property.** `crates/spt/src/monicverb.rs`
`classify_monic_verb(verb, exists, elevation)` TAKES the process's real elevation
(the handlers read `elevation::current()` and pass it) and ignores it; the unit
table asserts every verb x record-present/absent yields the same outcome under
Elevated / NotElevated / Unknown. Deliberately not signature-level absence — an
absent parameter is an absence someone can later fill, a threaded-and-inert one
reds a test whichever direction a gate points.

**add/update split** = the two verbs refuse opposite states (add will not replace,
update will not invent), each refusal naming the other verb.

**T2 TAIL closed.** `list_monics` returns `MonicEntry { peer, monic: Option<_> }`,
husks included and named as unreadable; two-part test pins warning-says-not-
classified + list-names-it-unreadable. ⭐ A THIRD state fell out that the ruling did
not name and the first render got WRONG: present-but-unreadable is neither of the
write verbs' states, so `update` on a husk refused with "holds no monic" about a
peer whose file is right there, and the list's own hint pointed at that refusing
verb. Caught by smoke-testing the real binary, not by the units. Fixed with a
second store predicate (`record_present` — is there a FILE, as against
`has_monic` — is there a CLASSIFICATION): update refuses a husk AS unreadable and
names `add`; add says it is replacing a husk rather than overwriting silently.

**clone reuses the fork seam** per ruling (c): T1's whole-directory copy is now
`ContextStore::copy_mind_files(src, dst, keep, msg)` and both callers point it at
different subtrees — no parallel copy path. Clone keeps (and reports) a destination
judgement instead of trampling it unless `--overwrite`, stamps copies
`MonicOrigin::Clone`, copies the source mind AT ITS TIP (an uncommitted worktree
edit is not yet mind), a husk travels as a husk, and a clone with nothing to copy
mints no branch/worktree/commit.

Gate evidence at 7f652f2: spt-store units 369, spt units 487, contextstore suite 8
(fork seam refactor), clippy --workspace --all-targets -D warnings clean, xtask
check OK (reference.md regenerated — 5 new public verbs), traceable-reqs exit 0.
Not run locally: int/nextest legs (T10 rung not earned yet) and spt-daemon --lib
(live-host deadlock class; CI is the authority).

Next: T4 (knocker monic — `--monic` on `knock approve` is NEW clap surface, wire
`new-code`'s recorded-but-inert value into the store, correct #53's close-out line).

#### W5 T4 — DONE @0e1a074 (2026-07-31, todlando)
`knock approve <id> --monic <msg>` (NEW clap surface — recon finding 1 confirmed:
`--monic` existed on `new-code` only) + `new-code --monic` now PERSISTS as
`KnockCode.monic: Option<MonicImprint{owner,text}>`, imparted at REDEMPTION.
`REQ-KNOCK-MONIC-IMPART` minted (impl+unit; doc → T9).

Rules, all through ONE shared primitive (`monic::impart_knock_monic`) so the two
paths cannot diverge: the note is about the DAEMON-STAMPED id (same id the grant's
whitelist entry names); a knocker with no endpoint id — a bare-terminal human,
stamped as their node — is refused LOUDLY with the grant standing (a classification
under a node id matches no delivery-edge lookup while reading back as though someone
was classified); an existing classification is KEPT and quoted with the replace
command (a standing judgement outranks one arriving with a grant; a husk is not a
classification, so imparting fixes one); the note rides the grant and never changes
it — imparted only AFTER the grant commits, skipped on a refusal, and an unopenable
mind costs a loud line, never a retracted approval.

Owner is RECORDED not derived: a node-target code is minted by the ER and a node has
no mind, so the imprint carries its owner endpoint from the mint. Field is additive
(pre-field code still reads; no note ⇒ no key).

#53 close-out CORRECTED IN PLACE (replacement discipline, not an appended
retraction) — both comments carried it: 5144135973 ("--monic accepted and recorded
but inert", no verb named) and 5142766849 ("--monic flags" plural). Both now name
new-code as the only W4 verb and W5 T4 as where approve gains one.

Gate evidence at 0e1a074: spt-store 371, spt 488, clippy --workspace clean, xtask
check OK (reference.md regenerated), traceable-reqs exit 0. Smoke-tested live:
approve-with-note imparts (origin = imparted at a knock), human-knock refuses the
note and keeps the grant, second approve KEEPS the standing judgement, a seeded
code record's imprint deserializes and takes the guard at redemption.

Store chain T1→T4 COMPLETE. Next: T6 (trust-warning composer at the wan.rs delivery
edge, over T5's verdict @a7884b8) → T7 (elevation-gated override text) → T8
(delivery-edge trigger + mnemonics-json attr) → T9 docs → T10 int if earned.

#### W5 T6 — DONE @e8e81d2 (2026-07-31, todlando) — all four holds honored
`REQ-TRUST-WARNING` minted (impl+unit; doc → T9). Seams: pure decision
`spt-daemon::access::warrants_trust_warning(PassReason, holds_monic)`; composer +
reserved author in NEW `spt-store::trustwarn` (`TRUST_WARNING_AUTHOR = "spt-access"`,
not a legal endpoint id ⇒ unforgeable authorship); edge wiring
`wan.rs::maybe_warn_about_stranger`, ahead of the three delivery legs and AFTER the
replay check (a duplicate does not re-warn).

Holds: verdict-only (no second access-store read — the pass reason IS the posture
question, since a matched entry means the chain never reached a mode tier);
Explicit+Wildcard both warn; SameNode/Reply/PostureOpen never; block alongside the
envelope, never in the body; default text = sensitive data / state mutation /
forwarding on their behalf.

⭐ HOT-PATH FINDING: `has_monic` routes through ContextStore → `monics_dir` →
`ensure_worktree` = a GIT SPAWN, and the warning asks its question on the inbound
message path. Added `monic::has_monic_at(tracked_root, owner, peer)` — pure-path
read, same fail-safe husk reading, no store, no worktree (sibling of
`live_role_file`'s read form; the whoami/project-index git-fanout class). Unit pins
that asking about an agent with no mind creates nothing.

✅ BOTH CALLS RULED IN THE BUILT DIRECTION (doyle, at T6's acceptance) and folded into
REQ-TRUST-WARNING's text @4401017 as constraints rather than notes:
1. UNPROVABLE SENDER still warns. `sender_proven=None` ⇒ no monic can exist ⇒ the
   literal rule (closed ∧ entry-passed ∧ no-monic) fires, and it is UNSUPPRESSIBLE
   since a monic is about an endpoint id. I took the fail-safe reading (a peer
   nobody can name is more of a stranger, not less) but made the text honest: it
   says there is nobody to record a note about and names the admitting RULE as the
   way out rather than printing a `monic add` that cannot run. If you would rather
   an unnameable sender NOT warn (unsuppressible-warning-trains-ignoring), it is a
   one-arm change. RULED: unsuppressible is correct BY DESIGN, not merely fail-safe — a
   peer with no provable id is the maximum stranger, the case the warning exists for, and
   silencing it would invert the design; it self-heals as the fleet's daemons come to
   stamp senders. The TEXT DISCIPLINE (name the admitting rule, never print a command
   that cannot run) is the ruled limit that keeps warn-more from becoming warn-noise.
2. OVERRIDE SCOPE (pre-empts T7). RATIFIED for T7 on this grounding: override text is
   agent-behavior instruction = a prompt-injection surface, and an unforgeable factual
   spine BOUNDS it — a whole-block override would let one elevated write hide who is
   knocking, which no legitimate override needs. State the scope in the verb's own
   output. Custom text replaces ONLY the advisory paragraph;
   the identity line and the classify line stay core-composed, so an override can
   change what the agent is cautioned about but never hide WHO is knocking. That is
   why the verb is elevation-gated at all — say if you want whole-block override.

Evidence at e8e81d2: spt-store 375, spt 488, spt-daemon access 18 + wan 6, clippy
--workspace clean, xtask check OK (no CLI surface in T6), traceable-reqs exit 0.
Not run: full spt-daemon --lib (live-host deadlock class, CI is authority).

Next: T7 (elevation-gated `endpoint trust-warning` override verb — elevation lives
there and nowhere else in this family) → T8 (delivery trigger + mnemonics-json attr,
resolving the compose_msg_event hand-rolled-grammar divergence) → T9 docs → T10 int.

#### W5 T7 — DONE @062b158 (2026-07-31, todlando) — ratified scope built as ruled
`spt endpoint trust-warning <show|set|reset> [--owner <id>]`.
`REQ-TRUST-WARNING-OVERRIDE` minted (impl+unit; doc → T9). Seams: store CRUD
`spt-store::trustwarn::{validate_override, override_at, set_override_at,
clear_override_at}` + `override_for/set_override/clear_override` wrappers;
path `spt-store::perch::trust_warning_file` (single-path-source); pure verb
decision NEW `spt::trustwarnverb::classify_trust_warning_verb`; edge wiring
`wan.rs::maybe_warn_about_stranger` (was passing `None`).

RATIFIED SCOPE BUILT VERBATIM: custom text replaces the ADVISORY only; identity
line and classify line stay core-composed. `set` prints
`trustwarn::OVERRIDE_SCOPE_NOTICE` — the scope AND its grounding
(prompt-injection surface bounded by an unforgeable spine) in the verb's own
output, as ruled.

⭐ STORAGE IS NODE-LOCAL, NOT THE MIND. `identity/trust/trust-warnings/<id>`,
one file per endpoint, beside the access store. The mind tier was the obvious
home (it is where monics live) and is WRONG here: a mind file replicates
between every instance of an agent, so an override filed there lets a peer
instance's mind push warning text onto this node — the exact injection surface
the elevation gate exists to hold shut, reopened by a sync. Node-local security
material is the only tier that is both unreplicated and the neighbourhood the
access rules already live in.

TWO BUILDER CALLS FOR RULING (both built in the direction stated, both
one-arm changes if ruled the other way):
1. READS ARE NOT GATED — only `set`/`reset` are. Showing what an endpoint is
   told leaks no authority (unlike the pairing-code fetch, where the gated
   thing IS the secret), and it is the visibility that makes a planted
   override discoverable; gating it would protect nothing and hide the only
   thing worth auditing. The gate keys on `TrustWarnVerb::writes()`, so a
   later verb is gated by what it does rather than by being remembered.
2. A LENGTH BOUND AS A SECOND WRITE-TIME REFUSAL (`MAX_OVERRIDE_CHARS =
   2000`). Not in the ruling; my call. The classify line sits BELOW the
   advisory, so an unbounded override buries the line an override may not
   touch — the whole-block harm reached by length instead of by scope. The
   other refusal is a blank override (withdrawal is `reset`).

Evidence at 062b158: spt-store 381, spt 493, spt-daemon wan 6, clippy
--workspace clean, xtask gen+check OK (reference.md regenerated — the new verb
plus column realignment), traceable-reqs exit 0. Smoked the real binary against
a throwaway SPT_HOME: unelevated `set` and `reset` both refuse with the elevated
re-run of the same verb and leave the trust dir non-existent; `show` renders the
block against a stand-in peer with the way-out line for the current state; a
planted override (what an elevated write produces) reads back as
`overridden: true` and replaces the advisory only; a second endpoint is
unaffected; an unreadable record (a directory where a record goes) falls back to
the default advisory. NOT SMOKED: the elevated write itself — it needs a UAC
prompt on this host, so the gate's allow-arm is unit-only (`Elevation::Elevated
⇒ Proceed`) and the stored-file effect was proven by planting the same bytes.

✅ ACCEPTED (doyle, 2026-07-31) — all four calls ruled in the built direction:
1. NODE-LOCAL STORAGE is the load-bearing insight of the task, not merely
   approved. Override text is node-sovereign security material; monics follow
   the mind. **That asymmetry is deliberate and T9 must state one sentence per
   side**, so nobody later "fixes" the inconsistency by moving one to match the
   other.
2. UNGATED READS approved as the structural shape — keying on
   `TrustWarnVerb::writes()` gates future verbs by what they DO rather than by
   list maintenance.
3. MAX_OVERRIDE_CHARS approved with the grounding written into the req text:
   whole-block harm reachable by length is a real bypass class, and the
   blank-set refusal closes the shell-mishap arm.
4. ELEVATED-WRITE GAP accepted WITHOUT a live elevated run — the gate table
   proves the decision arm, planted bytes prove the effect, and
   `elevation::current()` is long-standing shared infrastructure rather than
   this wave's code. The live composition goes on the release wave's
   FIELD-VERIFY checklist, where an elevated shell exists anyway.
Rulings folded into REQ-TRUST-WARNING-OVERRIDE's stage comment by replacement.

Warning chain T5→T7 COMPLETE. Next: T8 (delivery-edge trigger + mnemonics-json
attr) → T9 docs → T10 int if earned.

**T8 ordering, ruled at T7's acceptance:** the `compose_msg_event` →
`compose_typed_event` consolidation lands FIRST within the task, then the attr
rides the single renderer. Keep the keys-are-compile-time-constants sentence in
the module doc (leg-3 ruling).

#### W5 T8 — DONE (todlando) — leg 1 @c90c1e6, attr leg below

**Leg 1 DONE @c90c1e6.** `compose_msg_event` AND `compose_alarm_event` now
delegate to `compose_typed_event` (the alarm sweep ruled in at the same
acceptance: "the grammar exists once" is false while any sibling hand-rolls it).
Byte-identity pins over attr escaping, body escaping, the anonymous sender, and
the alarm's two-attr order. Keys-are-compile-time-constants stated in
`compose_typed_event`'s doc.

**Doyle's T8 rulings (2026-07-31), both my recommendations approved:**
- **Q1 — SENDER-MATCH ONLY for W5.** Content / custom-json triggers defer to the
  now-signal milestone where the trigger vocabulary is ratified once for both
  consumers. Two constraints: **T9 states the limit honestly** (sender-match
  today; the CONTEXT §mnemonics sentence describes the destination, not this
  wave), and **the T2 record's future trigger-spec field is committed-additive
  NOW in REQ-MONIC-STORE's text** — a pre-field record reads as a sender
  trigger, so W5 records never need migration.
- **Q2 — evaluate at the emit renderers**, not wan.rs: a monic that only fires
  when the peer happens to be remote is a classification the agent cannot rely
  on. Constraints: **ONE evaluation primitive** (the match rule must be
  un-driftable per site); **census the call sites authoritatively before
  building** (D-2 lesson — surface any site that knows its recipient but cannot
  be converted rather than leaving it silently attr-less); **the trust warning
  STAYS at wan.rs** — it needs the gate verdict, which exists only there, and
  the two edges are different concerns.

**CENSUS DONE (grep-authoritative, `cfg(test)` separated). 8 call sites of
`render_event_whole` / `render_event_lines`; 7 production, 1 test-only. EVERY
production site is recipient-aware — nothing has to stay attr-less:**

| # | site | recipient in hand |
|---|------|-------------------|
| 1 | `spt/api/delivery.rs:331` `emit_event` | callers `cmd_poll(id)` / `cmd_poll_shell(id)` — thread `id` in |
| 2 | `spt/api/startup.rs:933` `deliver` closure | `id` in scope (the listening endpoint) |
| 3 | `spt/api/startup.rs:1098` `emit` | called inside the same `id` scope — thread it in |
| 4 | `spt/api/worker.rs:139` `cmd_worker_poll` | `id` (the worker perch) |
| 5 | `spt/cli.rs:8490` `cmd_ring` reply | the resolved `from` — we are the recipient of the reply |
| 6 | `spt/cli.rs:8616` `emit` in `run_listen(id, …)` | `id` |
| 7 | `spt-daemon/inject.rs:85` `try_spt_hosted_inject` | `target` |
| — | `spt-daemon/notif.rs:769` | **test assertion**, not a delivery site |

Note on site 1: `cmd_poll_shell`'s recipient is a shell link, not an agent — a
shell has no mind, so the primitive reads nothing and the attr is absent. That is
the honest answer rather than a special case. **Doyle at the census's acceptance:
correctly NOT special-cased — a link with no mind reading nothing IS the
primitive telling the truth, and ONE SENTENCE IN THE MODULE DOC suffices; and
evaluation living in the owner-aware renderers rather than lifted per-caller is
the right containment — the callers hand over identity, the renderer owns the
rule.**

`spt-msg` already depends on `spt-store`, so the owner-aware renderers can call
the primitive directly — the layering does not force evaluation up into each
caller (which is what would let the match rule drift).

**ATTR LEG DONE.** Built exactly as ruled:

- `spt_store::monic::matched_for_delivery(tracked_root, owner, sender) ->
  Option<String>` — the ONE evaluation primitive. Sender match only. Returns the
  JSON array; `None` when nothing matched, so the attr is present **iff**
  something matched (an empty array would be a second shape saying
  "evaluated, no match" that no consumer has a use for).
- `has_monic_at` and `matched_for_delivery` now share ONE private
  `read_monic_at` — the warning path and the attr path read the same bytes
  through the same rule, and a unit asserts they AGREE over every state a record
  can be in (present / absent / husk / traversal id). Two copies of that rule
  would have been two chances to fix only one.
- `spt_proto::event::compose_msg_event_with(from, extra, body)` +
  `EVENT_ATTR_MNEMONICS_JSON`; plain `compose_msg_event` is now this with no
  extras, so "what a msg envelope is" stays in one place while the delivery edge
  adds to it. Keys stay compile-time constants; values ride the composer's
  escape.
- `spt_msg::emit::render_event_{lines,whole}_for(owner, from, body)` — and
  **the owner-less forms are GONE, not kept alongside**. A renderer that does
  not need the recipient is the shape a future delivery surface reaches for by
  habit, arriving attr-less with nothing failing. Private `_at(tracked_root, …)`
  siblings exist for the tests, so composition is exercised against a fixture
  mind rather than the ambient home.
- All 7 production sites converted (the 5 `cli.rs emit` callers are one site —
  the function). The test-only `notif.rs` site now renders `_for("ling", …)`.

**CENSUS CORRECTION (replacement discipline).** The census table's site-1 row
said `emit_event`'s callers were `cmd_poll` **and `cmd_poll_shell`**, and the
note under it explained that `cmd_poll_shell`'s shell-link recipient has no mind
so the attr is simply absent. **`cmd_poll_shell` does not call `emit_event` at
all** — it is the ADR-0020 RAW-passthrough carve-out (`shellchan::restamp_for_drain`
straight to stdout, guarded by `notify_shell_e2e`), so it never composed an
`<EVENT>` in the first place. `emit_event` has exactly ONE caller. The
mindless-recipient case is therefore not on any shipped path today; it is still
stated in the primitive's doc and pinned by a unit (`no-mind-here` reads nothing
and gets no attr), because that is the property, but doyle's "one sentence in
the module doc suffices" was ruled about a call site that does not exist.

**STATED LIMIT — typed bodies carry no attr.** `is_typed_event_envelope` bodies
pass through verbatim, so notify / user-msg / echo_commune / file_drop deliveries
carry no `mnemonics-json`. Splicing an attribute into a finished envelope means
re-hand-rolling the grammar leg 1 just consolidated. Two consequences worth
stating rather than discovering: (1) machinery envelopes are not peer chatter, so
this is mostly the right answer anyway; (2) the ONE case where it bites is
`wan.rs restamp_wan_user_msg` — a FORGED `user-msg` is re-stamped into a typed
plain-`msg` envelope, which then rides as a typed body and gets no attr. The
trust warning still fires for that sender (it is composed at the WAN edge from
the gate verdict, untouched by this leg), so what is lost is the monic ride, not
the caution. Flagged for T9; a fix would be re-composing the restamp through the
owner-aware renderer, which needs the recipient threaded into `restamp_*` and is
not W5 scope.

**REQ work.** `REQ-MONIC-DELIVERY-TRIGGER` minted (impl+unit ACTIVE, doc deferred
to T9 with the sender-match limit stated honestly). `REQ-MONIC-STORE`'s
record-shape sentence AMENDED BY REPLACEMENT to carry the committed-additive
trigger-spec field: a pre-field record reads as a sender trigger, so W5 records
never migrate.

**Evidence.** clippy workspace clean · `traceable-reqs check` exit 0 ·
spt-proto 61 / spt-msg 44 / spt-store 382 / spt --bin spt 493 / spt-daemon
`inject:: notif:: wan::` all green (the `adapter_translate_proof_gates_on_commit`
red is the known `--bin spt` filter artifact — it passes once the
`translate_proof_fixture` `[[bin]]` is built, verified again here).
**Mutation-proven** against a green baseline, restore sha256-verified on all
three touched files: M1 husk-counts-as-match → 4 reds incl. the store agreement
row; M2 attr-always-present + M3 attr-before-`from` → 5 reds; M4 raw-splice
(escape bypassed) → 3 reds incl. the metacharacter row.
**Smoked on the real binary** (throwaway `SPT_HOME`, torn down): a classified
sender's message arrives
`<EVENT type="msg" from="doyle" mnemonics-json="[{&quot;peer&quot;:&quot;doyle&quot;,…}]">`
on BOTH the live listener stream and the `api poll` hook drain, byte-identical;
an unclassified sender's arrives with no attr on both.

#### W5 T9 — DONE (todlando) — the docs leg, doyle's seven-item checklist

**Docs-only change: ZERO `.rs` files touched.** The whole delta is
`docs-site/src/networking/monics.md` (new), `knocking.md`,
`instances/overview.md`, `SUMMARY.md`, `CONTEXT.md`, `traceable-reqs.toml`.

**New guide — `networking/monics.md`, "Monics and the trust warning."** One
page, because the two halves only make sense against each other: an access rule
says a peer MAY SPEAK, a monic says what you DECIDED about them, and the warning
is the gap between them. Sections: mind-tier/per-peer storage with the
merge-substrate argument · add/update refusing opposite states · the unreadable
row (delivery edge and listing deliberately say different things, each useful to
its own reader) · clone through the fork seam · never-elevation-gated with the
"checked, not intended" phrasing · the warning with its default block quoted ·
the three non-warning cases AS A TABLE with the wildcard row beside them · the
unprovable-sender limit with its unnamed-sender block quoted so the text
discipline is visible rather than described · the override with its partial
scope and the writes-gated/reads-never asymmetry · the storage asymmetry under a
heading that says it is deliberate · the envelope attr with the sender-match
limit.

**Doyle's seven items, one by one:**

1. **Sender-match limit** — stated in the guide ("Today the trigger is sender
   match only") AND in CONTEXT §mnemonics. CONTEXT's "content / sender / custom
   json" sentence KEPT as the destination, but it now SAYS SO in as many words
   ("*That trigger list is the DESTINATION.*") with a SHIPPED TODAY (W5) clause
   under it. **A destination sentence a reader takes for shipped behavior is the
   pasted-activation-claim class** — the same shape caught three times in the
   registry at W4 — so marking it was worth the two clauses.
2. **Restamp limit** — named `restamp_wan_user_msg` EXACTLY, but **in
   REQ-MONIC-DELIVERY-TRIGGER's text, not the guide** (amended by replacement).
   A public docs-site page is the wrong home for an internal Rust symbol; the
   requirement is where a future wave looks and it is durable. The guide states
   the same limit in user language (a message whose sender could not be proven
   is re-stamped into a typed envelope, so that sender's monic does not ride —
   the trust warning still fires, so what is lost is the note, not the caution).
   The amended text also names the lift (thread the recipient into `restamp_*`)
   and says it goes through the BOARD, not a neighbouring change.
3. **Storage asymmetry, one sentence per side** — guide §"Why the two are stored
   differently on purpose" (two bullets, one per side) and CONTEXT §trust warning
   carries the same pair. Both are worded as a DECISION so the next reader does
   not tidy one to match the other.
4. **Fork conflict-exclusion clause** — `instances/overview.md` §"What a fork
   carries": whole mind tier at tip, enumerated from the tree; the live-role.md
   field loss named as the paid-for evidence; EXACTLY ONE EXCLUSION (unresolved
   conflict artifacts) stated as a decision, because a fork starts clean.
5. **Unprovable-sender unsuppressible-warn limit** — guide §"A sender nobody can
   name is still warned about", with the actual unnamed-sender block quoted. The
   text discipline is shown, not asserted: the block names the admitting RULE
   and prints no classify command, and the section closes on WHY (an instruction
   the reader cannot carry out is what turns an unsuppressible warning into noise
   an agent learns to skip).
6. **Doc-stage flips** — all six deferred reqs plus REQ-FORK-MIND-COMPLETE now
   `["doc", "impl", "unit"]`. Every one has a REAL `<!-- [doc->REQ-*] -->` tag on
   the doc section that states its contract — none at a file top.
   REQ-FORK-MIND-COMPLETE's comment previously said "no doc evidence exists: the
   fork's public contract does not describe which files ride"; that sentence was
   REPLACED, since item 4 is precisely what made it false.
7. **`xtask gen` no-op** — confirmed: `gen` rewrote `cli/reference.md` and git
   reports it UNMODIFIED. The per-task regen discipline held. `xtask check` OK.

**CENSUS CORRECTION IN CONTEXT.md (replacement discipline, TWO stale claims).**
Tagging §trust warning meant reading it against what shipped, and it did not
match:

- it said the warning fires when "the effective posture is **closed** ∧ the
  message passed the gate" — the shipped condition is the GATE'S VERDICT saying
  an **ENTRY** admitted the sender. Not a paraphrase: posture-closed-and-passed
  is silent about same-node and Reply, both of which pass a closed posture and
  **never warn**. The three non-warning cases were **absent from CONTEXT
  entirely** — they were ratified 2026-07-31 and had only ever been written into
  the REQ. Replaced with the verdict formulation and the three cases, plus the
  warning-wildcard row.
- it documented the override surface as
  `spt endpoint trust-warning [--id <ID>] [--overwrite]`. **That surface never
  shipped** — it is a pre-build sketch. The real one is
  `<show|set <text>|reset> [--owner <id>]`, reads ungated, and the scope is
  PARTIAL. Replaced.

Both were corrected by REPLACEMENT of the sentence, not by a note beside it.

**BUILDER CALL FOR DOYLE — no KNOWN-HAZARDS entry. Recommendation: NONE, and
this is the one T9 judgement worth ruling on.** The wave's one field-paid defect
is the fork enumeration copy (live-role.md silently dropped), which IS
hazard-shaped and has real evidence. I did not file it, because the hazard list
is a conformance checklist of `REQ-HAZARD-*` requirements each owing a test, and
this invariant ALREADY has a requirement (REQ-FORK-MIND-COMPLETE) whose gate is
exactly the test a hazard entry would demand — a fork carrying a file NO CONSTANT
IN THE CRATE NAMES. A second entry restating it would be a duplicate that can
drift from its own guard, which is worse than no entry. Nothing in T5 or T8
turned up a hazard on real evidence. Rule if you disagree; the entry is cheap to
add and I have the wording ready.

**Evidence.** `traceable-reqs check` exit 0, with all seven reqs reading
`+doc +impl +unit` · `cargo run -p xtask -- gen` = NO diff on the generated
reference · `xtask check: OK` · `cargo clippy --workspace --all-targets -D
warnings` clean · **`mdbook build` clean, and the rendered HTML checked rather
than assumed**: `networking/monics.html` exists, `id="the-trust-warning"` is
present, and both new cross-links resolve to it
(`knocking.html` → `monics.html#the-trust-warning`,
`instances/overview.html` → `../networking/monics.html`). No test suites run and
none claimed — zero `.rs` changed, and clippy `--all-targets` already compiled
every test target.
