# Autostart census: spt test binaries that bring up a daemon tree on demand (main abaa1696)

Asked by doyle (`HDDEXZNE`, `YITQXO5A`), following todlando's W10 rig finding. Static census; nothing was built. Scripts are in this directory: `census.py` writes `rows.json`, and `population.json` holds the verb join.

## 1. Which CLI verbs autostart (verb map, reverse call graph from `ensure_running`, every deciding edge hand-read)

- **UNCONDITIONAL** (after input validation): every `spt api <sub>`, because `api::run` calls `ensure_daemon()` as its first statement. Also `serve` (all but `lan-firewall`), `fetch`, `seal mint`, `seal enroll-authenticator`, `endpoint digest`, `subnet create`, `subnet join`, `shell drive` and `shell tunnel`.
- **Explicit spawn outside `ensure_running`:** `node start` and `daemon start` (`start_daemon()`).
- **CONDITIONAL:**
  - `send`: with `--attachment`; with `--seal`; or when the local outcome is NoPerch and `--active-only` is not set, which is the WAN leg.
  - `endpoint list` and `subnet status`: when a node row is probed.
  - `endpoint fork`, `wake`, `suspend`, `shutdown`, `start`, `resume`, and `go`: remote targets, or local shells with stdin receipt.
  - `shell spawn`, `cmd`, `send`, `relink` and `teardown`: stdin receipt or `@node`.
  - The remote arms of `knock`, `adapter add -vs`, `adapter update`, and bare `update`.
- **Does NOT autostart: `ready`.** It runs `run_listen` into `spt_msg::ReadyAgent`, and spt-msg has no spt-daemon dependency. This corrects the rig finding's premise that `ready` or `send` brings the tree up: in busy_window_axis_e2e, the driver is `api ... poll`.
- Every autostart declines when `<SPT_HOME>/daemon-stop.inhibit` exists.

## 2. Population (top-level `crates/spt/tests/*.rs`, the xtask detector's own scope)

- 118 binaries. 51 are HEAVY; 44 of those match the current detector. Control: every detector-true binary is HEAVY, which reproduces xtask green; every HEAVY name exists as a binary.
- **39 binaries pass an UNCONDITIONAL autostart verb path: 14 HEAVY, 25 NOT HEAVY.** The current detector reads all 25 as light. Its literal is `daemon run`, `daemon brain` or `supervise_brain(`, so it misses `api` AND the explicit `daemon start`.
- The 25 not-HEAVY binaries. Here stop = a literal `daemon stop` or `node stop` array; other teardown is a hit count from a grep of other teardown forms, still to be read:
  - **ZERO teardown of any form, predicted leakers (6):** `gateway_e2e`, `io_state_payload_e2e`, `listen_seed_retry_e2e`, **`poll_envelope_e2e`** (`api --adapter spt poll` under SPT_HOME), `shell_hints_e2e`, `shortform_dispatch_e2e`.
  - **No literal stop, but some other teardown form present (unread, 12):** `bind_adapter_profile_persist_e2e`, `boundary_ready_strand_e2e`, `contract_e2e`, `create_bind_rest_active_e2e`, `drive_e2e`, `live_bind_firsthost_e2e`, `live_firsthost_e2e`, `live_resolve_e2e`, `psyche_sid_custody_e2e`, `shell_sleepwake_e2e`, `tunnel_e2e`, `worker_lifecycle_e2e`.
  - **Literal daemon stop present (7):** `boundary_events_e2e`, `broker_stop_endpoint_deny_e2e` (`daemon start` only), `daemon_stop_convoy_e2e` (it also writes the inhibit), `io_events_poll_e2e`, `io_events_undriven_kinds_e2e`, `midturn_span_e2e`, `now_signal_delta_e2e`.
- **47 more binaries pass only CONDITIONAL verbs.** They are unclassified: whether a condition is met needs a per-test read. One is CONFIRMED: **`active_only_never_relay_e2e`** sends `send remotegw` without `--active-only` on purpose, per its own comment "it takes the WAN leg". That is `wan_send` then `ensure_running`, with ZERO teardown.
- The leak column is STATIC: "no teardown found" is a claim about spelling. A measured arm (one run per candidate, counting daemon trees under the test home) is not done.

## 3. Detector widening proposal (`xtask spawns_daemon_tree`)

- **Widen to:** the source runs the spt exe (`CARGO_BIN_EXE_spt`) AND passes a string-array verb path whose head is in the UNCONDITIONAL set, or is `daemon start` / `node start`. That is in addition to today's three literals.
- **Exemption:** a binary that writes `daemon-stop.inhibit` in every home it uses cannot autostart, and may be marked light explicitly (for example with a `heavy-exempt: inhibit` comment the check reads), because autostart declines there.
- **CONDITIONAL verbs cannot be decided statically** (the condition is runtime data). Options: an opt-in marker, or classify `send` without `--active-only` to an unknown perch as heavy. The first is safer; `active_only_never_relay_e2e` is the motivating case for either.
- **NEGATIVE CONTROL** (static port of the rule, against `89fd4412:crates/spt/tests/busy_window_axis_e2e.rs`): current rule False, widened rule TRUE on its `["api", ...]`, and golden.yml at 89fd4412 lists it 0 times. So the widened check reds on it as it stands. It must also pass its own motivating-case control: every HEAVY binary the current rule catches is still caught, since the widening is an OR.
- **Cost, stated plainly:** the widened rule reclassifies 25 binaries on main as HEAVY at once. Phase B runs HEAVY binaries serially, so the golden wall grows by their summed durations, which are unmeasured. Alternative per binary: tests that do not NEED a daemon write the inhibit up front (cheap, and it also kills their leak). Tests that do need one get a teardown guard plus HEAVY.
- Registered as infra debt: proposed **IR-156**, number pending doyle.
