feat(lanhost): admit the bootstrap listener's bound TCP port by port and remote releases#304 W2 / #297, ADR-0059 Amendment 2. A Windows node served /install to its own loopback and nothing else: the installer and firewall.rs create only an executable-scoped inbound UDP rule for QUIC, and lanhost bound TCP 0.0.0.0 with no firewall reconciliation at all. Nothing was wrong with the bind; nothing had ever asked the host to admit it. THE RULE IS WRITTEN FOR THE PORT THAT WAS BOUND. `LanUp::port` is the ephemeral port whenever 0 was requested, and it disagrees with the resolved port in exactly the rig and override cases this surface exists for — a rule built from `resolve_lan_port` admits a socket nobody is listening on and reads green forever. TWO RULES, ONE NAME, TWO AUDIENCES (doyle ruled it, 2026-09-11). The tailnet half admits 100.64.0.0/10 on every profile; the LAN half admits LocalSubnet on Private and Domain ONLY. `LocalSubnet` on every profile would mean a laptop on hotel wifi serving its binary to that wifi's subnet the moment the listener comes up — bounded rather than arbitrary, but wider than the operator's own hand rule and wider than #297 asks for, on a surface whose payload is an executable. One name is still one ownership key: netsh lets rules share a name, a query returns all of them, a delete takes all of them. What the pair costs is a verification rule, and it is the failure mode the split would otherwise have introduced: A PORT MATCH ALONE IS NOT RECONCILED. If the tailnet rule lands and the LAN one fails, every port in the dump is still the bound port, so both remote scopes must be present or the pair is rewritten; `write_lan_rule` returns true only when BOTH adds succeed. PORT+REMOTE SCOPE, NEVER PROGRAM. A program-scoped rule names one executable path and is orphaned by the next rebuild's hash. This is not a style preference: the W-0 preflight measured it on HFENDULEAM before this code existed. A peer's datagram INSIDE a port+remote-scoped range arrived; the identical datagram from the identical binary outside it did not; and three TCP fetches covered only by program-scoped allow rules were dropped, with those rules naming exactly the listening binaries. `profile=any`, AND THAT IS A MEASUREMENT. This lane's own registration commit said a tailnet interface classifies Public and scoped the rule to it. `Get-NetConnectionProfile` on the reporting box says PRIVATE. A Public-only rule would have admitted nothing on the very machine #297 was filed from while passing every RemoteIP assertion a unit cell can make, so the classification — which belongs to the operator's machine, not to this product — is not bet on. The requirement title and the amendment are corrected by replacement, not annotated. TWO SEAMS, NOT A FLAG. `start_at`/`stop` bind and serve and touch no firewall, which is what every fixture wants; `start_admitted`/`stop_admitted` add the reconcile and are what the daemon's serve verb calls. `SPT_INSTALL_NO_FIREWALL` is honoured as a second line, not the first. OWNERSHIP IS NAME PLUS PORT. The rule is "spt-core lan-bootstrap TCP" — deliberately not the hand-made "spt lan-bootstrap 5470" this box carries, and not the UDP `RULE_NAME`. A rule this product did not write is never read, never repointed and never deleted. REFUSAL NEVER READS AS UNREACHABLE, and success never reads as reachability. An unelevated or blocked write leaves the listener SERVING with one warning naming the unmet condition, the exact elevated command, and BOTH layers that produce the identical silence (Windows policy, tailnet ACL) as candidates rather than one confident cause. This node never edits tailnet policy. A created rule is a local fact; only another machine can witness the end-to-end claim. STOP TEARS THE LISTENER DOWN FIRST and returns its result; the removal rides alongside and can only add a residual line. A firewall this process cannot edit must never hold a socket open. Activation rides WITH the evidence, never ahead of it (the rule this manifest states at REQ-DOCS-CHANGELOG-PAGE): TCP-ADMISSION and ADMISSION-REFUSAL-HONEST to doc+impl+unit, RULE-OWNERSHIP-LIFECYCLE to impl+unit. Every `int` stage stays OFF — W-3 delivers them — and REMOTE-WITNESS stays [] until W-2. Six unit cells, each falsifiable by a product-side mutation: the bound port vs the configured one; port+remote+profile scope with `extract_rule_programs` asserting the program-scope negative; the unelevated refusal (still SERVING, exact command, both layers, no reachability claim); the four-row ownership table and three removal outcomes with the two name-collision negatives; non-Windows verify-only; and the CI gate returning before the effector touches the host. NOT CLAIMED HERE: that the reconcile runs at start rather than at bind. There is no listener in a unit cell to produce a bound port, so that ordering is int evidence and rides W-3's lan_bootstrap_e2e rather than a unit cell pretending to it. Builds only — no test executed, on doyle's word. `cargo test -p spt-daemon --lib --no-run` and `cargo test -p spt --bins --no-run` both exit 0 with zero warnings, exits captured directly rather than from a pipeline's tail. releases#304 releases#297 Co-authored by: todlando Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01V8rmw2TwXUSA88iFjYV3ii