# Twohost execution plan v2 -- REQ-WEB-LAN-BOOTSTRAP-FIREWALL int stage (candidate b6bbaf22)

hertz 2026-09-12 06:1xZ. Supersedes v1 (06:07Z) on all six points doyle raised. QUEUED AFTER A7, NO GRANT.
v1 is kept beside this file; this is a replacement, not an annotation.

## Geometry (unchanged, and the reason is the reason)

Cells are role A, the FETCHING side; the file has no role B. The pair being witnessed is Windows firewall
state, so HFENDULEAM SERVES and the three arms EXECUTE ON KITSUBITO. Role A must separate a DROP from a
REFUSAL: on Windows a refused connect costs ~2.05 s (IR-120), on Linux closed and open ports sit within
66 us, so the two faces separate by KIND on Linux and by nothing reliable on Windows. No latency threshold
appears in any assertion.

## 1. THE LISTENER AND THE PATH ARE BRACKETED; NEITHER FACE IS READ ALONE (doyle 1)

A timeout does not establish a firewall drop (a blackhole route, a dead listener or a wedged accept loop
produce the same face), and a RST does not establish that a listener answered (a closed port RSTs with no
listener at all). So every arm carries, in the same window:

- SERVER-LOCAL LISTENER LIVENESS, on HFENDULEAM, before and after the arm:
  `Get-NetTCPConnection -LocalPort 29470 -State Listen | Select-Object LocalAddress,OwningProcess`
  plus `Get-Process -Id <OwningProcess>` -- the listener is proven UP by the OS, not by its own log line.
- INDEPENDENT PEER REACHABILITY, from kitsubito, in the same command as the arm, on a port that is NOT the
  tested one: `ping -c 3 <server>` and `curl -sS -o /dev/null -w '%{http_code} %{time_total}\n' --max-time 10
  http://<server>:5470/install; echo "curl_exit=$?"`. 5470 is the operator's admitted surface and is READ
  ONLY here -- never altered, never created, never removed (doyle 6). A 200 on 5470 while 29470 times out is
  what makes the 29470 timeout a PORT-SPECIFIC drop rather than a dead path.
- The blocked arm's verdict is therefore a CONJUNCTION: 3/3 TimedOut on 29470 AND the listener proven
  listening on 29470 AND the peer proven able to reach this host on another port, all inside one window.

## 2. THE BLOCKED PRECONDITION IS AN EFFECTIVE-ADMISSION CENSUS, NOT AN OWNED-PAIR COUNT (doyle 2)

Pair ABSENT does not mean admission absent: this box carries ~26 preserved enforced program-scoped Private
rules naming target/debug and actions-runner binaries including spt.exe (IR-111's population), and any of
them may admit the very executable under test on any port. Before the blocked arm may run:

- Enumerate ActiveStore effective rules and keep the ones whose filters ALL cover the tested flow -- the
  CONJUNCTION, per IR-115: Enabled True, Direction Inbound, Action Allow, Protocol TCP, LocalPort covering
  29470, Program covering the serving executable's path, Profile covering the serving interface's category,
  LocalAddress covering the serving address, RemoteAddress covering kitsubito's address. Report
  EnforcementStatus for each, never PersistentStore presence or a rule's name.
- The precondition holds only if that set is EMPTY. If it is not empty, THE PRECONDITION IS MADE TRUE BY
  CHOOSING THE SUBJECT, NEVER BY EDITING THE HOST: serve from a COPY of the product binary at a path no
  program rule names (its own sha recorded, see 4), and/or move to a port no rule covers, re-running the
  census until the set is empty. NO operator rule is disabled, deleted or narrowed -- 5470's rules least of
  all -- and if no subject choice empties the set, the blocked arm is REPORTED UNRUNNABLE on this host and
  the other two arms still stand. An arm made possible by mutating someone else's admission state is not
  this requirement's evidence.

## 3. CONTROLS THAT PROVE THE CENSUS CAN SEE, WITHOUT ADMITTING THE TESTED FLOW (doyle 3)

v1's control (a rule known to carry LocalPort 29470) could itself admit the flow and defeat the blocked arm.
Replaced, and the replacement CREATES NOTHING:

- Port-filter visibility: the operator's existing 5470 rule, asserted present in the dump WITH LocalPort
  5470 read from its port filter. Proves the port column is readable. Admits nothing on 29470.
- Program-filter visibility: one existing rule asserted present WITH a non-empty Program string. Proves the
  program column is readable. Chosen from the preserved population, read only.
- Both controls must appear in the SAME dump as the census they validate; a dump that cannot show both has
  measured nothing (IR-115). A census whose port test is denied reads every rule as port=Any, so the dump
  also records its own privilege level, and `netsh` forms are used only with `verbose`.
- Negative control for the census itself: a deliberately non-matching filter (a port nothing carries) must
  return ZERO rows in the same dump -- otherwise the filter is not filtering and the empty set in 2 is not
  an empty set.

## 4. THE EXPECTED HASH COMES FROM THE DISK BYTES BEFORE ANY FETCH (doyle 4)

v1 derived the expectation partly from the response under test. Corrected -- the order is now the point:

1. On HFENDULEAM, identify the served artifact on disk independently of any HTTP response: the staged-set
   path for the triple, its size, and `Get-FileHash -Algorithm SHA256`. Record that value as EXPECTED_SHA.
2. Record SEPARATELY the SERVING EXECUTABLE's own sha256 and its source commit -- the binary doing the
   serving is not the artifact being served, and neither is rig candidate b6bbaf22. Three distinct hashes,
   three separate lines in the record: rig candidate, serving executable (+ its source sha), served artifact.
3. Only then set `SPT_TWO_HOST_BOOTSTRAP_SHA=<EXPECTED_SHA>` and run the admitted arm. The cell hashes the
   RESPONSE and compares to that env value, and it already refuses an empty sha -- so the comparison is
   response-against-disk, never response-against-itself. The sidecar's `artifact_sha256` is then a THIRD
   party to the same claim and may disagree with either side; that disagreement is a finding, not a retry.

## 5. EXACT COMMANDS, ONE INTENDED CELL PER INVOCATION (doyle 5)

Build (kitsubito, at the candidate, its own pool, `pool-claim` at lane start):

    cd ~/spt-core-hertz-linux && git rev-parse HEAD   # must print b6bbaf22e1ffda82561f7b3416a57c065dae09fc
    git status --porcelain                            # must be EMPTY
    cargo nextest list -p spt-daemon --test twohost_bootstrap --ignored    # must list exactly 3 ids

Harness liveness control, BEFORE any state is built, no rig env:

    env -u SPT_TWO_HOST cargo nextest run -p spt-daemon --test twohost_bootstrap --run-ignored ignored-only \
      --success-output immediate
    # REQUIRED: exit 101, 0 passed, 3 failed, every failure MISCONFIGURED RIG.
    # A green here means the opt-in guard is gone: STOP, no arm below means anything.

Each arm, exactly one cell selected BY NAME as well as by selector, so a selector mismatch cannot pass as a
result:

    SPT_TWO_HOST=1 SPT_TWO_HOST_ROLE=a SPT_TWO_HOST_PEER_IP=<server> \
    SPT_TWO_HOST_BOOTSTRAP_PORT=29470 SPT_TWO_HOST_BOOTSTRAP_TRIPLE=x86_64-pc-windows-msvc \
    SPT_TWO_HOST_BOOTSTRAP_ARM=<blocked|admitted|stopped> \
    SPT_TWO_HOST_BOOTSTRAP_SHA=<EXPECTED_SHA> \
    cargo nextest run -p spt-daemon --test twohost_bootstrap --run-ignored ignored-only \
      -E 'test(bootstrap_<arm>_from_second_machine)' --success-output immediate
    # REQUIRED: "1 test run", 1 passed, 0 failed. Any "SKIP <arm>: ARM=..." line in the output VOIDS the
    # arm -- a selector-mismatch green witnesses nothing (the cell says so itself).
    # --success-output immediate is mandatory: a nextest GREEN prints the arm's census lines NOWHERE.

Transitions (HFENDULEAM, ONE WRITER, each announced with its UTC time, each census taken by the same command
that makes the transition, before and after):

    # blocked:  listener UP on 29470, effective-admission set EMPTY per 2 (no rule edited)
    # admitted: from blocked -> run the product's own reconcile ELEVATED; census the owned pair present
    # stopped:  from admitted -> stop ONLY the 29470 listener; the owned pair STAYS present
    # 5470 and its rules are untouched in every transition.

## 6. TERMINATION IS DISTINGUISHED FROM A RESULT

Three outcomes that look alike in a log and are not: (a) the cell's own observed timeout -- its eprintln
names face and elapsed_ms and nextest reports the cell; (b) an assertion failure -- nextest reports the
panic line; (c) an EXTERNALLY KILLED harness -- an incomplete attempt whose termination is recorded, e.g. a
137, with no assertion line and possibly no cell output at all. (c) is a VOID arm, never a red, and never
evidence for or against any face (IR-113/IR-116). Every arm therefore records its own wall-clock start and
end, its exit code, and the counts line; an arm whose exit is not accompanied by its cell's own output is
reported VOID and re-run.

## 7. TEARDOWN RESTORES THE RECORDED INITIAL STATE (doyle 6)

Not "removed or left present" -- that was a choice masquerading as a contract. The initial state is RECORDED
before the first transition (effective-admission census, owned-pair census, 29470 listener state, 5470
listener and rule state, process list) and teardown restores exactly it: the 29470 listener stopped, the
owned pair returned to its recorded initial presence/absence, 5470 and its rules verified UNCHANGED against
the initial dump, and every process started for the rig asserted ABSENT on BOTH hosts against a live
positive control. No file server is used anywhere in this rig, including as a route control (IR-119).
Teardown runs in the same breath as the last arm, not after the verdict is named, and its evidence is a
process/state list, never a conclusion.

## 8. What this plan does not claim

It does not establish that the cells pass, and it does not license a landing. Traceability is done (0.4.1
and the pinned 0.2.0 both exit 0 on b6bbaf22; session copies preserved beside this file). int stays
unchanged on the unlanded candidate. If any precondition in 2 cannot be made true without touching state
that is not mine, the affected arm is reported UNRUNNABLE rather than made to work.
