Repository navigation
Owner marker: admit a remote borrowing stack under a documented token-plus-allow-list condition (#688) - #689
Draft
pithead-developer[bot] wants to merge 8 commits into
Conversation
…-plus-allow-list condition (#688) The #670 busy guard only exempted a loopback-only harness, which pithead's real-upgrade leg (reaching the rig over the LAN) can never satisfy. The owner marker now holds three lines — live pid, the harness's temporary ACCESS_TOKEN, and the token it replaced — and the guard admits a request only while that token is the active one, api_allow_from pins exactly one host, and the receiver recorded that host as the change's source. Pid-only markers admit nothing; markers whose token or allow-list are still the rig's own keep answering busy. Co-Authored-By: Claude Code <noreply@anthropic.com>
…-i 4) Co-Authored-By: Claude Code <noreply@anthropic.com>
…its first line (#688) Co-Authored-By: Claude Code <noreply@anthropic.com>
Contributor
|
FAIL What I investigatedReview details are in the private record because they contain sensitive information. Reviewed head: What are the gaps
|
…pped sources (#688) Review round: the appliers now read the recorded source before _claim_staged, so a concurrent stage's orphan prune can no longer delete a claimed change's peer file; the prune logs its unlink failures; the guard admits a dual-stack receiver's ::ffff:<v4> source against an IPv4 pin; docs state that the token swap takes effect on receiver restart and that the guard checks ACCESS_TOKEN alone. Test table gains bare-IPv6, /128, IPv4-mapped and wide-prefix rows, a static consume-before-claim assert, and drops added comments. Co-Authored-By: Claude Code <noreply@anthropic.com>
The pithead-control fixture job keeps the runner's rig lock through restore, so the replay applies landing under the three-line marker are applied-despite-the-lock, and a new end-of-phase probe posts a current scalar from the rig's global address (outside the 127.0.0.1/32 pin): the guard must settle it busy, which discards the change and leaves no residue. Offline harness drives the probe honest: busy settles, an applied non-pinned change fails the phase (busy-applied), a refused port skips (busy-refused). Co-Authored-By: Claude Code <noreply@anthropic.com>
…obe (#688) The table in tests/worker-proof.py is maintained by hand and test-worker-proof.py keeps it equal to the count derived from the harness sources; the new in-phase ok raises each replay group 9 to 10. Co-Authored-By: Claude Code <noreply@anthropic.com>
Co-Authored-By: Claude Code <noreply@anthropic.com>
…r the merged connect body) Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What's the problem
rigforge#678 made
control_upgrade/control_applyanswerbusywhile a bench holds the rig reservation lock, so a borrowing stack's changes can't land mid-loan. The only exemption was the #670 owner marker — a file naming a live pid — and docs/operations.md documented it as reserved for an e2e phase whoseapi_allow_fromis loopback-only. pithead#3985's real-upgrade leg reaches the rig over the LAN, so under that contract a remote borrowing stack can never hold the marker: while its own harness runs, its upgrades getbusyforever. rigforge#687 (parent) tracks the pithead-side consumption; the condition itself belongs here.How I fixed it
The marker becomes self-describing and the guard learns to distrust it until proven otherwise:
ACCESS_TOKEN, and the original token it replaced (empty if none). The borrowing stack swaps the rig's token for its own and pinsapi_allow_fromto exactly one host it controls (loopback or its own address), then restores both and clears the marker after the window._rig_owner_marker_admits(util/control-guards.sh): admits only when the marker is a regular euid-owned non-symlink with exactly three lines, the pid is alive, the marker's token is nonempty, differs from the recorded original, and equals the currently activeACCESS_TOKEN— andapi_allow_frompins exactly one host (bare IP, /32, or /128; unset or any wider prefix refuses) and the change was received from that host. A dual-stack receiver records an IPv4 peer in::ffff:-mapped form, so that mapped form of a pinned IPv4 host admits; nothing else does. A pid-only marker admits nothing (red at the previous head by design), keeping Refuse a remote upgrade while a bench has the rig borrowed; the bench's restore silently undoes it #670's dead-pid semantics.client_address[0]durably asspool/.peer-<cid>before the staged change (peerless stages and orphans are pruned under the same queue lock, and prune now logs unlink failures instead of swallowing them), and the root appliers consume it once via_control_consume_sourcebefore_control_claim_stagedand pass it to the guard. Missing or empty source never admits — fail closed.ACCESS_TOKENSentries are intentionally not checked (that would break the One access token per consuming stack, so a bench borrow never needs prod's token #516 e2e phase); the docs state pinning them is a harness duty for the window, and that the token swap takes effect only once the control receiver restarts with the new config — the ordinary config-apply path does. The status vocabulary and tests/contract/v1 are unchanged.The review round on af5ae99 reproduced a race in the first ordering (a concurrent stage's orphan prune could delete the peer file of an already-claimed, not-yet-consumed change), flagged that a
::ffff:-mapped source fails an IPv4 pin on a dual-stack receiver, and caught two docs claims the docs hadn't actually made. The approach stands; the ordering, normalization and docs move: consume-before-claim closes the race structurally rather than by lock-window timing, and the two doc sentences were added.Here is the proof
make test-suite):tests/test-control-upgrade.shcovers all three AC cases — admit for the pinned borrowing host (swapped token, red at current head: a pid-only marker now assertsbusyinstead),busyfrom an address outside the pinned host, andbusyfor a marker whose token or allow-list is still the rig's own — plus branch rows for bare IPv4, bare IPv6,/128and the IPv4-mapped admit, no-source, wide-prefix (192.0.2.0/24,fd00::/64) and dead-pid refusals, driven table-style through the realcontrol_upgrade/control_applyfixtures, and a static assert pinning consume-before-claim in both appliers.tests/test-control-spool-cap.pycovers the peer twin, the cap ignoring peer files, prune of peerless/torn-down twins, and peer-write failure residue.python3 tests/test-control-spool-cap.pyandtests/control-revocation.pypass;FILE_BUDGET_REQUIRE_BASE=1 bash scripts/lint-file-budget.shgreen (both grown files kept ≤400 lines, no new ceilings); shellcheck clean at warning+; the admit rule's address-form matrix checked by a standalone probe (7/7).upgrade/replaynames — the upgrade applier exercises insidecontrol, and the replay phases arecontrol-replay-thermal/control-replay-pools, which only a runner-queuedpithead-controlfixture job may run, because the fixture is what keeps the reservation held through restore (ordinary tier4-e2e jobs hand the rig lock to the harness, so anappliedthere proves the marker/token contract but not admission against a held lock). Job 3834 rancontrolandaccess-tokenson f2655ad: both phases set the three-line marker exactly as the harness contract says and reachedapplied(11 rows; its only red was[control] miner or refresh-state observation failed, a bench miner-observation flake with no issue linked, absent on the rerun) —bench-ci evidence 3834@f2655ad569c8a4ae376d608933e06244274a554a. To make the reservation-holding and busy proofs real rather than asserted, this head adds an end-of-replay probe: with the marker set and the runner reservation held, a POST of a current scalar from the rig's global address (outside the127.0.0.1/32pin) must settlebusy, which discards the change; the offline replay harness drives it (busy settles, an applied non-pinned change fails the phase, a refused port skips) and the worker-proof counts follow. Three rounds of fixture-job submissions for the replay phases (6 jobs, two pushed heads, two bench nodes, two days) have each errored ~8s after start with checkout success and reserve failure —no free worker compatible with the pithead-control pool probe; verify approved input and source identities, zero phase rows — including the latest pair on head f823c02 (bench-ci evidence 3906@f823c022401a0f04346a6940a4251e1265d9a99c); develop has since merged into the branch (worker-proof pass-count table conflict resolved to the derived 12, full CI tiers 1-3 green on the merged head) so submissions no longer hit the base-conflict gate, and the fixture jobs on that head (bench-ci evidence 4023@775e173eb635b7697e9607faf216b44373483e52) errored with the same reserve refusal in 8s while bench health showed healthy nodes with free lendable rigs — seven identical refusals across four rounds over four days is a stable bench-side pairing state, not load: a bench operator must verify the fixture pool-probe approved input and source identities before any fixture job can run, and the evidence will be quoted here the moment one does; re-checks of bench availability reported it clear twice and reserve still found no compatible free worker. This is a bench-side pairing state this PR cannot change, so the reservation-held admit/busy proof is blocked on the bench operator verifying the fixture pool-probe input and source identities, and will be quoted here the moment a fixture job runs.ponytail-review: minimal — one guard helper + one consume helper + receiver source record; the two reviewer passes (verifier, security) returned clean with no dropped lines.
Closes #688
🤖 Generated with Claude Code