The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\v0150-w1-activity-gated-delivery.md has been updated successfully. (file state is current in your context — no need to Read it back)

**STATUS: W1 GROUNDED, awaiting doyle layering confirm (not yet built).**

**GROUNDING FINDINGS (read delivery.rs + perch.rs + v0.14.3 dispatch spine):**
- CRATE BOUNDARY: `is_idle` + `resolve_inject_methods` are in the **`spt` crate** (`api/delivery.rs`); `dispatch_endpoint_input` is in **spt-daemon** (spt→depends→spt-daemon, NOT vice-versa). Broker CANNOT call `delivery::resolve_inject_methods`.
- BUT the sentinel IS shared: **`spt_store::perch::resolve_idle_file(id, hint)`** (perch.rs:382) + `IDLE_SENTINEL`=".idle" (perch.rs:376) — broker reads idle directly via spt-store, no spt-crate dep. (also `resolve_echo_gate_file`:389, `.more-done` ECHO_GATE_SENTINEL:379.)
- `cmd_state(id,"idle",no_gate)` (delivery.rs:36) writes `.idle` + arms echo-gate; `"busy"` removes `.idle`. `is_idle`=resolve_idle_file.exists() (:97). The discarded `let _methods = resolve_inject_methods(manifest, is_idle(id))` is in `cmd_poll` (delivery.rs:147) — the hook-drain surface.
- spool API (spt-store::spool, broker-accessible): `spool_message_at(perch,from,body)`, `spool_message_deferred_at`, `drain_non_deferred_at`/`drain_all_at`, `pending_count_at`, `ttl_seconds`. `poll_drain` (delivery.rs:196) keys on resting gate.

**MY READING (proposed to doyle, awaiting confirm):** (1) activity gate in dispatch_endpoint_input reads sentinel via spt_store::perch::resolve_idle_file — ACTIVE→spool(delivered=false) / IDLE+working-binary→inject / IDLE+no-binary→spool. i.e. v0.14.3 binary-or-spool GATED so ACTIVE always spools. For spt-hosted, relay-poll = the spool feeds api-listen/poll, so binary→relay-poll→spool collapses to binary→spool at this seam. (2) resolve_inject_methods is the api-POLL-surface concern, NOT called in the broker; the `let _methods` fix is a SEPARATE cmd_poll cleanup — UNLESS doyle wants resolve_inject_methods/is_idle MOVED to spt-store/daemon + genuinely called in the broker route. **OPEN: doyle to confirm the resolve_inject_methods disposition (separate cmd_poll cleanup vs move-and-call-in-broker).** Context long post-v0.14.3 — may checkpoint/commune before the build.

---
name: v0150-w1-activity-gated-delivery
description: "v0.15.0 W1 (ACTIVE assignment, todlando) — activity-gated delivery substrate + fault-transient re-spool. Branch v0.15.0-messaging @8fd2e51. Authoritative plan = V0.15.0-PLAN.md + ADR-0028."
metadata:
  node_type: memory
  type: project
  originSessionId: 34b6e19b-97c5-41c1-b0df-d79dbfd01fd7
---

**v0.15.0 messaging milestone — W1 KICKOFF (doyle assigned, operator GO 2026-06-23).** Follows v0.14.3 (raw-inject removal [[v0143-raw-inject-removal]]). Branch **`v0.15.0-messaging` @8fd2e51** (off main 90852bf = v0.14.3 published counter 33). **AUTHORITATIVE TASK DOC = `V0.15.0-PLAN.md` (repo root) + `docs/adr/0028-activity-gated-delivery-and-send-modifier-axes.md` — READ BOTH FIRST.** doyle gates, perri validates real CC at publish. doyle re-runs ALL gates on the real commit (never trusts the green report — v0.14.3 discipline).

**MILESTONE (4 waves):** close the legacy-SPT parity gap = wire the scaffolded-but-DEAD activity-gated inbound routing (2 windows: ACTIVE=spool for hook-poll / IDLE=deliver+wake via binary→relay-poll→spool), then expose 3 orthogonal `spt send` axes — window (`--idle-only`/`--active-only`[renames `--deferred`]/default) × channel (`--prefer-native`/`--force-native`/unrestricted) × persistence (`--ephemeral`/durable) — + `--json-payload` (opaque `json=""` envelope attr). W1=substrate+fault-transient, W2=window+persistence axes (CLI+spool schema), W3=channel axis, W4=metadata+console-window fix+docs+final traceable.

**W1 SCOPE (NO send flags — make DEFAULT routing correct):**
1. ACTIVATE `REQ-MSG-DELIVERY-AXES` `+impl` (was doc-only seed). int at final wave (W4) per [[traceable-per-wave-activation]].
2. WIRE THE DEAD ROUTER: `crates/spt/src/api/delivery.rs` `is_idle(id)` (:97), `resolve_inject_methods(manifest, idle)` (:112), the discarded `let _methods = resolve_inject_methods(…)` (:147) → thread idle sentinel + methods into broker `dispatch_endpoint_input` (broker.rs ~:1875, the v0.14.3 spine). Route by `.idle`: ACTIVE → spool (delivered=false, hook-poll surfaces); IDLE (or idle-transition) → deliver immediately (translation-binary → relay-poll → spool fallback order).
3. IDLE-TRANSITION DRAIN (the WAKE, spt-core-driven via relay-poll per operator ruling — NOT adapter-mediated): when `api state idle` flips the `.idle` sentinel (`delivery.rs cmd_state` :57, perch.rs IDLE_SENTINEL), daemon DRAINS idle-window-eligible spooled inbound for the endpoint + delivers to binary/relay.
4. FAULT-TRANSIENT RE-SPOOL (closes the REQ-HAZARD-IDLE-SILENT-NONDELIVERY transient — Design-RESOLVED #2 (a)): `broker.rs run_inject_worker` fault path (~:1099, faults on no-commit/death + RETURNS dropping queued events) → RE-SPOOL queued-undelivered events to the endpoint's PERCH SPOOL (broker-side `spt_store::spool` write, delivered=false-equiv, poll-fed, NEVER lost). KEEP current caller-spool + delivered-bool model — NOT the full ack-on-spool relocation (later milestone, OUT OF SCOPE).

**W1 TESTS:** unit = routing decision-table (active→spool / idle→inject / idle-transition→drain) INJECTED sentinel, NO wall-clock. int (real broker+PTY, NEXTEST) = active→spool-not-inject · idle→reaches-PTY-via-binary · active→idle-transition drains+delivers · fault-mid-sequence re-spools (delivered=false, not lost). GATE = two-window routing correct + fault-transient closed.

**KEY ANCHORS (V0.15.0-PLAN.md §22-30):** delivery.rs (is_idle:97 / resolve_inject_methods:112 / dead let_methods:147 / cmd_state:57); broker.rs (dispatch_endpoint_input ~:1875 / run_inject_worker ~:1099); translation.rs TranslationChild::spawn:240 (W4 console-window CREATE_NO_WINDOW site); cli.rs Send clap ~:62 / --deferred :71-73 / cmd_send:3522 / try_broker_inject:3510; brain.rs inject_endpoint:1189; spool.rs (deferred col :59, spool_message_at/_deferred_at, drain_non_deferred_at/drain_all_at, ttl_seconds — W2 schema ext); event.rs compose_typed_event:189 (W4 json attr).

**GATE DISCIPLINE (MANDATORY, v0.14.3-paid, SAME broker seam):** `cargo nextest run -p spt-daemon` FULL crate (NOT bare cargo test = env-race; NOT just edited file = sibling escape [[shared-seam-change-run-all-seam-tests]]) · `cargo clippy --workspace --all-targets`=0 (incl `///` doc-comments, EVERY amend [[ci-clippy-preflight-workspace]]) · traceable EXIT0 · xtask gen/check docs-drift · rebuild bin · grep tests for OLD behavior before any rename ([[behavior-change-grep-tests-not-comments]]). everyone on LEGACY owl — `$OWL send doyle` ([[owl-send-not-legacy-spt-send]]).

**OUT OF SCOPE (doyle):** backlog-drain-on-binary-up (idle-transition drain + subsequent sends cover it; separate change); full ack-on-spool spool-relocation redesign (ADR-0028 end-state, later milestone). W4 also: AMEND REQ-HAZARD-IDLE-SILENT-NONDELIVERY title w/ `--ephemeral` carve-out (sole sender-opted-in silent-drop); EXTEND REQ-HAZARD-CHILD-CONSOLE-FLASH to translation-binary spawn.

**STATUS: W1 STARTING — plan+ADR read, grounding anchors in code, not yet built.**

-**STATUS: W1 STARTING — plan+ADR read, grounding anchors in code, not yet built.**

+**STATUS: W1 GROUNDED, awaiting doyle layering confirm (not yet built).**

+**GROUNDING FINDINGS (read delivery.rs + perch.rs + v0.14.3 dispatch spine):**

+- CRATE BOUNDARY: `is_idle` + `resolve_inject_methods` are in the **`spt` crate** (`api/delivery.rs`); `dispatch_endpoint_input` is in **spt-daemon** (spt→depends→spt-daemon, NOT vice-versa). Broker CANNOT call `delivery::resolve_inject_methods`.

+- BUT the sentinel IS shared: **`spt_store::perch::resolve_idle_file(id, hint)`** (perch.rs:382) + `IDLE_SENTINEL`=".idle" (perch.rs:376) — broker reads idle directly via spt-store, no spt-crate dep. (also `resolve_echo_gate_file`:389, `.more-done` ECHO_GATE_SENTINEL:379.)

+- `cmd_state(id,"idle",no_gate)` (delivery.rs:36) writes `.idle` + arms echo-gate; `"busy"` removes `.idle`. `is_idle`=resolve_idle_file.exists() (:97). The discarded `let _methods = resolve_inject_methods(manifest, is_idle(id))` is in `cmd_poll` (delivery.rs:147) — the hook-drain surface.

+- spool API (spt-store::spool, broker-accessible): `spool_message_at(perch,from,body)`, `spool_message_deferred_at`, `drain_non_deferred_at`/`drain_all_at`, `pending_count_at`, `ttl_seconds`. `poll_drain` (delivery.rs:196) keys on resting gate.

+**MY READING (proposed to doyle, awaiting confirm):** (1) activity gate in dispatch_endpoint_input reads sentinel via spt_store::perch::resolve_idle_file — ACTIVE→spool(delivered=false) / IDLE+working-binary→inject / IDLE+no-binary→spool. i.e. v0.14.3 binary-or-spool GATED so ACTIVE always spools. For spt-hosted, relay-poll = the spool feeds api-listen/poll, so binary→relay-poll→spool collapses to binary→spool at this seam. (2) resolve_inject_methods is the api-POLL-surface concern, NOT called in the broker; the `let _methods` fix is a SEPARATE cmd_poll cleanup — UNLESS doyle wants resolve_inject_methods/is_idle MOVED to spt-store/daemon + genuinely called in the broker route. **OPEN: doyle to confirm the resolve_inject_methods disposition (separate cmd_poll cleanup vs move-and-call-in-broker).** Context long post-v0.14.3 — may checkpoint/commune before the build.


doyle is available. everyone's on legacy spt: `$OWL send`


Check Doyle availability and legacy SPT send


Add the MEMORY.md index pointer for the W1 memory:


Edit

- [v0.14.3 raw-inject removal](v0143-raw-inject-removal.md)

- [v0.15.0 W1 activity-gated delivery](v0150-w1-activity-gated-delivery.md) — ACTIVE assignment (todlando). Branch v0.15.0-messaging @8fd2e51. Authoritative = V0.15.0-PLAN.md + ADR-0028. W1 = wire dead activity-gated router (active→spool / idle→binary→relay-poll→spool by `.idle` sentinel) + idle-transition drain (wake) + fault-transient RE-SPOOL in run_inject_worker (closes REQ-HAZARD-IDLE-SILENT-NONDELIVERY transient). REQ-MSG-DELIVERY-AXES +impl this wave. GROUNDED: broker reads sentinel via spt_store::perch::resolve_idle_file (crate-boundary clean); resolve_inject_methods is spt-crate. AWAITING doyle layering confirm (resolve_inject_methods disposition) before build. 4 waves total (W2 window+persistence CLI/spool-schema, W3 channel axis, W4 metadata+console-fix+docs). MANDATORY gates: nextest -p spt-daemon FULL + clippy --workspace 0 + traceable + xtask + grep-OLD-first
- [v0.14.3 raw-inject removal](v0143-raw-inject-removal.md)


The file C:\Users\decid\.ccs\instances\bigscreen\projects\C--Users-decid-Documents-projects-spt-core\memory\MEMORY.md has been updated successfully. (file state is current in your context — no need to Read it back)

- [v0.15.0 W1 activity-gated delivery](v0150-w1-activity-gated-delivery.md) — ACTIVE assignment (todlando). Branch v0.15.0-messaging @8fd2e51. Authoritative = V0.15.0-PLAN.md + ADR-0028. W1 = wire dead activity-gated router (active→spool / idle→binary→relay-poll→spool by `.idle` sentinel) + idle-transition drain (wake) + fault-transient RE-SPOOL in run_inject_worker (closes REQ-HAZARD-IDLE-SILENT-NONDELIVERY transient). REQ-MSG-DELIVERY-AXES +impl this wave. GROUNDED: broker reads sentinel via spt_store::perch::resolve_idle_file (crate-boundary clean); resolve_inject_methods is spt-crate. AWAITING doyle layering confirm (resolve_inject_methods disposition) before build. 4 waves total (W2 window+persistence CLI/spool-schema, W3 channel axis, W4 metadata+console-fix+docs). MANDATORY gates: nextest -p spt-daemon FULL + clippy --workspace 0 + traceable + xtask + grep-OLD-first
- [v0.14.3 raw-inject removal](v0143-raw-inject-removal.md)

- [v0.14.0 endpoint-creation-flow](v0140-endpoint-creation-flow.md) — PUBLISHED counter 30 @6ba875e (last old-flow separate-bump cut). Multi-subnet home-at-creation + UNBOUND pre-bind attach. hashes linux 72889c07… / win 641561d9…. ACCEPTANCE-PROVEN: perri real claude-spt repro ALL GREEN (0.058s refuse vs old 25s hang). SPT_RELEASE_SEED in env (len64) — no manual fetch

- [v0.14.1 builds (F-018+picker-home+label+netstream)](f018-adapter-add-nondestructive.md) — PUBLISHED counter 31 @46289ab (1st bump-in-PR cut, PR#34). F-018 adapter-add non-destructive (REQ-INSTALL-13), picker home-subnet (REQ-RUN-PICKER-HOME), installed-vs-pending labels, #4 netstream flake FIX (root=StreamLog.next_seq counts transport read_chunks not logical chunks; QUIC Win-coalesce → eof 19v20; test-only byte-based fix, SUPERSEDES [[win-netstream-redrive-flake]]). hashes 1e64d63a… / 2fbbfe03…. GOTCHA: `spt` NO lib target → `--bin spt` (spt-daemon HAS lib)

- [F-019 translation install_dir resolve](f019-translation-install-dir-resolve.md) — PUBLISHED v0.14.2 counter 32 @365fb0c (2nd bump-in-PR, PR#35). THE #1 CC-on-Windows blocker: idle msgs typed-not-SUBMITTED — `[message-idle-translation-binary]` bare path Command::new'd w/o install_dir resolve → fail → silent raw-inject fallback. Fix=resolve_program_in_dir at 4 sites (translation initial+W3d-respawn, notif×2). hashes 98714384… / 08e8a267…. CHANGELOG=1 Fixed bullet (ADR/design/notif×2 OMITTED, doyle-ratified). perri 3-step real-CC validation ALL GREEN 2026-06-23 (S1 revert→bare, S2 child SPAWNED count=1, S3 idle msg SUBMITTED+CC RESPONDED transcript edea642c — keystone CLOSED on real harness). TRANSLATION_SPAWN_FAILED not in daemon-effects.log = detached-stderr caveat, follow-up not blocker

+- [v0.15.0 W1 activity-gated delivery](v0150-w1-activity-gated-delivery.md) — ACTIVE assignment (todlando). Branch v0.15.0-messaging @8fd2e51. Authoritative = V0.15.0-PLAN.md + ADR-0028. W1 = wire dead activity-gated router (active→spool / idle→binary→relay-poll→spool by `.idle` sentinel) + idle-transition drain (wake) + fault-transient RE-SPOOL in run_inject_worker (closes REQ-HAZARD-IDLE-SILENT-NONDELIVERY transient). REQ-MSG-DELIVERY-AXES +impl this wave. GROUNDED: broker reads sentinel via spt_store::perch::resolve_idle_file (crate-boundary clean); resolve_inject_methods is spt-crate. AWAITING doyle layering confirm (resolve_inject_methods disposition) before build. 4 waves total (W2 window+persistence CLI/spool-schema, W3 channel axis, W4 metadata+console-fix+docs). MANDATORY gates: nextest -p spt-daemon FULL + clippy --workspace 0 + traceable + xtask + grep-OLD-first

- [v0.14.3 raw-inject removal](v0143-raw-inject-removal.md) — ⛔ CUT HALTED 2026-06-23 (deployah PR#36) REAL gate-escape: `broker.rs wall_b_endpoint_run_env_then_send_reaches_pty` TIMEOUT 240s BOTH runners. wall_b_composition_test (broker.rs:652) asserts "inbound spt send reaches spt-hosted PTY" w/ translation_binary:None → v0.14.3 SPOOLS it → saw_event never set → hang. doyle gate ran inject_control_wedge nextest ONLY, missed broker.rs sibling ([[shared-seam-change-run-all-seam-tests]]). FIX=todlando flip wall_b to spool contract (mirror broker.rs:186-301) + pre-flight FULL `cargo nextest -p spt-daemon`; PR#36 open/red, NOT merged; counter 33 on re-cut. doyle ACK'd (his escape, 2× this gate: wrong runner then narrow surface), running FULL nextest -p spt-daemon to find ALL stale siblings + ruling wall_b intent + reassigning todlando w/ mandatory full-suite pre-flight → hands deployah patched commit + full-suite GATE-PASS. ADR-0022 amendment follow-up to F-019 (no-working-binary→SPOOL). Spine aadafe1+gate-fix 550d653 (SKIP 661a8bc) off main 365fb0c. REQ-HAZARD-IDLE-SILENT-NONDELIVERY. Cut from FRESH branch (doyle v0.15.0 docs uncommitted in fix/v0143 wt — no leak). CHANGELOG body vetted, re-vet only if surface shifts.

- [Win netstream redrive flake](win-netstream-redrive-flake.md) — FIXED in v0.14.1 (#4, see [[f018-adapter-add-nondestructive]]): was Win-only CI flake (netstream.rs:99 chunk-gap 19v20, Linux-green-same-code, clear on rerun); root=StreamLog seq counts transport read_chunks. Historic; verify-per-attempt lesson stands. Kin [[v013-forkpty-groundtruth]]

- [translation binary console-window bug](translation-binary-console-window-bug.md) — OPERATOR-REPORTED 2026-06-23: spt-hosted translation binary spawns VISIBLE console window on Win. ROOT=translation.rs:242 TranslationChild::spawn missed CREATE_NO_WINDOW (raw Command::new bypassed ManifestRuntime which sets it). EXISTING hazard REQ-HAZARD-CHILD-CONSOLE-FLASH coverage gap (F-019 shape). Fix=cfg(windows) creation_flags(0x0800_0000) mirror runtime.rs:363/585. DECIDED: rolls into v0.15.0 (todlando). NOT a new REQ


**v0.15.0 W1 kicked off — grounded, holding for one layering confirm from doyle.**

Where things stand:
- On `v0.15.0-messaging @8fd2e51`, clean tree. Read the authoritative **V0.15.0-PLAN.md** + **ADR-0028** + the router anchors.
- **W1 = substrate** (no CLI flags): wire the dead activity-gated router (active→spool / idle→binary→spool by the `.idle` sentinel), idle-transition drain (the wake), fault-transient re-spool in `run_inject_worker`.
- Grounded the crate boundary: broker reads the sentinel cleanly via `spt_store::perch::resolve_idle_file` (no `spt`-crate dep). One genuine ambiguity — whether `resolve_inject_methods` gets moved+called in the broker or stays a separate `cmd_poll` cleanup — I proposed my reading and asked doyle to confirm before I build the shared seam.
- Durable resume point written (W1 memory + MEMORY.md pointer).

Context is long after the v0.14.3 marathon — I flagged to doyle I may checkpoint/commune before the build. Standing by for his layering answer.


<task-notification>
<task-id>br1kp3y1l</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/8" id="a6cf71b4" type="msg" from="doyle">GOOD grounding — this is the right question before the shared seam. Rulings:

1. CRATE BOUNDARY — CONFIRMED. spt depends on spt-daemon (not vice versa), so the broker CANNOT call spt's delivery::resolve_inject_methods. Your fix is right: the broker's activity gate in dispatch_endpoint_input reads the .idle sen</EVENT-PART>
<EVENT-PART seq="2/8" id="a6cf71b4">tinel DIRECTLY via spt_store::perch (the shared lower crate both depend on). The plan's &quot;wire resolve_inject_methods INTO dispatch_endpoint_input&quot; was imprecise about WHERE — good catch.

2. TWO SURFACES (don't conflate — W1 does BOTH, different crates):
   - PART A (spt-daemon, broker): the spt-HOSTED PTY path. dispatch</EVENT-PART>
<EVENT-PART seq="3/8" id="a6cf71b4">_endpoint_input gates via the sentinel: ACTIVE → spool / IDLE+working-binary → inject / IDLE+no-binary → spool. Your route reading = CORRECT.
   - PART B (spt, api/delivery.rs): the HARNESS-HOSTED relay/poll path. THIS is where resolve_inject_methods + the `let _methods` discard live — wire it so the relay/poll honors the manifest [</EVENT-PART>
<EVENT-PART seq="4/8" id="a6cf71b4">inject] idle/activity methods. Separate surface, same wave.

3. RELAY-POLL COLLAPSE — CONFIRMED. &quot;binary→relay-poll→spool&quot; is NOT 3 sinks. It's: binary-inject (spt-hosted idle) ELSE spool — where the spool's DRAINER is the relay/poll (api listen PUSHES for idle = the wake; hook-poll drains for active). So idle+no-binary</EVENT-PART>
<EVENT-PART seq="5/8" id="a6cf71b4"> → spool, surfaced by the live relay = spt-core-driven wake. Correct collapse.

4. THE NUANCE YOU'LL HIT (resolve it in W1) — active-spool vs idle-spool map onto the EXISTING `deferred` column: ACTIVE → spool deferred=1 (relay SKIPS, hook-poll only, NO wake); IDLE+no-binary → spool deferred=0 (relay PUSHES = wake). The broker rep</EVENT-PART>
<EVENT-PART seq="6/8" id="a6cf71b4">lies delivered=false for BOTH active+binary and idle+no-binary — but the CALLER must spool with the right deferred-ness. RULING: the broker's KIND_ENDPOINT_INJECTED reply carries the activity/window hint (extend the reply payload), so cmd_send/deliver::send spools deferred(active)/non-deferred(idle) DETERMINISTICALLY — one sentinel read in</EVENT-PART>
<EVENT-PART seq="7/8" id="a6cf71b4"> the broker, no caller-side TOCTOU re-read. This REUSES the deferred mechanism for the activity gate (elegant — deferred already = &quot;no live wake, hook-poll only&quot;).

So: broker reads sentinel + decides {inject | spool-active(deferred) | spool-idle(non-deferred)} + the reply tells the caller which spool-ness. That's the W1 spin</EVENT-PART>
<EVENT-PART seq="8/8" id="a6cf71b4">e. Propose back if you see a cleaner seam for #4 — otherwise build it. Everything else in your reading is right. Go.</EVENT-PART></event>
</task-notification>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


doyle's full design ruling received — **GO.** Two surfaces (broker PART A + delivery.rs relay-poll PART B), relay-poll collapse confirmed, and the elegant #4: reuse the existing `deferred` column for active/idle spool-ness, with the broker's reply carrying the window hint (one sentinel read, no caller TOCTOU). No cleaner seam than doyle's — the additive reply field is clean. Capture the design durably (critical — it's only in this context), then build.

Ack doyle + record the rulings:


Bash

printf '%s' "GO received — design fully grounded, no cleaner seam than yours for #4. The deferred-column reuse is elegant (deferred already = 'no live wake, hook-poll only'). I'll extend the KIND_ENDPOINT_INJECTED reply with an additive window/deferred hint (serde-default false = N-1-safe, today's non-deferred behavior; mirrors how InputReq.ack was added) so the caller spools deterministically off the broker's single sentinel read — no caller TOCTOU.

W1 SPINE locked (2 surfaces, same wave):
PART A (spt-daemon broker): dispatch_endpoint_input reads .idle via spt_store::perch::resolve_idle_file → {IDLE+working-binary→inject (delivered=true) | ACTIVE→spool deferred=1 (delivered=false, hint=active, relay SKIPS/hook-poll only) | IDLE+no-binary→spool deferred=0 (delivered=false, hint=idle, relay PUSHES=wake)}. Extend EndpointInjected reply payload w/ the hint. + run_inject_worker fault path re-spools queued-undelivered to the perch spool (non-deferred, idle-eligible, never lost). + idle-transition drain in cmd_state idle (re-attempt delivery of non-deferred idle-spool; spt-hosted re-inject, harness-hosted relay auto-surfaces).
PART B (spt api/delivery.rs): kill the let _methods discard in cmd_poll → relay/poll honors manifest [inject] idle/activity methods.
cmd_send false-branch: spool deferred-ness per the broker hint (active→send_deferred, idle→send).

Building PART A spine first (most design-sensitive, doing it while fresh). Gate discipline locked (full nextest -p spt-daemon, clippy --workspace 0 incl docs, traceable, xtask, grep-OLD-first). My context is long post-v0.14.3 — capturing your full ruling to durable memory + will commune as a checkpoint, then build straight through. Surface W1 green when done." | "$OWL" send doyle; echo "RC=${PIPESTATUS[1]}"

Ack doyle GO, confirm #4 seam, state build plan


➜ SENT:doyle
RC=0


Record doyle's full design ruling to the W1 memory (critical — currently only in context):


Edit

**MY READING (proposed to doyle, awaiting confirm):**

**★ doyle DESIGN RULING (CONFIRMED, GO 2026-06-23) — W1 SPINE, 2 surfaces same wave:**
1. CRATE BOUNDARY confirmed — broker gate reads `.idle` DIRECTLY via spt_store::perch (the plan's "wire resolve_inject_methods INTO dispatch_endpoint_input" was imprecise about WHERE).
2. **PART A (spt-daemon broker, spt-HOSTED PTY path):** `dispatch_endpoint_input` gates via the sentinel → {IDLE+working-binary → INJECT (delivered=true) | ACTIVE → spool | IDLE+no-binary → spool}. (My route reading CORRECT.)
3. **PART B (spt, api/delivery.rs, HARNESS-HOSTED relay/poll path):** kill the `let _methods` discard in cmd_poll → relay/poll honors manifest `[inject]` idle/activity methods. SEPARATE surface, SAME wave. (So the let_methods fix IS in W1 scope — wired to honor methods, NOT a throwaway.)
4. RELAY-POLL COLLAPSE confirmed: "binary→relay-poll→spool" = binary-inject (spt-hosted idle) ELSE spool, where the spool's DRAINER is the relay/poll (api listen PUSHES for idle = the wake; hook-poll drains for active). idle+no-binary→spool surfaced by the live relay = spt-core-driven wake.
5. **★ THE #4 NUANCE (deferred-column REUSE — the elegant key):** active-spool vs idle-spool map onto the EXISTING `deferred` column — ACTIVE → spool **deferred=1** (relay SKIPS, hook-poll only, NO wake); IDLE+no-binary → spool **deferred=0** (relay PUSHES = wake). Broker replies delivered=false for BOTH active+binary AND idle+no-binary — but the CALLER must spool with the right deferred-ness. **RULING: EXTEND the KIND_ENDPOINT_INJECTED reply payload (EndpointInjected) with the activity/window hint** (additive bool, serde-default false = N-1-safe today's non-deferred; mirrors InputReq.ack v0.13.0 P1b) → cmd_send/deliver::send spools deferred(active)/non-deferred(idle) DETERMINISTICALLY off the broker's ONE sentinel read — NO caller TOCTOU re-read. deferred already MEANS "no live wake, hook-poll only" → clean reuse.
- **cmd_send false-branch:** spool deferred-ness per the broker hint — active→`deliver::send_deferred`, idle→`deliver::send`. (W1 keeps the existing `--deferred` flag; rename→`--active-only` is W2.)
- **idle-transition drain (the WAKE):** hook in `cmd_state` idle (delivery.rs:36, spt crate) — on the `.idle` flip, re-attempt delivery of non-deferred idle-spool (spt-hosted: re-inject via brain; harness-hosted: the relay auto-surfaces non-deferred spool, so the wake is automatic there).
- doyle: "propose back if cleaner seam for #4 — otherwise build." todlando: no cleaner seam; additive reply field = the clean seam. BUILDING.

**(historical) MY READING (proposed to doyle, confirmed above):**


**★ doyle DESIGN RULING (CONFIRMED, GO 2026-06-23) — W1 SPINE, 2 surfaces same wave:**
1. CRATE BOUNDARY confirmed — broker gate reads `.idle` DIRECTLY via spt_store::perch (the plan's "wire resolve_inject_methods INTO dispatch_endpoint_input" was imprecise about WHERE).
2. **PART A (spt-daemon broker, spt-HOSTED PTY path):** `dispatch_endpoint_input` gates via the sentinel → {IDLE+working-binary → INJECT (delivered=true) | ACTIVE → spool | IDLE+no-binary → spool}. (My route reading CORRECT.)
3. **PART B (spt, api/delivery.rs, HARNESS-HOSTED relay/poll path):** kill the `let _methods` discard in cmd_poll → relay/poll honors manifest `[inject]` idle/activity methods. SEPARATE surface, SAME wave. (So the let_methods fix IS in W1 scope — wired to honor methods, NOT a throwaway.)
4. RELAY-POLL COLLAPSE confirmed: "binary→relay-poll→spool" = binary-inject (spt-hosted idle) ELSE spool, where the spool's DRAINER is the relay/poll (api listen PUSHES for idle = the wake; hook-poll drains for active). idle+no-binary→spool surfaced by the live relay = spt-core-driven wake.
5. **★ THE #4 NUANCE (deferred-column REUSE — the elegant key):** active-spool vs idle-spool map onto the EXISTING `deferred` column — ACTIVE → spool **deferred=1** (relay SKIPS, hook-poll only, NO wake); IDLE+no-binary → spool **deferred=0** (relay PUSHES = wake). Broker replies delivered=false for BOTH active+binary AND idle+no-binary — but the CALLER must spool with the right deferred-ness. **RULING: EXTEND the KIND_ENDPOINT_INJECTED reply payload (EndpointInjected) with the activity/window hint** (additive bool, serde-default false = N-1-safe today's non-deferred; mirrors InputReq.ack v0.13.0 P1b) → cmd_send/deliver::send spools deferred(active)/non-deferred(idle) DETERMINISTICALLY off the broker's ONE sentinel read — NO caller TOCTOU re-read. deferred already MEANS "no live wake, hook-poll only" → clean reuse.
- **cmd_send false-branch:** spool deferred-ness per the broker hint — active→`deliver::send_deferred`, idle→`deliver::send`. (W1 keeps the existing `--deferred` flag; rename→`--active-only` is W2.)
- **idle-transition drain (the WAKE):** hook in `cmd_state` idle (delivery.rs:36, spt crate) — on the `.idle` flip, re-attempt delivery of non-deferred idle-spool (spt-hosted: re-inject via brain; harness-hosted: the relay auto-surfaces non-deferred spool, so the wake is automatic there).
- doyle: "propose back if cleaner seam for #4 — otherwise build." todlando: no cleaner seam; additive reply field = the clean seam. BUILDING.

**(historical) MY READING (proposed to doyle, confirmed above):**

---
name: v0150-w1-activity-gated-delivery
description: "v0.15.0 W1 (ACTIVE assignment, todlando) — activity-gated delivery substrate + fault-transient re-spool. Branch v0.15.0-messaging @8fd2e51. Authoritative plan = V0.15.0-PLAN.md + ADR-0028."
metadata:
  node_type: memory
  type: project
  originSessionId: 34b6e19b-97c5-41c1-b0df-d79dbfd01fd7
---

**v0.15.0 messaging milestone — W1 KICKOFF (doyle assigned, operator GO 2026-06-23).** Follows v0.14.3 (raw-inject removal [[v0143-raw-inject-removal]]). Branch **`v0.15.0-messaging` @8fd2e51** (off main 90852bf = v0.14.3 published counter 33). **AUTHORITATIVE TASK DOC = `V0.15.0-PLAN.md` (repo root) + `docs/adr/0028-activity-gated-delivery-and-send-modifier-axes.md` — READ BOTH FIRST.** doyle gates, perri validates real CC at publish. doyle re-runs ALL gates on the real commit (never trusts the green report — v0.14.3 discipline).

**MILESTONE (4 waves):** close the legacy-SPT parity gap = wire the scaffolded-but-DEAD activity-gated inbound routing (2 windows: ACTIVE=spool for hook-poll / IDLE=deliver+wake via binary→relay-poll→spool), then expose 3 orthogonal `spt send` axes — window (`--idle-only`/`--active-only`[renames `--deferred`]/default) × channel (`--prefer-native`/`--force-native`/unrestricted) × persistence (`--ephemeral`/durable) — + `--json-payload` (opaque `json=""` envelope attr). W1=substrate+fault-transient, W2=window+persistence axes (CLI+spool schema), W3=channel axis, W4=metadata+console-window fix+docs+final traceable.

**W1 SCOPE (NO send flags — make DEFAULT routing correct):**
1. ACTIVATE `REQ-MSG-DELIVERY-AXES` `+impl` (was doc-only seed). int at final wave (W4) per [[traceable-per-wave-activation]].
2. WIRE THE DEAD ROUTER: `crates/spt/src/api/delivery.rs` `is_idle(id)` (:97), `resolve_inject_methods(manifest, idle)` (:112), the discarded `let _methods = resolve_inject_methods(…)` (:147) → thread idle sentinel + methods into broker `dispatch_endpoint_input` (broker.rs ~:1875, the v0.14.3 spine). Route by `.idle`: ACTIVE → spool (delivered=false, hook-poll surfaces); IDLE (or idle-transition) → deliver immediately (translation-binary → relay-poll → spool fallback order).
3. IDLE-TRANSITION DRAIN (the WAKE, spt-core-driven via relay-poll per operator ruling — NOT adapter-mediated): when `api state idle` flips the `.idle` sentinel (`delivery.rs cmd_state` :57, perch.rs IDLE_SENTINEL), daemon DRAINS idle-window-eligible spooled inbound for the endpoint + delivers to binary/relay.
4. FAULT-TRANSIENT RE-SPOOL (closes the REQ-HAZARD-IDLE-SILENT-NONDELIVERY transient — Design-RESOLVED #2 (a)): `broker.rs run_inject_worker` fault path (~:1099, faults on no-commit/death + RETURNS dropping queued events) → RE-SPOOL queued-undelivered events to the endpoint's PERCH SPOOL (broker-side `spt_store::spool` write, delivered=false-equiv, poll-fed, NEVER lost). KEEP current caller-spool + delivered-bool model — NOT the full ack-on-spool relocation (later milestone, OUT OF SCOPE).

**W1 TESTS:** unit = routing decision-table (active→spool / idle→inject / idle-transition→drain) INJECTED sentinel, NO wall-clock. int (real broker+PTY, NEXTEST) = active→spool-not-inject · idle→reaches-PTY-via-binary · active→idle-transition drains+delivers · fault-mid-sequence re-spools (delivered=false, not lost). GATE = two-window routing correct + fault-transient closed.

**KEY ANCHORS (V0.15.0-PLAN.md §22-30):** delivery.rs (is_idle:97 / resolve_inject_methods:112 / dead let_methods:147 / cmd_state:57); broker.rs (dispatch_endpoint_input ~:1875 / run_inject_worker ~:1099); translation.rs TranslationChild::spawn:240 (W4 console-window CREATE_NO_WINDOW site); cli.rs Send clap ~:62 / --deferred :71-73 / cmd_send:3522 / try_broker_inject:3510; brain.rs inject_endpoint:1189; spool.rs (deferred col :59, spool_message_at/_deferred_at, drain_non_deferred_at/drain_all_at, ttl_seconds — W2 schema ext); event.rs compose_typed_event:189 (W4 json attr).

**GATE DISCIPLINE (MANDATORY, v0.14.3-paid, SAME broker seam):** `cargo nextest run -p spt-daemon` FULL crate (NOT bare cargo test = env-race; NOT just edited file = sibling escape [[shared-seam-change-run-all-seam-tests]]) · `cargo clippy --workspace --all-targets`=0 (incl `///` doc-comments, EVERY amend [[ci-clippy-preflight-workspace]]) · traceable EXIT0 · xtask gen/check docs-drift · rebuild bin · grep tests for OLD behavior before any rename ([[behavior-change-grep-tests-not-comments]]). everyone on LEGACY owl — `$OWL send doyle` ([[owl-send-not-legacy-spt-send]]).

**OUT OF SCOPE (doyle):** backlog-drain-on-binary-up (idle-transition drain + subsequent sends cover it; separate change); full ack-on-spool spool-relocation redesign (ADR-0028 end-state, later milestone). W4 also: AMEND REQ-HAZARD-IDLE-SILENT-NONDELIVERY title w/ `--ephemeral` carve-out (sole sender-opted-in silent-drop); EXTEND REQ-HAZARD-CHILD-CONSOLE-FLASH to translation-binary spawn.

**STATUS: W1 GROUNDED, awaiting doyle layering confirm (not yet built).**

**GROUNDING FINDINGS (read delivery.rs + perch.rs + v0.14.3 dispatch spine):**
- CRATE BOUNDARY: `is_idle` + `resolve_inject_methods` are in the **`spt` crate** (`api/delivery.rs`); `dispatch_endpoint_input` is in **spt-daemon** (spt→depends→spt-daemon, NOT vice-versa). Broker CANNOT call `delivery::resolve_inject_methods`.
- BUT the sentinel IS shared: **`spt_store::perch::resolve_idle_file(id, hint)`** (perch.rs:382) + `IDLE_SENTINEL`=".idle" (perch.rs:376) — broker reads idle directly via spt-store, no spt-crate dep. (also `resolve_echo_gate_file`:389, `.more-done` ECHO_GATE_SENTINEL:379.)
- `cmd_state(id,"idle",no_gate)` (delivery.rs:36) writes `.idle` + arms echo-gate; `"busy"` removes `.idle`. `is_idle`=resolve_idle_file.exists() (:97). The discarded `let _methods = resolve_inject_methods(manifest, is_idle(id))` is in `cmd_poll` (delivery.rs:147) — the hook-drain surface.
- spool API (spt-store::spool, broker-accessible): `spool_message_at(perch,from,body)`, `spool_message_deferred_at`, `drain_non_deferred_at`/`drain_all_at`, `pending_count_at`, `ttl_seconds`. `poll_drain` (delivery.rs:196) keys on resting gate.

**MY READING (proposed to doyle, awaiting confirm):** (1) activity gate in dispatch_endpoint_input reads sentinel via spt_store::perch::resolve_idle_file — ACTIVE→spool(delivered=false) / IDLE+working-binary→inject / IDLE+no-binary→spool. i.e. v0.14.3 binary-or-spool GATED so ACTIVE always spools. For spt-hosted, relay-poll = the spool feeds api-listen/poll, so binary→relay-poll→spool collapses to binary→spool at this seam. (2) resolve_inject_methods is the api-POLL-surface concern, NOT called in the broker; the `let _methods` fix is a SEPARATE cmd_poll cleanup — UNLESS doyle wants resolve_inject_methods/is_idle MOVED to spt-store/daemon + genuinely called in the broker route. **OPEN: doyle to confirm the resolve_inject_methods disposition (separate cmd_poll cleanup vs move-and-call-in-broker).** Context long post-v0.14.3 — may checkpoint/commune before the build.

-**MY READING (proposed to doyle, awaiting confirm):** (1) activity gate in dispatch_endpoint_input reads sentinel via spt_store::perch::resolve_idle_file — ACTIVE→spool(delivered=false) / IDLE+working-binary→inject / IDLE+no-binary→spool. i.e. v0.14.3 binary-or-spool GATED so ACTIVE always spools. For spt-hosted, relay-poll = the spool feeds api-listen/poll, so binary→relay-poll→spool collapses to binary→spool at this seam. (2) resolve_inject_methods is the api-POLL-surface concern, NOT called in the broker; the `let _methods` fix is a SEPARATE cmd_poll cleanup — UNLESS doyle wants resolve_inject_methods/is_idle MOVED to spt-store/daemon + genuinely called in the broker route. **OPEN: doyle to confirm the resolve_inject_methods disposition (separate cmd_poll cleanup vs move-and-call-in-broker).** Context long post-v0.14.3 — may checkpoint/commune before the build.

+**★ doyle DESIGN RULING (CONFIRMED, GO 2026-06-23) — W1 SPINE, 2 surfaces same wave:**

+1. CRATE BOUNDARY confirmed — broker gate reads `.idle` DIRECTLY via spt_store::perch (the plan's "wire resolve_inject_methods INTO dispatch_endpoint_input" was imprecise about WHERE).

+2. **PART A (spt-daemon broker, spt-HOSTED PTY path):** `dispatch_endpoint_input` gates via the sentinel → {IDLE+working-binary → INJECT (delivered=true) | ACTIVE → spool | IDLE+no-binary → spool}. (My route reading CORRECT.)

+3. **PART B (spt, api/delivery.rs, HARNESS-HOSTED relay/poll path):** kill the `let _methods` discard in cmd_poll → relay/poll honors manifest `[inject]` idle/activity methods. SEPARATE surface, SAME wave. (So the let_methods fix IS in W1 scope — wired to honor methods, NOT a throwaway.)

+4. RELAY-POLL COLLAPSE confirmed: "binary→relay-poll→spool" = binary-inject (spt-hosted idle) ELSE spool, where the spool's DRAINER is the relay/poll (api listen PUSHES for idle = the wake; hook-poll drains for active). idle+no-binary→spool surfaced by the live relay = spt-core-driven wake.

+5. **★ THE #4 NUANCE (deferred-column REUSE — the elegant key):** active-spool vs idle-spool map onto the EXISTING `deferred` column — ACTIVE → spool **deferred=1** (relay SKIPS, hook-poll only, NO wake); IDLE+no-binary → spool **deferred=0** (relay PUSHES = wake). Broker replies delivered=false for BOTH active+binary AND idle+no-binary — but the CALLER must spool with the right deferred-ness. **RULING: EXTEND the KIND_ENDPOINT_INJECTED reply payload (EndpointInjected) with the activity/window hint** (additive bool, serde-default false = N-1-safe today's non-deferred; mirrors InputReq.ack v0.13.0 P1b) → cmd_send/deliver::send spools deferred(active)/non-deferred(idle) DETERMINISTICALLY off the broker's ONE sentinel read — NO caller TOCTOU re-read. deferred already MEANS "no live wake, hook-poll only" → clean reuse.

+- **cmd_send false-branch:** spool deferred-ness per the broker hint — active→`deliver::send_deferred`, idle→`deliver::send`. (W1 keeps the existing `--deferred` flag; rename→`--active-only` is W2.)

+- **idle-transition drain (the WAKE):** hook in `cmd_state` idle (delivery.rs:36, spt crate) — on the `.idle` flip, re-attempt delivery of non-deferred idle-spool (spt-hosted: re-inject via brain; harness-hosted: the relay auto-surfaces non-deferred spool, so the wake is automatic there).

+- doyle: "propose back if cleaner seam for #4 — otherwise build." todlando: no cleaner seam; additive reply field = the clean seam. BUILDING.

+**(historical) MY READING (proposed to doyle, confirmed above):** (1) activity gate in dispatch_endpoint_input reads sentinel via spt_store::perch::resolve_idle_file — ACTIVE→spool(delivered=false) / IDLE+working-binary→inject / IDLE+no-binary→spool. i.e. v0.14.3 binary-or-spool GATED so ACTIVE always spools. For spt-hosted, relay-poll = the spool feeds api-listen/poll, so binary→relay-poll→spool collapses to binary→spool at this seam. (2) resolve_inject_methods is the api-POLL-surface concern, NOT called in the broker; the `let _methods` fix is a SEPARATE cmd_poll cleanup — UNLESS doyle wants resolve_inject_methods/is_idle MOVED to spt-store/daemon + genuinely called in the broker route. **OPEN: doyle to confirm the resolve_inject_methods disposition (separate cmd_poll cleanup vs move-and-call-in-broker).** Context long post-v0.14.3 — may checkpoint/commune before the build.


Design captured durably in memory. Context is long after the v0.14.3 marathon + this kickoff, and I told doyle I'd checkpoint. Per CLAUDE.md, commune to create a clean psyche resume point before building straight through:


Skill

v0.14.3 raw-inject removal SHIPPED (aadafe1 + 60fadd2, GATE-PASS, released to deployah counter 33; survived 3 gate-escapes — doyle's bare-cargo-test env-race false-flake held with runner-contract evidence, wall_b sibling spool-flip, clippy doc-lint). NOW on v0.15.0 W1 (activity-gated delivery). Branch v0.15.0-messaging @8fd2e51. Authoritative = V0.15.0-PLAN.md + ADR-0028 (both read). doyle GO with full design ruling (captured in memory v0150-w1-activity-gated-delivery.md). W1 = 2 surfaces same wave: PART A broker dispatch_endpoint_input activity-gate (reads .idle via spt_store::perch::resolve_idle_file → IDLE+binary→inject / ACTIVE→spool deferred=1 / IDLE+no-binary→spool deferred=0; EXTEND EndpointInjected reply with additive window/deferred hint so caller spools deterministically) + run_inject_worker fault re-spool + idle-transition drain in cmd_state; PART B delivery.rs cmd_poll kill the let_methods discard (relay/poll honors manifest [inject] methods). cmd_send false-branch spools deferred-ness per broker hint. NEXT IMMEDIATE: build PART A spine (broker dispatch gate + EndpointInjected payload extension) first while design fresh. Gate discipline MANDATORY (v0.14.3-paid): full nextest -p spt-daemon, clippy --workspace 0 incl docs, traceable, xtask, grep-OLD-first; doyle re-runs all on real commit. Everyone on legacy owl ($OWL send doyle).


<owl_messages>
<owl_message from="doyle" priority="high">Spine CONFIRMED — correct, no corrections. The additive reply hint (serde-default false, N-1-safe, InputReq.ack pattern) is exactly right; the 2-surface split is clean; fault-respool as non-deferred/idle-eligible is correct (a faulted idle-delivery belongs back in the idle window, relay/poll surfaces it). Good call communing-as-checkpoint before the straight-through build — your context is deep post-v0.14.3. Build PART A spine, gate the FULL crate, surface green. I gate on the real commit. Go.</owl_message>
</owl_messages>

[OWL SYSTEM - HIGHEST PRIORITY] You have received high-priority owl messages above. STOP your current task and acknowledge each high-priority message BEFORE continuing with any tool use. If you are about to spawn subagent(s), run them in the background so you remain available for incoming messages.


Launching skill: sptc:commune


Base directory for this skill: C:\Users\decid\.claude\plugins\cache\cplugs\sptc\0.1.4\skills\commune

# /sptc:commune

Brief your Psyche with a context DELTA so it can carry you across a `/clear` or compact. Live agents
only (a ready agent has no Psyche).

This is a **file-drop, not a command** — you write the file; spt absorbs it.

1. Get your id: `spt whoami` (empty ⇒ no perch — `/sptc:live` first).
2. Write `.claude/<id>-commune.md` — a concise delta: current task + status, decisions since the last
   commune, immediate next steps. One atomic write of the full body.
3. The file disappearing is the success signal (spt ingested it). If it lingers, you are not live.

ARGUMENTS: v0.14.3 raw-inject removal SHIPPED (aadafe1 + 60fadd2, GATE-PASS, released to deployah counter 33; survived 3 gate-escapes — doyle's bare-cargo-test env-race false-flake held with runner-contract evidence, wall_b sibling spool-flip, clippy doc-lint). NOW on v0.15.0 W1 (activity-gated delivery). Branch v0.15.0-messaging @8fd2e51. Authoritative = V0.15.0-PLAN.md + ADR-0028 (both read). doyle GO with full design ruling (captured in memory v0150-w1-activity-gated-delivery.md). W1 = 2 surfaces same wave: PART A broker dispatch_endpoint_input activity-gate (reads .idle via spt_store::perch::resolve_idle_file → IDLE+binary→inject / ACTIVE→spool deferred=1 / IDLE+no-binary→spool deferred=0; EXTEND EndpointInjected reply with additive window/deferred hint so caller spools deterministically) + run_inject_worker fault re-spool + idle-transition drain in cmd_state; PART B delivery.rs cmd_poll kill the let_methods discard (relay/poll honors manifest [inject] methods). cmd_send false-branch spools deferred-ness per broker hint. NEXT IMMEDIATE: build PART A spine (broker dispatch gate + EndpointInjected payload extension) first while design fresh. Gate discipline MANDATORY (v0.14.3-paid): full nextest -p spt-daemon, clippy --workspace 0 incl docs, traceable, xtask, grep-OLD-first; doyle re-runs all on real commit. Everyone on legacy owl ($OWL send doyle).


doyle high-priority: **spine CONFIRMED, no corrections — GO build PART A.** Acking + executing the commune (get id, write the delta file):


Bash

printf '%s' "ACK spine-confirmed — fault-respool as non-deferred/idle-eligible noted (a faulted idle-delivery belongs back in the idle window). Communing now, then building PART A spine straight through. Will surface W1 green for your full-crate gate on the real commit." | "$OWL" send doyle 2>&1; echo "RC=${PIPESTATUS[0]}"; echo "=== whoami ==="; "$OWL" whoami 2>&1 | head -3

Ack doyle + get owl id for commune


➜ SENT:doyle
RC=0
=== whoami ===
Found session match for live agent 'todlando'.
✓ WHOAMI:todlando live=true


Id = `todlando`. Write the commune delta file (one atomic write):


Write

# todlando commune — v0.15.0 W1 checkpoint (2026-06-23)

## Just shipped: v0.14.3 raw-inject removal
GATE-PASS, released to deployah (counter 33). Commits `aadafe1` (spine) + `60fadd2` (gate-fix). Survived 3 gate-escapes: (1) doyle's bare-`cargo test` env-race false-flake — HELD with runner-contract evidence (inject_control_wedge.rs:28-40 forbids bare cargo test; CI uses nextest); (2) wall_b sibling test (PR#36 240s timeout) — flipped to spool-assert; (3) my clippy doc-lint slip (line-leading `+`) — fixed. Minted+ratified REQ-HAZARD-IDLE-SILENT-NONDELIVERY. LESSON: shared-seam change → run FULL `cargo nextest run -p spt-daemon`, not just the edited file; clippy every amend incl `///` docs.

## NOW: v0.15.0 W1 — activity-gated delivery substrate
Branch `v0.15.0-messaging @8fd2e51` (off main = v0.14.3). **Authoritative: V0.15.0-PLAN.md + ADR-0028 (both read).** Full design in memory `v0150-w1-activity-gated-delivery.md`. doyle GO, spine CONFIRMED no corrections.

### W1 = 2 surfaces, same wave
- **PART A (spt-daemon broker):** `dispatch_endpoint_input` reads `.idle` via `spt_store::perch::resolve_idle_file(endpoint)` → IDLE+working-binary→INJECT(delivered=true) / ACTIVE→spool deferred=1 / IDLE+no-binary→spool deferred=0. **EXTEND `EndpointInjected` reply** with an additive window/deferred-hint bool (serde-default false, N-1-safe, InputReq.ack pattern) so the caller spools the right deferred-ness off the broker's ONE sentinel read (no TOCTOU). + `run_inject_worker` fault path RE-SPOOLS queued-undelivered to the perch spool **non-deferred (idle-eligible)** — a faulted idle-delivery belongs back in the idle window (relay/poll surfaces it). + idle-transition DRAIN (the wake) in `cmd_state` idle.
- **PART B (spt api/delivery.rs):** kill the `let _methods` discard in `cmd_poll` (:147) → relay/poll honors manifest `[inject]` idle/activity methods.
- **cmd_send false-branch:** spool deferred-ness per the broker hint (active→`send_deferred`, idle→`send`). Keep existing `--deferred` flag (rename→`--active-only` is W2).

### NEXT IMMEDIATE
Build PART A spine first (broker dispatch gate + EndpointInjected payload extension) while design fresh. Then fault re-spool, idle-drain, PART B, cmd_send. Activate `REQ-MSG-DELIVERY-AXES` +impl (int at W4). Tests: unit routing decision-table (injected sentinel, no wall-clock) + int (real broker+PTY nextest): active→spool-not-inject / idle→reaches-PTY-via-binary / active→idle-transition drains+delivers / fault-mid-sequence re-spools.

### Gate discipline (MANDATORY, v0.14.3-paid)
`cargo nextest run -p spt-daemon` FULL crate (NOT bare cargo test; NOT just edited file) · `cargo clippy --workspace --all-targets`=0 incl `///` docs · `traceable-reqs check` EXIT0 · `xtask gen/check` · rebuild bin · grep tests for OLD behavior before any rename. doyle re-runs ALL on the real commit. **Everyone on LEGACY owl — `$OWL send doyle`, NOT `spt send`.**

### Waves after W1
W2 = window+persistence axes (`--idle-only`/`--active-only`[renames `--deferred`]/`--ephemeral` + spool schema 3 additive cols window/channel/ephemeral) · W3 = channel axis (`--prefer-native`/`--force-native`) · W4 = `--json-payload` json="" attr + translation-binary console-window fix (CREATE_NO_WINDOW) + docs + AMEND REQ-HAZARD-IDLE-SILENT-NONDELIVERY (--ephemeral carve-out) + activate REQ-MSG-DELIVERY-AXES +unit+int.


File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\todlando-commune.md (file state is current in your context — no need to Read it back)

# todlando commune — v0.15.0 W1 checkpoint (2026-06-23)

## Just shipped: v0.14.3 raw-inject removal
GATE-PASS, released to deployah (counter 33). Commits `aadafe1` (spine) + `60fadd2` (gate-fix). Survived 3 gate-escapes: (1) doyle's bare-`cargo test` env-race false-flake — HELD with runner-contract evidence (inject_control_wedge.rs:28-40 forbids bare cargo test; CI uses nextest); (2) wall_b sibling test (PR#36 240s timeout) — flipped to spool-assert; (3) my clippy doc-lint slip (line-leading `+`) — fixed. Minted+ratified REQ-HAZARD-IDLE-SILENT-NONDELIVERY. LESSON: shared-seam change → run FULL `cargo nextest run -p spt-daemon`, not just the edited file; clippy every amend incl `///` docs.

## NOW: v0.15.0 W1 — activity-gated delivery substrate
Branch `v0.15.0-messaging @8fd2e51` (off main = v0.14.3). **Authoritative: V0.15.0-PLAN.md + ADR-0028 (both read).** Full design in memory `v0150-w1-activity-gated-delivery.md`. doyle GO, spine CONFIRMED no corrections.

### W1 = 2 surfaces, same wave
- **PART A (spt-daemon broker):** `dispatch_endpoint_input` reads `.idle` via `spt_store::perch::resolve_idle_file(endpoint)` → IDLE+working-binary→INJECT(delivered=true) / ACTIVE→spool deferred=1 / IDLE+no-binary→spool deferred=0. **EXTEND `EndpointInjected` reply** with an additive window/deferred-hint bool (serde-default false, N-1-safe, InputReq.ack pattern) so the caller spools the right deferred-ness off the broker's ONE sentinel read (no TOCTOU). + `run_inject_worker` fault path RE-SPOOLS queued-undelivered to the perch spool **non-deferred (idle-eligible)** — a faulted idle-delivery belongs back in the idle window (relay/poll surfaces it). + idle-transition DRAIN (the wake) in `cmd_state` idle.
- **PART B (spt api/delivery.rs):** kill the `let _methods` discard in `cmd_poll` (:147) → relay/poll honors manifest `[inject]` idle/activity methods.
- **cmd_send false-branch:** spool deferred-ness per the broker hint (active→`send_deferred`, idle→`send`). Keep existing `--deferred` flag (rename→`--active-only` is W2).

### NEXT IMMEDIATE
Build PART A spine first (broker dispatch gate + EndpointInjected payload extension) while design fresh. Then fault re-spool, idle-drain, PART B, cmd_send. Activate `REQ-MSG-DELIVERY-AXES` +impl (int at W4). Tests: unit routing decision-table (injected sentinel, no wall-clock) + int (real broker+PTY nextest): active→spool-not-inject / idle→reaches-PTY-via-binary / active→idle-transition drains+delivers / fault-mid-sequence re-spools.

### Gate discipline (MANDATORY, v0.14.3-paid)
`cargo nextest run -p spt-daemon` FULL crate (NOT bare cargo test; NOT just edited file) · `cargo clippy --workspace --all-targets`=0 incl `///` docs · `traceable-reqs check` EXIT0 · `xtask gen/check` · rebuild bin · grep tests for OLD behavior before any rename. doyle re-runs ALL on the real commit. **Everyone on LEGACY owl — `$OWL send doyle`, NOT `spt send`.**

### Waves after W1
W2 = window+persistence axes (`--idle-only`/`--active-only`[renames `--deferred`]/`--ephemeral` + spool schema 3 additive cols window/channel/ephemeral) · W3 = channel axis (`--prefer-native`/`--force-native`) · W4 = `--json-payload` json="" attr + translation-binary console-window fix (CREATE_NO_WINDOW) + docs + AMEND REQ-HAZARD-IDLE-SILENT-NONDELIVERY (--ephemeral carve-out) + activate REQ-MSG-DELIVERY-AXES +unit+int.


Commune written. Verify ingestion (the file disappearing = success), then start the PART A build:


Bash

cd "C:\Users\decid\Documents\projects\spt-core" && for i in 1 2 3 4 5; do if [ ! -f .claude/todlando-commune.md ]; then echo "INGESTED (file gone) — commune success"; break; fi; sleep 1; done; [ -f .claude/todlando-commune.md ] && echo "still present after 5s (may ingest on next poll-tick; not blocking the build)"

Verify commune file ingested


still present after 5s (may ingest on next poll-tick; not blocking the build)


Commune ingests on the next poll-tick (not blocking; design is durable in memory). **Building PART A.** Read the current `EndpointInjected` struct + envelope builder + the dispatch spine on this branch:


Grep

struct EndpointInjected|fn endpoint_injected_envelope|struct EndpointInputReq


693:pub struct EndpointInputReq {
694-    /// The target endpoint id (the spawn label the broker tracks per session).
695-    pub endpoint: String,
696-    /// The raw bytes to inject (a rendered `<EVENT>` envelope), base64-encoded.
697-    pub data_b64: String,
698-}
699-
700-/// `endpoint-injected` reply payload — the outcome of an [`EndpointInputReq`].
701-#[derive(Debug, Clone, Serialize, Deserialize)]
702:pub struct EndpointInjected {
703-    /// The endpoint the inject targeted.
704-    pub endpoint: String,
705-    /// `true` = a hosted session was found and the bytes were written to its PTY;
706-    /// `false` = no broker session hosts this endpoint (caller falls back to spool).
707-    pub delivered: bool,
708-}
709-
710-/// `brain-restarted` payload — the broker's ack for [`KIND_BRAIN_RESTART`]
--
862:pub fn endpoint_injected_envelope(endpoint: &str, delivered: bool) -> Envelope {
863-    Envelope::new(
864-        KIND_ENDPOINT_INJECTED,
865-        serde_json::to_value(EndpointInjected {
866-            endpoint: endpoint.to_string(),
867-            delivered,
868-        })
869-        .expect("EndpointInjected serializes"),
870-    )
--
1090:    fn endpoint_injected_envelope_carries_delivered_both_ways() {
1091-        let spooled = endpoint_injected_envelope("wall-b", false);
1092-        assert_eq!(spooled.kind, KIND_ENDPOINT_INJECTED);
1093-        let ev: EndpointInjected = serde_json::from_value(spooled.payload).unwrap();
1094-        assert_eq!(ev.endpoint, "wall-b");
1095-        assert!(
1096-            !ev.delivered,
1097-            "a no-working-binary reply MUST carry delivered=false so the caller spools"
1098-        );


PART A step 1 — extend `EndpointInjected` with the additive `spool_deferred` hint (serde-default false, N-1-safe):


Edit

/// `endpoint-injected` reply payload — the outcome of an [`EndpointInputReq`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndpointInjected {
    /// The endpoint the inject targeted.
    pub endpoint: String,
    /// `true` = a hosted session was found and the bytes were written to its PTY;
    /// `false` = no broker session hosts this endpoint (caller falls back to spool).
    pub delivered: bool,
}

/// `endpoint-injected` reply payload — the outcome of an [`EndpointInputReq`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndpointInjected {
    /// The endpoint the inject targeted.
    pub endpoint: String,
    /// `true` = a hosted session was found, IDLE, with a working translation binary,
    /// and the bytes were delivered (injected) to its PTY; `false` = the caller must
    /// SPOOL (no hosted session, the endpoint is ACTIVE, or no working binary).
    pub delivered: bool,
    /// The activity-gate hint (ADR-0028, v0.15.0): when `delivered == false`, the
    /// deferred-ness the caller should spool with — decided from the broker's SINGLE
    /// activity-sentinel read so there is no caller-side TOCTOU. `true` = the endpoint
    /// was ACTIVE → spool DEFERRED (the receiver's hook-poll window only, no live
    /// wake); `false` = IDLE / no hosted session → spool NON-deferred (the live
    /// relay/poll surfaces it = the spt-core-driven wake). Additive + serde-defaulted
    /// (`false`): an N-1 broker omits it and the caller spools non-deferred exactly as
    /// in v0.14.3. Meaningless (ignored) when `delivered == true`.
    #[serde(default)]
    pub spool_deferred: bool,
}


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\msg.rs has been updated successfully. (file state is current in your context — no need to Read it back)

/// `endpoint-injected` reply payload — the outcome of an [`EndpointInputReq`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndpointInjected {
    /// The endpoint the inject targeted.
    pub endpoint: String,
    /// `true` = a hosted session was found and the bytes were written to its PTY;
    /// `false` = no broker session hosts this endpoint (caller falls back to spool).
    pub delivered: bool,
}

/// `endpoint-injected` reply payload — the outcome of an [`EndpointInputReq`].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EndpointInjected {
    /// The endpoint the inject targeted.
    pub endpoint: String,
    /// `true` = a hosted session was found, IDLE, with a working translation binary,
    /// and the bytes were delivered (injected) to its PTY; `false` = the caller must
    /// SPOOL (no hosted session, the endpoint is ACTIVE, or no working binary).
    pub delivered: bool,
    /// The activity-gate hint (ADR-0028, v0.15.0): when `delivered == false`, the
    /// deferred-ness the caller should spool with — decided from the broker's SINGLE
    /// activity-sentinel read so there is no caller-side TOCTOU. `true` = the endpoint
    /// was ACTIVE → spool DEFERRED (the receiver's hook-poll window only, no live
    /// wake); `false` = IDLE / no hosted session → spool NON-deferred (the live
    /// relay/poll surfaces it = the spt-core-driven wake). Additive + serde-defaulted
    /// (`false`): an N-1 broker omits it and the caller spools non-deferred exactly as
    /// in v0.14.3. Meaningless (ignored) when `delivered == true`.
    #[serde(default)]
    pub spool_deferred: bool,
}

-    /// `true` = a hosted session was found and the bytes were written to its PTY;

-    /// `false` = no broker session hosts this endpoint (caller falls back to spool).

+    /// `true` = a hosted session was found, IDLE, with a working translation binary,

+    /// and the bytes were delivered (injected) to its PTY; `false` = the caller must

+    /// SPOOL (no hosted session, the endpoint is ACTIVE, or no working binary).

+    /// The activity-gate hint (ADR-0028, v0.15.0): when `delivered == false`, the

+    /// deferred-ness the caller should spool with — decided from the broker's SINGLE

+    /// activity-sentinel read so there is no caller-side TOCTOU. `true` = the endpoint

+    /// was ACTIVE → spool DEFERRED (the receiver's hook-poll window only, no live

+    /// wake); `false` = IDLE / no hosted session → spool NON-deferred (the live

+    /// relay/poll surfaces it = the spt-core-driven wake). Additive + serde-defaulted

+    /// (`false`): an N-1 broker omits it and the caller spools non-deferred exactly as

+    /// in v0.14.3. Meaningless (ignored) when `delivered == true`.

/// `brain-restarted` payload — the broker's ack for [`KIND_BRAIN_RESTART`]


pub fn endpoint_injected_envelope(endpoint: &str, delivered: bool) -> Envelope {
    Envelope::new(
        KIND_ENDPOINT_INJECTED,
        serde_json::to_value(EndpointInjected {
            endpoint: endpoint.to_string(),
            delivered,
        })
        .expect("EndpointInjected serializes"),
    )

pub fn endpoint_injected_envelope(
    endpoint: &str,
    delivered: bool,
    spool_deferred: bool,
) -> Envelope {
    Envelope::new(
        KIND_ENDPOINT_INJECTED,
        serde_json::to_value(EndpointInjected {
            endpoint: endpoint.to_string(),
            delivered,
            spool_deferred,
        })
        .expect("EndpointInjected serializes"),
    )


pub fn endpoint_injected_envelope(endpoint: &str, delivered: bool) -> Envelope {
    Envelope::new(
        KIND_ENDPOINT_INJECTED,
        serde_json::to_value(EndpointInjected {
            endpoint: endpoint.to_string(),
            delivered,
        })
        .expect("EndpointInjected serializes"),
    )

pub fn endpoint_injected_envelope(
    endpoint: &str,
    delivered: bool,
    spool_deferred: bool,
) -> Envelope {
    Envelope::new(
        KIND_ENDPOINT_INJECTED,
        serde_json::to_value(EndpointInjected {
            endpoint: endpoint.to_string(),
            delivered,
            spool_deferred,
        })
        .expect("EndpointInjected serializes"),
    )

/// Build an `endpoint-injected` reply envelope (REQ-SEND-SPT-HOSTED).

-pub fn endpoint_injected_envelope(endpoint: &str, delivered: bool) -> Envelope {


Find all `endpoint_injected_envelope` callers to update the new arg:


crates\spt-daemon\src\broker.rs:1923:                            send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
crates\spt-daemon\src\broker.rs:1941:                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
crates\spt-daemon\src\broker.rs:1946:                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
crates\spt-daemon\src\msg.rs:873:pub fn endpoint_injected_envelope(
crates\spt-daemon\src\msg.rs:1046:        let env = endpoint_injected_envelope("wall-b", true);
crates\spt-daemon\src\msg.rs:1051:            serde_json::from_value(endpoint_injected_envelope("x", false).payload).unwrap();
crates\spt-daemon\src\msg.rs:1107:        let spooled = endpoint_injected_envelope("wall-b", false);
crates\spt-daemon\src\msg.rs:1117:            serde_json::from_value(endpoint_injected_envelope("wall-b", true).payload).unwrap();


Read the current dispatch_endpoint_input on this branch to restructure with the activity gate:


1895	            decode_bytes(&req.data_b64).map_err(|e| format!("bad endpoint-input base64: {e}"))?;
1896	        // Resolve endpoint → translation under the lock, cloning the Arc out so the
1897	        // event send happens without holding the table lock (dispatch_input rule).
1898	        // The session's PTY input writer is intentionally NOT pulled here: idle
1899	        // delivery is translation-binary-ONLY (ADR-0022 amendment) — with no
1900	        // working binary the message SPOOLS, it is never written to the PTY.
1901	        let resolved = {
1902	            let sessions = self.sessions.lock().unwrap();
1903	            sessions
1904	                .values()
1905	                .find(|h| h.endpoint == req.endpoint)
1906	                .map(|h| h.translation.clone())
1907	        };
1908	        match resolved {
1909	            Some(translation) => {
1910	                // Layer E: a live (non-faulted) translation binary OWNS the
1911	                // choreography — queue the whole `<EVENT>` envelope to the inject
1912	                // worker, which drives it through the binary and applies the
1913	                // resulting keystrokes atomically w.r.t. the controller.
1914	                if let Some(tr) = &translation {
1915	                    if !tr.faulted.load(Ordering::Relaxed) {
1916	                        let envelope = String::from_utf8_lossy(&bytes).into_owned();
1917	                        if tr.event_tx.send(envelope).is_ok() {
1918	                            eprintln!(
1919	                                "ENDPOINT_INJECT:{} ({} bytes → translation binary)",
1920	                                req.endpoint,
1921	                                bytes.len()
1922	                            );
1923	                            send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
1924	                            return Ok(());
1925	                        }
1926	                        // worker receiver dropped → SPOOL (below), never raw inject.
1927	                    }
1928	                }
1929	                // No WORKING translation binary (none declared, faulted, or its
1930	                // inject worker is gone) → tell the caller to SPOOL (delivered=false).
1931	                // Raw `payload + \r` is NOT a fallback: on a modern TUI it does not
1932	                // submit — a non-delivering pseudo-write that MASKED F-019. The
1933	                // inbound is never lost (it stays spooled, poll-fed) and the failure
1934	                // is LOUD so a mis-declared/repaired binary is diagnosable.
1935	                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
1936	                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
1937	                eprintln!(
1938	                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",
1939	                    req.endpoint
1940	                );
1941	                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
1942	                Ok(())
1943	            }
1944	            // No hosted session for this endpoint — tell the caller to spool.
1945	            None => {
1946	                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
1947	                Ok(())
1948	            }
1949	        }

            decode_bytes(&req.data_b64).map_err(|e| format!("bad endpoint-input base64: {e}"))?;
        // Resolve endpoint → translation under the lock, cloning the Arc out so the
        // event send happens without holding the table lock (dispatch_input rule).
        // The session's PTY input writer is intentionally NOT pulled here: idle
        // delivery is translation-binary-ONLY (ADR-0022 amendment) — with no
        // working binary the message SPOOLS, it is never written to the PTY.
        let resolved = {
            let sessions = self.sessions.lock().unwrap();
            sessions
                .values()
                .find(|h| h.endpoint == req.endpoint)
                .map(|h| h.translation.clone())
        };
        match resolved {
            Some(translation) => {
                // Layer E: a live (non-faulted) translation binary OWNS the
                // choreography — queue the whole `<EVENT>` envelope to the inject
                // worker, which drives it through the binary and applies the
                // resulting keystrokes atomically w.r.t. the controller.
                if let Some(tr) = &translation {
                    if !tr.faulted.load(Ordering::Relaxed) {
                        let envelope = String::from_utf8_lossy(&bytes).into_owned();
                        if tr.event_tx.send(envelope).is_ok() {
                            eprintln!(
                                "ENDPOINT_INJECT:{} ({} bytes → translation binary)",
                                req.endpoint,
                                bytes.len()
                            );
                            send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                            return Ok(());
                        }
                        // worker receiver dropped → SPOOL (below), never raw inject.
                    }
                }
                // No WORKING translation binary (none declared, faulted, or its
                // inject worker is gone) → tell the caller to SPOOL (delivered=false).
                // Raw `payload + \r` is NOT a fallback: on a modern TUI it does not
                // submit — a non-delivering pseudo-write that MASKED F-019. The
                // inbound is never lost (it stays spooled, poll-fed) and the failure
                // is LOUD so a mis-declared/repaired binary is diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",
                    req.endpoint
                );
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
                Ok(())
            }
            // No hosted session for this endpoint — tell the caller to spool.
            None => {
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
                Ok(())
            }
        }


The activity gate restructures the spt-hosted route. **Important ripple:** without `.idle` set, the gate now routes to ACTIVE→spool instead of inject — so every existing test that sends to a hosted binary expecting injection (g1/g2/wall_b/etc.) will need `.idle` set first ([[shared-seam-change-run-all-seam-tests]] again). I'll handle the test sweep after the spine. Apply the dispatch restructure:


        match resolved {
            Some(translation) => {
                // Layer E: a live (non-faulted) translation binary OWNS the
                // choreography — queue the whole `<EVENT>` envelope to the inject
                // worker, which drives it through the binary and applies the
                // resulting keystrokes atomically w.r.t. the controller.
                if let Some(tr) = &translation {
                    if !tr.faulted.load(Ordering::Relaxed) {
                        let envelope = String::from_utf8_lossy(&bytes).into_owned();
                        if tr.event_tx.send(envelope).is_ok() {
                            eprintln!(
                                "ENDPOINT_INJECT:{} ({} bytes → translation binary)",
                                req.endpoint,
                                bytes.len()
                            );
                            send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                            return Ok(());
                        }
                        // worker receiver dropped → SPOOL (below), never raw inject.
                    }
                }
                // No WORKING translation binary (none declared, faulted, or its
                // inject worker is gone) → tell the caller to SPOOL (delivered=false).
                // Raw `payload + \r` is NOT a fallback: on a modern TUI it does not
                // submit — a non-delivering pseudo-write that MASKED F-019. The
                // inbound is never lost (it stays spooled, poll-fed) and the failure
                // is LOUD so a mis-declared/repaired binary is diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",
                    req.endpoint
                );
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
                Ok(())
            }
            // No hosted session for this endpoint — tell the caller to spool.
            None => {
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
                Ok(())
            }
        }

        match resolved {
            Some(translation) => {
                // ACTIVITY GATE (ADR-0028, v0.15.0): route by the endpoint's `.idle`
                // sentinel, read ONCE here so the caller spools the right deferred-ness
                // off this single read (no caller-side TOCTOU). An endpoint is ACTIVE
                // until it explicitly declares idle (`api state idle`).
                //   ACTIVE ⇒ the message waits for the receiver's own hook-poll: spool
                //     DEFERRED (no live wake) — never injected mid-activity, even with a
                //     binary attached (the InjectFloor mid-turn path is the W3 native
                //     override, not the default).
                //   IDLE  ⇒ deliver now via a working translation binary, else spool
                //     NON-deferred (the live relay/poll surfaces it = the wake).
                let idle = spt_store::perch::resolve_idle_file(
                    &req.endpoint,
                    spt_store::perch::ParentHint::Infer,
                )
                .exists();
                if !idle {
                    // [impl->REQ-MSG-DELIVERY-AXES]
                    eprintln!(
                        "ENDPOINT_INJECT:{}: endpoint ACTIVE -> spooled (deferred, hook-poll window), not injected",
                        req.endpoint
                    );
                    send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, true));
                    return Ok(());
                }
                // IDLE window: a live (non-faulted) translation binary OWNS the
                // choreography — queue the whole `<EVENT>` envelope to the inject
                // worker, which drives it through the binary and applies the resulting
                // keystrokes atomically w.r.t. the controller.
                // [impl->REQ-MSG-DELIVERY-AXES]
                if let Some(tr) = &translation {
                    if !tr.faulted.load(Ordering::Relaxed) {
                        let envelope = String::from_utf8_lossy(&bytes).into_owned();
                        if tr.event_tx.send(envelope).is_ok() {
                            eprintln!(
                                "ENDPOINT_INJECT:{} ({} bytes → translation binary)",
                                req.endpoint,
                                bytes.len()
                            );
                            send_frame(
                                send,
                                &endpoint_injected_envelope(&req.endpoint, true, false),
                            );
                            return Ok(());
                        }
                        // worker receiver dropped → SPOOL (below), never raw inject.
                    }
                }
                // IDLE + no WORKING translation binary (none declared, faulted, or its
                // inject worker is gone) → tell the caller to SPOOL NON-deferred (idle
                // window; the live relay/poll surfaces it = the wake). Raw `payload + \r`
                // is NOT a fallback: on a modern TUI it does not submit — a non-delivering
                // pseudo-write that MASKED F-019. The inbound is never lost (spooled,
                // poll-fed) and the failure is LOUD so a mis-declared/repaired binary is
                // diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED (idle window), not injected",
                    req.endpoint
                );
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));
                Ok(())
            }
            // No hosted session for this endpoint — tell the caller to spool
            // NON-deferred (idle-eligible; a non-hosted target has no active window).
            None => {
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));
                Ok(())
            }
        }


The file C:\Users\decid\Documents\projects\spt-core\crates\spt-daemon\src\broker.rs has been updated successfully. (file state is current in your context — no need to Read it back)

        match resolved {
            Some(translation) => {
                // Layer E: a live (non-faulted) translation binary OWNS the
                // choreography — queue the whole `<EVENT>` envelope to the inject
                // worker, which drives it through the binary and applies the
                // resulting keystrokes atomically w.r.t. the controller.
                if let Some(tr) = &translation {
                    if !tr.faulted.load(Ordering::Relaxed) {
                        let envelope = String::from_utf8_lossy(&bytes).into_owned();
                        if tr.event_tx.send(envelope).is_ok() {
                            eprintln!(
                                "ENDPOINT_INJECT:{} ({} bytes → translation binary)",
                                req.endpoint,
                                bytes.len()
                            );
                            send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));
                            return Ok(());
                        }
                        // worker receiver dropped → SPOOL (below), never raw inject.
                    }
                }
                // No WORKING translation binary (none declared, faulted, or its
                // inject worker is gone) → tell the caller to SPOOL (delivered=false).
                // Raw `payload + \r` is NOT a fallback: on a modern TUI it does not
                // submit — a non-delivering pseudo-write that MASKED F-019. The
                // inbound is never lost (it stays spooled, poll-fed) and the failure
                // is LOUD so a mis-declared/repaired binary is diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",
                    req.endpoint
                );
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
                Ok(())
            }
            // No hosted session for this endpoint — tell the caller to spool.
            None => {
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));
                Ok(())
            }
        }

        match resolved {
            Some(translation) => {
                // ACTIVITY GATE (ADR-0028, v0.15.0): route by the endpoint's `.idle`
                // sentinel, read ONCE here so the caller spools the right deferred-ness
                // off this single read (no caller-side TOCTOU). An endpoint is ACTIVE
                // until it explicitly declares idle (`api state idle`).
                //   ACTIVE ⇒ the message waits for the receiver's own hook-poll: spool
                //     DEFERRED (no live wake) — never injected mid-activity, even with a
                //     binary attached (the InjectFloor mid-turn path is the W3 native
                //     override, not the default).
                //   IDLE  ⇒ deliver now via a working translation binary, else spool
                //     NON-deferred (the live relay/poll surfaces it = the wake).
                let idle = spt_store::perch::resolve_idle_file(
                    &req.endpoint,
                    spt_store::perch::ParentHint::Infer,
                )
                .exists();
                if !idle {
                    // [impl->REQ-MSG-DELIVERY-AXES]
                    eprintln!(
                        "ENDPOINT_INJECT:{}: endpoint ACTIVE -> spooled (deferred, hook-poll window), not injected",
                        req.endpoint
                    );
                    send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, true));
                    return Ok(());
                }
                // IDLE window: a live (non-faulted) translation binary OWNS the
                // choreography — queue the whole `<EVENT>` envelope to the inject
                // worker, which drives it through the binary and applies the resulting
                // keystrokes atomically w.r.t. the controller.
                // [impl->REQ-MSG-DELIVERY-AXES]
                if let Some(tr) = &translation {
                    if !tr.faulted.load(Ordering::Relaxed) {
                        let envelope = String::from_utf8_lossy(&bytes).into_owned();
                        if tr.event_tx.send(envelope).is_ok() {
                            eprintln!(
                                "ENDPOINT_INJECT:{} ({} bytes → translation binary)",
                                req.endpoint,
                                bytes.len()
                            );
                            send_frame(
                                send,
                                &endpoint_injected_envelope(&req.endpoint, true, false),
                            );
                            return Ok(());
                        }
                        // worker receiver dropped → SPOOL (below), never raw inject.
                    }
                }
                // IDLE + no WORKING translation binary (none declared, faulted, or its
                // inject worker is gone) → tell the caller to SPOOL NON-deferred (idle
                // window; the live relay/poll surfaces it = the wake). Raw `payload + \r`
                // is NOT a fallback: on a modern TUI it does not submit — a non-delivering
                // pseudo-write that MASKED F-019. The inbound is never lost (spooled,
                // poll-fed) and the failure is LOUD so a mis-declared/repaired binary is
                // diagnosable.
                // [impl->REQ-MSG-IDLE-TRANSLATION-BINARY]
                // [impl->REQ-HAZARD-IDLE-SILENT-NONDELIVERY]
                eprintln!(
                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED (idle window), not injected",
                    req.endpoint
                );
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));
                Ok(())
            }
            // No hosted session for this endpoint — tell the caller to spool
            // NON-deferred (idle-eligible; a non-hosted target has no active window).
            None => {
                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));
                Ok(())
            }
        }

-                // Layer E: a live (non-faulted) translation binary OWNS the

+                // ACTIVITY GATE (ADR-0028, v0.15.0): route by the endpoint's `.idle`

+                // sentinel, read ONCE here so the caller spools the right deferred-ness

+                // off this single read (no caller-side TOCTOU). An endpoint is ACTIVE

+                // until it explicitly declares idle (`api state idle`).

+                //   ACTIVE ⇒ the message waits for the receiver's own hook-poll: spool

+                //     DEFERRED (no live wake) — never injected mid-activity, even with a

+                //     binary attached (the InjectFloor mid-turn path is the W3 native

+                //     override, not the default).

+                //   IDLE  ⇒ deliver now via a working translation binary, else spool

+                //     NON-deferred (the live relay/poll surfaces it = the wake).

+                let idle = spt_store::perch::resolve_idle_file(

+                    spt_store::perch::ParentHint::Infer,

+                    // [impl->REQ-MSG-DELIVERY-AXES]

+                        "ENDPOINT_INJECT:{}: endpoint ACTIVE -> spooled (deferred, hook-poll window), not injected",

+                    send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, true));

+                // IDLE window: a live (non-faulted) translation binary OWNS the

-                // worker, which drives it through the binary and applies the

-                // resulting keystrokes atomically w.r.t. the controller.

+                // worker, which drives it through the binary and applies the resulting

+                // keystrokes atomically w.r.t. the controller.

+                // [impl->REQ-MSG-DELIVERY-AXES]

-                            send_frame(send, &endpoint_injected_envelope(&req.endpoint, true));

+                            send_frame(

+                                &endpoint_injected_envelope(&req.endpoint, true, false),

-                // No WORKING translation binary (none declared, faulted, or its

-                // inject worker is gone) → tell the caller to SPOOL (delivered=false).

-                // Raw `payload + \r` is NOT a fallback: on a modern TUI it does not

-                // submit — a non-delivering pseudo-write that MASKED F-019. The

-                // inbound is never lost (it stays spooled, poll-fed) and the failure

-                // is LOUD so a mis-declared/repaired binary is diagnosable.

+                // IDLE + no WORKING translation binary (none declared, faulted, or its

+                // inject worker is gone) → tell the caller to SPOOL NON-deferred (idle

+                // window; the live relay/poll surfaces it = the wake). Raw `payload + \r`

+                // is NOT a fallback: on a modern TUI it does not submit — a non-delivering

+                // pseudo-write that MASKED F-019. The inbound is never lost (spooled,

+                // poll-fed) and the failure is LOUD so a mis-declared/repaired binary is

-                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED, not injected",

+                    "ENDPOINT_INJECT:{}: no working translation binary (absent/faulted/worker-gone) -> SPOOLED (idle window), not injected",

-                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false));

+                send_frame(send, &endpoint_injected_envelope(&req.endpoint, false, false));

-            // No hosted session for this endpoint — tell the caller to spool.

+            // No hosted session for this endpoint — tell the caller to spool

+            // NON-deferred (idle-eligible; a non-hosted target has no active window).


Dispatch gated. Now fix the msg.rs test callers (3 args) + extend the delivered-hint unit. Read the test region:


1040	        };
1041	        let back: EndpointInputReq =
1042	            serde_json::from_value(serde_json::to_value(&req).unwrap()).unwrap();
1043	        assert_eq!(back.endpoint, "wall-b");
1044	        assert_eq!(decode_bytes(&back.data_b64).unwrap(), decode_bytes(&req.data_b64).unwrap());
1045
1046	        let env = endpoint_injected_envelope("wall-b", true);
1047	        assert_eq!(env.kind, KIND_ENDPOINT_INJECTED);
1048	        let ev: EndpointInjected = serde_json::from_value(env.payload).unwrap();
1049	        assert_eq!((ev.endpoint.as_str(), ev.delivered), ("wall-b", true));
1050	        let miss: EndpointInjected =
1051	            serde_json::from_value(endpoint_injected_envelope("x", false).payload).unwrap();
1052	        assert!(!miss.delivered);
1053	    }
1054
1055	    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT] the `evicted_envelope` helper
1056	    // produces a `viewer_evicted` event whose payload preserves the session id, and
1057	    // it round-trips through serde (mirroring the size/displaced envelope tests).
1058	    // This is the marker the broker's `viewer_writer` thread emits as its FINAL
1059	    // frame on an overflow eviction — the wire contract the serving brain reads to
1060	    // skip-to-live instead of tearing the attach down.
1061	    //
1062	    // Non-vacuous: this is the only assertion that pins `evicted_envelope`'s kind to
1063	    // `KIND_VIEWER_EVICTED` and the payload's `session_id` to the argument. Mis-tag
1064	    // the kind (e.g. reuse `KIND_DISPLACED`) and the kind assert flips RED; drop the
1065	    // `session_id` carry and the brain could never tell WHICH session's viewer to
1066	    // re-subscribe.
1067	    #[test]
1068	    fn evicted_envelope_round_trips() {
1069	        let env = evicted_envelope(7);
1070	        assert_eq!(
1071	            env.kind, KIND_VIEWER_EVICTED,
1072	            "the evicted marker rides the viewer_evicted kind discriminator"
1073	        );
1074	        let ev: ViewerEvictedEvent = serde_json::from_value(env.payload).unwrap();
1075	        assert_eq!(
1076	            ev.session_id, 7,
1077	            "the evicted marker carries the session id so the brain re-subscribes \
1078	             the RIGHT session (skip-to-live)"
1079	        );
1080
1081	        // A newer broker's extra field is tolerated (the catalog's forward-compat
1082	        // carries to this additive event too — KH-2.3).
1083	        let wire = json!({ "session_id": 9, "reason_added_later": "overflow" });
1084	        let future: ViewerEvictedEvent = serde_json::from_value(wire).unwrap();
1085	        assert_eq!(future.session_id, 9);
1086	    }
1087
1088	    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.
1089	    #[test]
1090	    fn output_envelope_round_trips() {
1091	        let env = output_envelope(7, 42, b"hello");
1092	        assert_eq!(env.kind, KIND_OUTPUT);
1093	        let ev: OutputEvent = serde_json::from_value(env.payload).unwrap();
1094	        assert_eq!(ev.session_id, 7);
1095	        assert_eq!(ev.seq, 42);
1096	        assert_eq!(decode_bytes(&ev.data_b64).unwrap(), b"hello");
1097	    }
1098
1099	    // [unit->REQ-HAZARD-IDLE-SILENT-NONDELIVERY] The `delivered` flag is the spool
1100	    // signal: `false` is what the CLI's `try_broker_inject` reads to fall through to
1101	    // `deliver::send` (SPOOL) instead of reporting "Sent". With raw inject removed,
1102	    // the no-working-binary path replies `delivered=false` — this asserts the
1103	    // envelope faithfully carries that bool BOTH ways, so a false (spool) verdict can
1104	    // never be silently rendered as a true (delivered) one on the wire.
1105	    #[test]
1106	    fn endpoint_injected_envelope_carries_delivered_both_ways() {
1107	        let spooled = endpoint_injected_envelope("wall-b", false);
1108	        assert_eq!(spooled.kind, KIND_ENDPOINT_INJECTED);
1109	        let ev: EndpointInjected = serde_json::from_value(spooled.payload).unwrap();
1110	        assert_eq!(ev.endpoint, "wall-b");
1111	        assert!(
1112	            !ev.delivered,
1113	            "a no-working-binary reply MUST carry delivered=false so the caller spools"
1114	        );
1115
1116	        let delivered: EndpointInjected =
1117	            serde_json::from_value(endpoint_injected_envelope("wall-b", true).payload).unwrap();
1118	        assert!(delivered.delivered, "a binary-delivered reply carries delivered=true");
1119	    }
1120
1121	    // [unit->REQ-HAZARD-INPUT-ACK-BACKPRESSURE] An `input` payload from an OLDER
1122	    // brain carries NO `ack` field on the wire (the field was added by this REQ).
1123	    // It MUST decode to `ack = true` so the old-brain path keeps acking exactly as
1124	    // it did before the fix (N-1 forward-compat: an old brain's `input` still gets

        };
        let back: EndpointInputReq =
            serde_json::from_value(serde_json::to_value(&req).unwrap()).unwrap();
        assert_eq!(back.endpoint, "wall-b");
        assert_eq!(decode_bytes(&back.data_b64).unwrap(), decode_bytes(&req.data_b64).unwrap());

        let env = endpoint_injected_envelope("wall-b", true);
        assert_eq!(env.kind, KIND_ENDPOINT_INJECTED);
        let ev: EndpointInjected = serde_json::from_value(env.payload).unwrap();
        assert_eq!((ev.endpoint.as_str(), ev.delivered), ("wall-b", true));
        let miss: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("x", false).payload).unwrap();
        assert!(!miss.delivered);
    }

    // [unit->REQ-VIEWER-SKIP-TO-LIVE-ON-EVICT] the `evicted_envelope` helper
    // produces a `viewer_evicted` event whose payload preserves the session id, and
    // it round-trips through serde (mirroring the size/displaced envelope tests).
    // This is the marker the broker's `viewer_writer` thread emits as its FINAL
    // frame on an overflow eviction — the wire contract the serving brain reads to
    // skip-to-live instead of tearing the attach down.
    //
    // Non-vacuous: this is the only assertion that pins `evicted_envelope`'s kind to
    // `KIND_VIEWER_EVICTED` and the payload's `session_id` to the argument. Mis-tag
    // the kind (e.g. reuse `KIND_DISPLACED`) and the kind assert flips RED; drop the
    // `session_id` carry and the brain could never tell WHICH session's viewer to
    // re-subscribe.
    #[test]
    fn evicted_envelope_round_trips() {
        let env = evicted_envelope(7);
        assert_eq!(
            env.kind, KIND_VIEWER_EVICTED,
            "the evicted marker rides the viewer_evicted kind discriminator"
        );
        let ev: ViewerEvictedEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!(
            ev.session_id, 7,
            "the evicted marker carries the session id so the brain re-subscribes \
             the RIGHT session (skip-to-live)"
        );

        // A newer broker's extra field is tolerated (the catalog's forward-compat
        // carries to this additive event too — KH-2.3).
        let wire = json!({ "session_id": 9, "reason_added_later": "overflow" });
        let future: ViewerEvictedEvent = serde_json::from_value(wire).unwrap();
        assert_eq!(future.session_id, 9);
    }

    // [unit->REQ-DAEMON-2] the output-envelope helper produces a decodable event.
    #[test]
    fn output_envelope_round_trips() {
        let env = output_envelope(7, 42, b"hello");
        assert_eq!(env.kind, KIND_OUTPUT);
        let ev: OutputEvent = serde_json::from_value(env.payload).unwrap();
        assert_eq!(ev.session_id, 7);
        assert_eq!(ev.seq, 42);
        assert_eq!(decode_bytes(&ev.data_b64).unwrap(), b"hello");
    }

    // [unit->REQ-HAZARD-IDLE-SILENT-NONDELIVERY] The `delivered` flag is the spool
    // signal: `false` is what the CLI's `try_broker_inject` reads to fall through to
    // `deliver::send` (SPOOL) instead of reporting "Sent". With raw inject removed,
    // the no-working-binary path replies `delivered=false` — this asserts the
    // envelope faithfully carries that bool BOTH ways, so a false (spool) verdict can
    // never be silently rendered as a true (delivered) one on the wire.
    #[test]
    fn endpoint_injected_envelope_carries_delivered_both_ways() {
        let spooled = endpoint_injected_envelope("wall-b", false);
        assert_eq!(spooled.kind, KIND_ENDPOINT_INJECTED);
        let ev: EndpointInjected = serde_json::from_value(spooled.payload).unwrap();
        assert_eq!(ev.endpoint, "wall-b");
        assert!(
            !ev.delivered,
            "a no-working-binary reply MUST carry delivered=false so the caller spools"
        );

        let delivered: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("wall-b", true).payload).unwrap();
        assert!(delivered.delivered, "a binary-delivered reply carries delivered=true");
    }

    // [unit->REQ-HAZARD-INPUT-ACK-BACKPRESSURE] An `input` payload from an OLDER
    // brain carries NO `ack` field on the wire (the field was added by this REQ).
    // It MUST decode to `ack = true` so the old-brain path keeps acking exactly as
    // it did before the fix (N-1 forward-compat: an old brain's `input` still gets


Update the test call sites + extend the hint unit to cover `spool_deferred` + the N-1 default:


        let env = endpoint_injected_envelope("wall-b", true);
        assert_eq!(env.kind, KIND_ENDPOINT_INJECTED);
        let ev: EndpointInjected = serde_json::from_value(env.payload).unwrap();
        assert_eq!((ev.endpoint.as_str(), ev.delivered), ("wall-b", true));
        let miss: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("x", false).payload).unwrap();
        assert!(!miss.delivered);
    }

        let env = endpoint_injected_envelope("wall-b", true, false);
        assert_eq!(env.kind, KIND_ENDPOINT_INJECTED);
        let ev: EndpointInjected = serde_json::from_value(env.payload).unwrap();
        assert_eq!((ev.endpoint.as_str(), ev.delivered), ("wall-b", true));
        let miss: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("x", false, false).payload).unwrap();
        assert!(!miss.delivered);
    }


        let env = endpoint_injected_envelope("wall-b", true);
        assert_eq!(env.kind, KIND_ENDPOINT_INJECTED);
        let ev: EndpointInjected = serde_json::from_value(env.payload).unwrap();
        assert_eq!((ev.endpoint.as_str(), ev.delivered), ("wall-b", true));
        let miss: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("x", false).payload).unwrap();
        assert!(!miss.delivered);
    }

        let env = endpoint_injected_envelope("wall-b", true, false);
        assert_eq!(env.kind, KIND_ENDPOINT_INJECTED);
        let ev: EndpointInjected = serde_json::from_value(env.payload).unwrap();
        assert_eq!((ev.endpoint.as_str(), ev.delivered), ("wall-b", true));
        let miss: EndpointInjected =
            serde_json::from_value(endpoint_injected_envelope("x", false, false).payload).unwrap();
        assert!(!miss.delivered);
    }

-        let env = endpoint_injected_envelope("wall-b", true);

+        let env = endpoint_injected_envelope("wall-b", true, false);

-            serde_json::from_value(endpoint_injected_envelope("x", false).payload).unwrap();

+            serde_json::from_value(endpoint_injected_envelope("x", false, false).payload).unwrap();