File created successfully at: C:\Users\decid\Documents\projects\spt-core\.claude\doyle-commune.md (file state is current in your context — no need to Read it back)

<live-context>
Delta: Mesh-D8 is DONE (you held it as NEXT/pre-impl — update).

**D8 shipped exactly to plan, no deviations.** commit `0c5c4f5` (dev-freeform, LOCAL/unpushed). REQ-MESH-6 activated `[impl,unit]`.

- `probe_all(items, ceiling, max_inflight, probe)` added: one std::thread per probe wrapped in `run_bounded` (each individually ceiling-bounded — KH 5.3 preserved), processed in windows of `cap`=MAX_INFLIGHT(16), results stitched back order-stable. Wall ≈ one ceiling for k≤cap, ceil(k/cap) beyond (logged via eprintln when k>cap — no silent truncation). Threads + run_bounded, NO tokio.
- `cmd_subnet_status` --nodes arm rewritten to 3 passes: classify all subnets' rows (pure node_status_rows) → ONE concurrent batch across ALL subnets at once → stitch by cursor. Render unchanged (render_node_rows), so output identical to old serial path.
- Dead `probe_node` wrapper DELETED (clippy flagged it unused once the batch path took over). The real subnet-scoped probe `probe_node_serving` (wansend.rs, still carries impl->REQ-SUBNET-1/5) is now injected directly into `probe_all`'s closure. PROBE_TIMEOUT/MAX_INFLIGHT hoisted to module consts.
- 3 new units (all green): `probe_all_runs_concurrently_in_input_order` (N=8/D=200ms, <4×D + node3→false proves index mapping), `probe_all_cap_batches_into_windows` (N=20/cap=4/D=100ms, [5×D,9×D)), `probe_all_timeout_settles_false_without_stalling_batch` (one 1500ms wedged probe settles false in <4×150ms ceiling, batch not dragged).

**Verification — ALL gates green:** 105 spt-bin tests pass; clippy clean; `--no-default-features` builds; `traceable-reqs check` EXIT=0 (read raw — REQ-MESH-6 [impl,unit] satisfied); `cargo run -p xtask -- check` = "xtask check: OK" (docs-drift — D8 changed no --help flags as predicted, no-op regen).

Lesson confirmed: the injected-probe seam made the timing units fully deterministic (no network, no flake) — same mock-clock discipline that worked for run_bounded.
</live-context>
<project-context>
HEAD now `0c5c4f5` (dev-freeform): D7 `618845d` + D8 plan `0a3e036` + D8 impl `0c5c4f5`. All LOCAL, NOT pushed (user controls merge to main; push not requested this session).

Mesh arc: D0–D5b ✅ · D6 ✅ · D7 ✅ · **D8 ✅ (REQ-MESH-6 concurrent --nodes probes)** → mesh arc D0–D8 essentially COMPLETE.

IMMEDIATE NEXT = no active mesh work. Queued untouched separate track: DAEMON-LIFECYCLE-PLAN.md. Awaiting user direction on whether to start it or push/merge the mesh arc.

traceable-reqs binding still in force (tag in-commit, run check before done, raw exit codes). Any CLI --help change ⇒ xtask gen or docs-drift gate fails.
</project-context>


Commune queued (`doyle-commune.md` — Self listener ingests on next poll).

**Mesh-D8 done.** `0c5c4f5` (dev-freeform, local/unpushed).

- `probe_all()` fans `subnet status --nodes` probes concurrently across ALL subnets — one bounded batch (thread-per-probe under `run_bounded`, cap 16, order-stable stitch). Kills the post-D6 serial `k × 2.5s` loop.
- Dead `probe_node` wrapper deleted; `probe_node_serving` injected.
- 3 new units: concurrency+order, cap-batching, timeout-settles-false.

**Gates all green:** 105 tests · clippy · `--no-default-features` · traceable-reqs EXIT=0 (REQ-MESH-6 `[impl,unit]`) · xtask docs-drift OK.

Mesh arc D0–D8 **complete**. Next track (untouched): `DAEMON-LIFECYCLE-PLAN.md`. Push/merge = your call.


please write a tl;dr of the daemon lifecycle plan


<owl_reachability_notice>
You have an active owl perch. When spawning subagents or doing work, use run_in_background: true so you stay reachable for incoming messages.
</owl_reachability_notice>


CAVEMAN MODE ACTIVE (full). Drop articles/filler/pleasantries/hedging. Fragments OK. Code/commits/security: write normal.


1	# Daemon lifecycle UX plan — service-aware `start`/`stop` + cross-platform `run`
2
3	> Status: PLANNED (next session). Mint REQs before implementing (CLAUDE.md rule 3).
4	> Origin: live rollout 2026-06-08 — a manual `spt daemon run` on kitsubito
5	> fought the systemd `spt-daemon` user service over the broker socket
6	> (auto-restart-fail loop). User: "`start`/`stop` should manage the service
7	> when there is one; `run` must behave the same on every platform."
8
9	## Problems observed
10
11	1. **`start`/`stop` are service-blind.** `spt daemon start` (the alias added in
12	   `3392158`) just runs `Daemon::run()`; `spt daemon stop` just IPC-stops a
13	   running daemon. Neither touches the registered service manager. A user with
14	   a service installed expects `spt daemon start|stop` to drive *that*.
15	2. **`spt daemon run` is platform-inconsistent.**
16	   - Linux: runs the daemon **inline/foreground** (blocks the terminal).
17	   - Windows: ends up an **invisible background task** (a detached respawn).
18	   Same verb, different behavior.
19	3. **Auto-start competes with the service.** Any `spt` command that needs the
20	   daemon calls `ensure_running()` → `spawn_detached()`, which starts a *manual*
21	   daemon even when a managed service exists → two daemons fight for the socket
22	   (exactly the kitsubito loop).
23
24	## Current code (baseline)
25
26	- `crates/spt/src/cli.rs`: `cmd_daemon_run()` → `spt_daemon::Daemon::run()`
27	  (foreground loop); `cmd_daemon_stop()` → `is_running()` + `request_stop()`
28	  (IPC graceful); `DaemonCmd::Run` carries `visible_alias = "start"`.
29	- `crates/spt-daemon/src/daemon.rs`: `ensure_running()` → `spawn_detached()`
30	  (Linux fork-style; Windows `DETACHED_PROCESS | CREATE_NO_WINDOW`, de-elevated
31	  via `deelevate`). `request_stop()` = IPC stop.
32	- Installer-registered services:
33	  - Linux (`install.sh`): systemd **user** unit `~/.config/systemd/user/spt-daemon.service`,
34	    `ExecStart=<bin> daemon run`, `enabled`; `loginctl enable-linger` for pre-login boot.
35	  - Windows (`install.ps1`): at-logon **scheduled task** `spt-core daemon`
36	    (`schtasks /SC ONLOGON /TR "<bin> daemon run"`) — needs elevation to register.
37
38	## Target model (consistent across platforms)
39
40	| Verb | Semantics (BOTH platforms) |
41	|---|---|
42	| `spt daemon run` | **Foreground, blocking.** This process *is* the daemon; logs to stderr; Ctrl-C / SIGTERM stops it. No auto-detach, no respawn. Used by the service unit's `ExecStart` and by manual debugging. |
43	| `spt daemon start` | **Ensure up in background, idempotent, service-aware.** Service registered → delegate to the service manager. No service → detached spawn (today's `spawn_detached`). Already running → no-op ("already running"). Non-blocking. |
44	| `spt daemon stop` | **Bring down, service-aware.** Service-managed + active → stop via the manager (so it does not auto-restart-fight). Else → IPC `request_stop`. Idempotent. |
45	| `spt daemon status` | Unchanged + show **managed-by** (service vs manual) and the service's enable/boot state. |
46
47	- **`ensure_running()` routes through the service-aware `start` path** — the CLI
48	  must never spawn a competing manual daemon when a service is managing one.
49	- `start` is no longer an *alias* of `run`; it becomes its own verb with the
50	  background/service semantics. (`run` keeps the foreground meaning.)
51
52	## Platform service abstraction
53
54	New module (e.g. `crates/spt-daemon/src/service.rs`) — detect + control:
55
56	```
57	trait DaemonService { fn detected() -> bool; fn start()->Result; fn stop()->Result; fn is_active()->bool; fn managed_label()->&str; }
58	```
59
60	- **Linux — systemd user unit `spt-daemon`.**
61	  - detect: unit file present (`~/.config/systemd/user/spt-daemon.service`) or
62	    `systemctl --user list-unit-files spt-daemon.service` succeeds.
63	  - start/stop/is-active: `systemctl --user start|stop|is-active spt-daemon`.
64	  - This is what removes the double-daemon fight (stop the unit, don't IPC-kill
65	    a unit that will auto-restart).
66	- **Windows — open decision (see below).** Today only an at-logon scheduled task
67	  exists (a boot trigger, not a controllable service). Two routes:
68	  - (a) **Promote to a real Windows Service** (`sc.exe create` / a small service
69	    host) → clean `start`/`stop` parity with systemd. Heavier; needs elevation
70	    + a service-mode entry in the binary.
71	  - (b) **Keep the scheduled task for boot-start; `start` = detached spawn,
72	    `stop` = IPC stop.** Simpler; honest asymmetry (Windows has no per-user
73	    systemd equivalent). `status` says "manual (task auto-starts at logon)".
74
75	## Conflict guard
76
77	- `run` detects a managed service that is already active → **warn loudly** before
78	  taking the socket ("a managed spt-daemon service is active; running inline will
79	  fight it — `spt daemon stop` the service first, or use `spt daemon start`").
80	- `start` is idempotent: already-running (manual OR service) → no second daemon.
81
82	## Consistency fix detail (the Windows `run` respawn)
83
84	- Audit why Windows `spt daemon run` goes invisible-background (likely the
85	  `Daemon::run()` de-elevation respawn / a `spawn_detached` on the `run` path).
86	  Make `run` strictly foreground on Windows — the detached/invisible behavior
87	  lives ONLY in `start`. Keep `CREATE_NO_WINDOW` for the `start` detached spawn,
88	  not for foreground `run`.
89
90	## Requirements to mint first (rule 3)
91
92	Add to `traceable-reqs.toml` before coding (REQ-DAEMON-* family or extend
93	REQ-CLI-2 / REQ-DAEMON-*):
94	- `daemon start|stop` manage the registered service when one is present.
95	- `daemon run` is foreground-consistent on every platform.
96	- internal auto-start (`ensure_running`) prefers the service; never double-spawns.
97
98	## Test plan
99
100	- **unit:** service-detection truth table (unit present/absent); command routing
101	  (service vs detached); idempotent `start`; `stop` routing (managed→manager,
102	  manual→IPC); `run` is foreground on both (no fork).
103	- **int (kitsubito/Linux systemd rig):** `start`/`stop` drive the unit; no
104	  double-daemon; `ensure_running` from a cold CLI brings the unit up, not a
105	  manual daemon.
106	- **Windows:** `start` detached + idempotent; `run` foreground (no background
107	  task); `stop` IPC.
108
109	## Fleet follow-up
110
111	- v0.2.0 already registered the systemd unit on kitsubito + the scheduled-task
112	  attempt on HFENDULEAM (HFENDULEAM's task needs an elevated re-run — still open).
113	- After this ships: re-roll to the fleet so `start`/`stop` drive the managed
114	  service everywhere; document the daemon-lifecycle UX in the CLI reference +
115	  CHANGELOG.
116
117	## Open decisions for next session
118
119	1. **Windows service model:** real Windows Service (parity) vs scheduled-task +
120	   detached-spawn (simplicity). Leaning (b) for v1, (a) as a follow-up.
121	2. **Service detection source of truth** (unit-file probe vs `systemctl` query;
122	   the Windows equivalent).
123	3. Confirm `start` graduates from alias → first-class verb (and whether the
124	   `start` alias added in `3392158` is replaced wholesale).
125

# Daemon lifecycle UX plan — service-aware `start`/`stop` + cross-platform `run`

> Status: PLANNED (next session). Mint REQs before implementing (CLAUDE.md rule 3).
> Origin: live rollout 2026-06-08 — a manual `spt daemon run` on kitsubito
> fought the systemd `spt-daemon` user service over the broker socket
> (auto-restart-fail loop). User: "`start`/`stop` should manage the service
> when there is one; `run` must behave the same on every platform."

## Problems observed

1. **`start`/`stop` are service-blind.** `spt daemon start` (the alias added in
   `3392158`) just runs `Daemon::run()`; `spt daemon stop` just IPC-stops a
   running daemon. Neither touches the registered service manager. A user with
   a service installed expects `spt daemon start|stop` to drive *that*.
2. **`spt daemon run` is platform-inconsistent.**
   - Linux: runs the daemon **inline/foreground** (blocks the terminal).
   - Windows: ends up an **invisible background task** (a detached respawn).
   Same verb, different behavior.
3. **Auto-start competes with the service.** Any `spt` command that needs the
   daemon calls `ensure_running()` → `spawn_detached()`, which starts a *manual*
   daemon even when a managed service exists → two daemons fight for the socket
   (exactly the kitsubito loop).

## Current code (baseline)

- `crates/spt/src/cli.rs`: `cmd_daemon_run()` → `spt_daemon::Daemon::run()`
  (foreground loop); `cmd_daemon_stop()` → `is_running()` + `request_stop()`
  (IPC graceful); `DaemonCmd::Run` carries `visible_alias = "start"`.
- `crates/spt-daemon/src/daemon.rs`: `ensure_running()` → `spawn_detached()`
  (Linux fork-style; Windows `DETACHED_PROCESS | CREATE_NO_WINDOW`, de-elevated
  via `deelevate`). `request_stop()` = IPC stop.
- Installer-registered services:
  - Linux (`install.sh`): systemd **user** unit `~/.config/systemd/user/spt-daemon.service`,
    `ExecStart=<bin> daemon run`, `enabled`; `loginctl enable-linger` for pre-login boot.
  - Windows (`install.ps1`): at-logon **scheduled task** `spt-core daemon`
    (`schtasks /SC ONLOGON /TR "<bin> daemon run"`) — needs elevation to register.

## Target model (consistent across platforms)

| Verb | Semantics (BOTH platforms) |
|---|---|
| `spt daemon run` | **Foreground, blocking.** This process *is* the daemon; logs to stderr; Ctrl-C / SIGTERM stops it. No auto-detach, no respawn. Used by the service unit's `ExecStart` and by manual debugging. |
| `spt daemon start` | **Ensure up in background, idempotent, service-aware.** Service registered → delegate to the service manager. No service → detached spawn (today's `spawn_detached`). Already running → no-op ("already running"). Non-blocking. |
| `spt daemon stop` | **Bring down, service-aware.** Service-managed + active → stop via the manager (so it does not auto-restart-fight). Else → IPC `request_stop`. Idempotent. |
| `spt daemon status` | Unchanged + show **managed-by** (service vs manual) and the service's enable/boot state. |

- **`ensure_running()` routes through the service-aware `start` path** — the CLI
  must never spawn a competing manual daemon when a service is managing one.
- `start` is no longer an *alias* of `run`; it becomes its own verb with the
  background/service semantics. (`run` keeps the foreground meaning.)

## Platform service abstraction

New module (e.g. `crates/spt-daemon/src/service.rs`) — detect + control:

```
trait DaemonService { fn detected() -> bool; fn start()->Result; fn stop()->Result; fn is_active()->bool; fn managed_label()->&str; }
```

- **Linux — systemd user unit `spt-daemon`.**
  - detect: unit file present (`~/.config/systemd/user/spt-daemon.service`) or
    `systemctl --user list-unit-files spt-daemon.service` succeeds.
  - start/stop/is-active: `systemctl --user start|stop|is-active spt-daemon`.
  - This is what removes the double-daemon fight (stop the unit, don't IPC-kill
    a unit that will auto-restart).
- **Windows — open decision (see below).** Today only an at-logon scheduled task
  exists (a boot trigger, not a controllable service). Two routes:
  - (a) **Promote to a real Windows Service** (`sc.exe create` / a small service
    host) → clean `start`/`stop` parity with systemd. Heavier; needs elevation
    + a service-mode entry in the binary.
  - (b) **Keep the scheduled task for boot-start; `start` = detached spawn,
    `stop` = IPC stop.** Simpler; honest asymmetry (Windows has no per-user
    systemd equivalent). `status` says "manual (task auto-starts at logon)".

## Conflict guard

- `run` detects a managed service that is already active → **warn loudly** before
  taking the socket ("a managed spt-daemon service is active; running inline will
  fight it — `spt daemon stop` the service first, or use `spt daemon start`").
- `start` is idempotent: already-running (manual OR service) → no second daemon.

## Consistency fix detail (the Windows `run` respawn)

- Audit why Windows `spt daemon run` goes invisible-background (likely the
  `Daemon::run()` de-elevation respawn / a `spawn_detached` on the `run` path).
  Make `run` strictly foreground on Windows — the detached/invisible behavior
  lives ONLY in `start`. Keep `CREATE_NO_WINDOW` for the `start` detached spawn,
  not for foreground `run`.

## Requirements to mint first (rule 3)

Add to `traceable-reqs.toml` before coding (REQ-DAEMON-* family or extend
REQ-CLI-2 / REQ-DAEMON-*):
- `daemon start|stop` manage the registered service when one is present.
- `daemon run` is foreground-consistent on every platform.
- internal auto-start (`ensure_running`) prefers the service; never double-spawns.

## Test plan

- **unit:** service-detection truth table (unit present/absent); command routing
  (service vs detached); idempotent `start`; `stop` routing (managed→manager,
  manual→IPC); `run` is foreground on both (no fork).
- **int (kitsubito/Linux systemd rig):** `start`/`stop` drive the unit; no
  double-daemon; `ensure_running` from a cold CLI brings the unit up, not a
  manual daemon.
- **Windows:** `start` detached + idempotent; `run` foreground (no background
  task); `stop` IPC.

## Fleet follow-up

- v0.2.0 already registered the systemd unit on kitsubito + the scheduled-task
  attempt on HFENDULEAM (HFENDULEAM's task needs an elevated re-run — still open).
- After this ships: re-roll to the fleet so `start`/`stop` drive the managed
  service everywhere; document the daemon-lifecycle UX in the CLI reference +
  CHANGELOG.

## Open decisions for next session

1. **Windows service model:** real Windows Service (parity) vs scheduled-task +
   detached-spawn (simplicity). Leaning (b) for v1, (a) as a follow-up.
2. **Service detection source of truth** (unit-file probe vs `systemctl` query;
   the Windows equivalent).
3. Confirm `start` graduates from alias → first-class verb (and whether the
   `start` alias added in `3392158` is replaced wholesale).


**TL;DR — Daemon Lifecycle Plan** (status: PLANNED, REQs not yet minted)

**Origin:** 2026-06-08 kitsubito rollout — manual `spt daemon run` fought systemd `spt-daemon` user service over broker socket → auto-restart-fail loop. User mandate: "`start`/`stop` manage the service when one exists; `run` behaves same on every platform."

**3 problems:**
1. `start`/`stop` service-blind — just run/IPC-stop a daemon, ignore registered service manager.
2. `run` platform-inconsistent — Linux = foreground/blocking, Windows = invisible background task.
3. Auto-start (`ensure_running`→`spawn_detached`) spawns a *manual* daemon even when service exists → two daemons fight socket.

**Target model (both platforms):**
| Verb | Becomes |
|---|---|
| `run` | Foreground, blocking, no detach/respawn. Service unit's ExecStart + debug use. |
| `start` | Background, idempotent, **service-aware** — service present → delegate to manager; none → detached spawn; running → no-op. Stops being an alias of `run`. |
| `stop` | Service-aware — managed → stop via manager (no restart-fight); else IPC stop. |
| `status` | + show managed-by (service vs manual) + boot state. |

`ensure_running()` routes through service-aware `start` — never double-spawns.

**Key build:** new `service.rs` — `trait DaemonService { detected, start, stop, is_active, managed_label }`.
- Linux = systemd user unit (`systemctl --user start|stop|is-active`).
- Windows = **open decision**: (a) real Windows Service for parity, vs (b) keep at-logon scheduled-task for boot + `start`=detached/`stop`=IPC. Plan leans **(b) for v1, (a) follow-up**.

**Guards:** `run` warns loud if managed service active before grabbing socket; `start` idempotent.

**Before coding (CLAUDE.md rule 3):** mint REQ-DAEMON-* — (1) start/stop manage service, (2) run foreground-consistent, (3) ensure_running prefers service / never double-spawns.

**Tests:** unit (detection truth-table, command routing, idempotent start, stop routing, run-is-foreground); int on kitsubito systemd rig (no double-daemon); Windows (start detached, run foreground, stop IPC).

**3 open decisions:** Windows service model (a vs b) · detection source of truth (unit-file probe vs `systemctl` query) · confirm `start` graduates alias→first-class verb (replaces alias from `3392158`).

**Fleet follow-up:** re-roll to fleet after ship; HFENDULEAM scheduled-task still needs elevated re-run.


windows decision: stick to logon scheduled task -detection source of truth: you decide using best judgment -start graduates: yes --- proceed. note that you can collaborate with agent todlando for elevated work.


Decisions locked: Win=scheduled-task (b), detection=my call, `start`→first-class verb. Note todlando for elevated work. Proceed.

First gather baseline: daemon CLI cmds, ensure_running/spawn_detached, existing REQ-DAEMON entries.


Bash

grep -n "DaemonCmd\|cmd_daemon\|visible_alias\|fn run\|enum Daemon\|daemon run\|daemon start\|daemon stop" crates/spt/src/cli.rs | head -40

daemon CLI surface in cli.rs


186:        action: Option<DaemonCmd>,
339:enum DaemonCmd {
341:    /// point — any `spt` invocation that needs the daemon starts this
343:    #[command(visible_alias = "start")]
717:pub fn run() -> i32 {
808:        Cmd::Daemon { action } => match action.unwrap_or(DaemonCmd::Status) {
809:            DaemonCmd::Run => cmd_daemon_run(),
810:            DaemonCmd::Stop => cmd_daemon_stop(),
811:            DaemonCmd::Status => cmd_daemon_status(),
1244:/// Run the per-machine daemon (`spt daemon run` — the ex-hidden `spt daemon`
1250:fn cmd_daemon_run() -> i32 {
1260:/// `spt daemon stop` — graceful daemon shutdown over the seed-control
1265:fn cmd_daemon_stop() -> i32 {
1287:fn cmd_daemon_status() -> i32 {
1320:        println!("daemon: not running (`spt daemon run`, or any spt command auto-starts it)");
1785:fn run_listen(id: &str, once: bool, subnet: Option<&str>) -> i32 {
2393:         (any spt command auto-starts it, or `spt daemon run`).\n"
2651:fn run_bounded<T: Send + 'static>(
3381:/// the daemon runs the rendezvous + ceremony on the node-identity endpoint →
3504:                     - Is a member machine online (its spt daemon running)?\n\
5105:    // spawn sites pass `daemon run` now).
5115:                action: Some(DaemonCmd::Run)
5122:                action: Some(DaemonCmd::Run)
5128:                action: Some(DaemonCmd::Stop)
5134:                action: Some(DaemonCmd::Status)
7020:    fn run_bounded_yields_value_or_timeout_default() {


grep -n "REQ-DAEMON\|REQ-CLI" traceable-reqs.toml

existing daemon/cli req ids


43:id = "REQ-DAEMON-1"
47:id = "REQ-DAEMON-2"
51:id = "REQ-DAEMON-3"
55:id = "REQ-DAEMON-4"
686:# Planned activations: REQ-CLI-* at D1, REQ-SUBNET-5..8 at D2, REQ-INSTALL-6..8
687:# at D3, REQ-CONV-*/REQ-PAIR-8/REQ-DAEMON-5 at D4 (D4 also adds REQ-UPD-6 int
690:id = "REQ-CLI-1"
694:id = "REQ-CLI-2"
698:id = "REQ-CLI-3"
742:id = "REQ-DAEMON-5"
743:title = "Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task — a panic is caught, logged loudly, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death)"


339	enum DaemonCmd {
340	    /// Run the per-machine daemon in the foreground (the auto-start entry
341	    /// point — any `spt` invocation that needs the daemon starts this
342	    /// detached; operators rarely run it directly). `start` is an alias.
343	    #[command(visible_alias = "start")]
344	    Run,
345	    /// Stop the running daemon (graceful: acks, then exits).
346	    Stop,
347	    /// Node status: daemon state, member subnets, local endpoints (the
348	    /// bare `spt daemon` view).
349	    Status,
350	}
351
352	/// `spt access <action>` subcommands (REQ-SEC-1).
353	#[derive(Subcommand)]
354	enum AccessCmd {
355	    /// Whitelist a node for an endpoint (creates the restriction if absent).
356	    Allow {
357	        /// The endpoint to restrict.
358	        endpoint: String,
359	        /// The origin node's pubkey hex (as shown by `spt endpoint list` / pairing).
360	        node: String,
361	    },
362	    /// Remove a node from an endpoint's whitelist. Never widens: revoking the
363	    /// last node leaves the endpoint locked down (all unsolicited refused).
364	    Revoke { endpoint: String, node: String },
365	    /// Delete an endpoint's restriction entirely — back to default-open.
366	    Open { endpoint: String },
367	    /// List restrictions (all endpoints, or one).
368	    List { endpoint: Option<String> },

enum DaemonCmd {
    /// Run the per-machine daemon in the foreground (the auto-start entry
    /// point — any `spt` invocation that needs the daemon starts this
    /// detached; operators rarely run it directly). `start` is an alias.
    #[command(visible_alias = "start")]
    Run,
    /// Stop the running daemon (graceful: acks, then exits).
    Stop,
    /// Node status: daemon state, member subnets, local endpoints (the
    /// bare `spt daemon` view).
    Status,
}

/// `spt access <action>` subcommands (REQ-SEC-1).
#[derive(Subcommand)]
enum AccessCmd {
    /// Whitelist a node for an endpoint (creates the restriction if absent).
    Allow {
        /// The endpoint to restrict.
        endpoint: String,
        /// The origin node's pubkey hex (as shown by `spt endpoint list` / pairing).
        node: String,
    },
    /// Remove a node from an endpoint's whitelist. Never widens: revoking the
    /// last node leaves the endpoint locked down (all unsolicited refused).
    Revoke { endpoint: String, node: String },
    /// Delete an endpoint's restriction entirely — back to default-open.
    Open { endpoint: String },
    /// List restrictions (all endpoints, or one).
    List { endpoint: Option<String> },


1244	/// Run the per-machine daemon (`spt daemon run` — the ex-hidden `spt daemon`
1245	/// verb, M8 decision 5). Blocks serving the broker + seed-control channels
1246	/// until killed; a bind contention (another daemon already owns this home)
1247	/// surfaces as a non-zero exit.
1248	// [impl->REQ-DAEMON-3]
1249	// [impl->REQ-CLI-2]
1250	fn cmd_daemon_run() -> i32 {
1251	    match spt_daemon::Daemon::run() {
1252	        Ok(()) => 0,
1253	        Err(e) => {
1254	            eprintln!("DAEMON_EXIT: {e}");
1255	            1
1256	        }
1257	    }
1258	}
1259
1260	/// `spt daemon stop` — graceful daemon shutdown over the seed-control
1261	/// channel (M8 decision 5, REQ-CLI-2): the daemon acks, then exits its
1262	/// foreground loop. Idempotent UX: a daemon that is not running is already
1263	/// stopped (exit 0, said honestly).
1264	// [impl->REQ-CLI-2]
1265	fn cmd_daemon_stop() -> i32 {
1266	    if !spt_daemon::is_running() {
1267	        eprintln!("DAEMON_NOT_RUNNING (already stopped)");
1268	        return 0;
1269	    }
1270	    match spt_daemon::request_stop() {
1271	        Ok(()) => {
1272	            eprintln!("DAEMON_STOPPED");
1273	            0
1274	        }
1275	        Err(e) => {
1276	            eprintln!("DAEMON_STOP_FAIL: {e}");
1277	            1
1278	        }
1279	    }
1280	}
1281
1282	/// `spt daemon [status]` — the node status view (M8 decisions 23 + 25b,
1283	/// REQ-CLI-2): daemon state (running/stopped + the peer-pump heartbeat —
1284	/// a stalled pump renders honestly, never implied-healthy), the member
1285	/// subnets, and this node's local endpoints. Read-only and ungated.
1286	// [impl->REQ-CLI-2]
1287	fn cmd_daemon_status() -> i32 {
1288	    // Daemon state: the socket ping is liveness truth (never the pid file).
1289	    let running = spt_daemon::is_running();
1290	    if running {
1291	        let pid = std::fs::read_to_string(spt_daemon::daemon_pid_path())
1292	            .ok()
1293	            .map(|s| s.trim().to_string());
1294	        match pid {
1295	            Some(p) if !p.is_empty() => println!("daemon: running (pid {p})"),
1296	            _ => println!("daemon: running"),
1297	        }
1298	        // Pump liveness (M8 decision 23 leg 1): the heartbeat file the pump
1299	        // writes each tick (D4). Absent = no heartbeat written yet (an old
1300	        // daemon, or one mid-startup) — said as unknown, never as healthy.
1301	        match spt_daemon::read_pump_heartbeat() {
1302	            Some(at_ms) => {
1303	                let age = now_ms().saturating_sub(at_ms);
1304	                let stale_after = 3 * spt_daemon::DaemonConfig::load()
1305	                    .registry_pump_period
1306	                    .as_millis() as u64;
1307	                if age <= stale_after {
1308	                    println!("peer pump: live (last tick {}s ago)", age / 1000);
1309	                } else {
1310	                    println!(
1311	                        "peer pump: STALLED (last tick {}s ago — advertising/connecting \
1312	                         is not running; restart the daemon)",
1313	                        age / 1000
1314	                    );
1315	                }
1316	            }
1317	            None => println!("peer pump: no heartbeat recorded"),
1318	        }
1319	    } else {
1320	        println!("daemon: not running (`spt daemon run`, or any spt command auto-starts it)");
1321	    }
1322
1323	    // Member subnets (names + counts — the bare-subnet status rows).
1324	    let store = SubnetStore::load();
1325	    if store.subnets.is_empty() {
1326	        println!("subnets: (none — this node is standalone)");
1327	    } else {
1328	        let regs = crate::wansend::load_snapshots(&perch::identity_dir().join("registry"));
1329	        let roster = spt_store::roster::RosterStore::load();
1330	        let own_hex = spt_store::nodeid::load_or_create()
1331	            .map(|i| i.public_key().to_hex())
1332	            .unwrap_or_default();
1333	        println!("subnets:");

/// Run the per-machine daemon (`spt daemon run` — the ex-hidden `spt daemon`
/// verb, M8 decision 5). Blocks serving the broker + seed-control channels
/// until killed; a bind contention (another daemon already owns this home)
/// surfaces as a non-zero exit.
// [impl->REQ-DAEMON-3]
// [impl->REQ-CLI-2]
fn cmd_daemon_run() -> i32 {
    match spt_daemon::Daemon::run() {
        Ok(()) => 0,
        Err(e) => {
            eprintln!("DAEMON_EXIT: {e}");
            1
        }
    }
}

/// `spt daemon stop` — graceful daemon shutdown over the seed-control
/// channel (M8 decision 5, REQ-CLI-2): the daemon acks, then exits its
/// foreground loop. Idempotent UX: a daemon that is not running is already
/// stopped (exit 0, said honestly).
// [impl->REQ-CLI-2]
fn cmd_daemon_stop() -> i32 {
    if !spt_daemon::is_running() {
        eprintln!("DAEMON_NOT_RUNNING (already stopped)");
        return 0;
    }
    match spt_daemon::request_stop() {
        Ok(()) => {
            eprintln!("DAEMON_STOPPED");
            0
        }
        Err(e) => {
            eprintln!("DAEMON_STOP_FAIL: {e}");
            1
        }
    }
}

/// `spt daemon [status]` — the node status view (M8 decisions 23 + 25b,
/// REQ-CLI-2): daemon state (running/stopped + the peer-pump heartbeat —
/// a stalled pump renders honestly, never implied-healthy), the member
/// subnets, and this node's local endpoints. Read-only and ungated.
// [impl->REQ-CLI-2]
fn cmd_daemon_status() -> i32 {
    // Daemon state: the socket ping is liveness truth (never the pid file).
    let running = spt_daemon::is_running();
    if running {
        let pid = std::fs::read_to_string(spt_daemon::daemon_pid_path())
            .ok()
            .map(|s| s.trim().to_string());
        match pid {
            Some(p) if !p.is_empty() => println!("daemon: running (pid {p})"),
            _ => println!("daemon: running"),
        }
        // Pump liveness (M8 decision 23 leg 1): the heartbeat file the pump
        // writes each tick (D4). Absent = no heartbeat written yet (an old
        // daemon, or one mid-startup) — said as unknown, never as healthy.
        match spt_daemon::read_pump_heartbeat() {
            Some(at_ms) => {
                let age = now_ms().saturating_sub(at_ms);
                let stale_after = 3 * spt_daemon::DaemonConfig::load()
                    .registry_pump_period
                    .as_millis() as u64;
                if age <= stale_after {
                    println!("peer pump: live (last tick {}s ago)", age / 1000);
                } else {
                    println!(
                        "peer pump: STALLED (last tick {}s ago — advertising/connecting \
                         is not running; restart the daemon)",
                        age / 1000
                    );
                }
            }
            None => println!("peer pump: no heartbeat recorded"),
        }
    } else {
        println!("daemon: not running (`spt daemon run`, or any spt command auto-starts it)");
    }

    // Member subnets (names + counts — the bare-subnet status rows).
    let store = SubnetStore::load();
    if store.subnets.is_empty() {
        println!("subnets: (none — this node is standalone)");
    } else {
        let regs = crate::wansend::load_snapshots(&perch::identity_dir().join("registry"));
        let roster = spt_store::roster::RosterStore::load();
        let own_hex = spt_store::nodeid::load_or_create()
            .map(|i| i.public_key().to_hex())
            .unwrap_or_default();
        println!("subnets:");


windows decision: stick to logon scheduled task -detection source of truth: you decide using best judgment -start graduates: yes --- proceed. note that you can collaborate with agent todlando for elev…


grep -n "fn ensure_running\|fn spawn_detached\|fn request_stop\|fn is_running\|pub fn run\|CREATE_NO_WINDOW\|DETACHED_PROCESS\|deelevate\|daemon run\|\"run\"" crates/spt-daemon/src/daemon.rs | head -40; echo "---files---"; ls crates/spt-daemon/src

daemon.rs lifecycle fns + module files


44:    pub fn run() -> io::Result<()> {
61:        if let Some(invoker) = crate::deelevate::daemon_target_user() {
76:        if crate::deelevate::is_elevated() {
78:            match crate::deelevate::spawn_deelevated(
80:                &["daemon".to_string(), "run".to_string()],
244:        // daemon crash. The daemon runs until killed, so the stop flag here
264:pub fn is_running() -> bool {
272:pub fn request_stop() -> io::Result<()> {
280:pub fn ensure_running() -> io::Result<()> {
322:/// Windows via the UAC linked token ([`crate::deelevate::spawn_deelevated`];
329:fn spawn_detached() -> io::Result<()> {
334:        let args = ["daemon".to_string(), "run".to_string()];
335:        match crate::deelevate::spawn_deelevated(&exe, &args) {
338:                if crate::deelevate::is_elevated() {
368:        cmd.args(["daemon", "run"])
372:        if let Some(invoker) = crate::deelevate::daemon_target_user() {
404:/// process chain can be wedged by it. `DETACHED_PROCESS` ⇒ no console, so the
458:    // DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW: no
577:    fn is_running_tracks_the_seed_channel() {
---files---
access.rs
adapter_update.rs
applyhost.rs
attach.rs
brain.rs
broker.rs
codec.rs
config.rs
consent.rs
daemon.rs
deelevate.rs
digest.rs
digesthub.rs
dispatch.rs
effect.rs
endpoint.rs
frame.rs
grants.rs
lib.rs
lifecycle.rs
linkhost.rs
machineid.rs
msg.rs
nethost.rs
notif.rs
notifsync.rs
pairhost.rs
peerloop.rs
presence.rs
propagate.rs
psyrelay.rs
reconcile.rs
registryhost.rs
relay.rs
relcache.rs
release.rs
resthost.rs
resting.rs
seedmap.rs
seedproofx.rs
serveprobe.rs
shellchan.rs
shelldisc.rs
shellhost.rs
shellwake.rs
sync.rs
test_home.rs
transport.rs
update.rs
wan.rs
xfer.rs


40	impl Daemon {
41	    /// Run the daemon: bind the broker (served on a background thread) + the
42	    /// seed-control channel (the foreground loop + liveness anchor) and serve
43	    /// until the process is killed. The body of `spt daemon`.
44	    pub fn run() -> io::Result<()> {
45	        // Entry elevation guard (KNOWN-HAZARDS 5.7): a DIRECTLY elevated
46	        // `spt daemon` (sudo / elevated shell) must not serve with the
47	        // elevated token — its pipes would deny unelevated clients (Windows)
48	        // or root the user's state universe (Linux). Unix drops privileges
49	        // in-process before any state is touched; Windows cannot drop a
50	        // token in-place, so it respawns de-elevated via the UAC linked
51	        // token and exits (the child re-enters here unelevated). With no
52	        // unelevated identity to drop to, serving continues as-is, loudly —
53	        // a consistent universe, never a torn one.
54	        // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
55	        // Drop target: the ELECTED default user when one exists, else the
56	        // sudo invoker — electing it on first use (M8 decision 8,
57	        // REQ-INSTALL-6: the first `sudo spt` decides; thereafter every
58	        // elevated launch lands in the same account's universe).
59	        // [impl->REQ-INSTALL-6]
60	        #[cfg(unix)]
61	        if let Some(invoker) = crate::deelevate::daemon_target_user() {
62	            match invoker.drop_in_process() {
63	                Ok(()) => eprintln!("DEELEVATED: daemon dropped to uid {}", invoker.uid),
64	                // A drop target existed but the drop failed: serving as
65	                // root would root the user's state universe — a known-torn
66	                // state. ABORT (user-ratified 2026-06-06: refuse, don't limp).
67	                Err(e) => {
68	                    return Err(io::Error::other(format!(
69	                        "DEELEVATE_FAIL: {e} — refusing to serve as root over a user universe; \
70	                         run spt unelevated"
71	                    )));
72	                }
73	            }
74	        }
75	        #[cfg(windows)]
76	        if crate::deelevate::is_elevated() {
77	            let exe = std::env::current_exe()?;
78	            match crate::deelevate::spawn_deelevated(
79	                &exe.to_string_lossy(),
80	                &["daemon".to_string(), "run".to_string()],
81	            ) {
82	                Ok(Some(pid)) => {
83	                    eprintln!(
84	                        "DEELEVATED_RESPAWN: unelevated daemon pid {pid}; elevated copy exits"
85	                    );
86	                    return Ok(());
87	                }
88	                // Uniformly-elevated universe (no unelevated identity):
89	                // serving elevated is consistent there. Loud anyway.
90	                Ok(None) => eprintln!(
91	                    "ELEVATED_DAEMON: no unelevated identity to drop to — serving with the \
92	                     elevated token"
93	                ),
94	                // Target existed, respawn failed: an elevated daemon is a
95	                // known-torn state. ABORT (user-ratified 2026-06-06).
96	                Err(e) => {
97	                    return Err(io::Error::other(format!(
98	                        "DEELEVATE_FAIL: {e} — refusing to serve with the elevated token; run \
99	                         spt from an unelevated shell"
100	                    )));
101	                }
102	            }
103	        }
104
105	        let reg = Arc::new(SeedRegistry::new());
106
107	        // Boot housekeeping (KNOWN-HAZARDS 1.3): sweep stale `index.lock`
108	        // wedges out of the git-touching runtime the daemon hosts — the
109	        // context store's seed repo + its agent/project worktrees (a crashed
110	        // git leaves a 0-byte lock that blocks every later commit forever).
111	        // Path composed via the layout authority without opening the full
112	        // ContextStore on the boot path; a lock-less (or store-less) home
113	        // no-ops.
114	        // [impl->REQ-HAZARD-STALE-INDEX-LOCK]
115	        for lock in spt_store::branchstore::sweep_stale_index_locks(
116	            &spt_store::contextstore::seed_git_dir(&spt_store::perch::tracked_dir()),
117	            spt_store::branchstore::STALE_LOCK_AGE,
118	        ) {
119	            eprintln!("STALE_LOCK_SWEPT:{}", lock.display());
120	        }
121
122	        // Serve-state boot reset (M8 decision 6, REQ-SUBNET-5): the live
123	        // attachment state starts from the config's startup defaults — an
124	        // unsaved attach/detach flip deliberately does not survive a daemon
125	        // restart (`--save` is the persistence path).
126	        // [impl->REQ-SUBNET-5]
127	        {
128	            let att = spt_store::attachment::AttachmentStore::reset_to(
129	                &DaemonConfig::load().detached_subnets,
130	            );
131	            if let Err(e) = att.save() {
132	                eprintln!("ATTACHMENT_RESET_FAIL: {e} — serving with all subnets attached");
133	            }
134	            for name in &att.detached {
135	                eprintln!("SUBNET_DETACHED:{name} (startup default — `spt subnet attach {name}`)");
136	            }
137	        }
138
139	        // The broker's network host (D4a, ADR-0004 §B): the node's iroh endpoint
140	        // + live QUIC connections live in the broker so they survive a brain
141	        // restart. Identity is the durable node.key (stable name across daemon
142	        // restarts); relay stance comes from daemon.json; mDNS rides the
143	        // endpoint (closing the §B mDNS/relay-session row by construction). A
144	        // net failure degrades to a net-less broker — it must not cost PTY
145	        // hosting (mirrors the broker-bind soft-fail below).
146	        // [impl->REQ-DAEMON-2]
147	        let mut node_hex: Option<String> = None;
148	        let net = {
149	            let cfg = DaemonConfig::load();
150	            let relay = match cfg.relay {
151	                RelayChoice::N0 => RelayPolicy::N0Default,
152	                RelayChoice::Disabled => RelayPolicy::Disabled,
153	            };
154	            match spt_store::nodeid::load_or_create() {
155	                Ok(identity) => {
156	                    node_hex = Some(identity.public_key().to_hex());
157	                    let net_cfg = NetConfig {
158	                        identity,
159	                        relay,

impl Daemon {
    /// Run the daemon: bind the broker (served on a background thread) + the
    /// seed-control channel (the foreground loop + liveness anchor) and serve
    /// until the process is killed. The body of `spt daemon`.
    pub fn run() -> io::Result<()> {
        // Entry elevation guard (KNOWN-HAZARDS 5.7): a DIRECTLY elevated
        // `spt daemon` (sudo / elevated shell) must not serve with the
        // elevated token — its pipes would deny unelevated clients (Windows)
        // or root the user's state universe (Linux). Unix drops privileges
        // in-process before any state is touched; Windows cannot drop a
        // token in-place, so it respawns de-elevated via the UAC linked
        // token and exits (the child re-enters here unelevated). With no
        // unelevated identity to drop to, serving continues as-is, loudly —
        // a consistent universe, never a torn one.
        // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
        // Drop target: the ELECTED default user when one exists, else the
        // sudo invoker — electing it on first use (M8 decision 8,
        // REQ-INSTALL-6: the first `sudo spt` decides; thereafter every
        // elevated launch lands in the same account's universe).
        // [impl->REQ-INSTALL-6]
        #[cfg(unix)]
        if let Some(invoker) = crate::deelevate::daemon_target_user() {
            match invoker.drop_in_process() {
                Ok(()) => eprintln!("DEELEVATED: daemon dropped to uid {}", invoker.uid),
                // A drop target existed but the drop failed: serving as
                // root would root the user's state universe — a known-torn
                // state. ABORT (user-ratified 2026-06-06: refuse, don't limp).
                Err(e) => {
                    return Err(io::Error::other(format!(
                        "DEELEVATE_FAIL: {e} — refusing to serve as root over a user universe; \
                         run spt unelevated"
                    )));
                }
            }
        }
        #[cfg(windows)]
        if crate::deelevate::is_elevated() {
            let exe = std::env::current_exe()?;
            match crate::deelevate::spawn_deelevated(
                &exe.to_string_lossy(),
                &["daemon".to_string(), "run".to_string()],
            ) {
                Ok(Some(pid)) => {
                    eprintln!(
                        "DEELEVATED_RESPAWN: unelevated daemon pid {pid}; elevated copy exits"
                    );
                    return Ok(());
                }
                // Uniformly-elevated universe (no unelevated identity):
                // serving elevated is consistent there. Loud anyway.
                Ok(None) => eprintln!(
                    "ELEVATED_DAEMON: no unelevated identity to drop to — serving with the \
                     elevated token"
                ),
                // Target existed, respawn failed: an elevated daemon is a
                // known-torn state. ABORT (user-ratified 2026-06-06).
                Err(e) => {
                    return Err(io::Error::other(format!(
                        "DEELEVATE_FAIL: {e} — refusing to serve with the elevated token; run \
                         spt from an unelevated shell"
                    )));
                }
            }
        }

        let reg = Arc::new(SeedRegistry::new());

        // Boot housekeeping (KNOWN-HAZARDS 1.3): sweep stale `index.lock`
        // wedges out of the git-touching runtime the daemon hosts — the
        // context store's seed repo + its agent/project worktrees (a crashed
        // git leaves a 0-byte lock that blocks every later commit forever).
        // Path composed via the layout authority without opening the full
        // ContextStore on the boot path; a lock-less (or store-less) home
        // no-ops.
        // [impl->REQ-HAZARD-STALE-INDEX-LOCK]
        for lock in spt_store::branchstore::sweep_stale_index_locks(
            &spt_store::contextstore::seed_git_dir(&spt_store::perch::tracked_dir()),
            spt_store::branchstore::STALE_LOCK_AGE,
        ) {
            eprintln!("STALE_LOCK_SWEPT:{}", lock.display());
        }

        // Serve-state boot reset (M8 decision 6, REQ-SUBNET-5): the live
        // attachment state starts from the config's startup defaults — an
        // unsaved attach/detach flip deliberately does not survive a daemon
        // restart (`--save` is the persistence path).
        // [impl->REQ-SUBNET-5]
        {
            let att = spt_store::attachment::AttachmentStore::reset_to(
                &DaemonConfig::load().detached_subnets,
            );
            if let Err(e) = att.save() {
                eprintln!("ATTACHMENT_RESET_FAIL: {e} — serving with all subnets attached");
            }
            for name in &att.detached {
                eprintln!("SUBNET_DETACHED:{name} (startup default — `spt subnet attach {name}`)");
            }
        }

        // The broker's network host (D4a, ADR-0004 §B): the node's iroh endpoint
        // + live QUIC connections live in the broker so they survive a brain
        // restart. Identity is the durable node.key (stable name across daemon
        // restarts); relay stance comes from daemon.json; mDNS rides the
        // endpoint (closing the §B mDNS/relay-session row by construction). A
        // net failure degrades to a net-less broker — it must not cost PTY
        // hosting (mirrors the broker-bind soft-fail below).
        // [impl->REQ-DAEMON-2]
        let mut node_hex: Option<String> = None;
        let net = {
            let cfg = DaemonConfig::load();
            let relay = match cfg.relay {
                RelayChoice::N0 => RelayPolicy::N0Default,
                RelayChoice::Disabled => RelayPolicy::Disabled,
            };
            match spt_store::nodeid::load_or_create() {
                Ok(identity) => {
                    node_hex = Some(identity.public_key().to_hex());
                    let net_cfg = NetConfig {
                        identity,
                        relay,


260	    }
261	}
262
263	/// Whether a daemon is already serving the seed-control channel for this home.
264	pub fn is_running() -> bool {
265	    seedmap::ping(&seed_socket_name()).is_ok()
266	}
267
268	/// Gracefully stop the daemon serving this home (`spt daemon stop`, M8
269	/// decision 5 / REQ-CLI-2): the seed-control stop op — acked, then the
270	/// daemon's foreground loop returns and the process exits cleanly.
271	// [impl->REQ-CLI-2]
272	pub fn request_stop() -> io::Result<()> {
273	    seedmap::request_stop(&seed_socket_name())
274	}
275
276	/// Auto-start (REQ-DAEMON-3): ensure a daemon is up for this home. A no-op if one
277	/// already answers; otherwise spawn `spt daemon` detached and wait (bounded) for
278	/// it to bind. Idempotent + race-tolerant: if two callers spawn at once, one
279	/// wins the bind and the other exits — and this check passes either way.
280	pub fn ensure_running() -> io::Result<()> {
281	    if is_running() {
282	        return Ok(());
283	    }
284	    spawn_detached()?;
285	    let mut waited = Duration::ZERO;
286	    while waited < START_TIMEOUT {
287	        if is_running() {
288	            return Ok(());
289	        }
290	        thread::sleep(START_POLL);
291	        waited += START_POLL;
292	    }
293	    Err(io::Error::new(
294	        io::ErrorKind::TimedOut,
295	        "daemon did not come up within the start window",
296	    ))
297	}
298
299	/// Spawn `spt daemon` as a detached background process. Inherits the environment
300	/// — so the child resolves the **same** `SPT_HOME`, hence the same canonical
301	/// socket names — but drops the std handles so it outlives the launching CLI
302	/// call. The child is independent (not killed when the parent exits): on unix it
303	/// is reparented to init when the launcher exits; on Windows a child is not in
304	/// the parent's job by default.
305	///
306	/// **Windows handle-inheritance guard (KNOWN-HAZARDS 5.6).** A caller may
307	/// capture this process's stdout/stderr through a pipe (`Command::output()`, a
308	/// harness hook reading the command's output). On Windows `CreateProcess` runs
309	/// with `bInheritHandles = TRUE`, so the long-lived daemon would inherit that
310	/// pipe's write end and hold it open forever — the capturing reader never sees
311	/// EOF and hangs. Worse, inheritable pipe handles flow through EVERY
312	/// `bInheritHandles = TRUE` hop: stripping only this process's *std* handles
313	/// (the first guard shipped here) still leaked a grandparent capture's pipe
314	/// sitting in our handle table as a stray. So detached long-lived children are
315	/// spawned with **`bInheritHandles = FALSE`** ([`detached_no_inherit`]) — zero
316	/// handles flow, whatever the ancestry. Unix is immune (Rust marks pipe fds
317	/// `CLOEXEC`) and keeps the plain `Command` path.
318	///
319	/// **Elevation guard (KNOWN-HAZARDS 5.7).** The daemon must ALWAYS run
320	/// unelevated in the invoking user's universe, whichever command spawns it:
321	/// an elevated spawner (Windows UAC, `sudo`) de-elevates the child here —
322	/// Windows via the UAC linked token ([`crate::deelevate::spawn_deelevated`];
323	/// no handle inheritance by construction, so 5.6 holds on that path too),
324	/// unix by dropping the child to `SUDO_UID`/`SUDO_GID` with the invoker's
325	/// `HOME`. When no unelevated identity exists (UAC off, genuine root), the
326	/// spawn proceeds as-is with a loud warning — a consistent universe, never a
327	/// torn one.
328	// [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
329	fn spawn_detached() -> io::Result<()> {
330	    let exe = std::env::current_exe()?;
331	    #[cfg(windows)]
332	    {
333	        let exe = exe.to_string_lossy();
334	        let args = ["daemon".to_string(), "run".to_string()];
335	        match crate::deelevate::spawn_deelevated(&exe, &args) {
336	            Ok(Some(_pid)) => return Ok(()), // de-elevated child running
337	            Ok(None) => {
338	                if crate::deelevate::is_elevated() {
339	                    // No unelevated identity exists (UAC-off admin desktop,
340	                    // SYSTEM): the whole universe is uniformly elevated, so
341	                    // an elevated daemon is consistent there. Loud anyway.
342	                    eprintln!(
343	                        "ELEVATED_DAEMON: no unelevated identity to drop to — daemon inherits \
344	                         the elevated token"
345	                    );
346	                }
347	            }
348	            // A de-elevation TARGET existed but the spawn failed: an
349	            // elevated daemon here is a known-torn state (its pipes deny
350	            // the unelevated clients that target exists for). ABORT — do
351	            // not start a daemon that cannot serve (user-ratified
352	            // 2026-06-06: DEELEVATE_FAIL means spt won't function; refuse,
353	            // don't limp).
354	            Err(e) => {
355	                return Err(io::Error::other(format!(
356	                    "DEELEVATE_FAIL: {e} — refusing to start an ELEVATED daemon (it would deny \
357	                     unelevated clients); run spt from an unelevated shell"
358	                )));
359	            }
360	        }
361	        detached_no_inherit(&exe, &args)?;
362	        Ok(())
363	    }
364	    #[cfg(unix)]
365	    {
366	        use std::os::unix::process::CommandExt;
367	        let mut cmd = Command::new(exe);
368	        cmd.args(["daemon", "run"])
369	            .stdin(Stdio::null())
370	            .stdout(Stdio::null())
371	            .stderr(Stdio::null());
372	        if let Some(invoker) = crate::deelevate::daemon_target_user() {
373	            // Drop the child to the elected default user (else the sudo
374	            // invoker — M8 decision 8): std clears supplementary groups and
375	            // orders setgid-before-setuid when uid is set.
376	            // [impl->REQ-INSTALL-6]
377	            invoker.stamp_env(&mut cmd);
378	            cmd.uid(invoker.uid).gid(invoker.gid);
379	        }
380	        // setsid: detach into a NEW SESSION, not just a new process group —
381	        // a daemon auto-started inside an ssh/non-interactive session
382	        // otherwise dies with it (sshd HUPs the whole session on
383	        // disconnect; observed live in the 2026-06-06 fleet deploy, where
384	        // the freshly-spawned daemon vanished the moment the ssh command
385	        // returned). `process_group(0)` is NOT enough — the new group
386	        // still belongs to the dying session. Safety: this closure only
387	        // calls the async-signal-safe setsid(2).
388	        unsafe {
389	            cmd.pre_exec(|| {
390	                if libc::setsid() == -1 {
391	                    return Err(std::io::Error::last_os_error());
392	                }
393	                Ok(())
394	            });
395	        }
396	        cmd.spawn()?;
397	        Ok(())
398	    }
399	}
400
401	/// Spawn `program args…` detached with **`bInheritHandles = FALSE`** — the KH
402	/// 5.6 guard in its complete form: no handle (std or stray grandparent pipe)
403	/// flows into the immortal child, so no capturing caller anywhere up the
404	/// process chain can be wedged by it. `DETACHED_PROCESS` ⇒ no console, so the
405	/// child's std handles are simply absent (every such child runs stdio-null by
406	/// design anyway). `lpApplicationName` stays NULL so `program` resolves via
407	/// PATH like `Command` would. Shared by the daemon ([`spawn_detached`]) and
408	/// the shell binary ([`crate::shellhost::launch_shell`]). Raw `CreateProcessW`
409	/// declared inline to avoid a winapi dependency for one call.
410	// [impl->REQ-HAZARD-DETACHED-PIPE-INHERIT]
411	#[cfg(windows)]
412	pub(crate) fn detached_no_inherit(program: &str, args: &[String]) -> io::Result<u32> {
413	    use std::os::windows::ffi::OsStrExt;
414
415	    #[repr(C)]
416	    struct StartupInfoW {
417	        cb: u32,
418	        reserved: *mut u16,
419	        desktop: *mut u16,

    }
}

/// Whether a daemon is already serving the seed-control channel for this home.
pub fn is_running() -> bool {
    seedmap::ping(&seed_socket_name()).is_ok()
}

/// Gracefully stop the daemon serving this home (`spt daemon stop`, M8
/// decision 5 / REQ-CLI-2): the seed-control stop op — acked, then the
/// daemon's foreground loop returns and the process exits cleanly.
// [impl->REQ-CLI-2]
pub fn request_stop() -> io::Result<()> {
    seedmap::request_stop(&seed_socket_name())
}

/// Auto-start (REQ-DAEMON-3): ensure a daemon is up for this home. A no-op if one
/// already answers; otherwise spawn `spt daemon` detached and wait (bounded) for
/// it to bind. Idempotent + race-tolerant: if two callers spawn at once, one
/// wins the bind and the other exits — and this check passes either way.
pub fn ensure_running() -> io::Result<()> {
    if is_running() {
        return Ok(());
    }
    spawn_detached()?;
    let mut waited = Duration::ZERO;
    while waited < START_TIMEOUT {
        if is_running() {
            return Ok(());
        }
        thread::sleep(START_POLL);
        waited += START_POLL;
    }
    Err(io::Error::new(
        io::ErrorKind::TimedOut,
        "daemon did not come up within the start window",
    ))
}

/// Spawn `spt daemon` as a detached background process. Inherits the environment
/// — so the child resolves the **same** `SPT_HOME`, hence the same canonical
/// socket names — but drops the std handles so it outlives the launching CLI
/// call. The child is independent (not killed when the parent exits): on unix it
/// is reparented to init when the launcher exits; on Windows a child is not in
/// the parent's job by default.
///
/// **Windows handle-inheritance guard (KNOWN-HAZARDS 5.6).** A caller may
/// capture this process's stdout/stderr through a pipe (`Command::output()`, a
/// harness hook reading the command's output). On Windows `CreateProcess` runs
/// with `bInheritHandles = TRUE`, so the long-lived daemon would inherit that
/// pipe's write end and hold it open forever — the capturing reader never sees
/// EOF and hangs. Worse, inheritable pipe handles flow through EVERY
/// `bInheritHandles = TRUE` hop: stripping only this process's *std* handles
/// (the first guard shipped here) still leaked a grandparent capture's pipe
/// sitting in our handle table as a stray. So detached long-lived children are
/// spawned with **`bInheritHandles = FALSE`** ([`detached_no_inherit`]) — zero
/// handles flow, whatever the ancestry. Unix is immune (Rust marks pipe fds
/// `CLOEXEC`) and keeps the plain `Command` path.
///
/// **Elevation guard (KNOWN-HAZARDS 5.7).** The daemon must ALWAYS run
/// unelevated in the invoking user's universe, whichever command spawns it:
/// an elevated spawner (Windows UAC, `sudo`) de-elevates the child here —
/// Windows via the UAC linked token ([`crate::deelevate::spawn_deelevated`];
/// no handle inheritance by construction, so 5.6 holds on that path too),
/// unix by dropping the child to `SUDO_UID`/`SUDO_GID` with the invoker's
/// `HOME`. When no unelevated identity exists (UAC off, genuine root), the
/// spawn proceeds as-is with a loud warning — a consistent universe, never a
/// torn one.
// [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
fn spawn_detached() -> io::Result<()> {
    let exe = std::env::current_exe()?;
    #[cfg(windows)]
    {
        let exe = exe.to_string_lossy();
        let args = ["daemon".to_string(), "run".to_string()];
        match crate::deelevate::spawn_deelevated(&exe, &args) {
            Ok(Some(_pid)) => return Ok(()), // de-elevated child running
            Ok(None) => {
                if crate::deelevate::is_elevated() {
                    // No unelevated identity exists (UAC-off admin desktop,
                    // SYSTEM): the whole universe is uniformly elevated, so
                    // an elevated daemon is consistent there. Loud anyway.
                    eprintln!(
                        "ELEVATED_DAEMON: no unelevated identity to drop to — daemon inherits \
                         the elevated token"
                    );
                }
            }
            // A de-elevation TARGET existed but the spawn failed: an
            // elevated daemon here is a known-torn state (its pipes deny
            // the unelevated clients that target exists for). ABORT — do
            // not start a daemon that cannot serve (user-ratified
            // 2026-06-06: DEELEVATE_FAIL means spt won't function; refuse,
            // don't limp).
            Err(e) => {
                return Err(io::Error::other(format!(
                    "DEELEVATE_FAIL: {e} — refusing to start an ELEVATED daemon (it would deny \
                     unelevated clients); run spt from an unelevated shell"
                )));
            }
        }
        detached_no_inherit(&exe, &args)?;
        Ok(())
    }
    #[cfg(unix)]
    {
        use std::os::unix::process::CommandExt;
        let mut cmd = Command::new(exe);
        cmd.args(["daemon", "run"])
            .stdin(Stdio::null())
            .stdout(Stdio::null())
            .stderr(Stdio::null());
        if let Some(invoker) = crate::deelevate::daemon_target_user() {
            // Drop the child to the elected default user (else the sudo
            // invoker — M8 decision 8): std clears supplementary groups and
            // orders setgid-before-setuid when uid is set.
            // [impl->REQ-INSTALL-6]
            invoker.stamp_env(&mut cmd);
            cmd.uid(invoker.uid).gid(invoker.gid);
        }
        // setsid: detach into a NEW SESSION, not just a new process group —
        // a daemon auto-started inside an ssh/non-interactive session
        // otherwise dies with it (sshd HUPs the whole session on
        // disconnect; observed live in the 2026-06-06 fleet deploy, where
        // the freshly-spawned daemon vanished the moment the ssh command
        // returned). `process_group(0)` is NOT enough — the new group
        // still belongs to the dying session. Safety: this closure only
        // calls the async-signal-safe setsid(2).
        unsafe {
            cmd.pre_exec(|| {
                if libc::setsid() == -1 {
                    return Err(std::io::Error::last_os_error());
                }
                Ok(())
            });
        }
        cmd.spawn()?;
        Ok(())
    }
}

/// Spawn `program args…` detached with **`bInheritHandles = FALSE`** — the KH
/// 5.6 guard in its complete form: no handle (std or stray grandparent pipe)
/// flows into the immortal child, so no capturing caller anywhere up the
/// process chain can be wedged by it. `DETACHED_PROCESS` ⇒ no console, so the
/// child's std handles are simply absent (every such child runs stdio-null by
/// design anyway). `lpApplicationName` stays NULL so `program` resolves via
/// PATH like `Command` would. Shared by the daemon ([`spawn_detached`]) and
/// the shell binary ([`crate::shellhost::launch_shell`]). Raw `CreateProcessW`
/// declared inline to avoid a winapi dependency for one call.
// [impl->REQ-HAZARD-DETACHED-PIPE-INHERIT]
#[cfg(windows)]
pub(crate) fn detached_no_inherit(program: &str, args: &[String]) -> io::Result<u32> {
    use std::os::windows::ffi::OsStrExt;

    #[repr(C)]
    struct StartupInfoW {
        cb: u32,
        reserved: *mut u16,
        desktop: *mut u16,


Read elevation guard + hazard test before touching Windows `run` (KH 5.7 risk):


grep -rn "REQ-HAZARD-ELEVATED-DAEMON-SPAWN\|ELEVATED-DAEMON\|fn spawn_deelevated\|is_elevated\|invisible\|CREATE_NO_WINDOW\|DETACHED_PROCESS" crates/spt-daemon/src/deelevate.rs crates/spt-daemon/src/daemon.rs | head -30

elevation guard + hazard refs


crates/spt-daemon/src/deelevate.rs:36:// [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/deelevate.rs:42:pub use windows::{is_elevated, spawn_deelevated};
crates/spt-daemon/src/deelevate.rs:59:// [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/deelevate.rs:83:// [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/deelevate.rs:140:    // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/deelevate.rs:159:    // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/deelevate.rs:384:        // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/deelevate.rs:511:    // DETACHED_PROCESS and CREATE_NO_WINDOW (the detached_no_inherit combo)
crates/spt-daemon/src/deelevate.rs:526:    // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/deelevate.rs:527:    pub fn is_elevated() -> bool {
crates/spt-daemon/src/deelevate.rs:572:    // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/deelevate.rs:573:    pub fn spawn_deelevated(program: &str, args: &[String]) -> io::Result<Option<u32>> {
crates/spt-daemon/src/deelevate.rs:574:        if !is_elevated() {
crates/spt-daemon/src/deelevate.rs:582:        if token_is_elevated(shell) {
crates/spt-daemon/src/deelevate.rs:638:    fn token_is_elevated(token: isize) -> bool {
crates/spt-daemon/src/deelevate.rs:760:    // [unit->REQ-HAZARD-ELEVATED-DAEMON-SPAWN] the pure spawn decision:
crates/spt-daemon/src/deelevate.rs:801:    // [unit->REQ-HAZARD-ELEVATED-DAEMON-SPAWN] the de-elevation env overlay:
crates/spt-daemon/src/deelevate.rs:875:    // [unit->REQ-HAZARD-ELEVATED-DAEMON-SPAWN] sudo-invoker derivation is
crates/spt-daemon/src/daemon.rs:54:        // [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/daemon.rs:76:        if crate::deelevate::is_elevated() {
crates/spt-daemon/src/daemon.rs:328:// [impl->REQ-HAZARD-ELEVATED-DAEMON-SPAWN]
crates/spt-daemon/src/daemon.rs:338:                if crate::deelevate::is_elevated() {
crates/spt-daemon/src/daemon.rs:404:/// process chain can be wedged by it. `DETACHED_PROCESS` ⇒ no console, so the
crates/spt-daemon/src/daemon.rs:458:    // DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW: no


grep -rn "ELEVATED-DAEMON-SPAWN\|unit->REQ-HAZARD-ELEVATED" -- '*.rs' | head; echo "---ps1 task---"; grep -n "schtasks\|ONLOGON\|spt-core daemon\|TaskName\|/TN" install.ps1 2>/dev/null | head; echo "---sh unit---"; grep -n "spt-daemon\|systemctl\|ExecStart\|WantedBy\|enable-linger" install.sh 2>/dev/null | head

hazard unit test + installer service defs


grep: *.rs: No such file or directory
---ps1 task---
---sh unit---


Hazard unit test is pure (the spawn *decision* fn at deelevate.rs:760), not the `run` respawn — so I can adjust the `run` path safely with a mirror test. Locate installers + socket/label helpers:


git ls-files | grep -iE "install\.(sh|ps1)$|\.service$|\.plist$"; echo "---"; grep -rn "systemctl\|spt-daemon.service\|enable-linger\|schtasks\|ONLOGON" $(git ls-files | grep -iE "install\.(sh|ps1)$") 2>/dev/null | head -25

Find installers + service defs


installer/install.ps1
installer/install.sh
---
installer/install.ps1:187:    # OWN account without elevation. schtasks, not Register-ScheduledTask,
installer/install.ps1:192:            schtasks /Create /F /TN "$taskName" /SC ONLOGON /TR "`"$target`" daemon run" /RL LIMITED | Out-Null
installer/install.ps1:196:            Write-Output "spt-install: register manually:  schtasks /Create /F /TN `"$taskName`" /SC ONLOGON /TR `"`"$target`" daemon run`" /RL LIMITED"
installer/install.sh:123:# enable-linger so it starts at boot, before any login. Linger needs root
installer/install.sh:128:if [ "${SPT_INSTALL_NO_SERVICE:-0}" != "1" ] && command -v systemctl >/dev/null 2>&1; then
installer/install.sh:131:  cat > "$UNIT_DIR/spt-daemon.service" <<UNIT
installer/install.sh:143:  if systemctl --user daemon-reload 2>/dev/null \
installer/install.sh:144:     && systemctl --user enable --now spt-daemon.service 2>/dev/null; then
installer/install.sh:147:    SERVICE_NOTE="Wrote $UNIT_DIR/spt-daemon.service; could not enable it from here (no user session bus?). Enable with:
installer/install.sh:148:spt-install:   systemctl --user daemon-reload && systemctl --user enable --now spt-daemon"
installer/install.sh:153:    if loginctl enable-linger "$LINGER_USER" 2>/dev/null; then
installer/install.sh:159:spt-install: For a pre-login boot start, run once: sudo loginctl enable-linger $LINGER_USER"


120	# ---- systemd user service (M8 decision 17, REQ-INSTALL-8) ---------------------
121	# [impl->REQ-INSTALL-8] reboot-survivable daemon: a systemd USER unit (the
122	# daemon + its state stay in the user's universe — KNOWN-HAZARDS 5.7) plus
123	# enable-linger so it starts at boot, before any login. Linger needs root
124	# once; when this run is unelevated the exact command prints instead.
125	# Soft everywhere: a box without systemd (or without a user session bus)
126	# gets hints, never a failed install.
127	SERVICE_NOTE=""
128	if [ "${SPT_INSTALL_NO_SERVICE:-0}" != "1" ] && command -v systemctl >/dev/null 2>&1; then
129	  UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
130	  mkdir -p "$UNIT_DIR"
131	  cat > "$UNIT_DIR/spt-daemon.service" <<UNIT
132	[Unit]
133	Description=spt-core per-machine daemon
134
135	[Service]
136	ExecStart=$INSTALL_DIR/spt daemon run
137	Restart=on-failure
138	RestartSec=5
139
140	[Install]
141	WantedBy=default.target
142	UNIT
143	  if systemctl --user daemon-reload 2>/dev/null \
144	     && systemctl --user enable --now spt-daemon.service 2>/dev/null; then
145	    SERVICE_NOTE="Registered + started the spt-daemon systemd user service."
146	  else
147	    SERVICE_NOTE="Wrote $UNIT_DIR/spt-daemon.service; could not enable it from here (no user session bus?). Enable with:
148	spt-install:   systemctl --user daemon-reload && systemctl --user enable --now spt-daemon"
149	  fi
150	  # Linger = start at boot without a login. Root-only; hint otherwise.
151	  LINGER_USER="${SUDO_USER:-$(id -un)}"
152	  if [ "$(id -u)" = "0" ] && command -v loginctl >/dev/null 2>&1; then
153	    if loginctl enable-linger "$LINGER_USER" 2>/dev/null; then
154	      SERVICE_NOTE="$SERVICE_NOTE
155	spt-install: Enabled linger for $LINGER_USER (daemon starts at boot, pre-login)."
156	    fi
157	  else
158	    SERVICE_NOTE="$SERVICE_NOTE
159	spt-install: For a pre-login boot start, run once: sudo loginctl enable-linger $LINGER_USER"
160	  fi
161	fi
162
163	# ---- report ----------------------------------------------------------------
164	# Print the absolute path: until PATH refreshes, invoke it absolutely.

# ---- systemd user service (M8 decision 17, REQ-INSTALL-8) ---------------------
# [impl->REQ-INSTALL-8] reboot-survivable daemon: a systemd USER unit (the
# daemon + its state stay in the user's universe — KNOWN-HAZARDS 5.7) plus
# enable-linger so it starts at boot, before any login. Linger needs root
# once; when this run is unelevated the exact command prints instead.
# Soft everywhere: a box without systemd (or without a user session bus)
# gets hints, never a failed install.
SERVICE_NOTE=""
if [ "${SPT_INSTALL_NO_SERVICE:-0}" != "1" ] && command -v systemctl >/dev/null 2>&1; then
  UNIT_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user"
  mkdir -p "$UNIT_DIR"
  cat > "$UNIT_DIR/spt-daemon.service" <<UNIT
[Unit]
Description=spt-core per-machine daemon

[Service]
ExecStart=$INSTALL_DIR/spt daemon run
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
UNIT
  if systemctl --user daemon-reload 2>/dev/null \
     && systemctl --user enable --now spt-daemon.service 2>/dev/null; then
    SERVICE_NOTE="Registered + started the spt-daemon systemd user service."
  else
    SERVICE_NOTE="Wrote $UNIT_DIR/spt-daemon.service; could not enable it from here (no user session bus?). Enable with:
spt-install:   systemctl --user daemon-reload && systemctl --user enable --now spt-daemon"
  fi
  # Linger = start at boot without a login. Root-only; hint otherwise.
  LINGER_USER="${SUDO_USER:-$(id -un)}"
  if [ "$(id -u)" = "0" ] && command -v loginctl >/dev/null 2>&1; then
    if loginctl enable-linger "$LINGER_USER" 2>/dev/null; then
      SERVICE_NOTE="$SERVICE_NOTE
spt-install: Enabled linger for $LINGER_USER (daemon starts at boot, pre-login)."
    fi
  else
    SERVICE_NOTE="$SERVICE_NOTE
spt-install: For a pre-login boot start, run once: sudo loginctl enable-linger $LINGER_USER"
  fi
fi

# ---- report ----------------------------------------------------------------
# Print the absolute path: until PATH refreshes, invoke it absolutely.


180	        }
181	    }
182
183	    # ---- at-logon scheduled task (M8 decision 17, REQ-INSTALL-8) --------------
184	    # [impl->REQ-INSTALL-8] reboot-survivable daemon: an at-logon scheduled
185	    # task in the user's interactive session - no stored credentials, no
186	    # session-0 isolation (the daemon hosts terminals), registrable for the
187	    # OWN account without elevation. schtasks, not Register-ScheduledTask,
188	    # for the same PS 5.1 hardened-box reason as netsh above.
189	    if ($env:SPT_INSTALL_NO_SERVICE -ne '1') {
190	        $taskName = 'spt-core daemon'
191	        try {
192	            schtasks /Create /F /TN "$taskName" /SC ONLOGON /TR "`"$target`" daemon run" /RL LIMITED | Out-Null
193	            Write-Output "spt-install: registered the at-logon daemon task '$taskName' (starts at your next logon)."
194	        } catch {
195	            Write-Output "spt-install: warning: scheduled-task registration failed: $_"
196	            Write-Output "spt-install: register manually:  schtasks /Create /F /TN `"$taskName`" /SC ONLOGON /TR `"`"$target`" daemon run`" /RL LIMITED"
197	        }
198	    }
199
200	    # ---- report ---------------------------------------------------------------
201	    # Print the absolute path: the PATH change does not reach this terminal, so
202	    # the first invocation here must use it absolutely.
203	    Write-Output "spt-install: installed $target"
204	    & $target --version

        }
    }

    # ---- at-logon scheduled task (M8 decision 17, REQ-INSTALL-8) --------------
    # [impl->REQ-INSTALL-8] reboot-survivable daemon: an at-logon scheduled
    # task in the user's interactive session - no stored credentials, no
    # session-0 isolation (the daemon hosts terminals), registrable for the
    # OWN account without elevation. schtasks, not Register-ScheduledTask,
    # for the same PS 5.1 hardened-box reason as netsh above.
    if ($env:SPT_INSTALL_NO_SERVICE -ne '1') {
        $taskName = 'spt-core daemon'
        try {
            schtasks /Create /F /TN "$taskName" /SC ONLOGON /TR "`"$target`" daemon run" /RL LIMITED | Out-Null
            Write-Output "spt-install: registered the at-logon daemon task '$taskName' (starts at your next logon)."
        } catch {
            Write-Output "spt-install: warning: scheduled-task registration failed: $_"
            Write-Output "spt-install: register manually:  schtasks /Create /F /TN `"$taskName`" /SC ONLOGON /TR `"`"$target`" daemon run`" /RL LIMITED"
        }
    }

    # ---- report ---------------------------------------------------------------
    # Print the absolute path: the PATH change does not reach this terminal, so
    # the first invocation here must use it absolutely.
    Write-Output "spt-install: installed $target"
    & $target --version


Full picture. Detection decision: **Linux = unit-file presence at canonical XDG path** (cheap, no subprocess, honest); control ops shell to `systemctl --user`. Windows = logon task (decision b): not start/stop-controllable, only a status boot-hint.

Mint REQs first (CLAUDE.md rule 3). Read the REQ-DAEMON block + tail:


42	[[requirements]]
43	id = "REQ-DAEMON-1"
44	title = "One per-machine spt-daemon owning all per-machine state"
45	required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E (spawn→Psyche-loop→commune→brain-restart-survives→graceful-signoff) drives the consolidated daemon-hosted lifecycle through the real IPC
46	[[requirements]]
47	id = "REQ-DAEMON-2"
48	title = "Broker/brain split for seamless self-update"
49	required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E proves a brain kill+restart leaves the broker's PTY child + output stream intact and gapless (cross-cut with handoff.rs)
50	[[requirements]]
51	id = "REQ-DAEMON-3"
52	title = "Any api invocation auto-starts the daemon if absent"
53	required_stages = ["impl", "unit", "int"]
54	[[requirements]]
55	id = "REQ-DAEMON-4"
56	title = "Honor every KNOWN-HAZARDS invariant"
57	required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E proves graceful signoff runs the echo-before-teardown ordering (3.3) and removes the Self's ready marker in the consolidated daemon

[[requirements]]
id = "REQ-DAEMON-1"
title = "One per-machine spt-daemon owning all per-machine state"
required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E (spawn→Psyche-loop→commune→brain-restart-survives→graceful-signoff) drives the consolidated daemon-hosted lifecycle through the real IPC
[[requirements]]
id = "REQ-DAEMON-2"
title = "Broker/brain split for seamless self-update"
required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E proves a brain kill+restart leaves the broker's PTY child + output stream intact and gapless (cross-cut with handoff.rs)
[[requirements]]
id = "REQ-DAEMON-3"
title = "Any api invocation auto-starts the daemon if absent"
required_stages = ["impl", "unit", "int"]
[[requirements]]
id = "REQ-DAEMON-4"
title = "Honor every KNOWN-HAZARDS invariant"
required_stages = ["impl", "unit", "int"]  # int activated M3b-B9: the daemon E2E proves graceful signoff runs the echo-before-teardown ordering (3.3) and removes the Self's ready marker in the consolidated daemon


740	required_stages = ["impl", "unit"]  # activated M8-D4: spt_net pairing::ntp — minimal SNTP client (bounded UDP query, transmit-timestamp offset) behind a lazy in-process cache (queried on ceremony use, TTL-refreshed, never a background task, never touches the OS clock); pairhost ceremony clocks (join + respond + meet rotation) read ceremony_now_secs. unit = SNTP packet parse against a loopback mock server + offset math + unreachable→system-clock fallback. Skewed-clock rig pairing = acceptance 9
741	[[requirements]]
742	id = "REQ-DAEMON-5"
743	title = "Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task — a panic is caught, logged loudly, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death)"
744	required_stages = ["impl", "unit"]  # activated M8-D4: run_peer_pump writes pump_heartbeat_path (epoch ms, throttled) each live tick — the D1 reader renders it; spawn_peer_pump becomes the supervisor: catch_unwind around the loop body, PEER_PUMP_PANIC/FAIL logged loud, restart with doubling backoff capped at 5 min, reset on a healthy run. unit = heartbeat write/advance + backoff ladder caps + supervised restart after an injected panic
745	[[requirements]]
746	id = "REQ-HAZARD-EPOCH-RESET"
747	title = "Advertisement-epoch reset strands a node: peers' higher last-seen epoch drops the reset node's fresh advertisements as Stale until the counter outruns history. Common case (full reinstall/re-pair) is mitigated by REQ-SUBNET-7's ceremony eviction (peer-side epoch memory dies with the deleted row — acceptance-verified); the residual narrow slice (epoch file lost, identity kept) is documented, guard deferred to a field hit (4.11)"
748	required_stages = []  # rule 5 + M8 decision 24: documented hazard (KNOWN-HAZARDS 4.11 at D5), guard waits for a field hit of the narrow slice; the re-pair eviction path is tested under REQ-SUBNET-7
749
750	# ── Subnet full-mesh: membership seed-proof + roster-only relay (ADR-0017, 2026-06-08 grill) ──
751	# Minted inactive (rule 5: no required_stages until the mesh milestone executes). Design: ADR-0017 + SUBNET-MESH-PLAN.md + CONTEXT §Pairing & trust.

required_stages = ["impl", "unit"]  # activated M8-D4: spt_net pairing::ntp — minimal SNTP client (bounded UDP query, transmit-timestamp offset) behind a lazy in-process cache (queried on ceremony use, TTL-refreshed, never a background task, never touches the OS clock); pairhost ceremony clocks (join + respond + meet rotation) read ceremony_now_secs. unit = SNTP packet parse against a loopback mock server + offset math + unreachable→system-clock fallback. Skewed-clock rig pairing = acceptance 9
[[requirements]]
id = "REQ-DAEMON-5"
title = "Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task — a panic is caught, logged loudly, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death)"
required_stages = ["impl", "unit"]  # activated M8-D4: run_peer_pump writes pump_heartbeat_path (epoch ms, throttled) each live tick — the D1 reader renders it; spawn_peer_pump becomes the supervisor: catch_unwind around the loop body, PEER_PUMP_PANIC/FAIL logged loud, restart with doubling backoff capped at 5 min, reset on a healthy run. unit = heartbeat write/advance + backoff ladder caps + supervised restart after an injected panic
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"
title = "Advertisement-epoch reset strands a node: peers' higher last-seen epoch drops the reset node's fresh advertisements as Stale until the counter outruns history. Common case (full reinstall/re-pair) is mitigated by REQ-SUBNET-7's ceremony eviction (peer-side epoch memory dies with the deleted row — acceptance-verified); the residual narrow slice (epoch file lost, identity kept) is documented, guard deferred to a field hit (4.11)"
required_stages = []  # rule 5 + M8 decision 24: documented hazard (KNOWN-HAZARDS 4.11 at D5), guard waits for a field hit of the narrow slice; the re-pair eviction path is tested under REQ-SUBNET-7

# ── Subnet full-mesh: membership seed-proof + roster-only relay (ADR-0017, 2026-06-08 grill) ──
# Minted inactive (rule 5: no required_stages until the mesh milestone executes). Design: ADR-0017 + SUBNET-MESH-PLAN.md + CONTEXT §Pairing & trust.


Insert REQ-DAEMON-6/7/8 after REQ-DAEMON-5:


Edit

required_stages = ["impl", "unit"]  # activated M8-D4: run_peer_pump writes pump_heartbeat_path (epoch ms, throttled) each live tick — the D1 reader renders it; spawn_peer_pump becomes the supervisor: catch_unwind around the loop body, PEER_PUMP_PANIC/FAIL logged loud, restart with doubling backoff capped at 5 min, reset on a healthy run. unit = heartbeat write/advance + backoff ladder caps + supervised restart after an injected panic
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"

required_stages = ["impl", "unit"]  # activated M8-D4: run_peer_pump writes pump_heartbeat_path (epoch ms, throttled) each live tick — the D1 reader renders it; spawn_peer_pump becomes the supervisor: catch_unwind around the loop body, PEER_PUMP_PANIC/FAIL logged loud, restart with doubling backoff capped at 5 min, reset on a healthy run. unit = heartbeat write/advance + backoff ladder caps + supervised restart after an injected panic

# ── Daemon lifecycle UX: service-aware start/stop + foreground-consistent run (DAEMON-LIFECYCLE-PLAN.md, 2026-06-08 kitsubito restart-loop) ──
# Origin: a manual `spt daemon run` fought the systemd spt-daemon user service over the broker socket (auto-restart-fail loop). Windows decision: keep the at-logon scheduled task for boot only (NOT a start/stop-controllable service); start=detached spawn, stop=IPC. Detection source of truth: Linux = systemd user unit FILE presence at the canonical XDG path (cheap, no subprocess; control ops shell to `systemctl --user`).
[[requirements]]
id = "REQ-DAEMON-6"
title = "Service-aware `daemon start`/`stop`: when an OS service manager has a registered spt-daemon for this user, `spt daemon start` and `spt daemon stop` drive THAT service (so stop doesn't IPC-kill a unit that auto-restart-fights for the broker socket — the kitsubito 2026-06-08 loop). `start` graduates from a `run` alias to a first-class background verb (ensure-up, idempotent, non-blocking); stop routes managed→manager, manual→IPC. Linux=systemd user unit (`systemctl --user start|stop|is-active spt-daemon`, detected by unit-file presence); Windows=no controllable manager (the logon task is boot-only), so start=detached spawn / stop=IPC."
required_stages = ["impl", "unit"]  # activate at daemon-lifecycle execution. impl = service.rs DaemonService trait (SystemdUserService detect-by-unit-file + systemctl control; Windows none-controllable) + pure plan_start/plan_stop routers + start_daemon/stop_daemon outcome APIs wired into cmd_daemon_start/cmd_daemon_stop; DaemonCmd::Start split from Run (alias removed). unit = plan_start/plan_stop truth tables (running×detected, running×detected×active) + `daemon start` parses as its own verb + systemd unit-path derivation. int (systemd rig) deferred to fleet rollout follow-up (rule 5).
[[requirements]]
id = "REQ-DAEMON-7"
title = "`daemon run` is foreground-consistent on every platform: the invoking process IS the daemon, blocks until signalled, never auto-detaches or respawns into an invisible background task. The detached/de-elevated background behavior lives ONLY in `start`. Windows: an ELEVATED `daemon run` refuses with guidance (use `start`, or an unelevated shell) instead of respawning detached/de-elevated and vanishing (KH 5.7 preserved — it still never serves elevated)."
required_stages = ["impl", "unit"]  # impl = cmd_daemon_run guards: Windows elevated→refuse (exit 2) before Daemon::run() (the hazard-tagged in-process respawn stays as untouched defense-in-depth, now unreached via the CLI); a conflict warning when a managed service is already active before binding inline. Daemon::run() foreground loop itself unchanged. unit = the pure run-mode decision (elevated×windows→refuse vs foreground) + `daemon run`/`daemon start` no longer share an alias.
[[requirements]]
id = "REQ-DAEMON-8"
title = "Internal auto-start prefers the service: `ensure_running` (any spt command's implicit daemon start, REQ-DAEMON-3) routes through the service-aware start path — when a manager has a registered service it starts THAT, never a competing manual `spawn_detached` daemon that would fight the service for the socket."
required_stages = ["impl", "unit"]  # impl = ensure_running consults service::platform_service(): detected→service.start() else spawn_detached(), then the existing bounded bind-wait. unit = the plan_start router already proves the detected→ServiceStart vs ManualSpawn branch (shared with REQ-DAEMON-6); ensure_running's no-op-when-already-running short-circuit preserved.
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"


The file C:\Users\decid\Documents\projects\spt-core\traceable-reqs.toml has been updated successfully. (file state is current in your context — no need to Read it back)

required_stages = ["impl", "unit"]  # activated M8-D4: run_peer_pump writes pump_heartbeat_path (epoch ms, throttled) each live tick — the D1 reader renders it; spawn_peer_pump becomes the supervisor: catch_unwind around the loop body, PEER_PUMP_PANIC/FAIL logged loud, restart with doubling backoff capped at 5 min, reset on a healthy run. unit = heartbeat write/advance + backoff ladder caps + supervised restart after an injected panic
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"

required_stages = ["impl", "unit"]  # activated M8-D4: run_peer_pump writes pump_heartbeat_path (epoch ms, throttled) each live tick — the D1 reader renders it; spawn_peer_pump becomes the supervisor: catch_unwind around the loop body, PEER_PUMP_PANIC/FAIL logged loud, restart with doubling backoff capped at 5 min, reset on a healthy run. unit = heartbeat write/advance + backoff ladder caps + supervised restart after an injected panic

# ── Daemon lifecycle UX: service-aware start/stop + foreground-consistent run (DAEMON-LIFECYCLE-PLAN.md, 2026-06-08 kitsubito restart-loop) ──
# Origin: a manual `spt daemon run` fought the systemd spt-daemon user service over the broker socket (auto-restart-fail loop). Windows decision: keep the at-logon scheduled task for boot only (NOT a start/stop-controllable service); start=detached spawn, stop=IPC. Detection source of truth: Linux = systemd user unit FILE presence at the canonical XDG path (cheap, no subprocess; control ops shell to `systemctl --user`).
[[requirements]]
id = "REQ-DAEMON-6"
title = "Service-aware `daemon start`/`stop`: when an OS service manager has a registered spt-daemon for this user, `spt daemon start` and `spt daemon stop` drive THAT service (so stop doesn't IPC-kill a unit that auto-restart-fights for the broker socket — the kitsubito 2026-06-08 loop). `start` graduates from a `run` alias to a first-class background verb (ensure-up, idempotent, non-blocking); stop routes managed→manager, manual→IPC. Linux=systemd user unit (`systemctl --user start|stop|is-active spt-daemon`, detected by unit-file presence); Windows=no controllable manager (the logon task is boot-only), so start=detached spawn / stop=IPC."
required_stages = ["impl", "unit"]  # activate at daemon-lifecycle execution. impl = service.rs DaemonService trait (SystemdUserService detect-by-unit-file + systemctl control; Windows none-controllable) + pure plan_start/plan_stop routers + start_daemon/stop_daemon outcome APIs wired into cmd_daemon_start/cmd_daemon_stop; DaemonCmd::Start split from Run (alias removed). unit = plan_start/plan_stop truth tables (running×detected, running×detected×active) + `daemon start` parses as its own verb + systemd unit-path derivation. int (systemd rig) deferred to fleet rollout follow-up (rule 5).
[[requirements]]
id = "REQ-DAEMON-7"
title = "`daemon run` is foreground-consistent on every platform: the invoking process IS the daemon, blocks until signalled, never auto-detaches or respawns into an invisible background task. The detached/de-elevated background behavior lives ONLY in `start`. Windows: an ELEVATED `daemon run` refuses with guidance (use `start`, or an unelevated shell) instead of respawning detached/de-elevated and vanishing (KH 5.7 preserved — it still never serves elevated)."
required_stages = ["impl", "unit"]  # impl = cmd_daemon_run guards: Windows elevated→refuse (exit 2) before Daemon::run() (the hazard-tagged in-process respawn stays as untouched defense-in-depth, now unreached via the CLI); a conflict warning when a managed service is already active before binding inline. Daemon::run() foreground loop itself unchanged. unit = the pure run-mode decision (elevated×windows→refuse vs foreground) + `daemon run`/`daemon start` no longer share an alias.
[[requirements]]
id = "REQ-DAEMON-8"
title = "Internal auto-start prefers the service: `ensure_running` (any spt command's implicit daemon start, REQ-DAEMON-3) routes through the service-aware start path — when a manager has a registered service it starts THAT, never a competing manual `spawn_detached` daemon that would fight the service for the socket."
required_stages = ["impl", "unit"]  # impl = ensure_running consults service::platform_service(): detected→service.start() else spawn_detached(), then the existing bounded bind-wait. unit = the plan_start router already proves the detected→ServiceStart vs ManualSpawn branch (shared with REQ-DAEMON-6); ensure_running's no-op-when-already-running short-circuit preserved.
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"

title = "Pump liveness: the peer pump writes a last-tick heartbeat consumed by daemon status / subnet status (decision 23 render legs in REQ-CLI-2/REQ-SUBNET-8); the daemon supervises the pump task — a panic is caught, logged loudly, and the pump restarts with capped backoff (≤5 min), so a 5.9-class death self-heals visibly instead of silently halving the daemon (M8 decision 23; field motivation: hfenduleam 2026-06-07 half-death)"

+# ── Daemon lifecycle UX: service-aware start/stop + foreground-consistent run (DAEMON-LIFECYCLE-PLAN.md, 2026-06-08 kitsubito restart-loop) ──

+# Origin: a manual `spt daemon run` fought the systemd spt-daemon user service over the broker socket (auto-restart-fail loop). Windows decision: keep the at-logon scheduled task for boot only (NOT a start/stop-controllable service); start=detached spawn, stop=IPC. Detection source of truth: Linux = systemd user unit FILE presence at the canonical XDG path (cheap, no subprocess; control ops shell to `systemctl --user`).

+title = "Service-aware `daemon start`/`stop`: when an OS service manager has a registered spt-daemon for this user, `spt daemon start` and `spt daemon stop` drive THAT service (so stop doesn't IPC-kill a unit that auto-restart-fights for the broker socket — the kitsubito 2026-06-08 loop). `start` graduates from a `run` alias to a first-class background verb (ensure-up, idempotent, non-blocking); stop routes managed→manager, manual→IPC. Linux=systemd user unit (`systemctl --user start|stop|is-active spt-daemon`, detected by unit-file presence); Windows=no controllable manager (the logon task is boot-only), so start=detached spawn / stop=IPC."

+required_stages = ["impl", "unit"]  # activate at daemon-lifecycle execution. impl = service.rs DaemonService trait (SystemdUserService detect-by-unit-file + systemctl control; Windows none-controllable) + pure plan_start/plan_stop routers + start_daemon/stop_daemon outcome APIs wired into cmd_daemon_start/cmd_daemon_stop; DaemonCmd::Start split from Run (alias removed). unit = plan_start/plan_stop truth tables (running×detected, running×detected×active) + `daemon start` parses as its own verb + systemd unit-path derivation. int (systemd rig) deferred to fleet rollout follow-up (rule 5).

+title = "`daemon run` is foreground-consistent on every platform: the invoking process IS the daemon, blocks until signalled, never auto-detaches or respawns into an invisible background task. The detached/de-elevated background behavior lives ONLY in `start`. Windows: an ELEVATED `daemon run` refuses with guidance (use `start`, or an unelevated shell) instead of respawning detached/de-elevated and vanishing (KH 5.7 preserved — it still never serves elevated)."

+required_stages = ["impl", "unit"]  # impl = cmd_daemon_run guards: Windows elevated→refuse (exit 2) before Daemon::run() (the hazard-tagged in-process respawn stays as untouched defense-in-depth, now unreached via the CLI); a conflict warning when a managed service is already active before binding inline. Daemon::run() foreground loop itself unchanged. unit = the pure run-mode decision (elevated×windows→refuse vs foreground) + `daemon run`/`daemon start` no longer share an alias.

+title = "Internal auto-start prefers the service: `ensure_running` (any spt command's implicit daemon start, REQ-DAEMON-3) routes through the service-aware start path — when a manager has a registered service it starts THAT, never a competing manual `spawn_detached` daemon that would fight the service for the socket."

+required_stages = ["impl", "unit"]  # impl = ensure_running consults service::platform_service(): detected→service.start() else spawn_detached(), then the existing bounded bind-wait. unit = the plan_start router already proves the detected→ServiceStart vs ManualSpawn branch (shared with REQ-DAEMON-6); ensure_running's no-op-when-already-running short-circuit preserved.

title = "Advertisement-epoch reset strands a node: peers' higher last-seen epoch drops the reset node's fresh advertisements as Stale until the counter outruns history. Common case (full reinstall/re-pair) is mitigated by REQ-SUBNET-7's ceremony eviction (peer-side epoch memory dies with the deleted row — acceptance-verified); the residual narrow slice (epoch file lost, identity kept) is documented, guard deferred to a field hit (4.11)"

required_stages = []  # rule 5 + M8 decision 24: documented hazard (KNOWN-HAZARDS 4.11 at D5), guard waits for a field hit of the narrow slice; the re-pair eviction path is tested under REQ-SUBNET-7


REQs minted. Now check `lib.rs` exports + the `is_running`/socket helpers surface:


Bash

grep -n "pub mod\|pub use\|pub fn ensure_running\|pub fn is_running\|pub fn request_stop\|mod daemon\|mod deelevate\|mod service" crates/spt-daemon/src/lib.rs | head -50

spt-daemon public surface


106:pub mod access;
107:pub mod adapter_update;
108:pub mod applyhost;
109:pub mod attach;
110:pub mod brain;
111:pub mod broker;
112:pub mod codec;
113:pub mod config;
114:pub mod consent;
115:pub mod daemon;
116:pub mod deelevate;
117:pub mod digest;
118:pub mod digesthub;
119:pub mod dispatch;
120:pub mod effect;
121:pub mod endpoint;
122:pub mod frame;
123:pub mod grants;
124:pub mod lifecycle;
125:pub mod linkhost;
126:pub mod machineid;
127:pub mod msg;
128:pub mod nethost;
129:pub mod notif;
130:pub mod notifsync;
131:pub mod pairhost;
132:pub mod peerloop;
133:pub mod presence;
134:pub mod propagate;
135:pub mod psyrelay;
136:pub mod reconcile;
137:pub mod registryhost;
138:pub mod relay;
139:pub mod relcache;
140:pub mod release;
141:pub mod resthost;
142:pub mod resting;
143:pub mod seedmap;
144:pub mod seedproofx;
145:pub mod serveprobe;
146:pub mod shellchan;
147:pub mod shelldisc;
148:pub mod shellhost;
149:pub mod shellwake;
150:pub mod sync;
151:pub mod transport;
152:pub mod update;
153:pub mod wan;
154:pub mod xfer;
159:pub use access::{access_check, classify_inbound, note_outbound, AccessDecision, InboundClass};


grep -n "pub use daemon\|pub fn ensure_running\|pub fn is_running\|pub fn request_stop\|pub use deelevate\|is_elevated" crates/spt-daemon/src/lib.rs

daemon fn re-exports


170:pub use daemon::{ensure_running, is_running, request_stop, Daemon};


Baseline clear. Write `service.rs`: