---
name: derived-view-vs-on-disk-record
description: Neither `spt shell list` nor info.json is ground truth for shell liveness on Windows — the process table is; a killed shell can still read online while the daemon holds its handle.
metadata:
  node_type: memory
  type: feedback
  originSessionId: 5a501e88-c801-419d-adcf-c557c1980844
  modified: 2026-07-26T23:31:18.947Z
---

**Ground truth for "is this shell actually down" on Windows is the process table (`Get-Process` /
`Win32_Process`), never a status view — whenever the link-owning daemon is still resident.**
doyle-ruled 2026-07-26, root-caused on my own corpse pid.

`spt shell list` never consults the daemon: the derivation is a pure file+pid probe inside the CLI
process (recorded status + "is pid alive"). The pid probe asks `OpenProcess`, and Windows keeps a
TERMINATED process's kernel object openable for as long as ANY resident process still holds a handle
to it — the daemon that owned the link does. So `OpenProcess` succeeds, the probe answers "alive", and
the view reports the recorded `online` for a process killed minutes ago. Measured on pid 47608:
`OpenProcess` → handle succeeds; `GetExitCodeProcess` → 4294967295 (not 259 STILL_ACTIVE); process
table → ABSENT. Fix queued (counter 78/79): require `GetExitCodeProcess != STILL_ACTIVE` before
answering alive — one shared primitive heals every derivation gate.

Conversely `spt shell list` showing `offline` does NOT mean the on-disk record is healed: a poisoned
`info.json` (`status:"online"`, stale `shell.pid`) renders `offline` once a daemon restart closes the
handles and frees the object, while the record itself still lies. **Both views lie, in opposite
directions, for different reasons.**

**Same trap inside a TEST, 2026-07-26 (RESIDENT-SERVICE W1 leg E3).** A gate asserting a service
SURVIVED a shell teardown used `spt_store::proc::is_process_alive`; the supervisor holds the child
handle, so a service I killed outright still read alive and the control run PASSED. **Never use an OS
liveness probe to claim a process is STILL RUNNING while anyone holds its handle — watch it DO WORK
instead** (the mock now advances a heartbeat file; the control fails). The opposite direction is safe:
product code confirming a kill reads the corpse as alive, gets no confirmation, and refuses to declare
the ground clear.

**Why:** I called alchemy-0 "cleanly closed, nothing to kill" from the derived view alone; doyle held
the raw datum (record online, corpse pid, `close_shell` never ran) and my call would have discarded a
deliberately-held relink specimen. Then twice more in the same window I read one field and inferred a
record — a null `input_seq` without checking `input`, then without checking `partial`. Banked as
**read one field, infer the record**; perri and flynn each had an instance the same night.

**How to apply:** When state matters (field-verify, ledger evidence, a go/no-go), read the
authoritative artifact for the question you're actually asking — liveness → process table; durable
shell state → `AppData\Local\spt-core\owlery\<owner>\shells\<id>\` (`info.json`, `shell.pid`,
`<id>.tags.toml` cursor+armed, `<id>.toml` repo binding). Verify a pid by ID lookup, never a name
match. Before claiming a shape, enumerate the fields that DISTINGUISH the candidates and check each;
state negatives as counts over the whole window, not impressions.

**The social form is the dangerous one: borrowed confidence.** I adopted flynn's bracket ("the handle
freed matching your 04:00–04:16 window") into my own report as a finding. He had measured it for HIS
corpse only; I had no evidence it applied to mine, and never tested it. A peer's claim does not
announce which parts were measured and which were generalised — and it arrives carrying their
credibility, which a field reading does not. **Rule, now binding on both of us: when adopting a peer's
finding into my own report, state which of their evidence I verified and which I am taking on trust.**
Writing "flynn's bracket, unverified for my corpse" would have made the defect visible in my own
sentence. Corollary for what I emit: label measured-vs-generalised in my own claims, since that is the
cheaper place to catch it than in someone else's report.

**Two numbers side by side imply a comparison neither supports.** I offered a minute-granular `ls -la`
mtime as corroboration beside flynn's seconds-resolved stamp; he quoted two cursor spans as contrast
when one crossed a generation bump. Same species, caught in both directions in one session.

**And check the discriminator actually discriminates.** I offered two mechanisms for the stale-online
view (in-memory link authority vs cache/TTL) and proposed "does it flip after a daemon restart" as the
test. Both were wrong — the real cause (handle lifetime) was outside my hypothesis set, and my test
comes out TRUE under it too, so it would have appeared to confirm whichever I favored. A test both
hypotheses predict is not a discriminator, and a discriminator is worthless if the true cause was
never enumerated. See [[instrument-soundness-guards]] and [[premature-closure-guards]].
