# M5-D5 — resting-state arms (JIT plan)

**Status:** authored 2026-06-04 · D5a shipped `26c1419` FINAL (resting gate:
`resting::deferred_held`, `poll_drain` + `cmd_worker_poll` gated) · D5b shipped
(`StreamFamily::Rest` + `resthost` request/serve, `cmd_rest` qualified-form lift via
`wansend::wan_rest`, loopback int E2E) · D5c shipped (DEFERRED.md remote-fork note) ·
REQ-INST-6 `[impl, unit, int]` · **D5 CLOSED** (`aa88033` CI-green-FINAL; next:
`M5-D6-PLAN.md`). Upstream: D4 closed (`8bfa933`,
CI-green-FINAL — see `M5-D4-PLAN.md`). D5 arms the carried M4 resting seams: the
deferred-message resting gate (REQ-INST-6), the remote `spt suspend/wake <id@node>`
arm, and the remote-fork deferral note. Smallest M5 task — three slices, two of them
code. Authoritative: **CONTEXT §Instances** (resting model), `spt-daemon::resting`
module docs (the documented REQ-INST-6 seam, resting.rs ~40), KNOWN-HAZARDS 1.4/4.4,
M5-PLAN §D5, M4-D9-PLAN ("Remote `spt suspend id@node` arm = M5 seam").

## Goal

A resting instance's deferred (spool-only) rows **hold** — invisible to every consumer
— while it is dormant/suspended and surface **exactly once** after the wake edge; any
of your instances can be suspended/woken **from any node** (`spt suspend <id@node>`,
`spt wake <id@node>`) riding the remote-drive trust class; the remote fork arm's
deferral is recorded where a future builder will look.

## Researched code map (explorer-verified 2026-06-04)

- **REQ-INST-6 seam comment:** `spt-daemon::resting` module docs (~line 40): "the
  state machine here is its precondition, the gate itself stays unbuilt." The gate's
  input is durable (`read_rest` on `info.json`, M4-D9-2).
- **Deferred rows today:** `spt_store::spool` — `spool_message_deferred_at` writes
  `deferred = 1`; `drain_non_deferred_at` (event stream — KH 1.4/4.4: deferred never
  leaks there); `drain_all_at`/`peek_all_at` are the hook-channel consumers that DO
  see deferred rows. The hold side of the gate lands at those consumer sites; the
  release side at the wake edge.
- **Wake edge:** `resting::fire_wake_effects` (resurface_at_boundary + freshness-pull
  marker) — the release hook's home; `daemon_rest_event` / `BrainLifecycle::rest_event`
  both route it (the D4a cascade pattern).
- **Remote op routing:** `dispatch.rs` `StreamFamily` demux (Sync/Update/Notif/
  WanMsg/Registry/Attach/Xfer) — **no rest-op family yet**; request/serve pairs to
  mirror: `request_attach`/`serve_attach` (remote-drive, the trust-class sibling).
  Inbound gate: `access.rs` `access_check` on the **handshake-proven** origin node
  (ADR-0009 stateful firewall; REQ-HAZARD-WAN-ORIGIN-AUTH — never a payload field).
- **CLI refusal to lift:** `cli.rs cmd_rest` (~792) refuses `id@node` with
  `CROSS_NODE_M5`; addr grammar already parses `[subnet:]id[@node]`
  (`spt_proto::addr`, REQ-INST-10).
- **traceable-reqs:** `REQ-INST-6` registered, `required_stages = []` (rule-5 note
  names this milestone).

## Decisions (locked)

- **The gate is state-keyed at the consumer, not a new store flag.** A deferred row
  needs no new column: "held" = (row has `deferred = 1`) ∧ (owner's `read_rest` says
  Dormant|Suspended). The hook-channel consumer sites that surface deferred rows gate
  on the owner's rest record at read time; recordless/Active reads flow as today
  (legacy fallback unchanged). This extends KH 1.4/4.4's "all drain sites must agree"
  rule with one more agreement: **resting instances surface no deferred rows
  anywhere**.
- **Release = the wake edge surfaces them through the existing boundary machinery.**
  No new delivery path: the wake edge already runs `fire_wake_effects` →
  `resurface_at_boundary`; the gate simply stops hiding the rows once the persisted
  state is Active, and the wake boundary's existing hook-channel surfacing delivers
  them. "Exactly once" rides the spool's existing delivered-mark discipline — the
  gate adds no second bookkeeping.
- **Remote rest ops mirror the attach (remote-drive) pattern:** new
  `StreamFamily::Rest` + `request_rest`/`serve_rest` pair (`spt-daemon::resthost` or
  beside attach) carrying `(target_endpoint, event ∈ {Suspend, Wake}, op_id)`.
  Serve side: `access_check(endpoint, handshake_origin, Unsolicited)` → local
  `daemon_rest_event` (which runs the echo gate + D4a shell cascade on the target
  node — where the shells live, exactly right). **Ungated beyond the firewall** —
  same trust class as remote-drive (M5-PLAN D5b: "your instances from any node");
  no grant-store consultation.
- **CLI:** `cmd_rest` lifts the qualified-form refusal: bare id = local (unchanged);
  `id@node` resolves the peer (registry/known-addr, the attach resolution) and fires
  the rest op; `spt shutdown` stays local-only (an agent shuts down *its own*
  endpoint — remote shutdown is not a thing; suspend covers the remote need).
  Exit codes mirror local: edge / NO_EDGE / failure.
- **Idempotence over the wire:** `op_id` dedup via the existing effect-journal
  discipline only if the serve path mutates through the broker; `daemon_rest_event`
  is itself idempotent (pure-table no-edge), so a redelivered rest op is naturally
  a NO_EDGE — report, don't dedup.
- **D5c is one paragraph, not code:** the remote fork arm rides instantiate-anywhere
  (M5 scope decision 2) — note it in `docs/DEFERRED.md` beside the
  instantiate-anywhere row (and only there; the ROADMAP amendment already landed at
  D0a).

## Pieces (build order — one CI-green slice each)

1. **D5a — deferred-message resting gate** (`REQ-INST-6` → activate `[impl, unit]`).
   Gate the hook-channel deferred-row consumer sites on the owner's rest record
   (executor enumerates them by auditing every `drain_all_at`/`peek_all_at` caller —
   the KH 1.4 "all sites agree" sweep); wake edge releases by construction. Tests:
   deferred row invisible at a boundary while dormant AND suspended; visible exactly
   once after wake; non-deferred flow untouched while resting (the event stream was
   never gated — KH 1.4 holds); recordless perch = today's behavior.
2. **D5b — remote suspend/wake.** `StreamFamily::Rest` + request/serve pair +
   `cmd_rest` qualified-form lift; access-gate negative (unlisted origin refused
   when a whitelist exists); loopback int test (two in-proc daemons, the attach/xfer
   test pattern) — suspend cross-"node", deferred gate holds, wake cross-"node",
   release fires. Tag `[int->REQ-INST-6]` on the loopback leg; rig `[twohost]` leg
   waits for D9a.
3. **D5c — remote-fork deferral note** (docs-only, rides D5b's commit).

## NOT in D5
- Presence/MRA — D6. Cross-node shell link — D8c. Rig two-host legs — D9a.
- Remote `spt shutdown` — not a surface (suspend covers it).

## Conventions (carried)
- NO `cargo fmt`; tag evidence in the same commit; `traceable-reqs check` before done.
- Linux clippy `-D warnings` + `--no-default-features` stay green.
- Push per slice → sha-pinned FINAL; never push over an in-flight run.
- Unix liveness probes: remember the zombie rule (`spt_store::proc` is
  zombie-aware since `78e9e18`; reap with `reap_if_child` where the spawner lives on).
- Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.
