# W3 #349 HANDOFF + DORMANT SEND RESTRICTION — lane notes (todlando, 2026-09-26)

Lane `feat/351-w3-handoff` on a2ba486b (the W2 head doyle gated green). Worktree `.worktrees/351-w3`, pool claimed as `351-w3-handoff`.

## Release-note lines
- **A dormant instance may message only its active sibling.** From a dormant instance, `spt send` (and shortform tags), `spt ring`, and `shell send/cmd/drive/tunnel` refuse with `SEND_REFUSED_DORMANT`. The refusal names the active sibling (`id@node`) and the way out. Nothing is spooled.
  - Still allowed: a bare send to your own id (the recharge wake), and a send to the active sibling by `id@node`.
  - With no active instance, the refusal names `spt endpoint wake <id>@<this node>` as the way out.
  - A send from a plain terminal proves no instance, so it is not restricted.
- **`spt send --handoff <id>@<node>`** passes attention to a sibling. Only the active instance may send it. A dormant sibling takes active; a suspended one wakes and takes active. The sender goes dormant once the sibling's activation reaches it. An offline sibling, or a bare `--handoff <id>`, is refused before anything is sent.
- **Mixed versions:** a handoff to a node running an older spt is delivered as a normal message and prints `HANDOFF_NOT_TAKEN` (exit 1). The sender stays active. (doyle condition 5)

## Condition 1: measured before build
Shortform runs `cmd_send_verdict` in the `spt api state` CLI process, under the harness's environment. It does not run in the daemon, as the ruling had assumed.
- `dispatch_ingest_shortform` has two callers, `delivery.rs:475` and `:500`, both inside `cmd_state`.
- `cmd_state`'s only non-test caller is `ApiCmd::State` at `api/mod.rs:806`.

In a CC-child process on HFENDULEAM, `detect_self_id` resolved `todlando` through `OWL_SESSION_ID` and `SPT_ENDPOINT_ID`. That process was my Bash tool, not the hook itself.

The gate therefore keys on `SendExtras.proven_author` = the id that `gated()` proved. It falls back to the session-proven id. doyle accepted this.

The unit `a_dormant_authors_shortform_is_refused_without_a_session_env` clears `SPT_AGENT_ID`, `SPT_ENDPOINT_ID` and `OWL_SESSION_ID`, and asserts `detect_self_id() == None` as a precondition. Then:
- a dormant author's shortform to a peer is refused, with 0 rows spooled;
- the control (no proven author) is delivered, with 1 row spooled.

## Shape
- **Wire:** `WanMessage.handoff` (serde default, skipped when false). Reply token `handoff` ⇒ `WanRequestOutcome::HandoffTaken`.
- **Receiver** (`wan.rs`): `handoff_trigger` turns a same-id flagged message into a Wake at admission. `receive_wan_reply` answers `handoff` only when the message reached the agent AND `handoff_taken`: the intent is active, or the effective state is active.
- **Sender** (`cli.rs`):
  - `handoff_plan` checks the preconditions and pins the WAN leg.
  - `finish_handoff` maps the reply and writes nothing (condition 4).
  - The restriction runs once the send knows its head, before the admission gate.
- **Pure logic** lives in `spt_net::net::siblings`: `sibling_rows`, `dormant_send_refusal`, `plan_handoff`. The rig and the CLI read the same code. `node_qualifier` and `label_of` moved from `wansend` into `spt_net::net::registry`, as pub, and `wansend` re-exports them.

## Evidence (at the lane head, before commit)
- `cargo check --workspace --all-targets`: clean, 0 warnings.
- Targeted nextest across `spt` (bins), `spt-daemon` and `spt-net`: 120/120. All 12 new tests are in the population, confirmed by name.
- treqs on the working tree: REQ-INSTANCE-HANDOFF and REQ-DORMANT-SEND-RESTRICTION are `[OK]` at doc/impl/unit/int. It is to be re-run on the git archive of the committed sha.
- The int (twohost_axes cell 6) is written; the two-box climb is PENDING.

## UNPROVEN-LIVE (same posture as W2, ruling (a))
- The CLI call sites (`cmd_send_verdict` handoff and restriction legs, ring, shell verbs) are covered by units and by the library-level cell 6. No rig drives the CLI against a live registry: `is_running()` needs a seed daemon.
- Cell 6b runs on the rig's pinned liveness, so the suspended sibling's "resume" is instant there. A real resume (a cold harness reviving) rides #351 FIELD ACCEPTANCE. (doyle cell-6b ruling)
- IR-161 (core PR #285: the seed-control stub plus the home-canonical broker bind) would let the CLI arm run in the rig. It is not built; per doyle, not waiting on it.

## Carried to #350 (condition 7)
`presence::addressed_target` (`crates/spt-daemon/src/presence.rs:214`) resolves ONE addressed id's instances by most-recently-active, for addressed notifs. That is an instance resolution inside the notif domain, and it does not follow S6. W2 left it as is, and #349 does not touch it, because it is notif routing, not a send. Its callers are `notif.rs:335` (node scope) and `:339` (subnet scope), inside notif first-fire. Open question for the #350 census: should an addressed notif route to the ACTIVE instance, the same "route to active" question as shells-follow-active?
