feat(secondmate): hand the compartment's own home to the child spawn as the task home - #279
Merged
Merged
Conversation
…as the task home The compartment child lane had one gap left after the authorized task home landed: nothing set it. spawn_environment deliberately did nothing clever because the capability did not exist, so every child request was minted against the controller's home, came out primary-owned, and refused at verify_request naming owner_kind. The monitor now sets FM_SPAWN_TASK_HOME to the compartment's own home, taken from --home (the leased home the compartment's own spawn recorded in <primary state>/<id>.cloud-worktree), never reconstructed. FM_HOME still stays the controller's, because FM_HOME is what names the ONE money document; the assignment is made after the FM_STATE_OVERRIDE and FM_SECONDMATE_* pops, and its name is deliberately outside SPAWN_CLOUD_ENV_ALLOWLIST so it is consumed once instead of persisted into <id>.cloud-env as a durable foreign-home pin. The split moves four call sites with it, because fm-spawn now reads them from the task home: the child's project resolution, its backlog row, its brief, and its recorded execution result. A relay pass that cannot locate the compartment's own home refuses by name and delivers that refusal, rather than silently falling back to the primary. The docstring is rewritten to say what the method now does, keeping both rejected alternatives (the FM_AZURE_WORKER_STATE_DIR durable trap, and moving FM_HOME at a second money document) as the reasoning that stops a future edit reintroducing either. The real-spawn unit that pinned the old refusal now asserts admission, read back from the money document: owner_kind=secondmate, the parent pair, home_binding naming the compartment's home inside a document whose own home_binding names the primary, children_total incremented, no second controller document, and no FM_SPAWN_TASK_HOME in the generated cloud-env.
ruby-dlee
force-pushed
the
fm/compartment-monitor-task-home-v2
branch
from
August 20, 2026 23:53
314284e to
1ce8c12
Compare
This was referenced Aug 21, 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.
Third and last piece of the R2/R3 compartment-child lane. #278 (merged as
e2683acc) built the authorized task home inbin/fm-worker-lifecycle.pyand taughtbin/fm-spawn.shto acceptFM_SPAWN_TASK_HOME, deriveowner_kindfrom the task home's marker, and forward--task-home. Nothing set that variable. This does, from the compartment monitor, and the lane admits end to end.Base is
main. This branch touches ONLYbin/fm-secondmate-cloud-monitor.pyand its test file; it carries no #278 content.What changed
spawn_environmentnow setsFM_SPAWN_TASK_HOMEto the compartment's own home.FM_HOMEstill stays exactly as the monitor received it, becauseFM_HOMEis what names the ONE money document, and moving it is the trap the docstring refutes.The home comes from
--home, which the wrapper reads from the durable<primary state>/<id>.cloud-worktreethe compartment's own spawn wrote (bin/fm-secondmate-cloud-monitor.sh:75,395). It is not reconstructed from a task id, a naming convention, or a registry read.self.spawn_homeis the CONTROLLER's home and is not it.The assignment is made after the method's
FM_STATE_OVERRIDEandFM_SECONDMATE_*pops, and its name sits outside both popped namespaces. TheFM_STATE_OVERRIDEpop is an interlock, not a nicety:fm-spawnrefusesFM_SPAWN_TASK_HOMEcombined withFM_STATE_OVERRIDE, so a leg-scoped state override reaching this argv would refuse the whole lane. Correcting an earlier revision of this body and of the docstring: the code popsFM_STATE_OVERRIDEonly.FM_DATA_OVERRIDEandFM_PROJECTS_OVERRIDEare NOT popped.fm-spawnrefuses those two alongside a task home as well, so an inherited one fails the lane closed rather than silently re-pointing the child'sdata/orprojects/; it is a refusal this method relies on, not one it performs. The words were broadened, not the code.Four call sites move with the split, because
fm-spawnnow reads them from the task home rather than fromFM_HOME: the child's project resolution, its backlog row, its brief, and its recorded execution result. Without these the lane still refuses, just later and infm-spawn's own voice (see MUT-C below, which shows the brief being looked for under the primary).FM_SPAWN_TASK_HOMEis deliberately NOT inSPAWN_CLOUD_ENV_ALLOWLISTand does not reach<id>.cloud-env. That file is re-sourced by every LATER execute and release for the id, so a name landing there pairs a foreign home with all of them rather than with this one request. The durable record of the split is the queue item's owntask_homefield, which the release lane reads back throughauthority_home.The docstring is rewritten. It used to explain at length that the method deliberately did nothing because the capability was missing; that is now false. It states what the method does, and KEEPS both rejected-alternative paragraphs (the
FM_AZURE_WORKER_STATE_DIRdurable trap, and movingFM_HOMEat a second money document), because that reasoning is what stops a future edit reintroducing either. The module docstring's "NOT YET POSSIBLE" paragraph is replaced for the same reason.One simplification:
resolve_projectis the single place that decides the compartment's own home is usable, and it refuses by name before any side effect. An earlier revision of this branch repeated that check inspawn_child; that duplicate is gone, so the invariant has one owner.Proof 1: the lane end to end, read back from the money document
test_child_request_through_the_real_fm_spawn_with_crew_dispatchused to PIN the refusal, with a comment saying to rewrite it to assert admission once an assertable owner kind and a task-home parameter landed. Both landed. It now asserts admission, through every real component: a real compartment spawned by the realfm-spawn.sh, a real runner-emitted child request, the real relay, the realfm-spawn.shagain, and the realfm-worker-lifecycle.pycontroller.The child's actual entry in the PRIMARY's controller document, dumped from a run of that unit:
{ "assignment_generation": "asg-00000002", "home_binding": "fdd464e9e8ebcc8f7ecf8a9acf8de615747badd2587a4241d684aa0dc7e666f0", "owner_kind": "secondmate", "parent_task": "mast", "parent_task_generation": "spawn:a00f7262dd54d5d2", "role": "author", "schema": "fm.worker-request/v1", "slot": 2, "status": "assigned", "task": "mast-c9e2a9a87", "task_generation": "spawn:a0b18a95d1672ba8", "task_home": ".../spawn-real-spawn/mast-home" }owner_kind=secondmate, the parent pair present,home_binding= sha256 of the COMPARTMENT's home while the document's ownhome_binding= sha256 of the PRIMARY's,children_totalincremented on the parent's worker record, and no second controller document under the compartment home. The unit asserts every one of those, plus that the acceptance was delivered into the compartment inbox and a durable.accepted-*record exists with no.refused-*.Nothing in this lane was simulated. The only fixture is the Azure provider, as in every suite here.
Proof 2: ordinary spawns are byte-identical
git diff --name-only origin/mainreturns exactly two files, neither of which is on any ordinary spawn path:bin/fm-spawn.shandbin/fm-worker-lifecycle.pyare untouched by this branch, so an ordinary crewmate spawn and a LOCAL secondmate home requesting its own crewmates build the identicalrequestargv they build onmain.spawn_environmentis reachable only from the compartment child relay. The suites covering those lanes pass unchanged (below), includingtest_spawn_gate_off_flag_is_byte_identicalandtest_spawn_forwards_the_parent_pair_into_the_request.Proof 3: mutations
MUT-A, the allowlist exclusion. ADMIT-red. Added
FM_SPAWN_TASK_HOMEtoSPAWN_CLOUD_ENV_ALLOWLISTinbin/fm-spawn.shand ran the end-to-end unit. The generated<compartment home>/state/<child>.cloud-env, read off disk:Unmutated, the same file and the same grep:
The red is the durable pin actually landing in the file, not a message change. This is the grep of an actually-generated
.cloud-env, not an assertion about one.MUT-B, the rejected alternative. Replaced
env["FM_SPAWN_TASK_HOME"] = str(self.home)withenv["FM_HOME"] = str(self.home), the move the docstring refutes. Red:Exactly the outcome AMENDMENT 2 predicted: it refuses at parent liveness, naming the wrong cause. Probed the compartment home directly in the same run: no second
controller.jsonwas created, so on merged code that alternative refuses before it can fork the money authority. Stated as measured, not as a guarantee for every world.MUT-C, the assignment deleted (the pre-change state). Red:
The refusal names the PRIMARY's home, which is where the whole lane pointed before this change.
MUT-D, the task home sourced from
self.spawn_homeinstead ofself.home- the home confusion this split exists to prevent. Red:What I could not prove, stated plainly. MUT-B, MUT-C and MUT-D are REFUSAL-reds, not admit-reds, and I am not going to dress them up as admits. I could not construct an admit-red for the env assignment itself, and the reason is structural rather than a gap in the testing: every shape this change can produce is caught downstream by merged code that fails closed. A missing or wrong task home yields
owner_kind=primary, whichverify_requestrefuses the moment it carries a parent pair; a task home pointed anywhere that is not a seeded secondmate home is refused byfm-spawn's marker check; and a task home that survives that is still authorized inside the controller lock against the marker, the primary's registry, and the parent's assigned entry. The one place this change is the sole thing between the system and a wrong outcome is the durable.cloud-envpin, and that is MUT-A.Related: I originally also carried a tighten-only
owner_kindguard infm-spawn.sh(refusing a real secondmate home from naming a marker-less task home to mintprimary). It was discarded before this PR, because #278's requirement that the task home BE a seeded secondmate home already makes that direction unreachable - MUT-D is the executed evidence of that refusal.Rebase onto #277 (
2f359f97)Rebased. The rebase produced exactly two conflicts, both in
bin/fm-secondmate-cloud-monitor.pyand both PURE PROSE: the module-header bullet and thespawn_environmentdocstring.env["FM_SPAWN_TASK_HOME"] = str(self.home)sits in a separate hunk #277 does not touch and auto-merged one line below the closing marker, so taking #277's newer-looking text on either block would have shipped a file whose docstrings state at length that the relay "does not set FM_SPAWN_TASK_HOME" and "sets no FM_SPAWN_TASK_HOME", directly above the line that sets it. No test asserts on docstrings, so nothing would have caught it.Both blocks were resolved in favour of the post-#279 semantics, keeping #277's genuinely new information rather than discarding it: the summary of what #278 landed, that
authorize_task_homeproves the chain under the same lock hold that inserts and nothing in it is self-authorizing (marker, canonical registry link, then the unchanged child bounds), the historical warning that an earlier revision wrongly blamedverify_state's home fence, and #277's "move the TASK home, neverFM_HOME" punchline. The module bullet that read "STILL REFUSED HERE" now reads "ADMITTED HERE, and bounded", and names what still bounds it: depth one, fan-out, lifetime total and parent liveness, enforced byenforce_child_boundsunder the one lock over the one documentFM_HOMEstill names.#277's note calling "wiring the compartment's own home through as the task home" a deliberate follow-up appeared twice, both times inside the two conflict blocks, and both are gone. This PR is that follow-up.
git show 2f359f97confirms no third instance in the.py, and neitherbin/fm-secondmate-cloud-monitor.shnordocs/azure-workers.mdcarries a stale task-home claim.Both paragraphs that exist only on my side survived, confirmed by grep on the rebased file:
tests/fm-secondmate-cloud-monitor.test.shauto-merged. Both PRs' units survive and my fixture changes are intact. On the one reconciliation that matters: no #277 unit pins the refusal my unit now asserts as an admission.test_spawn_forwards_the_parent_pair_into_the_requestdoes assertparent_task is owned by secondmate-owned author requests only, but on a different shape - a--secondmateCOMPARTMENT spawn (role=secondmate) carrying a parent pair, which must refuse by the depth-one bound. Mine is arole=authorchild, which must be admitted. The other refusal units are schema-validation refusals that never reachcommand_request. No two units assert opposite outcomes on the same lane.MUT-A was re-run against the rebased head and still reproduces as an admit-red.
Suites
All through
python3 tests/run-one.py, never barebash, on the rebased head1ce8c12f, with exit codes captured:ok -not oktests/fm-secondmate-cloud-monitor.test.shtests/fm-spawn-cloud.test.shtests/fm-worker-lifecycle.test.shCorrecting an earlier revision of this body, which said the monitor suite had "39 units": that number was never measured. The rebased head has 50 dispatch entries emitting 50
oklines.Disagreement I have to flag rather than paper over. I was told
fm-spawn-cloudandfm-worker-lifecycleare red on macOS at base on merged main, host-coupled, green in CI, and not my regression, and asked to state that baseline instead of claiming a pass. I cannot state it, because I could not reproduce it. I ran both at base in a clean detached worktree on2f359f97and on this head, on macOS (Darwin 25.6.0), and got exit 0 with zeronot okevery time:I also re-ran
fm-spawn-cloudwithFM_HOMEpointed at the operator home in case the operator environment was the trigger: still exit 0. So the reported red is real for whoever saw it but is environment-specific in a way this machine does not reproduce, and I am not going to assert a baseline I did not observe in either direction. What I can say is the measured one: green at base and green on this head, identically, so these suites are unchanged by this PR either way. If the reviewers' environment is the authority here, the delta between the two setups is worth finding before it is written into a doc as settled.Test-fixture changes worth a reviewer's eye
The monitor suite's fixture world encoded the OLD rule in a comment: that the child's authorities live in the home the spawn runs under. Under the split they live in the task home, so
make_worldnow seedsprojects/,data/andstate/in the compartment home, and the assertions follow them there. The two homes are given DIFFERENTLY NAMED projects (alphain the compartment,primary-onlyin the primary) on purpose: a regression that read the primary'sprojects/now names the wrong directory instead of accidentally naming the right one.