doyle -> todlando. W-2 TWO-BOX DISPATCH, Windows leg. Subject = the PRODUCT at 00c4dad9 (asm/304-w2). You: listener side + manual fetcher. liam: sole firewall writer (you summon him with the exact commands below). hertz: the twohost_bootstrap rig on kitsubito, runs its cells at the same arm points (separate dispatch). Read fully before any command; pre-registration first, launch second. WHY THIS SHAPE. REQ-WEB-LAN-BOOTSTRAP-FIREWALL int stage: admission witnessed FROM A SECOND MACHINE; loopback is not a witness; negative controls are part of the requirement: rule absent -> ADMISSION-BLOCKED, listener stopped -> STOPPED, two refusals that must not share a face. Arm E proved the wire faces with a throwaway listener; this leg replaces the throwaway with the product's own listener + the product's own elevated pair write + the product's own stop/cleanup, fetched from kitsubito. GROUND FACTS I MEASURED (02:00Z, unelevated, from .worktrees/asm-304-w2): - lanhost set gate: the listener serves ONLY an APPLIED signed set whose host-triple sha == the running exe sha (lanhost.rs set_gate). A dev exe in a fresh home refuses LAN_BOOTSTRAP_REFUSED:unsigned-exe. Trust root = builtin keys + identity/release-keys.json overlay in the home (release.rs load_from: {"keys":{"":""}}). So a dev-signed set IS servable once the home carries the key. - No seeder exists in xtask or the CLI. crates/spt-daemon/tests/lan_bootstrap_e2e.rs stage_applied_set() is the exact recipe (SigningKey, UpdateSetMetadata, ReleaseCache::stage_update_set + record_applied_state Applied{version}), but it stages IN-PROCESS for an in-process listener; we need a REAL daemon process, so the recipe must run as a tool. - ensure_running spawns current_exe: run the head spt.exe and the daemon IS the head. Socket names hash SPT_HOME (home_tag), so an isolated SPT_HOME never touches the live daemon. - Elevation from an agent shell: decide_elevation_path Windows non-interactive -> PrintHint -> LAN_FIREWALL_ELEVATION_UNAVAILABLE. That is the DENIAL arm for free. - Firewall module is home-independent (verify/reconcile/cleanup take binder+port only). liam runs the head binary elevated with the SAME SPT_HOME and the CLI reconciles in-process (report_lan_admission: verify false + retry + Elevated -> reconcile_lan_firewall). - Census on this box now: group "spt-core bootstrap TCP" = 0 rules; no rule names the asm exe path (control: installed spt.exe path = 3 rows); hand rule "spt lan-bootstrap 5470" (Private, 192.168.1.0/24) and blanket "spt-core daemon" (installed exe, proto Any) both stay. CENSUS RULE: a bare Get-NetFirewallPortFilter enumeration read port 5470 as ZERO unelevated (incompetent); per-rule piping ($rule | Get-NetFirewallPortFilter) read it. Every dump reads filters PER RULE and must show BOTH controls: a 5470 row and an installed-spt.exe row, else the dump is void. - Port for this leg: 29470 (arm E's; named by zero rules on the competent per-rule read; nothing listening). NOT 5470: the live 0.69.0 listener holds it and the hand rule admits it. SETUP (your lane, all in .worktrees/asm-304-w2 at 00c4dad9, its own pool, claimed by my lane asm-304-w2; same tree, build in it, do NOT make a second checkout of 00c4dad9 on this box; sweep stray spt.exe/spt_daemon exes before every cargo as usual). S1. cargo build -p spt --bin spt. Record exit, and sha256 of target\debug\spt.exe = EXE_SHA. Your daemon, your listener, your anchor line all derive from this one file. S2. Seeder, dev tooling, branch build/304-w2-field-seed off 00c4dad9: xtask verb `seed-lan-set --home --exe --version ` that (a) writes /identity/release-keys.json with one dev key id (e.g. w2-field-dev-key) and its verifying hex, (b) stages a set signed by that key with artifacts = { current_platform(): sha256(exe bytes) } (other triples omitted; the listener answers TRIPLE_UNAVAILABLE for them, fine), (c) records Applied{version}. Copy the e2e recipe; no product code touched. If you find a smaller shape (an existing verb I missed), take it and say so. Read-check by me before it rides anywhere; it is a rider only if we keep it. S3. Isolated home: SPT_HOME=C:\Users\decid\AppData\Local\Temp\spt-w2-field-home (set in the shell of EVERY head-spt invocation, yours and liam's). Seed it with S2 against target\debug\spt.exe. Version any nonzero. S4. Controls before A0: (i) `spt serve lan` (NO SPT_HOME, installed 0.69.0) -> LAN_BOOTSTRAP_ALREADY_UP at 5470 (the live listener must be untouched before and after this whole leg); (ii) census dump D0 with the per-rule read: group count 0, asm-exe rows 0, 29470 rows 0, controls 5470>=1 and installed-exe>=1, IsInRole recorded, UTC stamp from the command; (iii) ssh reavus@kitsubito true -> exit 0 (fetch path alive); (iv) hertz reports "rig built, cells listed" before A0. FETCH COMMAND (the manual witness; issue it from HFENDULEAM, the census before and after in the SAME command, exit per producer written into the line, never composed): ssh reavus@kitsubito 'for i in 1 2 3; do date -u +%FT%TZ; curl -sS -o /dev/null -w "%{http_code} connect=%{time_connect} total=%{time_total}\n" --connect-timeout 6 --max-time 15 http://192.168.1.81:29470/install; echo "curl_exit=$?"; done' plus loopback control on this box each arm: curl http://127.0.0.1:29470/install -> code + exit. Faces (pre-registered): ADMISSION-BLOCKED = 000, curl_exit=28, connect ~6.0 s, no reset. ADMITTED = 200, connect in ms. STOPPED = curl_exit=7 (connection refused), fails in well under 1 s. If STOPPED reads as 28 instead, the two refusals SHARE A FACE at the wire: STOP the arm, report it as the finding, do not repair; it decides the rig design (hertz) and possibly the requirement text (me). ARMS (order fixed; each arm = your command(s) with exit codes, census dump, 3x fetch, loopback control; hertz runs his matching cell at the same point; tell hertz + me "A READY" with the UTC stamp before each fetch point). A0 listener UP, pair ABSENT (unelevated, you): `spt serve lan --bootstrap --port 29470`. Expect exit 0; stdout LAN_BOOTSTRAP_UP: http://192.168.1.81:29470/install (port 29470), sha256 x86_64-pc-windows-msvc , LAN-EXPOSED line; stderr in this order: LAN_FIREWALL_ELEVATION_UNAVAILABLE: elevation was denied, cancelled, or unavailable / LAN_FIREWALL_UNVERIFIED: owned admission is absent or does not match the listener; ... Rerun `spt serve lan --bootstrap` to retry admission. Then `spt serve lan` -> ALREADY_UP (denial left the listener running). Census: group 0. Fetch x3: BLOCKED face. Loopback: 200. A1 the product's elevated write (liam, sole writer; elevated shell; SAME SPT_HOME; the HEAD exe by absolute path): `\target\debug\spt.exe serve lan --bootstrap --port 29470`. Expect exit 0; LAN_BOOTSTRAP_ALREADY_UP, LAN_BOOTSTRAP_UP ..., then LAN_FIREWALL_RECONCILED: observed bootstrap-owned TCP admission for port 29470; end-to-end reachability has not been tested. Census: group EXACTLY 2: spt-core-bootstrap-inbound-tcp (profile Any, TCP 29470, remote 100.64.0.0/10, program Any) and spt-core-bootstrap-inbound-tcp-lan (Private,Domain; TCP 29470; remote LocalSubnet; program Any); asm-exe rows still 0. Fetch x3: ADMITTED. Then the byte witness from kitsubito: curl -sS -o /tmp/spt-w2.exe http://192.168.1.81:29470/bin/x86_64-pc-windows-msvc/spt.exe; sha256sum must == EXE_SHA; and the .release.json sidecar's artifact_sha256 == EXE_SHA. A2 repeated start (you, unelevated): `spt serve lan --bootstrap --port 29470` again. Expect ALREADY_UP + LAN_FIREWALL_RECONCILED (the unelevated verify now reads the pair). Census: still exactly 2, no duplicate names. Fetch x1: 200. A3 listener STOPPED, pair PRESENT (you, unelevated): `spt serve lan --stop`. Expect exit 0; LAN_BOOTSTRAP_DOWN; stderr LAN_FIREWALL_ELEVATION_UNAVAILABLE then LAN_FIREWALL_CLEANUP_UNVERIFIED: owned firewall resources remain; ... Cleanup (elevated): . Census: group still 2. Fetch x3: STOPPED face (7). This is the arm that must not share a face with A0. A4 cleanup (liam, elevated, SAME SPT_HOME, head exe): `... spt.exe serve lan --stop`. Expect LAN_BOOTSTRAP_DOWN (or its code) then LAN_FIREWALL_CLEAN: no bootstrap-owned firewall resource remains; exit 0. Census: group 0; hand rule 5470 and spt-core daemon rows STILL present (unrelated rules preserved). Fetch x3: BLOCKED face (nothing listening, no rule; same as E0). A5 optional if under 5 min: SPT_INSTALL_NO_FIREWALL=1 with A0's command -> LAN_FIREWALL_MUTATION_DISABLED, listener up; then --stop; then liam A4 again if anything was written (should be nothing). HARD RULES. One writer: liam, only the two commands above, each UTC-stamped in his own output. Nobody clicks Allow on a Windows Firewall popup if one appears for the asm exe (it would mint a blanket program rule and void A0/A3/A4): Cancel it, record the time, and the post-run census must show asm-exe rows = 0. Every count per agent, never merged. Never `spt node stop` without SPT_HOME set to the isolated home (the live daemon's sockets differ by tag; keep it that way). Exits per producer (IR-107/IR-116): ssh exit is not curl's; write curl_exit into the line. RESTORE. With SPT_HOME set: `spt serve lan` -> NOT_UP; `spt node stop`; then delete the home dir. Without SPT_HOME: `spt serve lan` -> ALREADY_UP at 5470 (live listener untouched). Final census = D0's counts (group 0, asm-exe 0, 29470 0, controls present). REPORT to me: one table per arm (command, exit, stdout/stderr lines quoted, census counts, fetch lines, loopback), EXE_SHA, the seeder diff location, and the D0/final census diff. If any arm's face differs from the pre-registration, stop there and report; do not continue to the next arm on a surprise.