---
name: idle-edge-w1-lega-activity-tests
description: IDLE-EDGE W1 Leg A (REQ-ACTIVITY-LINK-PUSH) test rig — the activity observer lives in the BRAIN CHILD so the int needs a real `spt daemon run` (not the in-process DriveHub rig); relink refuses while online; drive-vs-activity same-poll assertions must settle past the 200ms tick.
metadata:
  type: project
---

Leg A of IDLE-EDGE W1 (ADR-0048 decisions 1–3, activity frames on the shell link). 22 units + 1 int, all GREEN on this Windows host, mutation-verified RED.

**Rig choice is load-bearing — use a REAL daemon, not the in-process hub.**
The observer that turns a sentinel flip into a pushed frame is spawned in
`brainproc::run_brain` (beside shellwake/livehost), i.e. the BRAIN CHILD. The
other shell E2Es (`drive_e2e.rs`, `gateway_owner_shell_e2e.rs`) stand up an
in-process `serve_drive_control` + seed control — that rig would still pass with
`spawn_activity_observer` deleted from brainproc, and a consumer would get
silence forever. So `crates/spt/tests/activity_link_push_e2e.rs` spawns
`spt daemon run` (idle_edge_drain_e2e pattern: bogus `identity/node.key` for a
net-less daemon, `wait_for_ready_pid` on `brain.ready`) and only READS in-process.
The drive **socket** is served by the daemon PARENT (`daemon.rs`), the observer by
the brain child — both reachable over the same SPT_HOME.
MUTATION-CONFIRMED: commenting out the brainproc spawn reds the int at clause 2
("no activity frame with state=idle within 15s"), while clause 1 (the CLI-side
bind-time push) still passes — the two paths are cleanly separable.

**Gotchas that cost a run each (all real, all reproducible):**
- `shell relink` REFUSES an online shell: `SHELL_ALREADY_ONLINE:<alias>: relink is
  the online switch — nothing to do`. Break the link first with
  `spt_daemon::shellhost::close_shell(&owlery, owner, shell_id, Some(&shell))`
  (the drive_e2e pattern) — but note that also fires `drive_clear`, which wipes
  BOTH ephemeral slots, so it cannot be the carrier for a "stale frame survives"
  probe. For that, write a retired-token frame straight into the LIVE daemon with
  `spt_daemon::activity_write(&drive_socket_name(), owner, shell_id, &old_token, frame)`
  and assert no *idle*-state frame is ever served while the owner is busy
  (race-free: any legitimate observer push in that window is `busy`).
- The two slot classes are independent but NOT simultaneous. `shell drive` writes
  synchronously; the activity push lands up to one 200ms observer tick later. A
  "one poll serves both" assertion must drain to steady state, queue the drive
  frame, flip, then `sleep(2s)` WITHOUT polling and take a single poll. Polling in
  a loop drains the drive frame alone and reds a correct impl.
- `drive-poll` is take-and-clear, so any wait-loop consumes frames; and the
  observer only re-pushes when its (token,state,since) diff moves — a drained
  frame is NOT re-sent. Loop until the wanted state appears; never assume a
  re-poll re-serves.
- `spt_proto::event::parse_event` puts `type` in `attrs` too — an attr-ORDER
  assertion is `["type","from","state","since"]`, not `["from","state","since"]`.
- clippy `cloned_ref_to_slice_refs` fires on `&[x.clone()]` in test code
  (`-D warnings` in CI) — use `std::slice::from_ref(&x)`.

**Unit homes** (all in-file `#[cfg(test)] mod tests`, `[unit->REQ-ACTIVITY-LINK-PUSH]`
immediately above each fn): `spt-store/src/perch.rs` (stamp/sentinel), 
`spt-daemon/src/{shellchan,drivehub,activity}.rs`. `activity.rs`'s `observe_links`
takes the owlery as a parameter, so its tests need NO `SPT_HOME` and dodge the
perch env-lock entirely — build the owlery by hand (owner dir + `info.json`;
shell dir + `write_shell_info` + `shellhost::LINK_TOKEN_FILE`).

Run: `cargo test -p spt-store --lib perch::tests` · `cargo test -p spt-daemon --lib
{shellchan,drivehub,activity}::tests` (ONE positional filter per invocation) ·
`cargo test -p spt --test activity_link_push_e2e -- --test-threads=1` (~9s).

See [[w3e-live-update-int-keystone]] for the sibling real-daemon reap discipline.
