# #49 / #267 — bounded refresh observation plan

**Status:** prepared, **not admitted**. No execution grant. W2 run `20260912T231404Z` holds the
execution window. Author: todlando. Gater: doyle.

**What this observes:** whether a blocked controller sink reaches eviction across one planned brain
restart, and whether a PTY's output and input progress resume — with session and connection
attribution. **What it cannot decide:** the clean-close hypothesis. See §8.

---

## 1. Subject selection — bytes, not a path

| item | value |
| --- | --- |
| executable path | `C:\Users\decid\AppData\Local\spt-core\bin\spt` |
| **executable sha256 (the pin)** | `689503ad9fc6cfde637cbcacf21c42faf130ba465a0df6c356cf290e2adafb85` |
| reported version | `spt 0.69.0` |
| mtime at planning | 2026-09-10 21:14 |

**The sha256 is the pin, not the path.** Re-hash immediately before the run. **If the hash differs
from the value above, the run does not start**: the subject must be re-selected and this plan
re-admitted. A changed binary is a different subject, not a recorded detail.

**Source provenance — UNVERIFIED, and recorded as such.** The installed binary predates current
`main`, so **current `main` is not its source provenance**. The plausible release commit is v0.69.0 =
`16df0e41be04ca4614a792f7e70e4d1bdec60b24` (2026-09-10), which is consistent with the binary's
version string and mtime — but **I have not verified that these bytes were built from that commit**,
and no artifact on this box binds them. Every source citation in this plan (`brainproc.rs:1081`,
`broker.rs:2842`/`:3787`/`:2815`, `applyhost.rs:364`) is read at **current main** and is therefore a
claim about the source tree, **not** a proven claim about the executable under test. Treat any
line-number citation as indicative; if a run's behaviour disagrees with a citation, the citation is
the suspect.

## 2. Isolation — binding

**The resident fleet daemon is never refreshed.** At planning time 18 `spt.exe` processes authenticate
to the resident daemon (pid 4732): agent perches under `spt-mobile-host.exe`, `omp-spt` chains, and
this session's own perch.

- Isolated `SPT_HOME` under a run-specific directory, with its own broker socket.
- The isolated home **must not exist** beforehand. If it does, the run does not start and the
  existing directory is left untouched.
- One disposable PTY session, created for this run only.
- If the isolated daemon cannot be brought up cleanly within the §3 startup bound, **the run does not
  start**.

### 2.1 Exact targeting — every command, with identity checks before the two dangerous ones

**Every** command in the run carries the isolated home explicitly: `SPT_HOME=<ISO_HOME>` is set on
each invocation. No command relies on an inherited or ambient value, and no command is issued from a
shell whose environment was mutated once and trusted thereafter.

**Before `daemon refresh` and before `daemon stop`, all four checks must pass. Any failure and the
command does not run:**

1. `SPT_HOME` resolves to `<ISO_HOME>` — read back from the command's own environment, not assumed.
2. The broker socket the command will address is the isolated one, not the fleet's.
3. The target daemon pid was **created by this run**, established by ancestry.
4. The target pid is **not 4732**, and is not an ancestor of this session.

Refresh and stop are the only two verbs that act on a daemon's lifecycle; both are gated by all four.

## 3. Bounds — numeric, chosen now

**Two separate budgets, and a total.** The observation deadline **ends experimental activity and
starts cleanup — it never skips cleanup.** Cleanup holds its own maximum on top.

| budget | value |
| --- | --- |
| **observation budget** (all experimental activity, from run start) | **300 s** |
| **cleanup budget** (separate, begins when observation ends for any reason) | **120 s** |
| **total bound, run start to done** | **420 s** |

| phase | nominal cap | on breach |
| --- | --- | --- |
| isolated daemon up + session attached | 60 s | run does not start |
| `t0` appears after refresh is issued | 60 s | record "restart marker not observed"; continue within the observation budget |
| observation after `t0` | 120 s | stop observing; report recovery-after-interval or no-recovery-within-the-observed-interval |
| reattach: detach, then fresh attach | 30 s to attach, 60 s observed | report as its own arm |
| cleanup confirmation | 120 s (its own budget) | preserve everything and report unconfirmed (§6) |

**Every observation and reattach phase is additionally capped by the REMAINING observation budget.**
A phase's nominal cap is an upper bound, not an entitlement: if only 40 s of the 300 s remain, that
phase gets 40 s. **A phase cut short by the remaining budget is REPORTED AS TRUNCATED**, naming the
phase and the time it actually got — a truncated phase's result is never presented as a completed
observation, and its absence of an event falls under §8's "not observed" rule.

**When the observation budget is exhausted, experimental activity stops and cleanup begins
immediately** under §6, with its own 120 s. Exhausting the observation budget is a normal ending, not
an error, and it never licenses skipping or shortening cleanup.

**The observation budget is independent of `t0` because `t0` may never arrive.** Every phase cap is
relative to `t0` or to a local step; without a run-start-relative budget, a refresh that produces no
`BRAIN_UPDATE_RESTART` would leave the run with no defined end. If `t0` never appears, that absence
is a recorded result — and per §8 it is "not observed", not proof the restart did not happen.

## 4. Observation

`t0` = `BRAIN_UPDATE_RESTART` (brainproc.rs:1081, per §1's provenance caveat). Daemon stderr captured
to a file for the whole window, starting before the session is attached.

| id | question | observable | attribution |
| --- | --- | --- | --- |
| Q2 | does a blocked sink reach eviction? | `BRAIN_SUBSCRIBER_STALL_EVICT` (broker.rs:2842, :3787) | carries `conn=` and a session id |
| Q4 | what survives reattach? | §5, after one detach and one fresh attach | session id |
| Q5 | does output progress resume, and when? | §5 | session id |
| Q6 | is queued input preserved and applied in order? | §5 | session id |

**Q2 is not a deadline measurement.** `evict − t0` runs from the restart signal; the sink deadline
runs from **blocked-write onset**, which nothing emits.

## 5. Output and input progress — two clocks, and what each can prove

The disposable session runs a generator emitting, on its own cadence:

```
PROG <n> <generated-at>
```

and the observer records, for every line, **its own receipt timestamp**. **Both timestamps are kept
for every line.** They answer different questions and neither substitutes for the other:

- **generation timestamp + counter** — what the child did, and when the child did it.
- **receipt timestamp** — when the observer was delivered it.

**The trap:** after a freeze a burst arrives, and buffered output released late is indistinguishable
**by arrival alone** from progress resuming.

**What each combination establishes, stated exactly:**

- Pre-`t0` counters and pre-`t0` generation stamps, received after `t0` → **buffered output being
  released.** The transport resumed. It says nothing about the child.
- Counter above the pre-`t0` maximum **and** generation stamp after `t0` → **the child progressed
  after `t0`.** This does **not** establish that the child was executing *contemporaneously with
  delivery*: such a line may itself have been generated and then buffered before reaching the
  observer. **Arrival timing is evidence of delivery, never proof of contemporaneous child
  execution.**
- The **gap between generation and receipt**, per line, is the only figure that speaks to delivery
  lag, and it is reported per line rather than averaged.

Record the highest counter and latest generation stamp seen before `t0` explicitly — the baseline the
whole question turns on.

### 5.1 Input — three distinct observables, never conflated

During the freeze, send inputs each carrying a **unique tag**, never a repeated keystroke. For each
tag, three things are separately observable and are **recorded separately**:

1. **terminal echo** — the tag appearing in the stream. Proves the byte reached the terminal's echo
   path. **Proves nothing about the child.**
2. **the child's tagged acknowledgement** — output the child itself emits naming that tag.
3. **resulting state** — an effect the child produces that is distinct from both its echo and the
   tag's text.

"The queue drained" may be said only when every tag shows **2 and 3**, in the order sent.

**A missing tag at the observation deadline is "NOT OBSERVED WITHIN THE WINDOW" — not established
loss.** A late drain past 120 s and a genuine loss are indistinguishable to a finite observation, and
the plan records the former wording. A duplicated tag is **replay**; out-of-order tags are their own
finding. Byte counts and "output resumed" establish none of this.

## 6. Cleanup — retention, not deletion

- Stop the isolated daemon **under §2.1's four checks**; confirm its brain and broker are gone by pid
  and by socket, and that every process the run created is gone **by ancestry**.
- **Retain the isolated home and every captured artifact after shutdown** — stderr capture and PTY
  transcript included. No directory deletion is required by this observation.
- **If cleanup is uncertain, preserve — never remove.** An uncertain teardown leaves process
  identities and artifacts intact for inspection and is reported as unconfirmed.
- The captured log is copied out before anything else is considered.

## 7. What the source says, and what that is worth

`broker.rs:2806-2812` states the killed-generation mechanism in the code's own voice and contrasts it
with a cleanly-closed conn being "simply absent". **That docstring describes intended behavior. It is
not independent evidence that the mechanism occurs**, and this plan does not treat it as such.

Its exclusion of `by: Some` controllers from the promotion predicate **does not exclude indirect
effects on those viewports**. Connection identity is **mapped** from the run's own attribution
(`conn=`, session id) and never inferred.

## 8. Unanswered questions — explicit

1. **Q1 — does the outgoing generation's connection close?** **UNRESOLVED.** No existing token
   observes it; `BRAIN_CONN_RETIRED` has one emission site (`livehost.rs:1042`, a session-query error
   path) and would not fire here. Withdrawn, not proxied.
2. **Q3 — does promotion wait on drained state?** **UNRESOLVED.** `local_controller_wedged`
   (broker.rs:2815) is a pure predicate that emits nothing.
3. **Blocked-write onset is unobservable**, so the 15 s sink deadline cannot be confirmed here.
4. **Generation attribution is unverified** on every token except `BRAIN_PROMOTED`.
5. **Executable-to-source provenance is unverified** (§1). Source citations are claims about the
   tree, not about the bytes under test.
6. **A missing `t0`, a missing eviction, or a missing input tag are each "not observed"**, never
   established absence.
7. **One isolated daemon with one session is not the field's condition.** A negative result does not
   clear the field path and must not be reported as doing so.
8. **The clean-close hypothesis is not decidable by this experiment.**
9. **#302 is not merged into this.** Whether it relates is untested and deliberately left open.
