TaskCreate

[{"content":"Mint REQ-HAZARD-LIVEHOST-BOOT-RACE in traceable-reqs.toml (registry-first, gater-owned)","status":"in_progress","priority":"high"},{"content":"Delegate v0.8.1 W/T planning + execution to todlando (owl brief: REQ + symptom + hypotheses + patch dir + test-seam)","status":"pending","priority":"high"},{"content":"Gate todlando's v0.8.1 work (code-read + real-daemon E2E green + traceable EXIT=0)","status":"pending","priority":"high"},{"content":"Drive v0.8.1 release (deployah: bump+tag+sign+publish, counter 17)","status":"pending","priority":"medium"},{"content":"Ping perri to re-run live-agent-Psyche proof on v0.8.1","status":"pending","priority":"medium"}]


<tool_use_error>InputValidationError: TaskCreate failed due to the following issues:
The required parameter `subject` is missing
The required parameter `description` is missing
An unexpected parameter `tasks` was provided

TaskCreate creates ONE task per call and has no `tasks` or `todos` parameter. Call TaskCreate once per task, passing `subject` (a brief title) and `description` (what needs to be done) as top-level string parameters.

This tool's schema was not sent to the API — it was not in the discovered-tool set derived from message history. Without the schema in your prompt, typed parameters (arrays, numbers, booleans) get emitted as strings and the client-side parser rejects them. Load the tool first: call ToolSearch with query "select:TaskCreate", then retry this call. For reference, this tool's input schema is: {"$schema":"https://json-schema.org/draft/2020-12/schema","type":"object","properties":{"subject":{"description":"A brief title for the task","type":"string"},"description":{"description":"What needs to be done","type":"string"},"activeForm":{"description":"Present continuous form shown in spinner when in_progress (e.g., \"Running tests\")","type":"string"},"metadata":{"description":"Arbitrary metadata to attach to the task","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["subject","description"],"additionalProperties":false}</tool_use_error>

InputValidationError: [
  {
    "expected": "string",
    "code": "invalid_type",
    "path": [
      "subject"
    ],
    "message": "Invalid input: expected string, received undefined"
  },
  {
    "expected": "string",
    "code": "invalid_type",
    "path": [
      "description"
    ],
    "message": "Invalid input: expected string, received undefined"
  },
  {
    "code": "unrecognized_keys",
    "keys": [
      "tasks"
    ],
    "path": [],
    "message": "Unrecognized key: \"tasks\""
  }
]


Bash

echo "OWL=$OWL"; echo "branch:"; git -C /c/Users/decid/Documents/projects/spt-core branch --show-current 2>/dev/null; echo "--- list ---"; "$OWL" list 2>&1 | head -40

Check OWL env, branch, agent roster


Command running in background with ID: b2uc94eiw. Output is being written to: C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\13c96a50-2b78-4c16-a995-1da2b1836fd8\tasks\b2uc94eiw.output. You will be notified when it completes. To check interim output, use Read on that file path.


<task-notification>
<task-id>b2uc94eiw</task-id>
<tool-use-id>toolu_018DeQ7UpszHFDy5iJTQroVc</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\13c96a50-2b78-4c16-a995-1da2b1836fd8\tasks\b2uc94eiw.output</output-file>
<status>completed</status>
<summary>Background command "Check OWL env, branch, agent roster" completed (exit code 0)</summary>
</task-notification>


Skipping the formal task tool (schema not loaded; I'll track inline). Minting the REQ now.


Edit

[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"

# --- Added 2026-06-16 (v0.8.1): perri's v0.8.0 dogfood surfaced a harness-hosted live agent that goes ONLINE but never gets a Psyche on a net-less/unpaired node (boot-race); rule 3 register-before-satisfy, rule 5 activate per wave as evidence lands ---
[[requirements]]
id = "REQ-HAZARD-LIVEHOST-BOOT-RACE"
title = "The brain's live-host reconcile runs net-INDEPENDENTLY and survives the net-less boot-race: a harness-hosted live agent that is state=live_agent + status=online with a resolvable adapter manifest (psyche_init present) MUST get a daemon-hosted Psyche (LIVEHOST_PSYCHE) on a net-less / unpaired / peer-pump-STALLED node — the Psyche lifecycle needs no net/pump, so whatever upstream net-attach starves the brain from reaching livehost::spawn_live_host (brainproc.rs:230) must self-heal exactly as the dispatcher/peer-pump boot-race self-heal does (REQ-DAEMON-9). Silent-failure was masked because state=live_agent + alive=true are stamped UNCONDITIONALLY, independent of the psyche-host online-stamp (perri's diagnostic: host-predicate fully passes — state/status/adapter/psyche_init all resolve — yet reconcile_once never produced a Psyche). The establish→online ints exercise an IN-PROCESS daemon and the real-brain test hand-writes the online perch, so the production broker→brain-child→run_brain→spawn_live_host→reconcile_once boot path was NEVER E2E-exercised end-to-end; closing the test seam (a REAL detached daemon: api seed + listen a live agent on a net-less/unpaired node → assert LIVEHOST_PSYCHE) is part of this hazard."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.1 work pending); todlando activates [impl,unit,int] per wave as the net-independent reconcile fix + the real-detached-daemon E2E land
[[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)

# --- Added 2026-06-16 (v0.8.1): perri's v0.8.0 dogfood surfaced a harness-hosted live agent that goes ONLINE but never gets a Psyche on a net-less/unpaired node (boot-race); rule 3 register-before-satisfy, rule 5 activate per wave as evidence lands ---
[[requirements]]
id = "REQ-HAZARD-LIVEHOST-BOOT-RACE"
title = "The brain's live-host reconcile runs net-INDEPENDENTLY and survives the net-less boot-race: a harness-hosted live agent that is state=live_agent + status=online with a resolvable adapter manifest (psyche_init present) MUST get a daemon-hosted Psyche (LIVEHOST_PSYCHE) on a net-less / unpaired / peer-pump-STALLED node — the Psyche lifecycle needs no net/pump, so whatever upstream net-attach starves the brain from reaching livehost::spawn_live_host (brainproc.rs:230) must self-heal exactly as the dispatcher/peer-pump boot-race self-heal does (REQ-DAEMON-9). Silent-failure was masked because state=live_agent + alive=true are stamped UNCONDITIONALLY, independent of the psyche-host online-stamp (perri's diagnostic: host-predicate fully passes — state/status/adapter/psyche_init all resolve — yet reconcile_once never produced a Psyche). The establish→online ints exercise an IN-PROCESS daemon and the real-brain test hand-writes the online perch, so the production broker→brain-child→run_brain→spawn_live_host→reconcile_once boot path was NEVER E2E-exercised end-to-end; closing the test seam (a REAL detached daemon: api seed + listen a live agent on a net-less/unpaired node → assert LIVEHOST_PSYCHE) is part of this hazard."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.1 work pending); todlando activates [impl,unit,int] per wave as the net-independent reconcile fix + the real-detached-daemon E2E land
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"

title = "Net-bind boot-race resilience: a daemon that comes up net-less (NetHost::start failed — e.g. the systemd unit autostarted before the network/DNS stack was ready, `Failed to create an address lookup service`) must SELF-HEAL — retry the net bring-up in the background with capped backoff and, on success, attach net to the broker + spawn the dispatcher/peer-pump (which today are gated on `net_up` at boot and so never start, leaving the node silently unreachable until a manual restart — kitsubito 2026-06-08). Status surfaces the net-less state honestly (a net-less broker renders as 'no connection', not only a pump-STALLED line with a bogus pre-boot heartbeat age). The installer's autostart unit waits for the network (`Wants=/After=network-online.target`) as belt-and-suspenders."

required_stages = ["impl", "unit"]  # activate v0.3.1. impl = Broker.net → OnceLock<NetHost> + attach_net(); Daemon::run factors spawn_net_consumers(), fast-path attaches at boot, slow-path spawns a background NetHost::start retry supervisor (capped backoff) that attaches + spawns consumers on success (NET_BIND_RETRY/NET_ATTACHED logs); cmd_daemon_status renders net-less as 'no connection' + treats a heartbeat older than daemon start as 'no tick yet' not a giant age; install.sh unit gains network-online ordering. unit = backoff ladder caps + OnceLock single-attach (consumers spawn exactly once) + the status net-less/heartbeat-age render classifier.

+# --- Added 2026-06-16 (v0.8.1): perri's v0.8.0 dogfood surfaced a harness-hosted live agent that goes ONLINE but never gets a Psyche on a net-less/unpaired node (boot-race); rule 3 register-before-satisfy, rule 5 activate per wave as evidence lands ---

+title = "The brain's live-host reconcile runs net-INDEPENDENTLY and survives the net-less boot-race: a harness-hosted live agent that is state=live_agent + status=online with a resolvable adapter manifest (psyche_init present) MUST get a daemon-hosted Psyche (LIVEHOST_PSYCHE) on a net-less / unpaired / peer-pump-STALLED node — the Psyche lifecycle needs no net/pump, so whatever upstream net-attach starves the brain from reaching livehost::spawn_live_host (brainproc.rs:230) must self-heal exactly as the dispatcher/peer-pump boot-race self-heal does (REQ-DAEMON-9). Silent-failure was masked because state=live_agent + alive=true are stamped UNCONDITIONALLY, independent of the psyche-host online-stamp (perri's diagnostic: host-predicate fully passes — state/status/adapter/psyche_init all resolve — yet reconcile_once never produced a Psyche). The establish→online ints exercise an IN-PROCESS daemon and the real-brain test hand-writes the online perch, so the production broker→brain-child→run_brain→spawn_live_host→reconcile_once boot path was NEVER E2E-exercised end-to-end; closing the test seam (a REAL detached daemon: api seed + listen a live agent on a net-less/unpaired node → assert LIVEHOST_PSYCHE) is part of this hazard."

+required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.1 work pending); todlando activates [impl,unit,int] per wave as the net-independent reconcile fix + the real-detached-daemon E2E land

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


Prompt:
<task-notification>
<task-id>b2uc94eiw</task-id>
<tool-use-id>toolu_018DeQ7UpszHFDy5iJTQroVc</tool-use-id>
<output-file>C:\Users\decid\AppData\Local\Temp\claude\C--Users-decid-Documents-projects-spt-core\13c96a50-2b78-4c16-a995-1da2b1836fd8\tasks\b2uc94eiw.output</output-file>
<status>completed</status>
<summary>Background command "Check OWL env, branch, agent roster" completed (exit code 0)</summary>
</task-notification>


1	OWL=C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe
2	branch:
3	m11-shell-substrate
4	--- list ---
5	⚠ (2 orphan directories present — run `owl doctor --fix` to GC)
6	✓ ACTIVE:deployah
7	✓   - ACTIVE:deployah-psyche
8	✓ ACTIVE:doyle
9	✓   - ACTIVE:doyle-psyche
10	✓ ACTIVE:perri
11	✓   - ACTIVE:perri-psyche
12	✓ ACTIVE:solar
13	✓   - ACTIVE:solar-psyche
14	✓ ACTIVE:todlando
15	✓   - ACTIVE:todlando-psyche
16

OWL=C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe
branch:
m11-shell-substrate
--- list ---
⚠ (2 orphan directories present — run `owl doctor --fix` to GC)
✓ ACTIVE:deployah
✓   - ACTIVE:deployah-psyche
✓ ACTIVE:doyle
✓   - ACTIVE:doyle-psyche
✓ ACTIVE:perri
✓   - ACTIVE:perri-psyche
✓ ACTIVE:solar
✓   - ACTIVE:solar-psyche
✓ ACTIVE:todlando
✓   - ACTIVE:todlando-psyche


todlando ACTIVE (executor), deployah ACTIVE (release), perri ACTIVE. I'm doyle on `m11-shell-substrate`. v0.8.1 is a patch off shipped v0.8.0 — needs a fresh branch off `main`. Checking main state, then I'll land the REQ mint on a clean v0.8.1 branch as the registry-first base for todlando.


Bash

cd /c/Users/decid/Documents/projects/spt-core
echo "=== current edit ==="; git status --short
echo "=== main vs origin/main ==="; git log -1 --oneline main 2>/dev/null; git log -1 --oneline origin/main 2>/dev/null
echo "=== latest tag ==="; git tag --sort=-creatordate | head -3
echo "=== main contains v0.8.0? ==="; git tag --merged main 2>/dev/null | grep -i 0.8 || echo "(no 0.8 tag on main)"
echo "=== version in workspace Cargo.toml ==="; grep -m1 '^version' Cargo.toml 2>/dev/null || grep -m1 'version' Cargo.toml | head -1

Inspect main, tags, version for v0.8.1 branch base


=== current edit ===
 M traceable-reqs.toml
=== main vs origin/main ===
70294ef release: v0.8.0 changelog vet (doyle) — correct how-to bullets + polish
70294ef release: v0.8.0 changelog vet (doyle) — correct how-to bullets + polish
=== latest tag ===
v0.8.0
v0.7.3
v0.7.2
=== main contains v0.8.0? ===
v0.8.0
=== version in workspace Cargo.toml ===
version = "0.7.3"


main = v0.8.0 (70294ef). Branch `m11-shell-substrate` has post-v0.8.0 docs commits not in main. v0.8.1 must branch off main. My REQ edit was against the branch's file — I'll redo it cleanly off main to avoid drift.


cd /c/Users/decid/Documents/projects/spt-core
git checkout -- traceable-reqs.toml
git checkout -b v0.8.1-livehost-bootrace main 2>&1 | tail -3
echo "=== on branch ==="; git branch --show-current
echo "=== anchor present in main's toml? ==="; grep -n 'REQ-HAZARD-EPOCH-RESET' traceable-reqs.toml | head -1

Discard edit, branch v0.8.1 off main, verify anchor


Switched to a new branch 'v0.8.1-livehost-bootrace'
=== on branch ===
v0.8.1-livehost-bootrace
=== anchor present in main's toml? ===
872:# per decision 19), REQ-HAZARD-EPOCH-RESET stays inactive by decision 24.) ---


Grep

id = "REQ-HAZARD-EPOCH-RESET"|id = "REQ-DAEMON-9"


945:id = "REQ-DAEMON-9"
949:id = "REQ-HAZARD-EPOCH-RESET"


947	required_stages = ["impl", "unit"]  # activate v0.3.1. impl = Broker.net → OnceLock<NetHost> + attach_net(); Daemon::run factors spawn_net_consumers(), fast-path attaches at boot, slow-path spawns a background NetHost::start retry supervisor (capped backoff) that attaches + spawns consumers on success (NET_BIND_RETRY/NET_ATTACHED logs); cmd_daemon_status renders net-less as 'no connection' + treats a heartbeat older than daemon start as 'no tick yet' not a giant age; install.sh unit gains network-online ordering. unit = backoff ladder caps + OnceLock single-attach (consumers spawn exactly once) + the status net-less/heartbeat-age render classifier.
948	[[requirements]]
949	id = "REQ-HAZARD-EPOCH-RESET"

required_stages = ["impl", "unit"]  # activate v0.3.1. impl = Broker.net → OnceLock<NetHost> + attach_net(); Daemon::run factors spawn_net_consumers(), fast-path attaches at boot, slow-path spawns a background NetHost::start retry supervisor (capped backoff) that attaches + spawns consumers on success (NET_BIND_RETRY/NET_ATTACHED logs); cmd_daemon_status renders net-less as 'no connection' + treats a heartbeat older than daemon start as 'no tick yet' not a giant age; install.sh unit gains network-online ordering. unit = backoff ladder caps + OnceLock single-attach (consumers spawn exactly once) + the status net-less/heartbeat-age render classifier.
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"


Anchor confirmed (line 948-949). Re-applying the REQ mint on main's file.


Edit

[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"

# --- Added 2026-06-16 (v0.8.1): perri's v0.8.0 dogfood surfaced a harness-hosted live agent that goes ONLINE but never gets a Psyche on a net-less/unpaired node (boot-race); rule 3 register-before-satisfy, rule 5 activate per wave as evidence lands ---
[[requirements]]
id = "REQ-HAZARD-LIVEHOST-BOOT-RACE"
title = "The brain's live-host reconcile runs net-INDEPENDENTLY and survives the net-less boot-race: a harness-hosted live agent that is state=live_agent + status=online with a resolvable adapter manifest (psyche_init present) MUST get a daemon-hosted Psyche (LIVEHOST_PSYCHE) on a net-less / unpaired / peer-pump-STALLED node — the Psyche lifecycle needs no net/pump, so whatever upstream net-attach starves the brain from reaching livehost::spawn_live_host (brainproc.rs:230) must self-heal exactly as the dispatcher/peer-pump boot-race self-heal does (REQ-DAEMON-9). Silent-failure was masked because state=live_agent + alive=true are stamped UNCONDITIONALLY, independent of the psyche-host online-stamp (perri's diagnostic: host-predicate fully passes — state/status/adapter/psyche_init all resolve — yet reconcile_once never produced a Psyche). The establish→online ints exercise an IN-PROCESS daemon and the real-brain test hand-writes the online perch, so the production broker→brain-child→run_brain→spawn_live_host→reconcile_once boot path was NEVER E2E-exercised end-to-end; closing the test seam (a REAL detached daemon: api seed + listen a live agent on a net-less/unpaired node → assert LIVEHOST_PSYCHE) is part of this hazard."
required_stages = []  # rule 5: minted inactive 2026-06-16 (v0.8.1 work pending); todlando activates [impl,unit,int] per wave as the net-independent reconcile fix + the real-detached-daemon E2E land
[[requirements]]
id = "REQ-HAZARD-EPOCH-RESET"


Verifying traceable parses, then committing the registry-first base.


Bash

cd /c/Users/decid/Documents/projects/spt-core
# verify traceable parses + check passes (required_stages=[] → trivially covered)
traceable-reqs check 2>&1 | tail -8 || echo "EXIT=$?"

Run traceable-reqs check


  [OK] REQ-UPD-7  required: [impl, unit]  stages: -doc +impl +unit -int
       Origin-source update bootstrap (`spt update fetch`): pull the latest signed release directly from the GitHub release origin (`SaberMage/spt-releases`) — the per-platform artifact + its `<asset>.release.json` SignedRelease metadata — and stage it through the EXISTING verify→stage pipeline (the same `plan_verified` gate: two-key signature + channel + monotonic rollback floor + SHA-256), after which the normal consent-notif / `spt update apply` flow is unchanged. Closes the peer-only-discovery gap (REQ-UPD-1): a first-in-fleet / isolated node can update with no peer to pull from. The signed-release anchor keeps the GitHub transport untrusted-but-verified.
  [OK] REQ-UPD-8  required: [impl, unit]  stages: -doc +impl +unit -int
       Platform-safe `spt update fetch` + apply platform-guard (v0.3.1 cross-OS brick fix): `spt update fetch` stages the signed multi-platform `SignedUpdateSet` (`update-set.json` + every platform artifact it names), never a platform-blind single `SignedRelease`, so local apply selects `current_platform()` and P2P re-serve lets each peer select ITS own platform. Defense-in-depth: `apply_staged` REFUSES a staged single-release artifact unless it is platform-stamped for THIS node (an unstamped pre-v0.3.2 single, or a single stamped for another OS, fail-safe refuses — the guard that alone prevents the v0.3.1 brick where a Linux ELF was applied as `spt.exe`). UX: a friendly post-apply message (`Updated spt-core to vX.Y.Z.` + changelog URL) driven by an additive `product_version` metadata field, with a release-counter fallback when absent.
  [OK] REQ-UPD-9  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `gh_release` adapter [update] avenue (optional signing): an adapter declares `[update] avenue = "gh_release", repo = "user/repo"` (+ optional `asset`, default `adapter.spt`; + optional Ed25519 `signing_key`); spt-core's ripple compares the repo's LATEST GitHub release version against the installed adapter version and, when newer, auto-updates by fetching the release `.spt` archive (the REQ-INSTALL-9 `--release` fetch primitive) → verifies the `.spt` against `signing_key` if declared, else HTTPS+GitHub first-acquisition trust → re-extracts + re-registers the adapter root. Lets a harness adapter ship updates from its own GitHub releases with NO signing tooling or plugin coupling (removes the perri file_pull/delegated avenue blockers). Acquisition-trust mirrors `--release` + the installer first-fetch; does not alter spt-core self-update (REQ-UPD-1..8).
  [OK] REQ-WHOAMI-1  required: [doc, impl, unit]  stages: +doc +impl +unit -int
       `spt whoami` is a thin ALIAS for `spt endpoint list` (full output: the SELF pin + the subnet roster) — the standalone bare-id command is dropped (the `id=$(spt whoami)` capture was never a real pattern: env vars don't persist between agent tool calls). The one new render: the `endpoint list` SELF pin carries the Self endpoint's authored `endpoint description` (info::read_info(...).resources) when present, inline after the liveness state. whoami stays a top-level hot-path verb (parse unchanged, REQ-MSG-9).


cd /c/Users/decid/Documents/projects/spt-core
traceable-reqs check >/dev/null 2>&1; echo "traceable EXIT=$?"
git add traceable-reqs.toml
git commit -q -F - <<'EOF'
v0.8.1 base: mint REQ-HAZARD-LIVEHOST-BOOT-RACE (registry-first)

perri's v0.8.0 dogfood surfaced a real spt-core defect: a harness-hosted
live agent (api seed + api listen) goes ONLINE but never gets a Psyche on a
net-less/unpaired/peer-pump-STALLED node. Host-predicate fully passes
(state=live_agent, status=online, adapter resolvable, psyche_init present)
yet reconcile_once never produces LIVEHOST_PSYCHE.

Mint the hazard now (rule 3: register before satisfying) as the v0.8.1 base.
Inactive required_stages — the executor activates [impl,unit,int] per wave as
the net-independent reconcile fix + the real-detached-daemon E2E land.

Co-authored by: doyle
EOF
echo "=== committed ==="; git log -1 --oneline

Commit REQ mint on v0.8.1 branch


traceable EXIT=0
=== committed ===
2aab895 v0.8.1 base: mint REQ-HAZARD-LIVEHOST-BOOT-RACE (registry-first)


REQ minted + committed (2aab895), traceable EXIT=0. Pushing the base branch so todlando can build from it, then delegating.


cd /c/Users/decid/Documents/projects/spt-core
git push -u origin v0.8.1-livehost-bootrace 2>&1 | tail -4

Push v0.8.1 base branch


remote:
branch 'v0.8.1-livehost-bootrace' set up to track 'origin/v0.8.1-livehost-bootrace'.
To https://github.com/SaberMage/spt-core.git
 * [new branch]      v0.8.1-livehost-bootrace -> v0.8.1-livehost-bootrace


Base pushed. REQ title carries the full diagnostic (truncation-robust). Sending todlando a focused delegation brief that points at the authoritative REQ + files + hypotheses + the non-negotiable test seam.


cd /c/Users/decid/Documents/projects/spt-core
OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"
"$OWL" send todlando doyle <<'EOF'
DELEGATION — build v0.8.1 (livehost boot-race). You own W/T planning + execution; I gate + drive release. Operator-directed.

BASE: branch v0.8.1-livehost-bootrace (off main = v0.8.0), commit 2aab895 already mints the REQ. `git fetch && git checkout v0.8.1-livehost-bootrace`. Build the fix here; PR → ping me (doyle) to gate.

REQ: REQ-HAZARD-LIVEHOST-BOOT-RACE in traceable-reqs.toml — its TITLE is the full diagnostic, read it first. Minted required_stages=[]; YOU activate [impl,unit,int] per wave as evidence lands (same-commit tags).

DEFECT (perri's v0.8.0 dogfood, real spt-core bug): a harness-hosted live agent (`api seed`+`api listen`) goes ONLINE but never gets a Psyche on a net-less/unpaired/peer-pump-STALLED node. Host-predicate FULLY passes (state=live_agent, status=online, adapter=claude-spt:live resolvable, psyche_init present) yet reconcile_once never emits LIVEHOST_PSYCHE. Correlate: "peer pump: STALLED" reappears after every restart = the net-less boot-race; the live-host reconcile shares the brain boot lifecycle and is starved.

KEY FILES: crates/spt-daemon/src/livehost.rs (reconcile_once / host_one / spawn_live_host) · brainproc.rs (run_brain :159 → connect_retry → spawn_live_host UNCONDITIONAL @ :230, before heartbeat) · broker.rs (bind_in_with_net @575 binds the IPC listener INDEPENDENT of net, attach_net + REQ-DAEMON-9 self-heal).

HYPOTHESES (ground in source+runtime, do NOT presume — perri already refuted doyle's first guess cleanly): H1 brain reaches :230 but reconcile starved by an upstream net-attach gate; H2 spawn_psyche FAILS silently — check LIVEHOST_PSYCHE_FAIL log; may be REQ-INSTALL-11 install-dir binary resolution (the psyche binary not found) — perri's blocked proof; H3 real detached `api listen` doesn't actually stamp status=online OR the brain child isn't spawned at all in daemon-run mode; H4 the real broker→brain-child boot path diverges from the in-process test harness.

MANDATE (3 things): (1) root-cause the net-less starvation AND the persistent peer-pump STALL on a single UNPAIRED Windows node (perri's REQ-DAEMON-9 self-heal isn't firing — may be same root). (2) FIX: live-host reconcile runs net-INDEPENDENTLY + survives the boot-race (mirror the dispatcher/peer-pump self-heal). (3) NON-NEGOTIABLE TEST SEAM: a REAL detached-daemon E2E — api seed + listen a live agent on a net-less/unpaired node → assert LIVEHOST_PSYCHE. The existing establish→online ints use an IN-PROCESS daemon and the real-brain test hand-writes the perch, so the production broker→brain-child→run_brain→spawn_live_host→reconcile path was NEVER exercised. The test MUST spawn the real brain child or it doesn't catch the bug.

WHY IT MATTERS: this unblocks perri's last two parity proofs — REQ-INSTALL-11 install-dir binary resolution (the psyche must actually SPAWN to exercise it) + the F-006 interim PATH-copy. Confirm install-dir resolution is exercised once the Psyche spawns.

GATE CRITERIA (pre-flight before pinging me): clippy --workspace --all-targets -D warnings (exact CI invocation, not -p) · traceable-reqs check EXIT=0 · docs-drift gen if any CLI touched · the real-daemon E2E green. Then I code-read + verify the E2E + gate → deployah ships v0.8.1 (counter 17) → I ping perri to re-validate.

Plan your own waves/tasks. Self-drive. Ping doyle at PR-ready.
EOF
echo "send rc=$?"

Send v0.8.1 delegation brief to todlando


➜ SENT:todlando
send rc=0


Brief sent to todlando (self-drives; will ping at PR-ready). My poll listener delivers that event — no need to poll. Updating my Psyche so a future resume knows v0.8.1 is delegated-and-in-flight.


Write

DELTA — v0.8.1 livehost boot-race DELEGATED to todlando (operator-directed, 2026-06-16).

STATE CHANGE since resume:
- Grounded the defect in source. Broker IPC listener binds INDEPENDENT of net (broker.rs:575, degrades net-less, REQ-DAEMON-9 self-heals net in bg) → brain connect_retry should succeed net-less → spawn_live_host (brainproc.rs:230) IS unconditional + pre-heartbeat. So the real gap = the production broker→brain-child→run_brain→spawn_live_host→reconcile_once boot path was NEVER E2E-exercised (establish→online ints use IN-PROCESS daemon; the real-brain test HAND-WRITES the perch, bypassing brain-boot). Bug lives in that untested path.
- MINTED REQ-HAZARD-LIVEHOST-BOOT-RACE (required_stages=[], rule-3 register-first). Its TITLE carries the full diagnostic. Committed @2aab895 on NEW branch v0.8.1-livehost-bootrace (off main=v0.8.0), pushed. traceable EXIT=0.
- DELEGATED to todlando via owl: full brief (REQ + symptom + 4 hypotheses + 3-part mandate + NON-NEGOTIABLE real-detached-daemon E2E + gate criteria). todlando owns W/T planning + execution, self-drives, pings doyle at PR-ready.

MY NEXT (when todlando pings PR-ready): code-read + verify the real-daemon E2E actually spawns the brain child + traceable EXIT=0 → GATE → deployah ships v0.8.1 (counter 17) → ping perri to re-validate. Operator: "see it through to release publish, inform perri when done."

WHY IT MATTERS: unblocks perri's last two parity proofs (REQ-INSTALL-11 install-dir binary resolution — psyche must SPAWN to exercise it — + F-006 PATH-copy). v0.8.1 closes the last live-agent-Psyche gap from perri's dogfood.

HOLDING for todlando's PR-ready ping. Listener up (survived /clear).


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)

DELTA — v0.8.1 livehost boot-race DELEGATED to todlando (operator-directed, 2026-06-16).

STATE CHANGE since resume:
- Grounded the defect in source. Broker IPC listener binds INDEPENDENT of net (broker.rs:575, degrades net-less, REQ-DAEMON-9 self-heals net in bg) → brain connect_retry should succeed net-less → spawn_live_host (brainproc.rs:230) IS unconditional + pre-heartbeat. So the real gap = the production broker→brain-child→run_brain→spawn_live_host→reconcile_once boot path was NEVER E2E-exercised (establish→online ints use IN-PROCESS daemon; the real-brain test HAND-WRITES the perch, bypassing brain-boot). Bug lives in that untested path.
- MINTED REQ-HAZARD-LIVEHOST-BOOT-RACE (required_stages=[], rule-3 register-first). Its TITLE carries the full diagnostic. Committed @2aab895 on NEW branch v0.8.1-livehost-bootrace (off main=v0.8.0), pushed. traceable EXIT=0.
- DELEGATED to todlando via owl: full brief (REQ + symptom + 4 hypotheses + 3-part mandate + NON-NEGOTIABLE real-detached-daemon E2E + gate criteria). todlando owns W/T planning + execution, self-drives, pings doyle at PR-ready.

MY NEXT (when todlando pings PR-ready): code-read + verify the real-daemon E2E actually spawns the brain child + traceable EXIT=0 → GATE → deployah ships v0.8.1 (counter 17) → ping perri to re-validate. Operator: "see it through to release publish, inform perri when done."

WHY IT MATTERS: unblocks perri's last two parity proofs (REQ-INSTALL-11 install-dir binary resolution — psyche must SPAWN to exercise it — + F-006 PATH-copy). v0.8.1 closes the last live-agent-Psyche gap from perri's dogfood.

HOLDING for todlando's PR-ready ping. Listener up (survived /clear).


v0.8.1 launched. Status:

**Done:**
- **Grounded defect** in source. Broker IPC listener binds independent of net (broker.rs:575) → brain connect succeeds net-less → `spawn_live_host` (brainproc.rs:230) is unconditional + pre-heartbeat. Real gap = production `broker→brain-child→run_brain→spawn_live_host→reconcile_once` boot path **never E2E-tested** (existing ints use in-process daemon; real-brain test hand-writes the perch, bypassing brain-boot). Bug lives there.
- **Minted** `REQ-HAZARD-LIVEHOST-BOOT-RACE` (rule-3 register-first, title = full diagnostic). Committed `2aab895` on new branch `v0.8.1-livehost-bootrace` (off main=v0.8.0), pushed. traceable EXIT=0.
- **Delegated to todlando** — owns W/T planning + execution. Brief: REQ + symptom + 4 hypotheses + 3-part mandate (root-cause net-less starvation + pump-stall · net-independent reconcile fix · **non-negotiable real-detached-daemon E2E**) + gate criteria. Self-drives, pings doyle at PR-ready.
- **Psyche updated** (commune dropped).

**Roles:** todlando builds → doyle gates (code-read + verify real-daemon E2E spawns brain child + traceable EXIT=0) → deployah ships v0.8.1 (counter 17) → doyle pings perri to re-validate.

**Now:** holding for todlando's PR-ready ping. Poll listener up (survived /clear) — delivers it as EVENT, no polling needed. Unblocks perri's last two parity proofs (REQ-INSTALL-11 + F-006).


<task-notification>
<task-id>bg7cqpvni</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/3" id="a6a7ad6c" type="msg" from="todlando">todlando — v0.8.1 livehost boot-race delegation ACCEPTED. On branch v0.8.1-livehost-bootrace @2aab895; read the REQ-HAZARD-LIVEHOST-BOOT-RACE diagnostic. Root-cause investigation underway (tracing the broker→brain-child→run_brain→spawn_live_host→reconcile_once boot path + the REQ-DAEMON-9 self-heal pattern </EVENT-PART>
<EVENT-PART seq="2/3" id="a6a7ad6c">to mirror).

Your delegation msg truncated at part 8/11 — resend parts 9-11 (cut off mid-sentence at &quot;...the existing establish→online ints use an IN-PROCESS daemon and the real-brain test hand-writes the perch, so the production broke...&quot;). Likely the rest of the test-seam spec + PR/gate process + any constraints. Proceedi</EVENT-PART>
<EVENT-PART seq="3/3" id="a6a7ad6c">ng on the clear 3-part mandate meanwhile; will fold in the tail.</EVENT-PART></event>
</task-notification>


<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.


todlando ACCEPTED + root-causing. My brief truncated at part 8/11 (the ~9-part owl cap — known gotcha). Resending the tail (test-seam completion + why-it-matters + gate criteria).


Bash

cd /c/Users/decid/Documents/projects/spt-core
OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"
"$OWL" send todlando doyle <<'EOF'
TAIL (parts 9-11 of the delegation, truncated by owl part-cap):

...so the production broker→brain-child→run_brain→spawn_live_host→reconcile path was NEVER exercised. The real-daemon E2E MUST spawn the real brain child (not in-process) or it doesn't catch the bug. That E2E is the non-negotiable gate artifact.

WHY IT MATTERS: unblocks perri's last two parity proofs — REQ-INSTALL-11 install-dir binary resolution (the psyche must actually SPAWN to exercise it) + the F-006 interim PATH-copy. Confirm install-dir resolution is exercised once the Psyche spawns (H2 candidate: spawn_psyche may fail because the psyche binary isn't found — check LIVEHOST_PSYCHE_FAIL).

GATE CRITERIA (pre-flight before pinging me): clippy --workspace --all-targets -D warnings (exact CI invocation, NOT -p) · traceable-reqs check EXIT=0 · docs-drift gen if any CLI touched · the real-daemon E2E green. Then I code-read + verify the E2E spawns the brain child + gate → deployah ships v0.8.1 (counter 17) → I ping perri.

Plan your own waves/tasks, self-drive, ping doyle at PR-ready. That's the whole brief.
EOF
echo "send rc=$?"

Resend truncated tail to todlando


Tail resent (rc=0). todlando now has the full brief — root-causing on the 3-part mandate, will fold the tail in.

**Holding for PR-ready ping.** Listener up. Logged the owl part-cap truncation as the cause (>8 parts truncates tail — should've front-loaded or split; did split on todlando's flag).


<task-notification>
<task-id>bg7cqpvni</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/9" id="14e5082c" type="msg" from="todlando">todlando — v0.8.1 root-cause GROUNDED IN RUNTIME, and it REFRAMES the hazard. Built the non-negotiable gate artifact FIRST and used it to reproduce — bug did NOT reproduce as a net-race.

REAL-DAEMON E2E (built): crates/spt/tests/livehost_bootrace_e2e.rs — launches the REAL `spt daemon run` broker → rea</EVENT-PART>
<EVENT-PART seq="2/9" id="14e5082c">l `spt daemon brain` CHILD (process, not in-process), net-less forced (corrupt node.key → NODE_KEY_FAIL, no retry), REAL `api seed`+persistent `api listen` establish→online, REAL psyche binary by a BARE token present ONLY in the adapter install source_dir (so REQ-INSTALL-11 install-dir resolution is the ONLY way it resolves), bounded 25s. </EVENT-PART>
<EVENT-PART seq="3/9" id="14e5082c">It PASSES net-less: LIVEHOST_PSYCHE fires with net fully starved. Negative control (psyche binary removed) → LIVEHOST_PSYCHE_FAIL &quot;program not found&quot; per tick.

FINDINGS (runtime + source):
1. H1 net-gating REFUTED BY RUNTIME: spawn_live_host @brainproc:230 is unconditional, BEFORE the heartbeat net-gate (consumer_gate gat</EVENT-PART>
<EVENT-PART seq="4/9" id="14e5082c">es only dispatcher/peer-pump). Net-independence ALREADY HOLDS on this tree — no behavior fix needed; the E2E locks it in.
2. install-dir resolution CORRECT + CONSISTENT: github/release registration sets source_dir = adapters/_github/&lt;safe&gt;/ (cli.rs:5470,5487); livehost (rec.source_dir) AND the api seam (resolve_ctx_manifest mod.rs:5</EVENT-PART>
<EVENT-PART seq="5/9" id="14e5082c">01) use the SAME source_dir. No divergence. resolve_program_in_dir works (E2E proves). So REQ-INSTALL-11 resolution is sound when the binary is actually in the install dir.
3. THE REAL spt-core GAP = the SILENT-FAILURE MASKING your REQ title names: spawn_psyche Err → LIVEHOST_PSYCHE_FAIL is eprintln-ONLY on the brain's INVISIBLE stderr (b</EVENT-PART>
<EVENT-PART seq="6/9" id="14e5082c">rain inherits the broker/foreground `spt daemon run` stderr; a harness watching only `api listen` stdout never sees it). Agent stays live_agent+alive+online (stamped unconditionally, startup.rs:284) with NO Psyche, NO harness-visible error = perri's exact symptom.

SO the net-boot-race premise doesn't hold here (you flagged H2 — it's c</EVENT-PART>
<EVENT-PART seq="7/9" id="14e5082c">loser, but resolution itself is correct). perri's real failure is most likely her adapter's psyche binary NOT being in the install dir (packaging) OR not shipped — which spt-core currently SWALLOWS silently.

PROPOSED v0.8.1 SCOPE:
A. KEEP the real-daemon E2E as the gate artifact (re-tag [int-&gt;REQ-HAZARD-LIVEHOST-BOOT-RACE]).
</EVENT-PART>
<EVENT-PART seq="8/9" id="14e5082c">B. SURFACE the silent host-failure: write a disk-observable signal on the live perch when spawn_psyche fails, so &quot;online but un-hosted&quot; is diagnosable by the harness (the real fix for the masking).
C. net-ind
...(truncated)</event>
</task-notification>


todlando reframed the hazard via runtime repro — exactly the grounded method I mandated. Built the E2E first, reproduced: **net-race premise REFUTED** (spawn_live_host already net-independent @ :230, pre-heartbeat-gate). Real bug = **silent-failure masking**: `spawn_psyche` Err → `LIVEHOST_PSYCHE_FAIL` is eprintln-only on the brain's invisible stderr, agent stamped online+alive+live_agent unconditionally (startup.rs:284) → perri sees "online, no Psyche, no error." That matches my REQ title's masking clause; the net-race framing was the wrong half.

This is a scope reframe = gater call. Endorsing with refinements. Msg truncated at 8/9 again — need part 9. Ruling + request, front-loaded so essentials survive truncation.


cd /c/Users/decid/Documents/projects/spt-core
OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"
"$OWL" send todlando doyle <<'EOF'
GATER RULING — reframe ENDORSED. Excellent runtime root-cause (built the gate artifact first, reproduced, refuted the net-race by RUNTIME not argument — exactly right). The REQ title's masking clause already names this; net-race was the wrong half. v0.8.1 scope A/B/C APPROVED with 4 refinements:

1. The host-failure signal MUST be HARNESS-REACHABLE, not just a log. perri reads PERCH STATE, never the brain's stderr. Land it as an info.json field on the live perch (e.g. psyche_host_error / last_host_fail + reason) surfaced by `spt endpoint list`/status (or an api state verb). That's the actual fix for perri's blindness — a disk eprintln she can't see is no better.

2. Do NOT gate/de-stamp status=online on host-failure. The agent IS online (reachable for messages); the missing piece is only the Psyche lifecycle. Brain-restart rehydrate (CONTEXT:194) legitimately has online-with-no-Psyche-yet windows. So: a SEPARATE psyche-host-health field, online stays authoritative.

3. Idempotent/rate-limited: reconcile retries every 5s (LIVE_RECONCILE_INTERVAL_MS) → a permanently-missing binary would spam. Make it a CURRENT-STATE stamp (overwrite last_host_error + ts + attempt count), CLEARED on successful host — not an append log.

4. REQ TITLE delta: since net-independence already holds (E2E LOCKS it, no behavior change), the REQ's PRIMARY deliverable shifts to "surface the silent psyche-host failure on the perch (harness-diagnosable)", with net-independence as a locked-in invariant. I own the registry — propose your title wording and I'll commit it, or I'll redraft. Activate [impl,unit,int]; int = the E2E (positive net-less host + negative missing-binary→signal-written-on-perch).

BOUNDARY: spt-core SURFACES the failure; it does NOT fix perri's packaging (her psyche binary missing from the install dir is her adapter's job). Once surfaced, perri diagnoses + fixes her side.

DOCS: new perch field is observable behavior → CONTEXT note + docs-drift gen if any CLI render changes + tag doc evidence.

RESEND part 9/9 (truncated again at 8 — the owl part-cap; the tail of C + anything after). Then proceed. Ping doyle at PR-ready for gate.
EOF
echo "send rc=$?"

Send gater ruling + request part 9 to todlando


Ruling sent.

**Reframe (todlando, runtime-grounded):** net-race REFUTED — `spawn_live_host` already net-independent. Real bug = silent-failure masking: `spawn_psyche` failure logged only to brain's invisible stderr; agent stamped online+alive with no Psyche, no harness-visible error = perri's symptom. perri's actual trigger likely her psyche binary missing from the install dir, which spt-core **swallows silently**.

**My gater ruling — scope ENDORSED + 4 refinements:**
1. Failure signal must land on **perch state** (info.json field surfaced by `endpoint list`/status), not just a log — perri can't see brain stderr.
2. Don't de-stamp `online` (agent is still message-reachable; only the Psyche is missing) — separate psyche-host-health field.
3. Current-state stamp, cleared on success — not append-spam (reconcile retries every 5s).
4. REQ-title delta: primary deliverable shifts to "surface the silent host-failure"; net-independence = locked-in invariant. I own the registry edit.

**Boundary held:** spt-core surfaces the failure; perri's packaging is perri's job.

Holding for part 9/9 (truncated again — owl part-cap) + todlando's PR-ready ping. Listener up.


<task-notification>
<task-id>bg7cqpvni</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/6" id="2d05aa6c" type="msg" from="todlando">todlando — scope A/B/C + 4 refinements ACK, all crisp, building now. Two things:

PROPOSED REQ-HAZARD-LIVEHOST-BOOT-RACE TITLE (you commit/redraft — you own the registry):
&quot;The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPEN</EVENT-PART>
<EVENT-PART seq="2/6" id="2d05aa6c">DENTLY. When reconcile_once→host_one→spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host</EVENT-PART>
<EVENT-PART seq="3/6" id="2d05aa6c">) and surfaced by `spt endpoint list`/status — never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing — brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-ho</EVENT-PART>
<EVENT-PART seq="4/6" id="2d05aa6c">st-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker→brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFAC</EVENT-PART>
<EVENT-PART seq="5/6" id="2d05aa6c">ES the failure; the adapter owns fixing its packaging.&quot;

PART 9/9 truncated AGAIN at part 8 (owl part-cap) — resend the tail of refinement C + anything after. Proceeding on what I have (A/B/C + 4 refinements are complete enough to build).

Will ping you at PR-ready with the full gate (clippy workspace, traceable EXIT0, the E</EVENT-PART>
<EVENT-PART seq="6/6" id="2d05aa6c">2E positive+negative, docs-drift).</EVENT-PART></event>
</task-notification>


todlando's proposed title is excellent + accurate — approving verbatim. To avoid a branch race (todlando's actively editing traceable-reqs.toml for stage activation), todlando applies the approved title in-commit rather than me committing separately. And the recurring "part 9" truncation: the substance is actually complete on both sides — net-independence-locked-by-E2E is understood + agreed, nothing material lost. Telling todlando to stop chasing it. Short msg to dodge the cap.


cd /c/Users/decid/Documents/projects/spt-core
OWL="C:/Users/decid/.claude/plugins/cache/cplugs/spt/1.11.25/owl.exe"
"$OWL" send todlando doyle <<'EOF'
TITLE APPROVED verbatim — commit it yourself in your evidence commit alongside the [impl,unit,int] activation (you're already editing traceable-reqs.toml; me committing separately would race your branch). I've ratified the wording as registry owner; you apply it.

PART 9: stop chasing it — substance is COMPLETE on both sides. Scope A/B/C + 4 refinements is the whole agreement; your scope-C (net-independence locked-by-E2E, no behavior change) is understood + endorsed. Nothing material was in the lost tail. The persistent truncation is just the owl ~8-part cap biting long bodies (logged it as a standing gotcha).

You're cleared to build. Ping doyle at PR-ready with the full gate (clippy --workspace --all-targets -D warnings, traceable EXIT=0, the E2E positive+negative, docs-drift). I gate → deployah ships → I ping perri.
EOF
echo "send rc=$?"

Approve title, clear todlando to build


Title approved verbatim (todlando applies it in-commit with stage activation — no branch race). Cleared to build.

**v0.8.1 converged on a sharper fix than the resume premise:**
- **Not** a net-race (that invariant already holds, locked by the new real-daemon E2E).
- **Real fix:** surface the silent psyche-host failure on the perch (info.json current-state field → `endpoint list`/status), so a harness reading perch state can diagnose "online but un-hosted." Boundary: spt-core surfaces; adapter owns packaging.

**Scope locked:** A) keep real-detached-daemon E2E as gate artifact · B) write harness-reachable host-failure signal · C) net-independence as E2E-locked invariant. REQ retitled to match (primary deliverable = surface the failure).

todlando building now; pings doyle at PR-ready with full gate (clippy workspace, traceable EXIT=0, E2E positive+negative, docs-drift). Then I gate → deployah ships v0.8.1 (counter 17) → ping perri.

Holding. Listener up — todlando's ping wakes me, no polling.


<task-notification>
<task-id>bg7cqpvni</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/3" id="18bb7788" type="msg" from="todlando">todlando — ACK all three:
1. Title APPROVED verbatim — I'll apply it myself in the evidence commit (alongside the [impl,unit,int] activation in traceable-reqs.toml), so no branch race. It's on me to land the exact wording.
2. Part 9 — dropped, not chasing. A/B/C + 4 refinements = the whole agreement, unde</EVENT-PART>
<EVENT-PART seq="2/3" id="18bb7788">rstood. (owl 8-part cap noted as the standing gotcha.)
3. Cleared. Build in flight (info.json psyche-host-health field + host_one write-on-fail/clear-on-success, status untouched + authoritative + current-state-overwrite/cleared-on-host + endpoint-list surfacing + E2E positive/negative + traceable activation + CONTEXT note). I review the bu</EVENT-PART>
<EVENT-PART seq="3/3" id="18bb7788">ild + apply the title + run the full gate (clippy --workspace --all-targets -D warnings, traceable EXIT0, E2E pos+neg, docs-drift), THEN ping you PR-ready. Self-driving till then.</EVENT-PART></event>
</task-notification>


todlando ACK — self-driving, build in flight (info.json psyche-host-health field + host_one write-on-fail/clear-on-success, status untouched, endpoint-list surfacing, E2E pos/neg, traceable activation, CONTEXT note). Will self-review + apply title + run full gate, then ping PR-ready.

No action pending. Holding for the PR-ready ping — listener delivers it. Will gate on arrival → deployah ships v0.8.1 → ping perri.


<task-notification>
<task-id>bg7cqpvni</task-id>
Monitor event: "« spt event »"
<event><EVENT-PART seq="1/6" id="f9db3dcc" type="msg" from="todlando">todlando — v0.8.1 PR-READY: https://github.com/SaberMage/spt-core/pull/17 (branch v0.8.1-livehost-bootrace @28d6e70, base main). CI now running.

WHAT LANDED (scope A/B/C + your 4 refinements):
- info.json: additive N-1-safe `psyche_host_error {reason, ts, attempts}` via the existing locked RMW. CURRENT-ST</EVENT-PART>
<EVENT-PART seq="2/6" id="f9db3dcc">ATE: overwritten each 5s retry, CLEARED on successful host. (refinement 3)
- host_one: stamps on spawn_psyche Err, clears on Ok. status UNTOUCHED + authoritative — separate psyche-host-health field. (refinements 1+2)
- `spt endpoint list`/whoami surfaces it inline: `psyche-host: FAILED (&lt;reason&gt;; &lt;n&gt; attempt(s); &lt;ts&gt;)</EVENT-PART>
<EVENT-PART seq="3/6" id="f9db3dcc">` after the liveness line — HARNESS-REACHABLE (perch state, not brain stderr). (refinement 1)
- REQ title: applied your APPROVED-VERBATIM wording myself, in the evidence commit alongside the [impl,unit,int] activation (no branch race). (refinement 4)
- CONTEXT.md note; boundary respected (spt-core surfaces; perri owns packaging).
<b</EVENT-PART>
<EVENT-PART seq="4/6" id="f9db3dcc">r>GATE ARTIFACT (your non-negotiable — verified): TWO real detached-daemon E2Es, both spawn the REAL `spt daemon run` broker → supervised `spt daemon brain` CHILD → real api seed + listen, net-less:
- livehost_bootrace_e2e (positive): net-less → Psyche HOSTED, psyche_host_error absent.
- livehost_psyche_fail_e2e (negative): psych</EVENT-PART>
<EVENT-PART seq="5/6" id="f9db3dcc">e binary MISSING → perch gains psyche_host_error (non-empty reason, attempts&gt;=1), status stays online, no nested psyche perch.

FULL GATE GREEN LOCALLY (re-ran on my final tree): clippy --workspace --all-targets -D warnings clean · traceable EXIT0 (REQ-HAZARD-LIVEHOST-BOOT-RACE [impl,unit,int]) · both E2Es pass · units pass · xt</EVENT-PART>
<EVENT-PART seq="6/6" id="f9db3dcc">ask check OK (no CLI --help render change).

Over to you: code-read + verify the E2E spawns the brain CHILD + CI green → gate → deployah ships v0.8.1 (counter 17) → you ping perri. Holler if the gate finds anything.</EVENT-PART></event>
</task-notification>


PR #17 ready @28d6e70. Gating now — won't rubber-stamp. Fetch + code-read the diff, then independently verify the non-negotiable (E2E spawns the REAL brain child), traceable, and clippy. Running targeted (memory: full int-suite hangs preflight; named tests only).


cd /c/Users/decid/Documents/projects/spt-core
git fetch origin v0.8.1-livehost-bootrace 2>&1 | tail -1
git checkout v0.8.1-livehost-bootrace 2>&1 | tail -2
git pull --ff-only 2>&1 | tail -2
echo "=== HEAD ==="; git log -1 --oneline
echo "=== commits since main ==="; git log --oneline main..HEAD
echo "=== diffstat vs main ==="; git diff --stat main..HEAD

Fetch + checkout PR #17 branch, show commits + diffstat


 * branch            v0.8.1-livehost-bootrace -> FETCH_HEAD
Your branch is up to date with 'origin/v0.8.1-livehost-bootrace'.
Already on 'v0.8.1-livehost-bootrace'
Already up to date.
=== HEAD ===
28d6e70 v0.8.1: harness-reachable psyche-host-failure signal (REQ-HAZARD-LIVEHOST-BOOT-RACE)
=== commits since main ===
28d6e70 v0.8.1: harness-reachable psyche-host-failure signal (REQ-HAZARD-LIVEHOST-BOOT-RACE)
2aab895 v0.8.1 base: mint REQ-HAZARD-LIVEHOST-BOOT-RACE (registry-first)
=== diffstat vs main ===
 CONTEXT.md                                   |   2 +
 crates/spt-daemon/src/livehost.rs            |  21 +-
 crates/spt-store/src/info.rs                 | 137 +++++++++++
 crates/spt/src/cli.rs                        |  86 ++++++-
 crates/spt/tests/livehost_bootrace_e2e.rs    | 344 +++++++++++++++++++++++++++
 crates/spt/tests/livehost_psyche_fail_e2e.rs | 298 +++++++++++++++++++++++
 traceable-reqs.toml                          |   5 +
 7 files changed, 881 insertions(+), 12 deletions(-)


Clean 2-commit branch, sensible diffstat. Reading the gate-critical pieces: both E2Es (verify real brain-child spawn), the livehost stamp/clear, the info.rs field, and the traceable activation.


1	//! v0.8.1 boot-race REPRODUCER (REQ-DAEMON-1) — a harness-hosted live agent goes
2	//! `status=online` but the REAL daemon brain never hosts its Psyche on a net-less
3	//! / unpaired node.
4	//!
5	//! This is the full-stack production-path E2E that the existing analogs stop short
6	//! of (doyle's mandate):
7	//!
8	//!   - `daemon_lifecycle_real_brain.rs` runs `run_brain` on a THREAD, uses a NOOP
9	//!     `cmd /C rem` psyche (a shell builtin that always resolves — it never
10	//!     exercises install-dir binary resolution), and HAND-WRITES the online perch.
11	//!   - This test launches the REAL `spt daemon run` broker PROCESS (which spawns a
12	//!     real supervised `spt daemon brain` CHILD), drives the REAL `api seed` +
13	//!     `api listen` establish→online path, and uses a REAL psyche binary referenced
14	//!     by a BARE program token that lives ONLY in the adapter install dir — so the
15	//!     daemon-host site MUST resolve it via REQ-INSTALL-11 install-dir resolution,
16	//!     not PATH.
17	//!
18	//! The disk-observable proxy for "the brain hosted the Psyche" is the nested
19	//! `<id>-psyche` perch's `info.json` appearing (mirrors `daemon_lifecycle_real_brain.rs`).
20	//! On a buggy tree this never appears within the bounded deadline.
21	//!
22	//! Bounded by design: every captured `spt` call has a deadline and the whole host
23	//! wait is a 25s polling loop, so a reproduced failure is a clean assert, never a
24	//! hang. All spawned processes (broker + supervised brain + listen) are reaped.
25	// [int->REQ-DAEMON-1]
26
27	use std::path::{Path, PathBuf};
28	use std::process::{Child, Command, Output, Stdio};
29	use std::time::{Duration, Instant};
30
31	mod common;
32	use common::CommandNoWindowExt;
33
34	use spt_store::perch::{self, ParentHint};
35
36	/// Best-effort cross-platform kill of a pid tree (mirrors the other E2E helpers).
37	/// Scoped to a SINGLE pid we spawned — never a machine-wide reap (a live broker
38	/// shares this box).
39	fn kill_pid(pid: u32) {
40	    #[cfg(windows)]
41	    let _ = Command::new("taskkill")
42	        .no_window()
43	        .args(["/PID", &pid.to_string(), "/F", "/T"])
44	        .output();
45	    #[cfg(unix)]
46	    let _ = Command::new("kill").args(["-9", &pid.to_string()]).output();
47	}
48
49	/// `Command::output()` with a deadline, run off-thread — bounds a hang into a
50	/// clean failure (the `live_firsthost_e2e` pattern).
51	fn output_bounded(mut cmd: Command, deadline: Duration) -> Output {
52	    let (tx, rx) = std::sync::mpsc::channel();
53	    std::thread::spawn(move || {
54	        let _ = tx.send(cmd.output());
55	    });
56	    rx.recv_timeout(deadline)
57	        .expect("captured spt call must complete within its deadline")
58	        .expect("run spt")
59	}
60
61	/// Read the brain `pid` out of a `brain.ready` body (self-describing JSON since
62	/// D6-1b). The come-up proof: the broker spawned a brain child.
63	fn ready_pid(body: &str) -> Option<u32> {
64	    serde_json::from_str::<serde_json::Value>(body)
65	        .ok()?
66	        .get("pid")?
67	        .as_u64()
68	        .map(|p| p as u32)
69	}
70
71	/// Poll `brain.ready` for a pid, up to `budget`. `None` if it never appears.
72	fn wait_for_ready_pid(path: &Path, budget: Duration) -> Option<u32> {
73	    let deadline = Instant::now() + budget;
74	    while Instant::now() < deadline {
75	        if let Ok(s) = std::fs::read_to_string(path) {
76	            if let Some(pid) = ready_pid(&s) {
77	                return Some(pid);
78	            }
79	        }
80	        std::thread::sleep(Duration::from_millis(50));
81	    }
82	    None
83	}
84
85	#[test]
86	fn netless_online_live_agent_is_hosted_by_the_real_daemon_brain() {
87	    // ── (1) Isolated SPT_HOME, NET-LESS / UNPAIRED: we create no subnet and run no
88	    //    pairing. The whole defect window is "online live agent on a node with no
89	    //    net". One test in this binary owns the process-global SPT_HOME; children
90	    //    are passed it explicitly via .env() anyway. ──
91	    let home = tempfile::tempdir().unwrap();
92	    std::env::set_var("SPT_HOME", home.path());
93
94	    // Force the truest NET-LESS / UNPAIRED condition: a CORRUPT node.key makes
95	    // `nodeid::load_or_create()` error, so `Daemon::run` computes `node_hex = None`
96	    // → it NEVER calls `try_start_net` and arms NO net-retry (daemon.rs:153-161,
97	    // "NODE_KEY_FAIL: identity unavailable — broker runs net-less, no retry"). The
98	    // brain's `net-status` therefore never reports `enabled`, so BRAIN_NET_CONSUMERS_UP
99	    // never fires — the exact net-starved node perri hit. (A corrupt key is NEVER
100	    // auto-regenerated by design — nodeid.rs module docs.)
101	    let identity_dir = home.path().join("identity");
102	    std::fs::create_dir_all(&identity_dir).unwrap();
103	    std::fs::write(identity_dir.join("node.key"), "not-a-valid-seed").unwrap();
104
105	    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
106	    let exe_suffix = spt_bin
107	        .extension()
108	        .map(|e| format!(".{}", e.to_string_lossy()))
109	        .unwrap_or_default();
110
111	    // ── (2) A live-capable adapter in COPY mode whose [session.psyche_init].command
112	    //    is a BARE program token (`psychebin`) naming a REAL psyche binary that
113	    //    ships in the adapter's install dir (= the registered `source_dir`). The
114	    //    fixture is a copy of the real `spt` binary invoked as `psychebin --version`
115	    //    (a real executable that exits 0 immediately, no side effects). The bare
116	    //    token is NOT on PATH, so spawn_psyche can ONLY resolve it via REQ-INSTALL-11
117	    //    install-dir resolution. ──
118	    let src = perch::spt_home().join("srcs").join("mocklive");
119	    std::fs::create_dir_all(&src).unwrap();
120
121	    // The real psyche binary fixture, by a BARE name, IN the install dir only.
122	    let psyche_bin = src.join(format!("psychebin{exe_suffix}"));
123	    std::fs::copy(&spt_bin, &psyche_bin).expect("stage the real psyche binary fixture");
124	    assert!(
125	        psyche_bin.is_file(),
126	        "the psyche binary fixture must exist in the install dir"
127	    );
128
129	    // The drops dir (commune/signoff watch) — irrelevant to the host proof but the
130	    // manifest needs a [session] block.
131	    let drops = home.path().join("drops");
132	    std::fs::create_dir_all(&drops).unwrap();
133	    let dir = drops.to_string_lossy().replace('\\', "/");
134
135	    // Command is the BARE token `psychebin` + a harmless fast-exit arg. NOT an
136	    // absolute path, NOT a shell builtin: it resolves only if install-dir
137	    // resolution fires.
138	    let manifest_toml = format!(
139	        "[adapter]\nname=\"mocklive\"\nkind=\"harness\"\nversion=\"1\"\n\
140	         min_spt_core_version=\"0\"\n\n\
141	         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
142	         [session.psyche_init]\ncommand='psychebin --version'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
143	    );
144	    let manifest_path = src.join("manifest.toml");
145	    std::fs::write(&manifest_path, &manifest_toml).unwrap();
146	    // Register in COPY mode (no [update] avenue ⇒ copy). The record's `source_dir`
147	    // is `src` — the install dir the brain resolves `psychebin` against.
148	    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();
149
150	    // Sanity: install-dir resolution of the bare token DOES find the fixture (so a
151	    // failure later is the WIRING not the fixture).
152	    let resolved =
153	        spt_runtime::registry::resolve_option(&perch::adapters_dir(), "mocklive").map(|(rec, _)| {
154	            spt_runtime::runtime::resolve_program_in_dir("psychebin", Path::new(&rec.source_dir))
155	        });
156	    assert!(
157	        resolved
158	            .as_ref()
159	            .map(|p| Path::new(p).is_file())
160	            .unwrap_or(false),
161	        "install-dir resolution must find the bare psychebin in source_dir (got {resolved:?})"
162	    );
163
164	    // ── (3) Launch the REAL daemon broker PROCESS. `spt daemon run` is the broker;
165	    //    it spawns a supervised `spt daemon brain` CHILD. Redirect the broker's
166	    //    stderr to a log file in the tempdir — the brain child inherits the broker's
167	    //    stderr (spawn_brain_child sets no stdio), so BRAIN_UP / LIVEHOST_PSYCHE /
168	    //    LIVEHOST_PSYCHE_FAIL (all eprintln! to the brain's stderr) land here. ──
169	    let brain_log = home.path().join("brain.stderr.log");
170	    let brain_log_file = std::fs::File::create(&brain_log).expect("create brain stderr log");
171	    let mut broker: Child = Command::new(&spt_bin)
172	        .no_window()
173	        .args(["daemon", "run"])
174	        .env("SPT_HOME", home.path())
175	        .stdout(Stdio::null())
176	        .stderr(Stdio::from(brain_log_file))
177	        .spawn()
178	        .expect("spawn spt daemon run (broker process)");
179
180	    // Come-up: the broker spawned a brain child that signalled ready. This is the
181	    // precondition — if the brain never comes up, the host claim is moot.
182	    let brain_pid = match wait_for_ready_pid(&home.path().join("brain.ready"), Duration::from_secs(30))
183	    {
184	        Some(p) => p,
185	        None => {
186	            let _ = broker.kill();
187	            let _ = broker.wait();
188	            panic!(
189	                "PRECONDITION FAILED: the real broker never spawned a ready brain child \
190	                 (brain.ready absent). Brain stderr:\n{}",
191	                std::fs::read_to_string(&brain_log).unwrap_or_default()
192	            );
193	        }
194	    };
195
196	    // ── (4) The REAL harness establish→online path: `api seed` then `api listen`.
197	    //    Seed keyed by THIS process's pid (alive — passes the recycle guard). The
198	    //    listen is the PERSISTENT (non --once) form: it marks the perch online and
199	    //    then BLOCKS in the relay loop until killed (the real harness leaves it
200	    //    running). Run it as a captured child whose stderr we keep, so we can see
201	    //    its BOUND/READY markers and reap it. ──
202	    let id = "bootrace-agent";
203	    let pid = std::process::id().to_string();
204	    let mp = manifest_path.to_string_lossy().to_string();
205
206	    let seed = {
207	        let mut cmd = Command::new(&spt_bin);
208	        cmd.no_window()
209	            .args([
210	                "api",
211	                "--adapter",
212	                "mocklive",
213	                "seed",
214	                "--pid",
215	                &pid,
216	                "--session-id",
217	                "sid-bootrace",
218	            ])
219	            .env("SPT_HOME", home.path());
220	        output_bounded(cmd, Duration::from_secs(30))
221	    };
222	    if !seed.status.success() {
223	        let _ = broker.kill();
224	        let _ = broker.wait();
225	        panic!(
226	            "PRECONDITION FAILED: api seed failed: {}",
227	            String::from_utf8_lossy(&seed.stderr)
228	        );
229	    }
230
231	    // The persistent live `api listen` — marks the perch status=online (live-capable
232	    // because the manifest declares [session.psyche_init]) then blocks. The harness
233	    // keeps this process alive for the session's lifetime.
234	    let listen_log = home.path().join("listen.stderr.log");
235	    let listen_log_file = std::fs::File::create(&listen_log).expect("create listen stderr log");
236	    let mut listen: Child = Command::new(&spt_bin)
237	        .no_window()
238	        .args([
239	            "api",
240	            "--adapter",
241	            "mocklive",
242	            "--manifest",
243	            &mp,
244	            "listen",
245	            id,
246	            "--parent-pid",
247	            &pid,
248	        ])
249	        .env("SPT_HOME", home.path())
250	        .stdout(Stdio::null())
251	        .stderr(Stdio::from(listen_log_file))
252	        .spawn()
253	        .expect("spawn the persistent api listen (harness-hosted live agent)");
254
255	    // Confirm the establish→online actually took: the Self perch carries
256	    // status=online (written by cmd_listen). Poll briefly — `listen` writes it
257	    // right after BOUND.
258	    let self_perch = perch::resolve_perch_path(id, ParentHint::Infer);
259	    let online = {
260	        let deadline = Instant::now() + Duration::from_secs(15);
261	        loop {
262	            let st = spt_store::info::read_info(&self_perch).and_then(|i| i.status);
263	            if st.as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {
264	                break true;
265	            }
266	            if Instant::now() >= deadline {
267	                break false;
268	            }
269	            std::thread::sleep(Duration::from_millis(100));
270	        }
271	    };
272	    if !online {
273	        kill_pid(listen.id());
274	        let _ = listen.wait();
275	        let _ = broker.kill();
276	        let _ = broker.wait();
277	        panic!(
278	            "PRECONDITION FAILED: the persistent `api listen` never marked the perch online.\n\
279	             listen stderr:\n{}",
280	            std::fs::read_to_string(&listen_log).unwrap_or_default()
281	        );
282	    }
283
284	    // ── (5) THE ASSERTION: the REAL daemon brain must host the Psyche. The
285	    //    disk-observable proxy is the nested `<id>-psyche` perch's info.json
286	    //    appearing (spawn_and_bind writes it AFTER a successful psyche spawn; a
287	    //    spawn failure logs LIVEHOST_PSYCHE_FAIL and writes nothing). Bounded 25s
288	    //    poll — on a buggy tree this SHOULD time out. ──
289	    let psyche_perch = perch::resolve_perch_path(
290	        &format!("{id}-psyche"),
291	        ParentHint::Explicit(id),
292	    );
293	    let deadline = Instant::now() + Duration::from_secs(25);
294	    let mut hosted = false;
295	    while Instant::now() < deadline {
296	        if spt_store::info::read_info(&psyche_perch).is_some() {
297	            hosted = true;
298	            break;
299	        }
300	        std::thread::sleep(Duration::from_millis(200));
301	    }
302
303	    // Capture diagnostics BEFORE teardown so the report has the real markers.
304	    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
305	    let listen_stderr = std::fs::read_to_string(&listen_log).unwrap_or_default();
306
307	    // Always surface the captured markers (the reproduction evidence).
308	    eprintln!("=== DIAGNOSTIC: hosted={hosted} brain_pid={brain_pid} ===");
309	    eprintln!("=== brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_FAIL) ===\n{brain_stderr}");
310	    eprintln!("=== listen stderr ===\n{listen_stderr}");
311	    eprintln!(
312	        "=== psyche perch info.json present: {} (path {}) ===",
313	        psyche_perch.join("info.json").exists(),
314	        psyche_perch.display()
315	    );
316
317	    // ── (6) Reap everything we spawned — scoped to OUR pids only. ──
318	    kill_pid(listen.id());
319	    let _ = listen.wait();
320	    kill_pid(brain_pid);
321	    let _ = broker.kill();
322	    let _ = broker.wait();
323
324	    // The harness-reachable psyche-host-health field (v0.8.1,
325	    // REQ-HAZARD-LIVEHOST-BOOT-RACE): on the SUCCESS path the live perch must carry
326	    // NO `psyche_host_error` — a successfully-hosted live agent shows clean (a prior
327	    // transient failure, if any, is cleared on the successful host).
328	    // [int->REQ-HAZARD-LIVEHOST-BOOT-RACE]
329	    let phe = spt_store::info::read_info(&self_perch).and_then(|i| i.psyche_host_error);
330
331	    assert!(
332	        hosted,
333	        "REQ-DAEMON-1: the REAL daemon brain must host the Psyche for a net-less ONLINE \
334	         live endpoint (the nested `{id}-psyche` perch must appear) within 25s — it did NOT.\n\
335	         === brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_FAIL land here) ===\n{}\n\
336	         === listen stderr ===\n{}",
337	        brain_stderr, listen_stderr,
338	    );
339	    assert!(
340	        phe.is_none(),
341	        "REQ-HAZARD-LIVEHOST-BOOT-RACE: a successfully-hosted live perch must carry NO \
342	         psyche_host_error (got {phe:?})",
343	    );
344	}
345

//! v0.8.1 boot-race REPRODUCER (REQ-DAEMON-1) — a harness-hosted live agent goes
//! `status=online` but the REAL daemon brain never hosts its Psyche on a net-less
//! / unpaired node.
//!
//! This is the full-stack production-path E2E that the existing analogs stop short
//! of (doyle's mandate):
//!
//!   - `daemon_lifecycle_real_brain.rs` runs `run_brain` on a THREAD, uses a NOOP
//!     `cmd /C rem` psyche (a shell builtin that always resolves — it never
//!     exercises install-dir binary resolution), and HAND-WRITES the online perch.
//!   - This test launches the REAL `spt daemon run` broker PROCESS (which spawns a
//!     real supervised `spt daemon brain` CHILD), drives the REAL `api seed` +
//!     `api listen` establish→online path, and uses a REAL psyche binary referenced
//!     by a BARE program token that lives ONLY in the adapter install dir — so the
//!     daemon-host site MUST resolve it via REQ-INSTALL-11 install-dir resolution,
//!     not PATH.
//!
//! The disk-observable proxy for "the brain hosted the Psyche" is the nested
//! `<id>-psyche` perch's `info.json` appearing (mirrors `daemon_lifecycle_real_brain.rs`).
//! On a buggy tree this never appears within the bounded deadline.
//!
//! Bounded by design: every captured `spt` call has a deadline and the whole host
//! wait is a 25s polling loop, so a reproduced failure is a clean assert, never a
//! hang. All spawned processes (broker + supervised brain + listen) are reaped.
// [int->REQ-DAEMON-1]

use std::path::{Path, PathBuf};
use std::process::{Child, Command, Output, Stdio};
use std::time::{Duration, Instant};

mod common;
use common::CommandNoWindowExt;

use spt_store::perch::{self, ParentHint};

/// Best-effort cross-platform kill of a pid tree (mirrors the other E2E helpers).
/// Scoped to a SINGLE pid we spawned — never a machine-wide reap (a live broker
/// shares this box).
fn kill_pid(pid: u32) {
    #[cfg(windows)]
    let _ = Command::new("taskkill")
        .no_window()
        .args(["/PID", &pid.to_string(), "/F", "/T"])
        .output();
    #[cfg(unix)]
    let _ = Command::new("kill").args(["-9", &pid.to_string()]).output();
}

/// `Command::output()` with a deadline, run off-thread — bounds a hang into a
/// clean failure (the `live_firsthost_e2e` pattern).
fn output_bounded(mut cmd: Command, deadline: Duration) -> Output {
    let (tx, rx) = std::sync::mpsc::channel();
    std::thread::spawn(move || {
        let _ = tx.send(cmd.output());
    });
    rx.recv_timeout(deadline)
        .expect("captured spt call must complete within its deadline")
        .expect("run spt")
}

/// Read the brain `pid` out of a `brain.ready` body (self-describing JSON since
/// D6-1b). The come-up proof: the broker spawned a brain child.
fn ready_pid(body: &str) -> Option<u32> {
    serde_json::from_str::<serde_json::Value>(body)
        .ok()?
        .get("pid")?
        .as_u64()
        .map(|p| p as u32)
}

/// Poll `brain.ready` for a pid, up to `budget`. `None` if it never appears.
fn wait_for_ready_pid(path: &Path, budget: Duration) -> Option<u32> {
    let deadline = Instant::now() + budget;
    while Instant::now() < deadline {
        if let Ok(s) = std::fs::read_to_string(path) {
            if let Some(pid) = ready_pid(&s) {
                return Some(pid);
            }
        }
        std::thread::sleep(Duration::from_millis(50));
    }
    None
}

#[test]
fn netless_online_live_agent_is_hosted_by_the_real_daemon_brain() {
    // ── (1) Isolated SPT_HOME, NET-LESS / UNPAIRED: we create no subnet and run no
    //    pairing. The whole defect window is "online live agent on a node with no
    //    net". One test in this binary owns the process-global SPT_HOME; children
    //    are passed it explicitly via .env() anyway. ──
    let home = tempfile::tempdir().unwrap();
    std::env::set_var("SPT_HOME", home.path());

    // Force the truest NET-LESS / UNPAIRED condition: a CORRUPT node.key makes
    // `nodeid::load_or_create()` error, so `Daemon::run` computes `node_hex = None`
    // → it NEVER calls `try_start_net` and arms NO net-retry (daemon.rs:153-161,
    // "NODE_KEY_FAIL: identity unavailable — broker runs net-less, no retry"). The
    // brain's `net-status` therefore never reports `enabled`, so BRAIN_NET_CONSUMERS_UP
    // never fires — the exact net-starved node perri hit. (A corrupt key is NEVER
    // auto-regenerated by design — nodeid.rs module docs.)
    let identity_dir = home.path().join("identity");
    std::fs::create_dir_all(&identity_dir).unwrap();
    std::fs::write(identity_dir.join("node.key"), "not-a-valid-seed").unwrap();

    let spt_bin = PathBuf::from(env!("CARGO_BIN_EXE_spt"));
    let exe_suffix = spt_bin
        .extension()
        .map(|e| format!(".{}", e.to_string_lossy()))
        .unwrap_or_default();

    // ── (2) A live-capable adapter in COPY mode whose [session.psyche_init].command
    //    is a BARE program token (`psychebin`) naming a REAL psyche binary that
    //    ships in the adapter's install dir (= the registered `source_dir`). The
    //    fixture is a copy of the real `spt` binary invoked as `psychebin --version`
    //    (a real executable that exits 0 immediately, no side effects). The bare
    //    token is NOT on PATH, so spawn_psyche can ONLY resolve it via REQ-INSTALL-11
    //    install-dir resolution. ──
    let src = perch::spt_home().join("srcs").join("mocklive");
    std::fs::create_dir_all(&src).unwrap();

    // The real psyche binary fixture, by a BARE name, IN the install dir only.
    let psyche_bin = src.join(format!("psychebin{exe_suffix}"));
    std::fs::copy(&spt_bin, &psyche_bin).expect("stage the real psyche binary fixture");
    assert!(
        psyche_bin.is_file(),
        "the psyche binary fixture must exist in the install dir"
    );

    // The drops dir (commune/signoff watch) — irrelevant to the host proof but the
    // manifest needs a [session] block.
    let drops = home.path().join("drops");
    std::fs::create_dir_all(&drops).unwrap();
    let dir = drops.to_string_lossy().replace('\\', "/");

    // Command is the BARE token `psychebin` + a harmless fast-exit arg. NOT an
    // absolute path, NOT a shell builtin: it resolves only if install-dir
    // resolution fires.
    let manifest_toml = format!(
        "[adapter]\nname=\"mocklive\"\nkind=\"harness\"\nversion=\"1\"\n\
         min_spt_core_version=\"0\"\n\n\
         [session]\ncommune_dir=\"{dir}\"\nsignoff_dir=\"{dir}\"\n\n\
         [session.psyche_init]\ncommand='psychebin --version'\ncwd=\"{{psyche_dir}}\"\nkeys=[]\n"
    );
    let manifest_path = src.join("manifest.toml");
    std::fs::write(&manifest_path, &manifest_toml).unwrap();
    // Register in COPY mode (no [update] avenue ⇒ copy). The record's `source_dir`
    // is `src` — the install dir the brain resolves `psychebin` against.
    spt_runtime::registry::register(&perch::adapters_dir(), &src, 1000).unwrap();

    // Sanity: install-dir resolution of the bare token DOES find the fixture (so a
    // failure later is the WIRING not the fixture).
    let resolved =
        spt_runtime::registry::resolve_option(&perch::adapters_dir(), "mocklive").map(|(rec, _)| {
            spt_runtime::runtime::resolve_program_in_dir("psychebin", Path::new(&rec.source_dir))
        });
    assert!(
        resolved
            .as_ref()
            .map(|p| Path::new(p).is_file())
            .unwrap_or(false),
        "install-dir resolution must find the bare psychebin in source_dir (got {resolved:?})"
    );

    // ── (3) Launch the REAL daemon broker PROCESS. `spt daemon run` is the broker;
    //    it spawns a supervised `spt daemon brain` CHILD. Redirect the broker's
    //    stderr to a log file in the tempdir — the brain child inherits the broker's
    //    stderr (spawn_brain_child sets no stdio), so BRAIN_UP / LIVEHOST_PSYCHE /
    //    LIVEHOST_PSYCHE_FAIL (all eprintln! to the brain's stderr) land here. ──
    let brain_log = home.path().join("brain.stderr.log");
    let brain_log_file = std::fs::File::create(&brain_log).expect("create brain stderr log");
    let mut broker: Child = Command::new(&spt_bin)
        .no_window()
        .args(["daemon", "run"])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::null())
        .stderr(Stdio::from(brain_log_file))
        .spawn()
        .expect("spawn spt daemon run (broker process)");

    // Come-up: the broker spawned a brain child that signalled ready. This is the
    // precondition — if the brain never comes up, the host claim is moot.
    let brain_pid = match wait_for_ready_pid(&home.path().join("brain.ready"), Duration::from_secs(30))
    {
        Some(p) => p,
        None => {
            let _ = broker.kill();
            let _ = broker.wait();
            panic!(
                "PRECONDITION FAILED: the real broker never spawned a ready brain child \
                 (brain.ready absent). Brain stderr:\n{}",
                std::fs::read_to_string(&brain_log).unwrap_or_default()
            );
        }
    };

    // ── (4) The REAL harness establish→online path: `api seed` then `api listen`.
    //    Seed keyed by THIS process's pid (alive — passes the recycle guard). The
    //    listen is the PERSISTENT (non --once) form: it marks the perch online and
    //    then BLOCKS in the relay loop until killed (the real harness leaves it
    //    running). Run it as a captured child whose stderr we keep, so we can see
    //    its BOUND/READY markers and reap it. ──
    let id = "bootrace-agent";
    let pid = std::process::id().to_string();
    let mp = manifest_path.to_string_lossy().to_string();

    let seed = {
        let mut cmd = Command::new(&spt_bin);
        cmd.no_window()
            .args([
                "api",
                "--adapter",
                "mocklive",
                "seed",
                "--pid",
                &pid,
                "--session-id",
                "sid-bootrace",
            ])
            .env("SPT_HOME", home.path());
        output_bounded(cmd, Duration::from_secs(30))
    };
    if !seed.status.success() {
        let _ = broker.kill();
        let _ = broker.wait();
        panic!(
            "PRECONDITION FAILED: api seed failed: {}",
            String::from_utf8_lossy(&seed.stderr)
        );
    }

    // The persistent live `api listen` — marks the perch status=online (live-capable
    // because the manifest declares [session.psyche_init]) then blocks. The harness
    // keeps this process alive for the session's lifetime.
    let listen_log = home.path().join("listen.stderr.log");
    let listen_log_file = std::fs::File::create(&listen_log).expect("create listen stderr log");
    let mut listen: Child = Command::new(&spt_bin)
        .no_window()
        .args([
            "api",
            "--adapter",
            "mocklive",
            "--manifest",
            &mp,
            "listen",
            id,
            "--parent-pid",
            &pid,
        ])
        .env("SPT_HOME", home.path())
        .stdout(Stdio::null())
        .stderr(Stdio::from(listen_log_file))
        .spawn()
        .expect("spawn the persistent api listen (harness-hosted live agent)");

    // Confirm the establish→online actually took: the Self perch carries
    // status=online (written by cmd_listen). Poll briefly — `listen` writes it
    // right after BOUND.
    let self_perch = perch::resolve_perch_path(id, ParentHint::Infer);
    let online = {
        let deadline = Instant::now() + Duration::from_secs(15);
        loop {
            let st = spt_store::info::read_info(&self_perch).and_then(|i| i.status);
            if st.as_deref() == Some(spt_store::liveness::STATUS_ONLINE) {
                break true;
            }
            if Instant::now() >= deadline {
                break false;
            }
            std::thread::sleep(Duration::from_millis(100));
        }
    };
    if !online {
        kill_pid(listen.id());
        let _ = listen.wait();
        let _ = broker.kill();
        let _ = broker.wait();
        panic!(
            "PRECONDITION FAILED: the persistent `api listen` never marked the perch online.\n\
             listen stderr:\n{}",
            std::fs::read_to_string(&listen_log).unwrap_or_default()
        );
    }

    // ── (5) THE ASSERTION: the REAL daemon brain must host the Psyche. The
    //    disk-observable proxy is the nested `<id>-psyche` perch's info.json
    //    appearing (spawn_and_bind writes it AFTER a successful psyche spawn; a
    //    spawn failure logs LIVEHOST_PSYCHE_FAIL and writes nothing). Bounded 25s
    //    poll — on a buggy tree this SHOULD time out. ──
    let psyche_perch = perch::resolve_perch_path(
        &format!("{id}-psyche"),
        ParentHint::Explicit(id),
    );
    let deadline = Instant::now() + Duration::from_secs(25);
    let mut hosted = false;
    while Instant::now() < deadline {
        if spt_store::info::read_info(&psyche_perch).is_some() {
            hosted = true;
            break;
        }
        std::thread::sleep(Duration::from_millis(200));
    }

    // Capture diagnostics BEFORE teardown so the report has the real markers.
    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    let listen_stderr = std::fs::read_to_string(&listen_log).unwrap_or_default();

    // Always surface the captured markers (the reproduction evidence).
    eprintln!("=== DIAGNOSTIC: hosted={hosted} brain_pid={brain_pid} ===");
    eprintln!("=== brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_FAIL) ===\n{brain_stderr}");
    eprintln!("=== listen stderr ===\n{listen_stderr}");
    eprintln!(
        "=== psyche perch info.json present: {} (path {}) ===",
        psyche_perch.join("info.json").exists(),
        psyche_perch.display()
    );

    // ── (6) Reap everything we spawned — scoped to OUR pids only. ──
    kill_pid(listen.id());
    let _ = listen.wait();
    kill_pid(brain_pid);
    let _ = broker.kill();
    let _ = broker.wait();

    // The harness-reachable psyche-host-health field (v0.8.1,
    // REQ-HAZARD-LIVEHOST-BOOT-RACE): on the SUCCESS path the live perch must carry
    // NO `psyche_host_error` — a successfully-hosted live agent shows clean (a prior
    // transient failure, if any, is cleared on the successful host).
    // [int->REQ-HAZARD-LIVEHOST-BOOT-RACE]
    let phe = spt_store::info::read_info(&self_perch).and_then(|i| i.psyche_host_error);

    assert!(
        hosted,
        "REQ-DAEMON-1: the REAL daemon brain must host the Psyche for a net-less ONLINE \
         live endpoint (the nested `{id}-psyche` perch must appear) within 25s — it did NOT.\n\
         === brain stderr (BRAIN_UP / LIVEHOST_PSYCHE / LIVEHOST_PSYCHE_FAIL land here) ===\n{}\n\
         === listen stderr ===\n{}",
        brain_stderr, listen_stderr,
    );
    assert!(
        phe.is_none(),
        "REQ-HAZARD-LIVEHOST-BOOT-RACE: a successfully-hosted live perch must carry NO \
         psyche_host_error (got {phe:?})",
    );
}


Positive E2E is rigorous — real `daemon run` broker→supervised brain CHILD (verified via brain.ready pid), TRUE net-less (corrupt node.key→NODE_KEY_FAIL, no retry), bare-token psyche binary in install-dir-only (forces REQ-INSTALL-11), bounded 25s, pid-scoped reaps (respects shared-runner hazard). The non-negotiable is genuinely met. Reading the negative E2E + the impl (livehost/info/traceable/cli).


250	        }
251	        std::thread::sleep(Duration::from_millis(200));
252	    }
253
254	    // Status must STILL be online (liveness is daemon-authoritative; the failure
255	    // stamp NEVER touches it). The psyche perch must NOT exist (spawn failed).
256	    let status_after = spt_store::info::read_info(&self_perch).and_then(|i| i.status);
257	    let psyche_perch_present = spt_store::info::read_info(&psyche_perch).is_some();
258
259	    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
260	    let listen_stderr = std::fs::read_to_string(&listen_log).unwrap_or_default();
261
262	    eprintln!("=== DIAGNOSTIC: stamped={stamped:?} status_after={status_after:?} brain_pid={brain_pid} ===");
263	    eprintln!("=== brain stderr (LIVEHOST_PSYCHE_FAIL lands here) ===\n{brain_stderr}");
264	    eprintln!("=== listen stderr ===\n{listen_stderr}");
265
266	    // ── (6) Reap everything we spawned — scoped to OUR pids only. ──
267	    kill_pid(listen.id());
268	    let _ = listen.wait();
269	    kill_pid(brain_pid);
270	    let _ = broker.kill();
271	    let _ = broker.wait();
272
273	    let stamped = stamped.unwrap_or_else(|| {
274	        panic!(
275	            "REQ-HAZARD-LIVEHOST-BOOT-RACE: an ONLINE live endpoint whose Psyche fails to host \
276	             (psyche binary absent) MUST stamp a harness-reachable psyche_host_error on its perch \
277	             info.json within 25s — it did NOT (the failure stayed silent on brain stderr).\n\
278	             === brain stderr ===\n{brain_stderr}\n=== listen stderr ===\n{listen_stderr}"
279	        )
280	    });
281	    assert!(
282	        !stamped.reason.is_empty(),
283	        "the stamped psyche_host_error must carry a non-empty reason (got {stamped:?})"
284	    );
285	    assert!(
286	        stamped.attempts >= 1,
287	        "the stamped psyche_host_error must carry attempts >= 1 (got {stamped:?})"
288	    );
289	    assert_eq!(
290	        status_after.as_deref(),
291	        Some(spt_store::liveness::STATUS_ONLINE),
292	        "status stays daemon-authoritative online — the failure stamp never touches it"
293	    );
294	    assert!(
295	        !psyche_perch_present,
296	        "a failed psyche spawn must write NO nested `{id}-psyche` perch"
297	    );

        }
        std::thread::sleep(Duration::from_millis(200));
    }

    // Status must STILL be online (liveness is daemon-authoritative; the failure
    // stamp NEVER touches it). The psyche perch must NOT exist (spawn failed).
    let status_after = spt_store::info::read_info(&self_perch).and_then(|i| i.status);
    let psyche_perch_present = spt_store::info::read_info(&psyche_perch).is_some();

    let brain_stderr = std::fs::read_to_string(&brain_log).unwrap_or_default();
    let listen_stderr = std::fs::read_to_string(&listen_log).unwrap_or_default();

    eprintln!("=== DIAGNOSTIC: stamped={stamped:?} status_after={status_after:?} brain_pid={brain_pid} ===");
    eprintln!("=== brain stderr (LIVEHOST_PSYCHE_FAIL lands here) ===\n{brain_stderr}");
    eprintln!("=== listen stderr ===\n{listen_stderr}");

    // ── (6) Reap everything we spawned — scoped to OUR pids only. ──
    kill_pid(listen.id());
    let _ = listen.wait();
    kill_pid(brain_pid);
    let _ = broker.kill();
    let _ = broker.wait();

    let stamped = stamped.unwrap_or_else(|| {
        panic!(
            "REQ-HAZARD-LIVEHOST-BOOT-RACE: an ONLINE live endpoint whose Psyche fails to host \
             (psyche binary absent) MUST stamp a harness-reachable psyche_host_error on its perch \
             info.json within 25s — it did NOT (the failure stayed silent on brain stderr).\n\
             === brain stderr ===\n{brain_stderr}\n=== listen stderr ===\n{listen_stderr}"
        )
    });
    assert!(
        !stamped.reason.is_empty(),
        "the stamped psyche_host_error must carry a non-empty reason (got {stamped:?})"
    );
    assert!(
        stamped.attempts >= 1,
        "the stamped psyche_host_error must carry attempts >= 1 (got {stamped:?})"
    );
    assert_eq!(
        status_after.as_deref(),
        Some(spt_store::liveness::STATUS_ONLINE),
        "status stays daemon-authoritative online — the failure stamp never touches it"
    );
    assert!(
        !psyche_perch_present,
        "a failed psyche spawn must write NO nested `{id}-psyche` perch"
    );


198	        let install_dir = registered
199	            .iter()
200	            .find(|(rec, _)| rec.name == parent)
201	            .map(|(rec, _)| std::path::PathBuf::from(&rec.source_dir));
202	        let Some(lifecycle) =
203	            BrainLifecycle::with_config_in(&manifest, &id, cfg.clone(), install_dir.as_deref())
204	        else {
205	            continue; // manifest declares no [session.psyche_init]
206	        };
207	        host_one(set, &id, lifecycle, info.session_id, reason);
208	    }
209	}
210
211	/// Host one live endpoint: sweep a stale signoff (3.2), spawn the Psyche (the
212	/// daemon online-stamp makes liveness daemon-authoritative), then start the
213	/// config-paced pulse driver on its own supervised thread with a held stop flag.
214	/// A Psyche-spawn failure tables nothing — the next sweep retries.
215	// [impl->REQ-DAEMON-1]
216	fn host_one(
217	    set: &Arc<LiveSet>,
218	    id: &str,
219	    lifecycle: BrainLifecycle,
220	    session_id: String,
221	    reason: StartReason,
222	) {
223	    lifecycle.sweep_on_start();
224	    // Resolve the live perch once — the same path the start-scan resolved it by
225	    // (resolve_perch_path/Infer), so the harness-reachable stamp lands on the
226	    // exact info.json `spt endpoint list` / `whoami` reads.
227	    let perch = perch::resolve_perch_path(id, ParentHint::Infer);
228	    match lifecycle.spawn_psyche(Some(&session_id), None) {
229	        Ok(h) => {
230	            eprintln!("LIVEHOST_PSYCHE:{id} pid={}", h.pid);
231	            // Recovered: CLEAR any prior psyche-host-failure stamp so a now-hosted
232	            // live agent shows clean. Best-effort — status is untouched.

        let install_dir = registered
            .iter()
            .find(|(rec, _)| rec.name == parent)
            .map(|(rec, _)| std::path::PathBuf::from(&rec.source_dir));
        let Some(lifecycle) =
            BrainLifecycle::with_config_in(&manifest, &id, cfg.clone(), install_dir.as_deref())
        else {
            continue; // manifest declares no [session.psyche_init]
        };
        host_one(set, &id, lifecycle, info.session_id, reason);
    }
}

/// Host one live endpoint: sweep a stale signoff (3.2), spawn the Psyche (the
/// daemon online-stamp makes liveness daemon-authoritative), then start the
/// config-paced pulse driver on its own supervised thread with a held stop flag.
/// A Psyche-spawn failure tables nothing — the next sweep retries.
// [impl->REQ-DAEMON-1]
fn host_one(
    set: &Arc<LiveSet>,
    id: &str,
    lifecycle: BrainLifecycle,
    session_id: String,
    reason: StartReason,
) {
    lifecycle.sweep_on_start();
    // Resolve the live perch once — the same path the start-scan resolved it by
    // (resolve_perch_path/Infer), so the harness-reachable stamp lands on the
    // exact info.json `spt endpoint list` / `whoami` reads.
    let perch = perch::resolve_perch_path(id, ParentHint::Infer);
    match lifecycle.spawn_psyche(Some(&session_id), None) {
        Ok(h) => {
            eprintln!("LIVEHOST_PSYCHE:{id} pid={}", h.pid);
            // Recovered: CLEAR any prior psyche-host-failure stamp so a now-hosted
            // live agent shows clean. Best-effort — status is untouched.


cd /c/Users/decid/Documents/projects/spt-core
echo "=== traceable diff ==="; git diff main..HEAD -- traceable-reqs.toml
echo "=== CONTEXT diff ==="; git diff main..HEAD -- CONTEXT.md

Show traceable + CONTEXT diffs


=== traceable diff ===
diff --git a/traceable-reqs.toml b/traceable-reqs.toml
index f8d990b..c6c1891 100644
--- a/traceable-reqs.toml
+++ b/traceable-reqs.toml
@@ -945,6 +945,11 @@ required_stages = ["impl", "unit"]  # impl = ensure_running consults service::pl
 id = "REQ-DAEMON-9"
 title = "Net-bind boot-race resilience: a daemon that comes up net-less (NetHost::start failed — e.g. the systemd unit autostarted before the network/DNS stack was ready, `Failed to create an address lookup service`) must SELF-HEAL — retry the net bring-up in the background with capped backoff and, on success, attach net to the broker + spawn the dispatcher/peer-pump (which today are gated on `net_up` at boot and so never start, leaving the node silently unreachable until a manual restart — kitsubito 2026-06-08). Status surfaces the net-less state honestly (a net-less broker renders as 'no connection', not only a pump-STALLED line with a bogus pre-boot heartbeat age). The installer's autostart unit waits for the network (`Wants=/After=network-online.target`) as belt-and-suspenders."
 required_stages = ["impl", "unit"]  # activate v0.3.1. impl = Broker.net → OnceLock<NetHost> + attach_net(); Daemon::run factors spawn_net_consumers(), fast-path attaches at boot, slow-path spawns a background NetHost::start retry supervisor (capped backoff) that attaches + spawns consumers on success (NET_BIND_RETRY/NET_ATTACHED logs); cmd_daemon_status renders net-less as 'no connection' + treats a heartbeat older than daemon start as 'no tick yet' not a giant age; install.sh unit gains network-online ordering. unit = backoff ladder caps + OnceLock single-attach (consumers spawn exactly once) + the status net-less/heartbeat-age render classifier.
+# --- Added 2026-06-16 (v0.8.1): perri's v0.8.0 dogfood surfaced a harness-hosted live agent that goes ONLINE but never gets a Psyche on a net-less/unpaired node (boot-race); rule 3 register-before-satisfy, rule 5 activate per wave as evidence lands ---
+[[requirements]]
+id = "REQ-HAZARD-LIVEHOST-BOOT-RACE"
+title = "The brain's daemon-hosted Psyche lifecycle surfaces a host-FAILURE on the live perch (harness-diagnosable) and runs net-INDEPENDENTLY. When reconcile_once→host_one→spawn_psyche fails for a state=live_agent+status=online endpoint (e.g. the adapter's psyche binary absent from its install dir, REQ-INSTALL-11), the failure MUST be written to the perch info.json as a CURRENT-STATE field (reason + ts + attempt count; overwritten each 5s retry, CLEARED on successful host) and surfaced by `spt endpoint list`/status — never left as an eprintln on the brain's invisible stderr where a harness reading only perch state is blind. status=online stays authoritative (agent reachable; only the Psyche is missing — brain-restart rehydrate legitimately has online-without-Psyche windows), so this is a SEPARATE psyche-host-health field, never a status de-stamp. Net-independence is a locked-in invariant: spawn_live_host (brainproc.rs:230) reaches the reconcile and hosts the Psyche on a net-less/unpaired/peer-pump-STALLED node, proven by a REAL detached-daemon E2E (real broker→brain-child, real api seed+listen, real install-dir psyche binary). spt-core SURFACES the failure; the adapter owns fixing its packaging."
+required_stages = ["impl", "unit", "int"]  # activated v0.8.1 2026-06-16 (todlando): impl = info.json psyche_host_error field + set_psyche_host_error helper, host_one write-on-fail/clear-on-success, SELF-pin render annotation; unit = info.rs round-trip+set/clear/increment + render_self_pin annotation; int = livehost_bootrace_e2e (real-daemon positive, psyche_host_error cleared) + livehost_psyche_fail_e2e (real-daemon negative, missing psyche binary → harness-reachable stamp)
 [[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)"
=== CONTEXT diff ===
diff --git a/CONTEXT.md b/CONTEXT.md
index 9d661ea..39bd34d 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -200,6 +200,8 @@ The Psyche companion's own perch, distinct from its paired LiveAgent's perch. Fi

 *I/O & trust boundary (ADR-0012):* the Psyche is a **sandboxed** actor — it may read and write files but **cannot send messages or reach the network itself**. Its inbound context arrives two ways: events/messages the daemon hands it, and **commune/signoff file-drops** (Self → daemon → Psyche; the *Summarizer* authors the commune delta). Its **sole outbound** is **reply/notify intents** the daemon relays as its **outbound proxy** — emitted as `<EVENT type="reply">`/`<EVENT type="notify">` (the shared envelope grammar). A *reply* reaches **only the sender it answers**; a *notify* reaches **only the agent's own user** — the Psyche carries no target and cannot address arbitrary endpoints (the daemon strips/re-stamps `from=` before relaying).

+*Psyche-host health — harness-reachable failure signal (v0.8.1, REQ-HAZARD-LIVEHOST-BOOT-RACE):* a LiveAgent's `status=online` is daemon-authoritative liveness and **stays authoritative** — but it does not by itself prove the daemon hosted a Psyche. When the brain's live-host reconcile fails to spawn the Psyche (e.g. the adapter's psyche binary is absent from its install dir, or the net-less boot-race starves the host), that failure was previously **silent** — only an `eprintln!` on the brain's invisible stderr, while a harness (and a human via `spt endpoint list` / `whoami`) reads **perch state**, never brain stderr. The Self perch's `info.json` therefore carries an additive, N-1-safe `psyche_host_error` field (`{reason, ts, attempts}`): a **current-state** stamp the reconcile **overwrites on each retry** (incrementing `attempts`) and **clears on a later successful host** — never an append log. It is **independent of `status`**: an online live agent with no Psyche reads `status=online` **and** a `psyche_host_error`, so the boot-race is diagnosable from perch state. `spt endpoint list`/`whoami` render it inline as a `psyche-host: FAILED (...)` annotation after the authoritative liveness line.
+
 **Summarizer**:
 The ephemeral, cheap model that builds a **commune delta** from a Self's recent turns and feeds it *into* the **Psyche** as inbound context. A distinct actor from the Psyche — different (cheaper) model, fire-and-forget, **no perch** (not an endpoint type). It authors *commune* deltas only, **never** *reply*/*notify*.
 _Avoid_: conflating with the Psyche; "echo-commune model".


Negative E2E solid (stamp with non-empty reason + attempts≥1, status stays online, no psyche perch, pid-scoped reaps). CONTEXT note is thorough. But one thing needs scrutiny: `host_one` now resolves `install_dir` and calls `with_config_in` (line 202-203). On the pre-v0.8.1 tree the host site used `with_config` (PATH-only). If this PR **changed** that, install-dir resolution at the host site was actually broken on main — that'd be a real root-cause fix, not just "surface the failure." Checking the livehost diff.


cd /c/Users/decid/Documents/projects/spt-core
git diff main..HEAD -- crates/spt-daemon/src/livehost.rs

Show livehost.rs diff vs main