# M5-D9 — rig int legs + activation sweep + closeout (JIT plan)

**Status:** CLOSED 2026-06-04 — D9a proven (run 26998058816, real_mode=true,
all 11 rungs; see piece 3) · D9b sweep done (REQ-EP-5/SHELL-1/NOTIF-2 int
flipped; REQ-CONSENT/REQ-INSTALL-4 int deferred with rationale in
docs/DEFERRED.md; ROADMAP M5-delivered entry; M5-PLAN closed). **M5 COMPLETE.**
CI-green-FINAL: run **26998500755** on `5bc996c` — full matrix + both
twohost roles green (the milestone's sha-pinned closeout run). Upstream: D8 closed (`18b1972` CI-green-FINAL — see
`M5-D8-PLAN.md`; D7 at `f0d032c`, ladder re-proven run 26994211671).
Authoritative: M5-PLAN §D9 + coverage map, `docs/TWO-HOST-RUNBOOK.md`,
`tests/twohost.rs` (the M4 ladder this extends), `docs/TRACEABILITY.md` rule 5.

## Researched code map

- **The ladder rig (extend, don't fork):** `tests/twohost.rs` — env-gated
  (`SPT_TWO_HOST=1` + role), every identity/address derived from the shared
  secret + static env (no runtime exchange, no SSH); role B = seed-holder/
  server (dispatcher + peer pump, reacts), role A = joiner/driver (drives
  rungs, asserts convergence); barriers = poll-until-deadline on observable
  store state. CI: `twohost-a`/`twohost-b` jobs, commit-tag-gated
  (`[twohost]`), `needs: test`.
- **M5 machinery the new rungs consume (all shipped):** remote rest op
  (`resthost`, D5b — `spt suspend/wake id@node`) · presence MRA + cross-node
  notif redirect (`presence`/`notif::first_fire_at`, D6) · cross-node shell
  link (`linkhost` + `shelllink` wire, D8c) · the notif render template seam
  (`notif::render_via_shell_notif_templates`, D8b) · the real notify shell
  (standalone `SaberMage/spt-shell-notify`; `--render-file` observable;
  ci.yml already checkout+builds it for the test job).

## Decisions (locked)

- **D9a = four new rungs appended to the ONE ladder** (one tagged run climbs
  M4 + M5 together — regression of the old rungs is free):
  1. **remote suspend/wake** — A suspends `ID_B@B` over the wire rest op,
     observes B's advertised status flip (registry row), wakes it back.
     Tags `[int->REQ-INST-8]`-adjacent evidence on the existing REQ-INST-6
     int (already activated; this is the rig leg the D5 plan deferred).
  2. **presence-shift redirect** — B's endpoint stamps the fresher
     `last_active_ms`; A produces a notif and asserts `RemoteTarget` (no
     local surface, no marks at A); B asserts the surface + marks landed
     there via replication. Tag `[int->REQ-PRES-1]` (the rig form of the
     loopback dispatch int).
  3. **cross-node shell relink + drive** — B holds an offline persistent
     instance of the notify adapter; A sends `relink` then a `notify`
     command over the shelllink wire; B asserts the binary relaunched and
     the command rendered (`--render-file`). Tags `[int->REQ-SHELL-1]`,
     `[int->REQ-SHELL-2]`.
  4. **notif→toast (the dogfood demo)** — with the user most-recently-active
     at B and B's notify-shell instance attached, A's `spt notify`-equivalent
     produce lands the toast render at B (the [session.notif] template
     fired by B's feed-apply surface). Tag `[int->REQ-NOTIF-2]`. This rung
     IS the M5-PLAN acceptance line ("an agent's spt notify surfaces as a
     real OS toast on whichever rig host the user last touched") — rendered
     to the observable file on CI (headless runners); a manual runbook step
     documents the real-toast eyeball check.
- **The notify-shell binary reaches the rig via the existing CI hook
  pattern:** the `twohost-a`/`twohost-b` jobs gain the same
  checkout+build steps the test job has; the test reads
  `SPT_TWO_HOST_NOTIFY_BIN` (absent ⇒ rungs 3–4 degrade to a trivial
  inline-template shell for the relink/drive mechanics and skip the real
  -adapter render assert with a loud log — the runbook documents both
  modes). The rig manifest re-templates spawn/notif with absolute paths
  exactly like `notify_shell_e2e.rs` does.
- **D9b sweep (rule 5 — activate what M5 delivered, defer loud):**
  - Flip `required_stages` += `int`: REQ-SHELL-1, REQ-SHELL-2, REQ-NOTIF-2,
    REQ-EP-5 (loopback int evidence exists from D3e/D8; the rig run is the
    cross-node strengthening).
  - REQ-CONSENT-1/2 stay `[impl, unit]` with rationale: the interactive
    escalation's end-to-end leg needs a real harness session answering a
    prompt — that is the downstream plugin's acceptance territory, not
    loopback-fakeable honestly.
  - REQ-INSTALL-4 stays `[impl, unit]`: `adapter add --github` int needs a
    real clone target; the standalone notify repo makes this POSSIBLE now —
    log as a candidate, defer past M5 (no milestone need).
  - Amend `ROADMAP.md` (M5 delivered paragraph, the M4 entry's pattern) +
    `CONTEXT.md` only where shipped behavior drifted from the glossary
    (expected: none — D8 was built TO the CONTEXT text) + `docs/DEFERRED.md`
    (confirm instantiate-anywhere / remote-exec / PresenceChannel /
    GameRobot-class shells; add the REQ-INSTALL-4 int candidate).
  - Close M5-PLAN; `traceable-reqs check` green; final `[twohost]` push =
    the sha-pinned CI-green-FINAL for the milestone.

## Pieces (build order)

1. **D9a-1 — CI plumbing.** twohost jobs gain the adapter checkout+build
   steps + `SPT_TWO_HOST_NOTIFY_BIN`; runbook gains the M5 rung section.
2. **D9a-2 — the four rungs** in `tests/twohost.rs` (A-side drives, B-side
   reacts/asserts; derived-state barriers, no wall-clock). Loud per-rung
   logging like the M4 rungs.
3. **D9a-3 — the verification run.** Push with `[twohost]`; record run id +
   conclusion here. Gap-close on drift.
   **DONE — three runs, two gap-closes, full green:**
   - 26997030279 (`bb5310b`) FAILED rung 9: the rig's RegistryHost mirrored
     snapshots to `home/registry`, but the presence fire path reads the
     canonical `identity/registry` — A's MRA saw no rows. Fixed `b518219`.
   - 26997709699 (`b518219`) **GREEN end to end** (all 11 rungs) but
     fallback shell mode at B: the twohost job's cargo context lacked the
     workspace `spt` binary. Fixed `620edf3` (in-job build step).
   - **26998058816 (`620edf3`) GREEN, `real_mode=true`** — the cross-node
     notify command RENDERED at B through the real standalone adapter and
     the toast notif rendered via the `[session.notif]` template: the
     M5-PLAN acceptance line, proven on the rig. Whole ladder ~13 s
     post-pairing.
4. **D9b — sweep + closeout.** Stage flips + deferral rationales in
   `traceable-reqs.toml`; ROADMAP/CONTEXT/DEFERRED amendments; close
   M5-PLAN + this plan; FINAL tagged run recorded.

## NOT in D9
- New capabilities of any kind. Anything the sweep finds missing becomes a
  documented deferral or a gap-close, never silent scope growth.

## 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.
- Commit trailer: `Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>`.
