# W3 JIT — comms-text family (#170 #171 #175 #176 #160)

## STATUS 2026-08-19: LANE CUT, SCOPED, NOTHING BUILT YET.

Lane `fix/w3-comms-text`, base **`825c214`** (the W2 tip) — doyle's ruling, asked and answered:
the assembly is a merge CHAIN in declared order (register stack → 0c86b2d → #164 → #178 → hertz →
W2 → W3), so a stacked base costs nothing at assembly while an independent base buys a textual
merge in the prose W2 just rewrote. W3 also mints REQs, so the `traceable-reqs.toml` conflict
argument applies verbatim. If W2's legs force a fixup it lands as a NEW commit on the W2 branch and
`825c214` stays an ancestor — nothing rebases.

W2 itself: shape ACCEPTED @`825c214`, all five judgement calls accepted, **final verdict pending
doyle's legs**. Lane stays UNPUSHED. Do not re-open #178 (PASS @`668dd6a`, pushed).

---

## The five items, each scoped at code

### #160 — `endpoint monic --help` grows a trigger table  (type: ADDITION)
**Cheapest and lowest-risk. Do it first.** The precedent is exact and already load-bearing:
`cli.rs:2004 SURFACE_HELP_SITES` + `after_help_at` (`cli.rs:2035`) + `wire_surface_sections`
(`cli.rs:2048`), composing from `spt_store::access::surface::control_surfaces()`.

Mirror it: a table-driven composer in `spt-store::monic` over `TriggerKind`, sited at
`&["endpoint","monic"]`. Copy the composer's ratified properties verbatim from `access.rs:324-390`:
- composed AT RENDER TIME from the table, never a maintained literal (a kind appears by existing);
- body split over an arbitrary table so a test can pass an INVENTED row and prove table-drivenness
  rather than inferring it from a snapshot where every row happens to agree;
- **PLAIN TEXT, no inline Markdown** — `access.rs:340` records why (`helpfmt` styles the CLI copy,
  and a second reader that never passes through that renderer would see literal asterisks).
- Third column is `TriggerKind::has_live_consumer()` (`monic.rs:110`), which already exists for
  exactly this purpose: "a surface can say so honestly rather than implying a match that will never
  fire." `user-input` / `agent-output` are ratified-but-INERT and the row must say so.

Row content matches the published docs table `docs-site/src/networking/monics.md:412-418`
(kind | watches | evaluated today). Docs table stays; the help composes the same words.

### #171 — reaching a `MSG`-less endpoint reports the wrong thing  (type: BUGFIX)
Wanted: `NO_PERCH:<id> is not listening` → `DENIED:some messages to <id> are blocked`.

Four `NO_PERCH` print sites, and **they are not one bug** — classify before touching:
- `cli.rs:10879` (`WanSendOutcome::NotFound`), `cli.rs:11105` (`RingOutcome::NoPerch`),
  `cli.rs:10833` (`SendOutcome::NoPerch`), `cli.rs:11557` (self, "this session owns no endpoint").
The last one is a DIFFERENT sentence about the caller, not the target — out of scope, leave it.

⚠ **UNMEASURED, and the whole item turns on it:** whether a msg-surface denial actually reaches
these sites as `NoPerch` at all. `wan.rs:861` returns `NoPerch` purely from `!perch_exists(target)`
— an EXISTENCE test, with no access consultation — and there is already a separate
`WanSendOutcome::Refused` → `WAN_REFUSED` (`cli.rs:10863`) for the access gate. So the reported
symptom may be the LOCAL path only, or may be a resolution-layer filter upstream of both.
**Measure which outcome a real MSG-denied endpoint produces BEFORE writing any text.** If it is
already `Refused`, this is a reword of the refused line, not the NO_PERCH line — and a reword of
NO_PERCH would then be a NEW bug (it would relabel genuinely-absent perches as denied).
Do not assume the issue's diagnosis; it names a symptom, and the symptom has two possible seams.

### #175 — `spt subnet create` key screens  (type: CHANGE)
Sites: admin-key screen `cli.rs:11952-11974` (`─── ADMIN KEY ({ceremony}) — shown once, here, and
never again ───` at `:11967`, "as a second entry" at `:11974`); member key at `cli.rs:11901`.
Wanted: prominent `--- ADMIN KEY ---` / `--- MEMBER KEY ---` headers, drop "as a second entry",
bulleted feature lists (exact bullet text is IN the issue body — copy it, don't paraphrase).

⚠ Two guards:
1. `cli.rs:31482` asserts `phase.contains("ADMIN KEY (create)") && phase.contains("never again")` —
   an existing unit pins the current header spelling. It must move WITH the text, in the same
   commit, or the reword reads as a break.
2. `["subnet","create"]` is in `SURFACE_HELP_SITES` (`cli.rs:2023`) — the surface section already
   rides this verb's help. New bullets must not duplicate what that section says.
The header is a CEREMONY screen, not `--help`, so the plain-text/Markdown rule above does not
bind it — but check which renderer it passes through before assuming either way.

### #176 — 'home' → 'anchor' ubiquitous language  (type: CHANGE)
**The sharpest item, and the one carrying a declared conflict.**

`CONTEXT.md:864` currently ratifies the OPPOSITE, verbatim:
`_Avoid_: "anchor subnet" (the engine-room's subnet is its *home subnet*)` — ratified at the
2026-07-28 access-control grill. #176 asks for exactly the word that line forbids. This is a
**reversal of a ratified glossary avoid**, requested by the operator (discord:reavo) and GREENLIT,
so it is legitimate — but it CANNOT be applied silently. The `_Avoid_` line must invert in the
same commit, or CONTEXT.md would sit in direct contradiction with the shipped vocabulary and the
next reader would treat the new word as a drift bug.

⚠ **Scope boundary — declare before building.** "Ubiquitous language" is the VOCABULARY.
`spt_store::info::PerchInfo.home_subnet` is an ON-DISK serde key in every perch `info.json`, and
`spt-net` pairing/wire carries it. Renaming the serde key is a STORAGE + WIRE break that would make
every existing perch read as anchor-less. **Plan: rename the vocabulary (docs, CLI text, CONTEXT,
comments, and any Rust identifier that is not a serialized key), keep the serde key `home_subnet`,
and if the field name itself is renamed, carry `#[serde(alias = "home_subnet")]` so old records
still load.** ADR-0010's TITLE is "Immutable home subnet" and ADR-0026's filename carries `home` —
an ADR is a dated decision record and its title is history; propose amending the BODY vocabulary
and leaving filenames/titles alone rather than rewriting the record.
Also note `rc.rs:301,314` render "HOME subnet" in the status line — user-visible, in scope.

### #170 — trust warning arrives as a separate message  (type: BUGFIX)
Site: `spt-daemon/src/wan.rs:893-899`, whose comment states the CURRENT design and its reason:
> the trust warning goes FIRST … **its own system-authored delivery under a reserved author, never
> spliced into the peer's body** (a warning inside the body is one a sender could forge).

The operator's complaint is that a separate delivery becomes a separate translation-binary PTY
injection under Claude Code, and wants it "delivered the same unintrusive way as a monic".

**The reconciliation, and it is the whole design of this item:** a monic does NOT ride in the body
either — it rides as an **envelope attribute** (`mnemonics-json`, set by the RECEIVING node). So
moving the trust warning to an envelope attribute satisfies the request while PRESERVING the
ratified unforgeability reason — the sender never authors it either way. This is NOT the
"splice into the body" the comment forbids, and the report must say so explicitly, because at a
glance it looks like the thing that was ruled out.

Cross-check before building: `docs-site/src/networking/monics.md:437` — "**A body that is already
a typed envelope carries no attribute.** Machinery deliveries … pass through verbatim." A trust
warning about a machinery delivery therefore has no attribute carrier, and the fail-safe must be
named rather than discovered. `emit.rs:341,351` holds the sibling fail-safe already.
`REQ-TRUST-WARNING` text will need amending in the same commit (the carrier changes; the rule
"closed ∧ entry-passed ∧ no-monic ⇒ warn" does not) — same shape as the W2 REQ-MSG-SENDER-STAMP
amendment doyle accepted.

---

## Build order (risk-ascending, so early commits are safe to keep if later ones churn)
1. **#160** — additive, precedent exact, no existing text moves.
2. **#175** — bounded text + one unit that must move with it.
3. **#171** — MEASURE FIRST; may be a reword, may be plumbing, may be an issue-diagnosis correction.
4. **#176** — wide sweep + a ratified-avoid inversion; declare scope boundary before the sweep.
5. **#170** — real carrier change + REQ amendment; the only one touching the delivery edge.

## Standing lane facts (carried from W2 — these cost real time to re-derive)
- `docs-site/src/cli/reference.md` is **GENERATED** from cli.rs doc comments. Never hand-edit.
  Run `cargo run -p xtask -- gen` then `check`. Any #160/#175/#176 CLI text edit regenerates it.
- A task notification's **exit code is not the verdict** — measured lying green 3× in one session.
  Read the log body: `grep -E "^test result|_EXIT="`.
- A wait loop that breaks on a condition does not ASSERT it (doyle's #178 F1). Assert what you
  waited for; a cell whose wall-clock equals its timeout budget is announcing that its wait failed.
- New requirement → `traceable-reqs.toml` FIRST, then satisfy it. Run `traceable-reqs check` before
  declaring done.
- Report to doyle WITH declared judgement calls. Fixups ride NEW commits, never amends.
- Push only on doyle's word. Shell is PACER-0; `alchemy-0` is not mine.

## Declared to doyle at scope time (before building)
- #176 vocabulary-vs-serde-key boundary, and the CONTEXT.md `_Avoid_` inversion.
- #170 envelope-attribute carrier as the reconciliation with the ratified no-splice rule.
- #171 diagnosis is unmeasured; the issue names a symptom with two candidate seams.

## doyle's ANSWERS (2026-08-19) — all three PROCEED, two carry corrections that CHANGE the build

**#176 — proceed, TWO corrections.**
1. The `_Avoid_` inversion must carry its supersede **INLINE**, not only in the commit message.
   Write CONTEXT.md:864 as current-truth "anchor subnet" **with a dated note**: ratified home
   2026-07-28, inverted by #176 operator greenlight 2026-08-19. Reason: a bare inversion is what a
   future grill (or doyle's own memory index) re-derives as drift. Operator vocabulary already
   drifted this way — SPT_MANTLE was called the engine-room ANCHOR on 2026-08-05 — so #176 is an
   ALIGNMENT, and the note is what stops the next grill re-litigating it.
2. ⚠ **MY SERDE PLAN HAD A WIRE BUG.** `#[serde(alias = "home_subnet")]` is **READ-compat only**:
   a field renamed to `anchor_subnet` with that alias **WRITES `anchor_subnet`**, and every N-1
   daemon plus every existing info.json reader loses the key. If the field is renamed it must be
   **`#[serde(rename = "home_subnet")]`** (byte-stable BOTH directions) — or keep the field name
   entirely. Storage/wire spelling stays frozen; the vocabulary rename **stops at the
   serialization boundary**. ADR titles/filenames stay: agreed.

**#170 — envelope-attribute reconciliation is sound ONLY with two fences named now.**
1. **Receiver-authored by construction:** the inbound value of that attribute must be
   IGNORED/STRIPPED regardless of what a sender supplies — the unforgeability lives THERE, not in
   the carrier choice. **Unit required**, mirroring `forged_origin_field_is_inert`: a forged
   inbound trust-warning attribute is inert. Without that arm the splice is rebuilt one layer up.
2. **Decide the surface BEFORE building:** if the attribute enters the EVENT envelope that is the
   PUBLISHED adapter surface — additive and N-1-safe only if adapters ignore unknown attributes,
   and the public envelope docs must gain it (**doc stage on the REQ**, plus a perri/emphasys
   release-ping at land). Check whether it is envelope-internal (wan wire only, stripped before
   EVENT) or adapter-visible; **the answer decides the REQ's stages**.

**#171 — endorsed exactly as stated.** Measure which outcome a real MSG-denied endpoint produces;
if the issue's diagnosis is wrong, report it back WITH the measurement — the reword of the WRONG
line would be a new bug, and I named it.

Build order accepted.

## W2 — GATE CLOSED, PASS @`825c214`, PUSHED 2026-08-19
All legs green at tip in doyle's worktree (clippy 0, spt-store 491/491, spt --bins 640/640, treqs
[OK], xtask check OK — real exits, real durations). Records: #167 comment 5348915323, #155 comment
5348915452 (the PARKED sender_proven sub-question is recorded there, unowned, for operator triage),
#154 comment 5348915660. `fix/w2-sender-banner-removal` pushed to origin on doyle's word. **The W3
base `825c214` is now a gated-PASS ancestor.** State flips go through alchemy next (doyle's).

## #160 — BUILT (this session), unit-green in spt-store; CLI legs running
- REQ **`REQ-CLI-MONIC-TRIGGER-SECTION`** minted in `traceable-reqs.toml` (doc/impl/unit) BEFORE code.
- `spt-store::monic`: `TriggerKind::id()` (wire spelling, exhaustive) + `TriggerKind::ALL` +
  `pub struct TriggerKindRow { kind, watches }` + `pub const TRIGGER_KINDS` + `trigger_kinds()` /
  `trigger_kinds_in(table)`. Only `watches` is written down — the wire token and the
  evaluated-today column are DERIVED (`id()`, `has_live_consumer()`), so no second place to learn
  whether `user-input` fires. Plain text, no Markdown, asserted at the composer.
- `crates/spt/src/cli.rs`: sibling const `TRIGGER_HELP_SITES = [["endpoint","monic"]]` +
  `wire_trigger_sections`, both attached through the SAME `after_help_at` seam. ⚠ The test helpers
  called `wire_surface_sections` to rebuild the tree, so a second section would have been invisible
  to every test — introduced `wire_help_sections` (calls both) and repointed all 4 call sites
  (binary + 3 test helpers).
- Units: store — invented-row table-drivenness, live-vs-inert third column, every variant published
  under the serde spelling, no-Markdown/no-internal-code. CLI — rides the sited verb and no sibling
  (containment IFF path in the const, so it follows a siting ruling with no test edit), and the
  rendered help names every token and marks the inert ones.
- Docs: `docs-site/src/networking/monics.md` — doc tag at "What a trigger can watch" + a sentence
  that the help composes the same rows. Then `xtask gen` + `check` (reference.md is GENERATED).
- **doyle GO'd the two flag sites** (named acceptance of my default, verified at source first):
  `monic add` + `monic update` joined `TRIGGER_HELP_SITES`, and BOTH `--triggers` enumerations were
  struck IN THE SAME COMMIT. Precisions honored: (1) only the enumeration sentence struck, the JSON
  example with its one `sender` matcher stays; (2) no pointer sentence — the positive twin asserts
  containment on the add/update help itself; (3) provenance named in the report.
- **#160 COMMITTED @`29c2024`.** Legs: spt --bin spt 642/642, spt-store --lib 489 (package 495),
  clippy 0, treqs [OK] +doc +impl +unit, xtask gen+check OK. Regen diff +7 lines at one site.
- **doyle ACCEPTED #160 with three named accepts, no corrections:** the `wire_help_sections` seam
  fix rides the lane (product wiring, not test rework); the 491-vs-495 reconciliation as stated
  (485 lib + 6 int at his W2 measure + my 4); the positive-twin siting. He LOGGED the reference.md
  no-descent fact for his own gate (register-row vs walk-widening vs declared-residual — his
  ruling, does not block me).

## #175 — COMMITTED @`c9544f7`, all legs green
- REQ **`REQ-SUBNET-KEY-SCREENS-LABELLED`** minted first (doc/impl/unit).
- Both displays lead with `--- ADMIN KEY (create) ---` / `--- MEMBER KEY ---` and carry a
  "What this key is for:" list copied VERBATIM from the issue body. One-shot warning moved to its
  own line under the header. Struck: "as a second entry" and the member blob's duplicate re-pair
  sentence — both absences ASSERTED.
- ⚠ **A PINNED UNIT WENT RED AND THAT WAS THE POINT:**
  `create_prints_joining_material_when_elevated` asserted `out.starts_with(<code>)` — the MEMBER KEY
  header displaced the code by one line. NOT relaxed to a `contains` (which would pass on a blob
  burying the code under the QR): it now splits the first line, asserts it EQUALS the label, and
  asserts the remainder starts with the code.
- ⚠ **FILTER TRAP, caught:** my first run filtered on `subnet` and BOTH pinned rows carry no
  "subnet" in their names — the filter read as coverage while touching neither. The full bin suite
  is what caught the red. Run the whole suite for text changes; a name filter is not a scope.
- Legs (real exits captured BEFORE any pipe): 642/642 `_TEST_EXIT=0`, clippy exit 0 / zero warning
  lines, gen+check exit 0 "xtask check: OK", treqs exit 0 with the REQ [OK]. reference.md UNCHANGED
  — ceremony screens are not help text.
- Declared to doyle and awaiting only if he objects: header keeps the `(create)` ceremony word; the
  removed re-pair sentence loses the "scan the QR for later" half (offer to restore as a 4th
  bullet); `show-code` gains no header.

## #175 — DECLARES RULED, fourth bullet LANDED @`f7e893a`
doyle ruled the five declares 4-accept/1-action: `(create)` ceremony word ACCEPTED (create/rotation
discriminant, load-bearing at two call sites), warning-on-its-own-line ACCEPTED, `show-code` stays
bare ACCEPTED, scope hold ACCEPTED — and ACTION: RESTORE the QR arm as a fourth bullet, because the
struck sentence had TWO arms and the issue's list duplicates only the show-code one. Also named: the
red-first tightening on the joining-material unit accepted as-is, and the subnet-filter self-catch is
the filters-read-as-coverage class.
`f7e893a` (new commit, amend forbidden): fourth bullet `- scan the QR above with your auth app to
pair again later`, the absence assertion kept AND given a positive twin that fails if the arm goes
missing again, overview.md's member paragraph carrying the same arm. Legs, exits captured before any
pipe: bins 642/642 exit 0, clippy 0, treqs 0, gen+check 0, reference.md UNCHANGED.

## #171 — MEASURED 2026-08-19; the issue's DIAGNOSIS IS WRONG. Awaiting doyle's ruling.
Measurement (`measure_171_denied_msg_is_refused_not_noperch`, one variable, perch ABSENT in BOTH
arms, exit 0): denied-msg ⇒ `Refused`; allowed-msg ⇒ `NoPerch`. The deny at `wan.rs:833` PRECEDES
the existence test at `:861`, so a MSG denial never reaches `NoPerch`. Two landed units already
leaned on this unnamed.
A denied send already renders `WAN_REFUSED:<t>@<node> — the receiver denied the message (access
gate)`: `WanOutcome::Refused` → wire `refused` → `WanRequestOutcome::Refused` →
`WanSendOutcome::Refused` → `cli.rs`.
The three in-scope sites CANNOT be the denial: local `deliver::send_windowed` is pure existence and
same-node is `Allow(SameNode)` (a local send to a MSG-less endpoint is DELIVERED); `NotFound` is
sender-side resolution, judged before the dial; `cmd_ring` has NO WAN leg at all.
The reword would break TWO things: it relabels genuinely-absent perches, AND the one seam that shows
NO_PERCH on a real denial is a DISCOVER deny, where "DENIED" LEAKS the existence of a deliberately
hidden endpoint (ratified 3× in `wansend.rs`: "Existence is not advertised"). DISCOVER is default-ON
since #180, so on a current build a stranger RESOLVES and gets WAN_REFUSED; NO_PERCH survives only
for an explicit DISCOVER deny, an N-1 peer, or a genuinely absent perch.
Offered doyle three options (recommended the third): close not-a-bug / sharpen WAN_REFUSED toward the
issue's INTENT ("some" is honest — per-sender, per-surface, reply exemption passes) / get reavo's
repro first.
⚠ **UNCOMMITTED IN THE TREE:** the measurement test in `crates/spt-daemon/src/wan.rs`. It is worth
keeping as a regression pin (denial and absence distinct, deny FIRST) but needs a REQ, and that turns
on doyle's ruling. Commit it with its REQ or strip it — do not let it drift.

## #171 RULED by doyle — measurement ACCEPTED IN FULL, refined option 3
Named accepts: the one-variable both-arms design is exactly what MEASURE-FIRST meant; the
three-site classification holds; face (b) (a DENIED label on the DISCOVER seam leaks a hidden
endpoint's existence) is DECISIVE and kills the literal ask independent of the repro. #175's fourth
bullet + positive twin accepted as landed.
1. COMMIT the measurement as a permanent pin with its OWN REQ, framed as the invariant it witnesses
   (denial ≠ absence, deny arm FIRST), NOT as a #171 resolution — DONE: `REQ-WAN-DENY-PRECEDES-
   EXISTENCE` (impl+unit, no doc/int, both declared) @`88177eb`, with the mint landing @`19de6a0`.
   ⚠ I staged only the source at `88177eb`, so THAT sha is treqs-RED; `19de6a0` repairs it forward
   (amend forbidden). doyle's gate must measure the TIP, not that commit. Say so in the report.
2. #171 itself goes STOP-AND-REFER — **doyle's action, not mine**: he comments the measurement,
   flags NEEDS-OPERATOR with the three repro questions. I do NOT close it and do NOT reword.
3. Option 2 (sharpen WAN_REFUSED toward "some messages … are blocked") is DEFERRED, not rejected —
   it rides the operator's answer.

## #176 — LANDED @`43dc3943`, all legs green, report sent with five declared calls
Legs (real exits before any pipe): gen 0, check 0 (no drift), bins 642/642, spt-store 489/489,
spt-daemon 854/854, clippy 0, treqs 0 with the REQ [OK], e2e trio 9/9.
Declared: (1) THREE tokens moved, not the two I scoped — `ANCHOR_REFUSED` was found mid-build and
is asserted by two e2e files, both updated; (2) ⚠ I REVERSED doyle's agreed position on ADR BODIES
— they keep their words and gain a dated note instead, because a re-spelled record cannot be told
from an original; same for the ROADMAP delivered log and F-0xx notes; (3) Rust identifiers do not
move (`assign_home`, `CreateHome`, `BindError::Home`, `home.rs`); (4) REQ ids carrying HOME stay;
(5) my REQ title broke the TOML parse with raw quotes — treqs exits **2** with `manifest_error`,
which is what an unescaped quote looks like from outside, NOT a coverage failure.

## #176 — build notes, REQ `REQ-VOCAB-ANCHOR-SUBNET` (doc/impl/unit, no int)
Swept so far: CONTEXT.md (glossary term renamed + the `_Avoid_` line INVERTED with its inline dated
supersede naming both dates; 9 more prose sites), PRD, docs/MANIFEST, docs-site (api, echo-commune,
manifest, instances/overview, engine-room-setup, networking/overview), Rust prose in `crates/spt`
(40 + 11 phrases), the THREE user-visible tokens (`ANCHOR_NOT_MEMBER`, `NO_ANCHOR_SUBNET`,
`ANCHOR_REFUSED` — the third asserted by TWO e2e files, both updated), a unit pinning the two notify
tokens AND the retired spelling's absence, and the FREEZE note at `PerchInfo.home_subnet`.
STILL TO DO: the `spt-daemon`/`spt-store` doc-comment prose (script ready at
`scratchpad/sweep_daemon.py`), `xtask gen` regen of reference.md, then the full leg set.
DECLARED BOUNDARY (report it): dated records are NOT rewritten — ADR-0010/0026 keep title, filename
AND body, each gaining a dated vocabulary note instead; the ROADMAP delivered-milestone log and
`docs/F-030-*` design doc likewise; REQ ids carrying HOME stay; and the serde key `home_subnet`
stays by KEEPING THE FIELD NAME (no `rename` needed, no `alias` ever — doyle's correction).

## OLD NEXT — #171, and it OPENS WITH A MEASUREMENT, not an edit
Three in-scope sites (`cli.rs` `WanSendOutcome::NotFound`, `RingOutcome::NoPerch`,
`SendOutcome::NoPerch`); the self-arm ("this session owns no endpoint") is OUT.
`wan.rs:861` returns `NoPerch` from a bare `!perch_exists(target)` with NO access consultation, and
`WanSendOutcome::Refused` → `WAN_REFUSED` already exists for the gate. So MEASURE which outcome a
real MSG-denied endpoint produces before writing any text. doyle endorsed this exactly: if the
issue's diagnosis is wrong, he wants the MEASUREMENT reported back, not a reword — rewording the
NO_PERCH line would relabel genuinely-absent perches as denied, which is a NEW bug.
⚠ Line numbers in this file's older sections are PRE-#160/#175; both commits shifted `cli.rs`.
Re-grep, never trust a number here.

## #170 — COMMITTED @`dc1c7532`. Surface resolved FIRST, then the two fences.

**THE DECISION (doyle's fence 2, answered before any edit): ADAPTER-VISIBLE.** Stages doc+impl+unit;
perri/emphasys ping rides doyle's gate close, not the commit. It was DECIDABLE, not preferential:
the warning is text the AGENT MUST READ, so an envelope-internal carrier stripped before the EVENT
would ship a caution that never surfaces. doyle accepted this as the resolution shape.

**THE LOAD-BEARING MEASUREMENT.** Both in-repo delivery surfaces put the RAW EVENT LINE into the
agent's context verbatim: `inject.rs:87` renders `render_event_whole_for` and injects those exact
bytes into the broker-held PTY; the relay listener prints the composed line to stdout. So the
attribute IS the surfacing for spt-hosted Claude Code — the complaint that opened #170 — with NO
adapter update needed. Adapter dependency is confined to a custody pipeline that re-renders.

**SECOND FACE (why the doc stage is load-bearing, not paperwork).** For `mnemonics-json`, "adapters
ignore unknown attributes" is harmless. For a trust warning, BEING IGNORED IS THE FAILURE. N-1
safety and the caution's delivery point in opposite directions, so the envelope doc MUST bind a
re-rendering adapter to surface it (precedent: monics.md:376 already binds carrying monics intact).

**CARRIER — no wire, no spool change.** The relay frame is rigid (`len(from)`, `from`, `body`) and
the spool is a SQLite row; widening either is an N-1 break INSIDE one node. Instead the WAN edge
composes the envelope itself through the emit renderer (so a matched monic still evaluates under the
one rule — monics.md:403 requires the two never disagree) and hands the finished typed envelope down
the existing legs, where `is_typed_event_envelope` passthrough carries it verbatim. Precedent at this
exact edge: `restamp_wan_user_msg` already hands a re-composed envelope down these same legs.

**FAIL-SAFE, NAMED NOT DISCOVERED** (the monics.md:444 cross-check paying out): a body that is
already a typed envelope carries no attribute, so there the warning KEEPS its own system-authored
delivery. The second injection survives exactly where no carrier exists and nowhere else.

**FENCE 1 HAS TEETH.** An inbound typed envelope rides verbatim TODAY, so a peer can hand us an EVENT
line carrying its own `trust-warning`. Every sender-supplied ingress strips it BEFORE anything is
attached. Unit mirrors `forged_origin_field_is_inert`.

**doyle's POINT-7 CRITERION — I BUILT THE FIRST ARM (class, not name).** A forged `mnemonics-json`
rides the same passthrough; the window PREDATES this change. His criterion: build the class arm IFF
one ingress arm can inertize the class as cheaply as the name. It can — `RECEIVER_COMPOSED_ATTRS` in
spt-proto — so the strip covers both. Legit author fields (`type`, `from`, `notif_id`, alarm times)
ride end-to-end intact; a key outside `[A-Za-z0-9_-]` is DROPPED rather than re-emitted unescaped.
⚠ The mnemonics face's own unit + tag are DELIBERATELY NOT in this lane — doyle files it at gate
close as remaining work under its own REQ. Say which arm was built in the report; I built the CLASS.

### Code shape
- `spt-proto/event.rs`: `EVENT_ATTR_TRUST_WARNING`, `RECEIVER_COMPOSED_ATTRS`,
  `strip_receiver_composed_attrs` (identity when nothing to strip — asserted as `Cow::Borrowed`, so
  the common path never silently becomes a parse-and-recompose of every delivery).
- `spt-msg/emit.rs`: `compose_line_warned_at` + `render_event_whole_warned_for`, which RETURNS
  whether the warning found a carrier — a return, not a second `is_typed_event_envelope` call at the
  caller, so the fail-safe cannot drift from the composition it guards.
- `spt-msg/deliver.rs`: `sender_supplied()` = trim + class strip, at `send` / `send_deferred` /
  `send_windowed`.
- `spt-daemon/wan.rs`: `owed_trust_warning` (decide+compose) / `deliver_warning_alongside` /
  `claim_warning_delivered` / `warn_alongside` (deliver-then-claim ORDER owned by production, not
  reassembled per caller); `deliver_admitted` split out so the cascade is a VALUE — the cadence claim
  turns on whether the message reached the agent, and three early returns cannot be asked that.
  `WanOutcome::reached_the_agent()`. Ingress strip sits right after `restamp_wan_user_msg`.
- REQ mint + `REQ-TRUST-WARNING` amended by replacement, both in the same commit.

### Legs at `dc1c7532` (real exits captured BEFORE any pipe)
gen 0 · check 0 "xtask check: OK" · reference.md UNCHANGED (no CLI text moved) · spt --bins 642/642
exit 0 · spt-store 489/489 exit 0 · spt-daemon --lib 857/857 exit 0 (was 854 — the three new WAN
units) · spt-proto + spt-msg libs 49 + 64 exit 0 · clippy exit 0, zero warning lines · treqs exit 0
with `[OK] REQ-TRUST-WARNING-ENVELOPE +doc +impl +unit`.

### ⚠ FIVE INTEGRATION SUITES RED ON THIS BOX — ALL ENVIRONMENT, NONE AN ASSERTION ABOUT DELIVERY
`cargo test -p spt-daemon --tests` on a live-agent node. Classify before believing:
- `attach_resize_capture` 3 red → **PRECONDITION, PROVEN**: needs a prebuilt fixture the invocation
  never builds. `cargo build -p mock-adapter --bin capture-player` then 5/5 GREEN, exit 0.
- `endpoint_survival` 3 red → `bind broker at the well-known name: PermissionDenied`. MY OWN live
  perch's broker holds that name; I cannot free it without going offline.
- `inject_control_wedge` 1 red → failed its own SANITY precondition ("the translation child must be
  live before teardown"), a pid-discovery race. Its 18 other rows — including
  `wan_arrival_to_idle_spt_hosted_injects_with_no_hook_poll` — PASSED.
- `notif_drain_validity` 1 red → `UNIQUE constraint failed: notifs.notif_id` in FIXTURE SETUP.
- `psyche_event_turn_e2e` 1 red → missing path + `MANIFEST_DEAD_KEY:mock`.
- `redispatch_stall` 1 red → "saw 0" over a 30s bound; starvation on a saturated box.
The suites that actually cover this change — `access`, `dispatch`, `wanmsg`, and inject_control_wedge's
WAN/inject rows — are GREEN. ⚠ ALSO: cargo STOPS after a failing test binary, so the first run never
reached the suites after `attach_resize_capture`; the real sweep needs `--no-fail-fast`.
⚠ The old "do not run spt-daemon --lib on this box (seedmap convoy deadlock)" trap is STALE — the full
lib suite ran clean twice at 857/857.

### The five reds, RESOLVED BY MEASUREMENT (not by classification) — 2026-08-19
I had classified all five as "environment" from their diagnostics. That is the thing my own
live-context lesson forbids, so each was measured. Rerun individually, serially, with the env scrub
(`env -u OWL_SESSION_ID -u SPT_AGENT_ID -u SPT_ENDPOINT_ID`):
- `inject_control_wedge` **19/19 exit 0** · `notif_drain_validity` **3/3 exit 0** ·
  `psyche_event_turn_e2e` **2/2 exit 0** — pure CONTENTION on a saturated box; they pass alone.
- `endpoint_survival` and `redispatch_stall` STILL red alone → measured at the BASE sha `43dc3943`
  (detached checkout, my work already committed so it was safe), one variable, same box:
  **BASE endpoint_survival 1 passed / 3 failed; BASE redispatch_stall 5 passed / 1 failed —
  IDENTICAL counts to my tip. PRE-EXISTING, PROVEN, not mine.** Branch restored to `dc1c7532`.
- ⚠ A tell I nearly missed: `endpoint_survival`'s failing SET SHIFTED between two runs at the SAME
  sha (a row that passed in run 1 failed in run 2, different line numbers). A failure set that moves
  under a fixed tree is announcing environment-dependence — worth reading as a signal rather than
  noise, and it is what made the base measurement obviously worth its cost.

### ⚠ SESSION RESTART MID-TASK — the reload's brief was WRONG about this lane
The CC process exited (killing nine background legs) and the reload's `<project-context>` described
KEYSTONE #182 W1 on `build/keystone-182-w1-access-gate`, and carried a line asserting
`fix/w3-comms-text` is "not part of this lane's active work" — a disclaimer aimed squarely at the one
command (`git branch`/`git status`) that exposes the mismatch. Ground truth: this lane, uncommitted
#170 diff intact. Filed as the fifth face of `resumed-session-reground-before-acting`: a stale brief
can pre-emptively explain away the ground-truth signal that would correct it, and this session's own
evidenced record outranks testimony written before it happened.

## W3 GATE CLOSED — PASS @`dc1c7532`, PUSHED 2026-08-19
doyle's rig, all legs green: bins 642/642, spt-store 495 (package count — my 489 is the lib figure),
spt-daemon lib 857/857, five REQs [OK], regen content-drift ZERO (his porcelain `M` was an EOL smudge,
`git diff` empty). Records: #160 comment 5349825086, #175 5349825579, #176 5349827544,
#170 5349829254. Pushed on his word: `origin/fix/w3-comms-text` =
`dc1c75328272d8b193bfff01d6f18e2130ec616a`, rev-parse verified against the gated sha — tested sha ==
pushed sha, nothing rebased. Assembly chain gains the W2→W3 tip.

Rulings at close:
- POINT-7 class arm ACCEPTED, cost-parity criterion met. The mnemonics unit+tag is now doyle's OWN
  FILED REQUEST — not mine, do not build it.
- #171 CLOSED **CUT** by the operator (older build, no repro). My `REQ-WAN-DENY-PRECEDES-EXISTENCE`
  pin rides regardless — the invariant outlives the ticket that surfaced it.
- The two pre-existing box reds (`endpoint_survival`, `redispatch_stall`) are recorded in the #170
  record as GOLDEN-TRIAGE BASELINE, explicitly not attributed to this lane.
- Both my rig facts banked to doyle's gate memory: `--tests` fail-fast tail-silence, and the stale
  daemon-lib trap. He confirms the seedmap-convoy refutation on a THIRD clean run and is updating
  that entry with the mechanism kept dated — a refuted trap is amended, not deleted.
- CONCIERGE W3 was the LAST BUILD WAVE. No queued dispatch; todlando is CLEAR.
