# #209 — inject-path census (measure-first, report-before-build)

todlando, 2026-08-22. Source read at **c62904e7** (= main = v0.59.0) in
`.worktrees/fix-209`. Every `file:line` below is at that sha.

Structured so **#200 consumes the same frame** (§6): the census asks, for each
path, *where the rule is DECLARED* and *where it is ENFORCED*, and whether an
arm reaches the terminus without crossing the enforce site.

---

## 1. Spot-check of doyle's filing measurements

All three confirmed at source, independently:

| doyle's claim | verdict | evidence |
|---|---|---|
| lock is SOUND, lives in `access_check_with_sender` step 0, BEFORE the same-node allow | **CONFIRMED** | `access.rs:323` fn head; step 0 `engine_room_inbound_refusal` at `:334`; step 1 same-node `Allow(PassReason::SameNode)` at `:339-341`. Comment names the same-node agent as the adversary, verbatim. |
| chain consulted at `wan.rs ~901` (wire receive) and `dispatch.rs ~1929` (presence probe), and those are the ONLY prod `surface::MSG` consults | **CONFIRMED, and now authoritative** | prod call sites of `access_check_with_sender` = exactly `wan.rs:898` and `dispatch.rs:1929`. Every other hit is a test: `access.rs:688-1417` (mod tests), `redeemop.rs:714-1214` (all inside the `#[cfg(test)] mod tests` opened at `redeemop.rs:417`). Plain `access_check(...)` has **zero** prod callers — all 20 hits are tests. |
| field proof: unsolicited local `spt send engine-room` delivered, zero `ENGINE_ROOM_INBOUND_LOCKED` | **CONFIRMED BY CONSTRUCTION** | `crates/spt/src/*` and `crates/spt-msg/src/*` contain **zero** occurrences of `access_check`, `InboundClass`, `classify_inbound`, or `engine_room_inbound` — the whole CLI-side and message-primitive surface never consults the chain at all. |

## 2. The census — every path terminating in a local delivery

Three delivery TERMINI exist on this node, and every path below ends in one:

- **T-INJECT** — `spt_daemon::inject::try_spt_hosted_inject` → `Brain::inject_endpoint`
  → broker `dispatch_endpoint_input` (`broker.rs:7069`) → `ENDPOINT_INJECT:<id>`.
  `try_spt_hosted_inject` (`inject.rs:75`) is the ONE shared PTY-inject leg —
  every injecting path funnels through it.
- **T-TCP** — `spt_msg::deliver::deliver_tcp` (`deliver.rs:96`) → the target's
  `PollListener` (`spt-msg/src/listener.rs`, `check_message` at `:130`).
- **T-SPOOL** — `spt_store::spool::spool_message_{at,windowed_at,deferred_at}`
  (`spool.rs:336/348/365`), drained later back into T-INJECT.

| # | Path | Entry site | Terminus | Chain? | Class fed |
|---|---|---|---|---|---|
| P1 | WAN wire receive | `wan.rs:898` | `deliver_admitted` `wan.rs:1041` → T-TCP / T-INJECT / T-SPOOL | **YES** | `classify_inbound` (Reply\|Unsolicited), `sender_proven`, `sender_origin` |
| P2 | WAN presence probe | `dispatch.rs:1929` | answers only, writes nothing | **YES** | `Unsolicited` hardcoded |
| P3 | Local CLI `spt send` (default / `--prefer-native`) | `cli.rs:10846` | T-INJECT | **NO** | — |
| P4 | Local CLI `spt send --force-native` | `cli.rs:10791` | T-INJECT | **NO** | — |
| P5 | Local CLI send fallback | `cli.rs:10849`, `:10853` → `deliver::send_windowed` `deliver.rs:216` | T-TCP or T-SPOOL | **NO** | — |
| P6 | Send-time already-parked drain | `cli.rs:10866` `drain_idle_spool` | T-INJECT | **NO** | — |
| P7 | Idle-EDGE spool drain | `livehost.rs:266` | T-INJECT | **NO** | — |
| P8 | Pulse-tick NATIVE drain (ER briefing belt) | `broker.rs:2072` `drain_spool_native` | T-INJECT | **NO** | — |
| P9 | API-side drain | `spt/src/api/delivery.rs:321` | T-INJECT | **NO** | — |
| P10 | `spt ring` broadcast | `ring.rs:173,183,190,205,233` → `deliver::deliver` `deliver.rs:128` | T-TCP or T-SPOOL | **NO** | — |
| P11 | Subnet notify | `notif.rs:220` `send_windowed` (WINDOW_ACTIVE_ONLY) | T-SPOOL | **NO** | — |
| P12 | ER session briefing | `briefing.rs:440` `spool_briefing_at` (author `BRIEFING_AUTHOR`) | T-SPOOL → P8 → T-INJECT | **NO** | daemon-authored, self-addressed |
| P13 | Shell relay frames | `shellchan.rs:235` `spool_shell_frame` | T-SPOOL (shell perch, never the ER perch) | **NO** | — |
| P14 | Translation-fault respool | `broker.rs:4712` `respool_envelope` | T-SPOOL | n/a | re-entry of an ALREADY-delivered envelope |
| P15 | Trust warning | `wan.rs:820-831` | T-TCP / T-INJECT / T-SPOOL | rides P1's admission | daemon-authored |
| P16 | Relay-listener accept (receiving half of P5 / P1-TCP) | `spt-msg/src/listener.rs:130` | harness directly | **NO** | — |
| P17 | Shortform adapter relay (`@<…@>`) | adapter-side; zero occurrences of "shortform" anywhere in `crates/` — it exits through the `spt send` CLI | = P3 | **NO** (inherits P3) | — |

**Score: 2 gated, 15 ungated.** Both gated paths are wire-inbound. Every
local-origin path is ungated, exactly as #209 states — and the bypass is
broader than "a same-node agent send": `ring`, `notify`, the API drain and the
relay listener are each an independent arm to the same termini.

## 3. Why one gate site is reachable, and where it is not

`deliver_admitted` (`wan.rs:1041`) is already the WAN side's single
post-admission cascade. **There is no local counterpart** — `cmd_send` inlines
the same cascade three times (`cli.rs:10791`, `:10846`, `:10849/:10853`), and
`ring` / `notif` each inline their own.

Gating at the three TERMINI is the wrong shape: T-SPOOL is written by both
arrivals and re-entries (P14 respool; P6-P9 drains reclaim rows), so a terminus
gate double-charges and would refuse the daemon its own re-spool.

The shape that holds: **one local admission function mirroring
`deliver_admitted`** — run the chain, then the local cascade — with P3/P4/P5,
P10 and P11 routed through it. Drains (P6-P9) then carry admission BY
CONSTRUCTION from enqueue time and need no second check.

### The spool-drain question you reserved

**Recommendation: gate at ADMISSION (enqueue/entry), never at drain.** Facts:

1. Spool rows carry no admission stamp today (`from_id`, body, window,
   deferred, `op_id` for WAN) — a drain-time gate would have to re-derive the
   origin, and for a locally-spooled row there is nothing to re-derive from.
2. WAN rows are admitted at receive and claimed exactly-once by `op_id`
   (`spool::spool_wan_message_at`). Re-checking at drain re-opens a settled
   decision: a row admitted while posture was Online would silently evaporate
   if the controller detached before the drain tick.
3. **P12 is the decider.** The ER session briefing is *daemon-authored and
   self-addressed* — it spools straight onto the ER's own perch under
   `BRIEFING_AUTHOR` and is drained by P8. A drain-time gate refuses the engine
   room its own briefing. An admission-time gate exempts it for free, because
   `spool_briefing_at` never crosses the admission function.

## 4. #211 seam constraint — where the tier slots, and what forecloses it

Chain order today (`access.rs:323`):
`step 0` ER lock → `step 1` same-node blanket `Allow(SameNode)` → `step 2` reply
exemption → `step 3` store load → `step 4` `AccessStore::decide`
(`spt-store/src/access.rs:1536`).

`decide`'s tiers: 1-3 per-endpoint rules → 4-5 node-scope rules → 6 endpoint
mode → 7 node mode → 8 captured-subnet mode → 9 implicit / surface-default.

The operator's tier sits **between the per-endpoint entries and the node tier**
— i.e. between tier 3 and tier 4 (and its mode twin between 6 and 7), inside
`decide`.

**The foreclosure:** same-node traffic short-circuits at step 1 and never
reaches `decide` at all. The naive fix therefore forecloses #211 silently.

| shape | #209 fixed? | #211 insertable at the named position? |
|---|---|---|
| **S1** route local calls into the chain, keep step 1 as a short-circuit | yes | **NO** — a tier could only slot between step 0 and step 1, which is not "between endpoint and node entries" |
| **S2** demote the same-node Allow to the chain BOTTOM for local origin | yes | yes — but tiers 6-8 become reachable by local traffic, so a `closed` node mode would refuse all local sends (**breaks the hole-punch negative control**) |
| **S3** carry `Origin::LocalNode` as DATA on `AccessRequest`; local traffic runs `decide`; the MODE tiers (6-8) ABSTAIN for local origin; the bottom stays `Allow(SameNode)` | yes | **yes, at the named position** |

**Recommended: S3.** Rule tiers keyed on remote node ids cannot match a local
origin, so today's local behavior is preserved by construction; abstaining the
mode tiers keeps a blanket posture from closing local traffic; and #211's tier
slots at 3.5 / 6.5 in `decide` with nothing to move. Local-ness becomes a
single-source discriminant on the request rather than a per-call-site
convention. **Not building the tier — naming its slot.**

## 5. The two unruled faces (narrowing-fails-open rule)

This narrows a previously-unconditional path, so both faces are declared before
the build, per the standing rule.

### Face 1 — the reply exemption is DEAD on the local path as written ⚠ blocking

`classify_inbound` (`access.rs:406`) correlates against `RecentOutbound`, which
is written only by `note_outbound` (`access.rs:418`). **`note_outbound` has
exactly ONE production caller: `wan.rs:335`**, on the WAN outbound leg. Every
other hit is a test.

A local `spt send` records nothing. So the moment a local gate exists, **every
local reply to the engine room classifies `Unsolicited` and is refused** — the
ER can ask a local agent a question and be structurally unable to receive the
answer. The loud arm then fires on legitimate traffic printing
`ENGINE_ROOM_INBOUND_LOCKED refusing msg from <our own node hex>`, which reads
like a wire arrival and mis-diagnoses itself.

**The fix must add the local outbound half** — record `note_outbound` at the
local send site, keyed on this node's own hex — or the reply exemption does not
survive the path, which is the one thing the brief says it must do on every
path. This is the loud-arm-fires-with-the-wrong-subject face.

### Face 2 — the gate predicate is an exact string compare; the perch resolver is not ⚠ needs measurement

`is_engine_room(id)` is `id == "engine-room"` (`engineroom.rs:59`), an exact
compare. `cmd_send` passes its `target` argument through **unresolved** to both
`is_spt_hosted_no_relay` and the perch resolver, and `resolve_perch_path` names
a directory. On Windows (NTFS, case-insensitive) `spt send Engine-Room` resolves
to the same perch directory while `is_engine_room("Engine-Room")` is **false** —
the gate skips and the bypass survives its own fix. Registry lookup may be
case-SENSITIVE on the same input, so the two halves can disagree.

Not measured end-to-end yet: a candidate, not a claim. It will be measured as
part of the witnessed red, and if real the gate must key on a CANONICALIZED
target, never the caller's spelling.

*(Not a face: `posture_now()`'s unreadable-info read. `engineroom.rs:263-271`
documents `unreadable perch ⇒ Offline`, the fail-closed direction, and it is
already ruled. Recorded so it is visibly checked rather than merely unmentioned.)*

## 6. What #200 consumes from this

Same declare-vs-enforce frame, one axis over:

- **DECLARE site** — where the rule is stated. #209: `classify_engine_room_inbound`
  (`access.rs:~440`, pure, unit-pinned). #200: `reserved_id_refusal`
  (`engineroom.rs:~85`, the "ONE sentence … ONE predicate" doc block).
- **ENFORCE site** — where a caller consults it. #209: 2 of 17 paths. #200: the
  mint verbs, in-client.
- **The shared defect shape** — an arm that reaches the terminus without
  crossing the enforce site. #209's arms are the 15 ungated local paths; #200's
  is any mint door that reaches the identity without asking the predicate, and
  `engineroom.rs:72` already names one in its own doc comment: *"`spt endpoint
  run engine-room` mints it today without the gate ever being consulted, because
  the gate guards the ATTACH and not the NAME."*
- Both resolve the same way: move enforcement to the ONE site every arm must
  cross (daemon-side for #200, the local admission function for #209), so a new
  arm inherits the rule instead of having to remember it.
