feat(worker): secondmate release authority and compartment status (R2/R3 PR 6) - #271
Merged
Conversation
ruby-dlee
force-pushed
the
fm/secondmate-release-authority
branch
from
August 20, 2026 14:12
8aae62c to
c6f3a12
Compare
…artment status projection
…partment status units
…mpleteness, and lineage
…ildless-surrender units
…urable chain extent
…closed on durable state
…on cannot be masked
…in-tip command gates
ruby-dlee
force-pushed
the
fm/secondmate-release-authority
branch
from
August 20, 2026 16:15
1cb4481 to
a0d8ad0
Compare
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.
Closes the R2/R3 release lane for secondmate compartments: the authority tool gains a compartment evidence mode,
surrendergains--confirm-orphan-children(retiring AMENDMENT 1's temporary live-children bypass), boundedstatusprojects live compartments, anddocs/azure-workers.mdcarries the B.9 delta sentences.Design refs
R2R3-DESIGN.mdB.7 (release and the receipts), B.9 (doc rewrites), C item 6 (this PR's scope), B.8 crash matrix row 3, AMENDMENT 1 (the bypass this closes).What changed
Compartment evidence mode (
bin/fm-worker-authority.py). Samefm.worker-release/v2bundle, same five receipt names, selected by the controller-owned worker role:fm_backend_target_stateoracle (unchanged).report_evidence's orderedcompletion.mdcontract reused verbatim.command_releaseowns.verify_release_against_workerandrelease_receiptare untouched; the compartment bundle verifies through them unmodified (zero diff, asserted below).Children-quiesced in
command_release: verified already landed in PR 1 at the exact refusal string. No code change; this PR adds the positive control.Surrender orphan confirm: a parent whose queue holds non-complete children refuses unless
--confirm-orphan-children. With the flag, every live child's queue entry gains a durablereparented_to: "primary"note in the same lock hold that records the surrender.Status projection: a
compartmentssection built fromcontroller.jsonfields only, additive.Adversarial review round (all findings addressed)
An adversarial review found two merge blockers, both proven end to end. Both are fixed with units and mutation proofs.
1. BLOCKER - the evidence mode trusted a local, operator-writable metadata line. Mode selection read
kind=from the task meta and never consulted the controller-ownedrole, though the whole worker record was already in hand. Proven: flipping one meta line (plus planting two compartment state files) let arole=authorworker holding an unlanded commit mint all five receiptsverdict=proved, pass the real release byte-path, and move the queue toreleasingwith the work still only local. Fixed:roledecides, and role/kind disagreement refuses in both directions, fail-closed, before any evidence runs. Second half, same finding: the compartment landing receipt was bound only to local files, with no tie to controller-owned repository lineage; it now applies the samemerge-base --is-ancestortether againstbindings.repository_generationthat the ordinary lane uses.2. BLOCKER - the landing proof was anchored inside the attacker's write set (three rounds). Each round narrowed it without fixing the shape:
state/directory as the mailbox, so F1/F2/F3 still minted.verified_tipread from that same attacker-writable file. So G1 (fresh self-consistent chain), G2 (one-entry re-genesis), and T7 (M9's forgery plus exactly one more field write, entry 1 left byte-identical) all minted and passed the real release CLI.The tip is the security boundary, so it now comes from the only trustworthy place in the system - the same controller-owned provenance that closed blocker 1:
compartment-chain-tipcommand records the verified tip on the worker record under the controller lock, monotonically: a rewind refuses, and the same sequence with a different digest refuses. Deliberately not part of the message lane - PR 3's invariant is that message ops touch no lifecycle state and a static test pins it, so recording the tip there would trade one hole for another.state.get("verified_tip")appears nowhere in the authority.Consequence, stated plainly: PR 4's monitor must be taught to call
compartment-chain-tipwhen its tip advances. Until it does, no compartment can release through the ordinary authority - they exit throughsurrender, which is the sanctioned lane and already exists. The refusal says exactly this.Verified on the reviewer's own harnesses repointed at this head:
r3.sh- F1/F2/F3/G1/G2/G3/G4 and H1-H9 all refuse;t7e2e.sh, extended to record a controller tip so it can distinguish fixed from bricked - the honest compartment with its bundle landed MINTS (rc=0), the honest-unlanded one refuses, M9's forgery refuses at the content address, and T7 refuses atdoes not reproduce the controller-owned verified tip.2b. BLOCKER (round four) -
landed_bundleswas a bare assertion. With an honest content-verified chain and an honest controller-owned tip, the receipt still took LANDING on trust:landedcame straight out oflanded_bundlesin the attacker-writable durable file, and the home worktree was never checked for the bundle's commits. Naming a digest there minted all five receipts and passed the real release with the commits nowhere in the home - one file write, no CLI and no hashing, falsifying the receipt's own sentence.Fixed by proving it from material already on disk: each declared or collected bundle's own tip commit (
git bundle list-heads) must be an ancestor of the home worktree's HEAD.landed_bundlesis now advisory and never decides; a declared bundle whose collected file is gone refuses, because nothing then names the commits it carried. Both the landing and worktree receipts share one proof function.Verified on the reviewer's
r4b.sh, extended with a real git bundle (theirs used a placeholder byte string, so it died at bundle parsing rather than at ancestry) and an honest control:landed_bundles=[](truth) → refuses:its commit 100e7253247e is not reachable from the home worktree headlanded_bundles=[65268244...](the lie) → refuses identicallylanded_bundles=[]→ rc=0 MINTEDAccepted trust boundary, stated plainly:
compartment-chain-tipattests without verifying. It never reads the mailbox, so a CLI-capable caller can record a tip for a chain they are about to plant (the reviewer's A1 and A2). This is not merge-blocking - round four raised the bar from "write a file understate/" to "invoke the controller CLI", the same class asrole- but the verb is an unverified attestation whose whole value is caller trust, and no privilege separation exists between "can writestate/" and "can execbin/fm-worker-lifecycle.sh". Making the verb verify the chain would only relocate the trust, so it is documented rather than papered over.3. MINOR - dangling
.chain-breaksymlink bypassed the freeze.exists()follows symlinks; nowos.path.lexists, matching theis_symlink()care taken elsewhere in the file.4. MINOR - the compartment worktree receipt was laxer than the ordinary one. It used
--untracked-files=no, so untracked never-added work passed. The justification did not survive scrutiny: the repo's.gitignorealready excludes the home's runtime paths, which--untracked-files=allnever lists anyway, and the monitor's gate answers "can I fast-forward here" while a release receipt answers "is anything left". Now--untracked-files=all, wrong justification deleted.5. MINOR -
orphaned_childrenwas unconditional. Every receipt'sevidence_digestis taken over the surrender block, so an unconditional"orphaned_children": 0moved all five digests on the ordinary childless lane. Now scoped to surrenders that actually orphaned children, pinned by a unit that recomputes each digest. My earlier claim that the childless matrix was "byte-unchanged" was true of the refusals but not of the minted bytes; it is now true of both.6. Residuals, confirmed and recorded.
report_evidenceistext.find()plus monotonic position, not a heading contract, so it accepts headings inside a fenced code block, mid-sentence in prose, concatenated with no whitespace, or as a body-less skeleton. Per B.7 it stays verbatim (it serves the ordinary lane too), sosecondmate_report_sectionsadds a compartment-only layer requiring each heading to open its own line, outside any fence, with content. R8 - a single.as each section body - still passes that layer; it is cosmetic, cannot hide unlanded work or a missing closeout, and closing it would mean inventing a prose-quality bar the ordinary lane does not have.json.loads), so trailing newlines, pretty-printing, and raw key reordering are tolerated. Any semantic change moves the digest, so it buys an attacker nothing - now stated in the docstring rather than left for the next reader..gitignorewithsecret*and leavessecret_work.pyon disk proves as quiesced, because--untracked-files=alldoes not list ignored files. This is byte-for-byte the ordinary lane's check.Named follow-up, NOT fixed here: worktree redirection (pre-existing, both lanes)
bin/fm-worker-authority.pytakes the compartment home path fromvalues["worktree"]in the attacker-writable task meta, andworktree_bindingis only copied into the proof, never checked against that path. Cloning the home into a throwaway directory, fetching the bundle there, and pointingworktree=at the clone MINTS and releases while the real home is still at base.This is pre-existing on base main and affects the ORDINARY lane too (proven by the reviewer against
38ae18ef), so this PR inherits it rather than introducing it, and it is deliberately out of scope here.The cheap fix for the follow-up:
worktree_bindingisdigest_value({"worktree": str(worktree), "git_dir": str(git_dir)})(fm-worker-lifecycle.py), andsecondmate_worktree_evidencealready computes both values - recomputing that digest and comparing it againstworker["bindings"]["worktree_binding"]refuses the decoy and passes every honest control. Applying the same toworktree_evidencecloses the ordinary lane.Test evidence
tests/fm-worker-lifecycle.test.sh- 30 units (25 before, 5 added):tests/fm-worker-authority-secondmate.test.sh- new, 5 units, hermetic:The landing units cover all three forgeries verbatim (F1, F2 plain and F2 isolating, F3 across five malformed shapes plus six malformed tips), a re-genesis chain, truncated/wiped/symlinked mailboxes, the lineage tether, and the honest "provably none" shape. The role cross-check unit reproduces the reviewer's escalation exactly: a real bare
originwith the base commit pushed and an unlanded commit on top, so the ordinary lane reaches its own landing proof and refuses. It asserts both refusal directions, the ordinary control, and a positive control. The mailbox units cover emptied-in-place, wiped, symlinked, verified-tip-only, and genuinely-empty-and-provable. The e2e unit still drives the real authority tool and the realreleaseCLI against real durable controller state.Fixing finding 2 exposed that my original fixture was unrealistic (
delivered_sequence=2with a single message file); it now builds a properly chained 1..N mailbox, which is a truer pin on PR 4's contract.Mutation proofs (13 total; each red then reverted, tree verified clean)
not ok - the surrender orphan-confirm contract is not enforcedreparented_tostampingKeyError: 'reparented_to'no refusal: lacks the terminal session statusAssertionError: []AssertionError: (0, '')- rc 0 where 2 requiredno refusal: a rewound or truncated outbox.chain-breakfollows symlinks againno refusal: frozen by a recorded outbox chain breakno refusal: not quiesced: uncommitted or untracked work remainsno refusal: content differs from its content addresscarries no verified chain tipwhere the delivered-sequence refusal was requiredno refusal: controller-owned worker record carries no verified chain tipAssertionError: L0: unlanded work proved landedlanded_bundlesauthoritative againAssertionError: L1: a lying landed_bundles still proved landingIsolating 9 and 11 each took several attempts, because cheaper gates kept catching my first synthetic forgeries and would have let me claim proofs that isolated nothing. The isolating attack for 9 keeps the mailbox exactly as the monitor wrote it and rewrites only the leg summary's body (deleting the collected bundle file too), so only the SHA-256 recompute stands. For 11 the tip must be genuinely absent from the worker record, not a sentinel value.
PR-4 coupling
Re-verified after rebase against MERGED
bec75a92(PR #269), byte-identical to the branch tip originally read: terminal status values and their leg-summary acks,last_summary/landed_bundles/kept_bundles/delivered_sequence/verified_tip, and the mailbox naming plus sticky.chain-break. Nothing is owed back to PR 4; its own suite stays green.Verification
bin/fm-lint.sh(full repo, 246 scripts): rc 0.test-capabilities.tsvandbehavior-test-durations.tsv.rv/atk/e2e.sh, repointed at this head): F1/F2/F3 refuse, honest control mints, honest-unlanded refuses.verify_release_against_worker: zero diff againstorigin/main.