feat(worker): claim-exempt secondmate message lane (R2/R3 PR 3) - #266
Merged
Conversation
…ant carve message-put/message-collect as their own provider verbs dispatched like inventory: bounded, content-addressed, idempotent data-plane blob transfers in the slot's own state container, enforced to the session/ namespace inside the op functions. Controller commands verify the exact assigned worker read-only and never touch claims, leases, or controller.json. The carve sentence lands next to the claim contract in docs/azure-workers.md.
… pin Fixture provider grows the two message verbs; the real provider ops run hermetically against a stubbed az for bounds, content addressing, replay, namespace, and collect semantics; the e2e unit proves message-put succeeds across an outstanding execute claim while a fresh compute action refuses; the static unit pins inventory to exactly three named blob reads and the message commands to zero claim-machinery call sites.
…ut replay proves the digest Adversarial review of the message lane: an existing local name is now judged without a transfer (digest metadata when stamped, exact size for digestless guest blobs), so a poll no longer re-downloads the whole outbox and cannot grow past the deadline; the 4096 hard refusal is replaced by a name-ordered --after cursor with a bounded marker walk, a per-call processing page, and a per-call transfer budget equal to the constant the subprocess deadline is sized from; put replay convergence now requires the stamped content_digest, refusing same-length different bytes and digestless foreign writers; the interim both-roles scope and the PR 4 assignment_generation delivery-fencing contract are stated in the docstrings.
This was referenced Aug 20, 2026
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
R2/R3 PR 3 (R2R3-DESIGN.md section C item 3, contract from B.1/B.4/B.9/D.3): the claim-exempt secondmate message lane. Includes the fixes from the adversarial review (incremental collect, digest-proven put replay, role-scope and fencing contract lines).
bin/fm-azure-worker-provider.py: two new raw provider verbs,message-putandmessage-collect, dispatched exactly likeinventory(no claim, no lease, no landed-code gate, never reachable through themutateverb).message-putuploads one bounded payload to the slot's own state container at a content-addressed name:session/in/<sha256>.json(valid JSON, 262144-byte bound) orsession/in/attach/<sha256>.bundle(binary, 256 MiB bound, no JSON requirement). Idempotent by content address: a replay converges only when the existing blob's stampedcontent_digestmetadata matches the computed digest (and the size matches); a mismatch, a same-length different-bytes blob, or a digestless foreign writer refuses.message-collectwalkssession/out/incrementally: an existing local name is judged WITHOUT a transfer (digest metadata when the writer stamped it, exact size for digestless guest-written blobs; the monitor's chain verification owns full integrity), so polls never re-pay collected history; new blobs download under a per-call transfer budget equal to the attach ceiling; the walk takes an optional--after <local-name>cursor, follows the service continuation marker across truncated listing pages (bounded), processes one bounded page per call, and reportscursorandmoreinstead of ever hard-refusing a deep mailbox. It does NO chain verification (the PR 4 monitor owns that) and never deletes or overwrites a local file: divergence refuses.require_session_blob_nameenforces the D.3 namespace boundary inside the op functions (any name outsidesession/, or a traversal alias, raises ProviderError), and the docstrings state the carve.bin/fm-worker-lifecycle.py:message-put/message-collectcommands. Each verifies the exact assigned worker with command_execute's own gates (assigned status, exact assignment generation, no release proof), read-only under the fleet lock, then calls the provider WITHOUT make_action/claim_pending/apply_pending/slot_lease: the one deliberate claim-exempt carve (design B.1). Neither op ever writes controller.json. The collect subprocess deadline is sized from the same constant the provider's per-call transfer budget equals, so the deadline covers what one call can actually fetch. The message verbs stay out of ACTION_TYPES so a message spec can never be stored as a pending claim.bin/fm-worker-lifecycle.sh: dispatch entries through the gated lane.docs/azure-workers.md: the B.9 carve sentence, verbatim, placed in the claim-contract paragraph.Why
A leg's execute claim occupies
pending_actions[slot]for its whole wall andclaim_pendingrefuses any different key on that slot, so a claimed message lane could never deliver during a leg, precisely when delivery matters (design B.1, defect ii of proposal A). Idempotency for this payload class comes from content addressing, which is stronger than a claim; the exemption is safe because the ops touch no compute, no money, and no lifecycle state.Contract notes for PR 4
message-putcan land in a recreated slot's container. The PR 4 monitor therefore stampsassignment_generationinside every message envelope and the session runner refuses envelopes naming a foreign generation. The contract line lives in the provider module docstring and themessage_putdocstring so PR 4 implements both sides.Tests
bash tests/fm-worker-lifecycle.test.sh: 22/22 units green (19 existing + 3 new), plus green runs of fm-azure-pilot, fm-spawn-cloud, fm-azure-runner, fm-gate-refuse, fm-worker-outcome-transport, andbin/fm-lint.sh/ shellcheck on the touched shell files.message_lane_provider_contract: drives the REAL provider ops against a stubbedazthat counts download invocations. Covers content-addressed naming; replay convergence with zero re-upload; exact refusal strings for the 262144-byte JSON bound, non-JSON payloads, empty payloads, and the attach bound (via the constant seam, never a 256 MiB fixture); replay divergence via digest metadata including the same-length different-bytes case and the digestless-foreign-writer case; namespace guard refusals (foreign names, traversal aliases, nested paths, unbounded sizes, symlinked local targets, malformed cursors); collect proving existing-name blobs are NOT re-downloaded (download counter pinned across the skip, the divergence refusal, and the digestless same-size skip), digestless size-mismatch refusal, cursor pagination collecting a mailbox deeper than the per-call page across three calls (page seam), the bounded marker walk collecting a five-blob mailbox through two-entry listing pages in one call, and the per-call transfer budget stopping early with an honest cursor (budget seam); plus an end-to-end stdin invocation of the real provider binary proving main() routesmessage-putinto the same bounded op.message_lane_claim_exemption: THE test the design says proposal A would have failed. Wedges a durable execute claim on the slot (outcome-disposition skew), thenmessage-putandmessage-collectboth succeed across it; positive control: a fresh compute-mutating execute on the same slot still refuses with the different-key claim refusal, and the durable claim survives the message traffic byte-identically. Also proves controller.json is byte-identical across every message op, CLI content-address/replay/attach behavior, collect fetch/skip/divergence/cursor (--after) through the wrapper, and the refusal shapes for unassigned tasks, wrong generations, and malformed flag pairs.message_lane_static_contract(design D.3): AST pin that providerinventoryreads exactly three named blob records (reservation.json,request.json,result.json) throughblob_record's exact-name show and contains no blob-list call; pins the message commands to zero claim-machinery call sites,mutatestill refused outsideprovider_mutate, message verbs absent from ACTION_TYPES, the size constants in step across controller and provider, the transfer budget equal to the deadline-sizing constant, the mailbox-depth hard refusal staying gone, the never-re-downloads and role-scope docstring lines, the assignment_generation fencing contract in both the module and op docstrings, the verbatim carve sentence within four lines of the claim contract, and the wrapper dispatch.Mutation proofs (each proven red, then reverted)
message_put->message_lane_provider_contractred:AssertionError: no refusal containing 'message payload exceeds its 262144-byte bound'.require_session_blob_nameto accept any string -> red:AssertionError: no refusal containing 'outside the session/ namespace'.command_message_putthrough the durable claim path (claim_pendingbefore the provider call) ->message_lane_claim_exemptionred:AssertionError: a message op rewrote controller.json(and the static pin also goes red on theclaim_pending(call site).AssertionError: no refusal containing 'differs from its content address'on the same-length different-bytes case.Honest limits
d2-worker-probe-evidence-2026-08-20.json); this PR's tests are hermetic against a stubbed/fixture az by design.