help setup-host and the README now lead with what a bare run does (free
default → 10.88; existing bridge → converge; claimed default → auto-pick
10.89…10.127, announced) and demote BOX_SUBNET to what it is: the pin for
scripted hosts, honored or refused, never overridden. The changelog names
the drill/rehearsal payoff — nested box-in-box with zero flags. The
doctor's this-machine #80 verdict stops saying "setup-host now refuses
this" (it no longer does, it picks around it): a poisoned stack predates
the fix or was pinned onto the uplink.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
choose_subnet is extracted with its helpers (the same awk seam as
subnet_claimant) and driven case by case against canned tables under the
shim ip: every pinned refusal unchanged (gateway-claimed, foreign
interface, disagreeing bridge, garbage — and a clearing pin used verbatim,
silently); bridge-present convergence to the bridge's own subnet with no
scan (case 2 precedes case 4) and no announcement on the plain default
re-run; the poisoned state (bridge AND uplink on one subnet) refusing
rather than converging; a free default staying 10.88; the nested tables
auto-picking 10.89 loudly, naming the DEFAULT GATEWAY claimant and the
BOX_SUBNET pin; a doubly-claimed host skipping to 10.90; and all 40
candidates claimed falling back to the old refusal, naming the end of the
scan range and BOX_SUBNET.
The driven whole-script fixtures move with the semantics: the refusal
paths now pin BOX_SUBNET=10.88.0.0/24 explicitly (the unpinned nested run
is no longer a refusal — it is the auto-pick, proven end to end: Host
ready, the announcement, and the bridge + ACL carve-out following the
pick to 10.89). The decision-precedes-mutation line-order check now pins
the choose_subnet call site. 339 → 370 checks.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
#86 shipped the refuse half of #80's fix#1; this completes the other half:
"refuse … or automatically select a non-colliding subnet". The bare
setup-host that used to stop an agent mid-drill inside a box now decides
the subnet itself. choose_subnet is the one place that decision happens,
before any mutation — BOX_GW, the bridge, the ACL carve-out, the firewall
and the doctor's expectations all derive from its answer. Four deliberate
cases:
1. explicit BOX_SUBNET — honored or refused, never silently overridden:
a script that says 10.90 gets 10.90 or a loud stop. The claimant
refusal and the bridge agree-gate are unchanged in spirit.
2. no pin, boxnet exists — converge to the bridge's own subnet: the
bridge IS the pin (boxes hold leases on it). The old agree-gate
refusal on a bare re-run against a moved bridge becomes convergence,
announced when the bridge is off-default, silent on the plain
default-host re-run. A FOREIGN claimant on the bridge's own subnet
still refuses — that is #80's poisoned state, and converging would
rebuild the blackouts; the refusal names the bridge move instead.
3. no pin, no bridge, 10.88.0.0/24 free — the default, as always.
4. no pin, no bridge, default claimed — the nested case (a drill or
rehearsal inside a box, whose own uplink owns 10.88): scan
10.89.0.0/24 … 10.127.0.0/24 in order, take the first free candidate,
announce the pick, the claimant and the BOX_SUBNET pin for scripts;
refuse only when every candidate is claimed. The scan only ever runs
bridge-less — an existing bridge is case 2, which precedes it.
Pure over ip (via subnet_claimant and the bridge read), so test/cli.sh
drives every case against canned tables with the shim ip.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>