From 41a2b62c430f1a61def147bcbd2eeabce5360c7c Mon Sep 17 00:00:00 2001 From: Talon Stark Date: Sun, 6 Sep 2026 11:16:56 -0400 Subject: [PATCH 1/3] fix(bin): refuse the behavior suite in the repository primary checkout A task worker's isolated worktree placement is verified exactly once, when its task starts, and nothing re-checks it afterwards. A worker that later changes directory into the repository's primary checkout runs its Git commands, and this branch-switching suite, against the one checkout every linked worktree resolves against and every landing merges into. A run that dies mid-suite can leave that checkout on a stray branch. bin/fm-test-run.sh now refuses that case. When FM_TASK_ID marks a task worker and the runner resolves to the primary checkout, every executing mode exits non-zero before selecting a suite, with one line naming the primary path and pointing at the assigned task worktree. The predicate is the one bin/fm-spawn.sh already uses for launch placement: the working tree's own git dir is the repository's common git dir, which separates the primary from every linked worktree even when their top levels differ. A run with no FM_TASK_ID set is unchanged, and so are the inspection modes, which execute nothing. When git resolves neither directory - a non-repository fixture, a detached copy - nothing proves this is the primary, so the run proceeds. bin/fm-spawn.sh sets the marker: ship and scout launches export FM_TASK_ID into the pane shell on the same pre-launch channel as GOTMPDIR, and the name joins the sanitized launch environment allowlist so an isolated launch keeps it. --- bin/fm-spawn.sh | 9 +++++ bin/fm-test-run.sh | 43 +++++++++++++++++++++++ docs/scripts.md | 2 +- tests/fm-kimi-harness.test.sh | 2 ++ tests/fm-test-run.test.sh | 64 +++++++++++++++++++++++++++++++++++ 5 files changed, 119 insertions(+), 1 deletion(-) diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index c933a60515f..4988df01301 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -3796,6 +3796,14 @@ spawn_record_traceparent() { # process (go build, go test, ...) inherit it. Sent before the launch command so # the env is set when the agent starts; the brief sleep lets the export land. spawn_send_text_line "$T" "export GOTMPDIR=$TASK_TMP/gotmp" +# Mark the pane as a task worker so bin/fm-test-run.sh can refuse to run the +# suite in the repository's primary checkout. Ship and scout workers are the +# ones assigned an isolated worktree; a secondmate runs its own home instead. +# The id reached a validated bare-slug charset above, so it carries no shell +# syntax of its own. +if [ "$KIND" = ship ] || [ "$KIND" = scout ]; then + spawn_send_text_line "$T" "export FM_TASK_ID=$ID" +fi # Send through the exact channel that already ships GOTMPDIR, so every backend # and harness - ship, scout, and secondmate - gets it before launch. Skipped # entirely when trace context is off. @@ -3819,6 +3827,7 @@ if [ "$LAUNCH_ENV_ENABLED" = 1 ]; then TMPDIR TMP TEMP GOTMPDIR TMUX TMUX_PANE HERDR_ENV HERDR_SESSION HERDR_SOCKET_PATH \ HERDR_PANE_ID CMUX_WORKSPACE_ID CMUX_SURFACE_ID CMUX_TAB_ID CMUX_PANEL_ID \ CMUX_SOCKET_PATH ZELLIJ ZELLIJ_SESSION_NAME ZELLIJ_PANE_ID FM_ZELLIJ_SESSION \ + FM_TASK_ID \ $LAUNCH_ENV_NAMES; do # Only validated names enter shell syntax. Values expand once, quoted, in # the pane shell and never become source text or spawn-process snapshots. diff --git a/bin/fm-test-run.sh b/bin/fm-test-run.sh index 0cd259591b2..9b587ac0a7a 100755 --- a/bin/fm-test-run.sh +++ b/bin/fm-test-run.sh @@ -86,6 +86,15 @@ # FM_TEST_SLOWEST rank= script= duration_ms= # FM_TEST_BUDGET max_wall_ms= duration_ms= (only with --max-wall-ms) # +# Placement refusal: +# A task worker is assigned an isolated worktree, and that placement is +# checked only when its task starts. When FM_TASK_ID marks such a worker and +# this runner resolves to the repository's PRIMARY checkout, every executing +# mode refuses before selecting a suite: the suite creates and switches +# branches, and the primary is the checkout every linked worktree resolves +# against. Inspection modes execute nothing and stay available, and a run with +# no FM_TASK_ID set is unchanged. +# # Exit status is non-zero if any selected script exits non-zero, a configured # --fail-on-gate-skip token appears, the measured duration exceeds # --max-wall-ms, timing-artifact finalization fails, or a concurrent worker @@ -192,6 +201,30 @@ now_iso() { date -u +%Y-%m-%dT%H:%M:%SZ } +# Enforce the placement refusal described in this script's header. +# +# The primary checkout is the working tree whose own git dir IS the repository's +# common git dir; every linked worktree has a git dir under it instead. That is +# the same predicate bin/fm-spawn.sh uses to keep a launch out of the primary, +# and unlike comparing top-level paths it still holds when the primary is +# reached through a different path. When git resolves neither directory - a +# non-repository fixture, a detached copy - nothing proves this is the primary, +# so the run proceeds. +refuse_primary_checkout_for_task() { + local task_id git_dir common_dir top + task_id=${FM_TASK_ID:-} + [ -n "$task_id" ] || return 0 + git_dir=$(git -C "$ROOT" rev-parse --absolute-git-dir 2>/dev/null) \ + && git_dir=$(cd "$git_dir" 2>/dev/null && pwd -P) || git_dir= + common_dir=$(git -C "$ROOT" rev-parse --path-format=absolute --git-common-dir 2>/dev/null) \ + && common_dir=$(cd "$common_dir" 2>/dev/null && pwd -P) || common_dir= + [ -n "$git_dir" ] && [ -n "$common_dir" ] || return 0 + [ "$git_dir" = "$common_dir" ] || return 0 + top=$(git -C "$ROOT" rev-parse --show-toplevel 2>/dev/null) \ + && top=$(cd "$top" 2>/dev/null && pwd -P) || top=$ROOT + die "refusing to run in the repository primary checkout $top while FM_TASK_ID=$task_id is set; run from the assigned task worktree instead" +} + cpu_count() { local n n=$(getconf _NPROCESSORS_ONLN 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 1) @@ -1818,6 +1851,16 @@ case "$PER_SCRIPT_TIMEOUT_SECS" in ''|*[!0-9]*) die "--per-script-timeout-secs requires a whole number of seconds (0 disables)" ;; esac +# Refuse before any suite is selected or run. The inspection modes execute +# nothing: --list-families, --list-concurrent-safe-families, --list-lanes, +# --check-coverage, --concurrent-safe-family-jobs-max and --aggregate-json have +# already exited above, and --list/--list-scheduled print their selection and +# exit below. An unset MODE still falls through to the usage error, so a caller +# who named no selection mode is told that rather than this. +if [ -n "${MODE:-}" ] && [ "$LIST_ONLY" -eq 0 ] && [ "$LIST_SCHEDULED" -eq 0 ]; then + refuse_primary_checkout_for_task +fi + case "${MODE:-}" in all) select_all diff --git a/docs/scripts.md b/docs/scripts.md index e8d301a8967..43d692db5a8 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -38,7 +38,7 @@ The shared no-mistakes gate refusal for fleet lifecycle entrypoints is summarize | `fm-install-herdr.sh` | Install CI's exact-version Herdr pin with official asset URL, SHA-256, and protocol checks | | `fm-install-treehouse.sh`| Install CI's exact-version Treehouse pin for real-Herdr E2E that needs spawn worktrees | | `fm-herdr-ci-cleanup.sh` | Snapshot and tear down only job-owned `fm-lab-*` sessions in the Herdr CI lane | -| `fm-test-run.sh` | Behavior-test runner: selection, portable lanes, bounded concurrency, budgets, coverage guard, timing/JSON | +| `fm-test-run.sh` | Behavior-test runner: selection, portable lanes, bounded concurrency, budgets, coverage guard, timing/JSON; refuses to execute in the repository primary checkout when `FM_TASK_ID` marks a task worker | | `fm-test-isolation-proof.sh` | Concurrent isolation harness and portable candidate set owner | | `fm-ensure-agents-md.sh` | Ensure a project's real `AGENTS.md`, its `CLAUDE.md` `@AGENTS.md` pointer, and self-governance guidance (explicit project mark documented in the helper's header and help) | | `fm-guard.sh` | Warn on primary-checkout tangles, main-session pending wakes, and unhealthy supervision | diff --git a/tests/fm-kimi-harness.test.sh b/tests/fm-kimi-harness.test.sh index 85578775f22..5235cb0d15a 100755 --- a/tests/fm-kimi-harness.test.sh +++ b/tests/fm-kimi-harness.test.sh @@ -222,6 +222,8 @@ test_kimi_launch_then_send_is_verified() { assert_present "$task_tmp/gotmp" "kimi spawn did not create its Go temp directory" assert_grep "export GOTMPDIR=$task_tmp/gotmp" "$CASE_DIR/tmux-calls.log" \ "kimi spawn did not export its Go temp directory into the pane" + assert_grep "export FM_TASK_ID=$id" "$CASE_DIR/tmux-calls.log" \ + "kimi spawn did not mark the pane with its task id" assert_grep 'BEGIN FIRSTMATE KIMI TURN-END HOOK' "$HOME_DIR/.kimi-code/config.toml" \ "kimi spawn did not install its guarded global hook region" assert_grep 'token=' "$WT_DIR/.fm-kimi-turnend" "kimi spawn did not write its token pointer" diff --git a/tests/fm-test-run.test.sh b/tests/fm-test-run.test.sh index 983ed01a92f..e443675a657 100755 --- a/tests/fm-test-run.test.sh +++ b/tests/fm-test-run.test.sh @@ -163,6 +163,69 @@ init_changed_fixture_repo() { git -C "$repo" -c user.name=test -c user.email=test@example.invalid commit -qm baseline } +# Build a repository with a primary checkout and one linked worktree, each +# holding a runnable copy of the runner and a probe suite that records the fact +# that it ran. Untracked copies are enough: the runner resolves its root from +# its own path, and the probe is named explicitly. +init_primary_and_linked_worktree() { + local repo=$1 linked=$2 tree + fm_git_init_commit "$repo" + git -C "$repo" worktree add --quiet -b linked-probe "$linked" + for tree in "$repo" "$linked"; do + mkdir -p "$tree/bin" "$tree/tests" + cp "$RUNNER" "$tree/bin/fm-test-run.sh" + chmod +x "$tree/bin/fm-test-run.sh" + cat >"$tree/tests/probe.test.sh" <"$tree/ran" +PROBE + chmod +x "$tree/tests/probe.test.sh" + done +} + +# A task worker's isolated placement is checked once, when the task starts. +# Nothing re-checks it, so a worker that later changes directory into the +# repository's primary checkout would run this branch-switching suite in the one +# checkout every linked worktree resolves against. The runner refuses that. +test_task_marker_refuses_the_primary_checkout() { + local tmp repo linked out rc + tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-primary.XXXXXX") + repo="$tmp/repo" + linked="$tmp/linked" + init_primary_and_linked_worktree "$repo" "$linked" + + # Marker set, primary checkout: refuse, name the primary, and run nothing. + out=$(FM_TASK_ID=probe-task "$repo/bin/fm-test-run.sh" tests/probe.test.sh 2>&1) && rc=0 || rc=$? + [ "$rc" -ne 0 ] || { rm -rf "$tmp"; fail "the runner must refuse the primary checkout under a task marker"; } + assert_contains "$out" "primary checkout" "refusal did not name the primary checkout" + assert_contains "$out" "FM_TASK_ID=probe-task" "refusal did not name the task marker" + assert_contains "$out" "task worktree" "refusal did not point at the task worktree" + assert_not_contains "$out" "FM_TEST_BEGIN" "the refusal must happen before any suite runs" + assert_absent "$repo/ran" "the refused run still executed a suite" + + # Marker set, linked worktree: the assigned placement, so the suite runs. + FM_TASK_ID=probe-task "$linked/bin/fm-test-run.sh" tests/probe.test.sh >/dev/null 2>&1 \ + || { rm -rf "$tmp"; fail "the runner must still run in a linked task worktree"; } + assert_present "$linked/ran" "the linked-worktree run did not execute its suite" + + # No marker: a person in their own checkout is unaffected. + "$repo/bin/fm-test-run.sh" tests/probe.test.sh >/dev/null 2>&1 \ + || { rm -rf "$tmp"; fail "an unmarked run in the primary checkout must be unchanged"; } + assert_present "$repo/ran" "the unmarked run did not execute its suite" + + # Inspection executes nothing, so it stays available even in the primary. + rm -f "$repo/ran" + out=$(FM_TASK_ID=probe-task "$repo/bin/fm-test-run.sh" --list tests/probe.test.sh 2>&1) \ + || { rm -rf "$tmp"; fail "--list must remain available under a task marker"; } + [ "$out" = "tests/probe.test.sh" ] \ + || { rm -rf "$tmp"; fail "--list under a task marker printed: $out"; } + assert_absent "$repo/ran" "--list must not execute a suite" + + rm -rf "$tmp" + pass "a task marker refuses execution in the primary checkout and leaves worktrees and inspection alone" +} + test_changed_runner_surfaces_select_their_family() { local tmp repo listed tmp=$(mktemp -d "${TMPDIR:-/tmp}/fm-test-run-owner-scope.XXXXXX") @@ -1447,6 +1510,7 @@ test_list_all_exact_suite_coverage test_family_selection test_single_script_selection test_changed_file_selection_is_conservative +test_task_marker_refuses_the_primary_checkout test_changed_runner_surfaces_select_their_family test_shell_line_ending_policy_selects_runner_contract test_changed_dependency_selection_and_unmapped_failure From 51a3279294ca141b9c44e3d0c5a4b39535ae8bcf Mon Sep 17 00:00:00 2001 From: Talon Stark Date: Sun, 6 Sep 2026 11:33:33 -0400 Subject: [PATCH 2/3] no-mistakes(review): clear inherited task marker in test lib; name resolved ROOT --- bin/fm-test-run.sh | 3 +-- tests/lib.sh | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bin/fm-test-run.sh b/bin/fm-test-run.sh index 9b587ac0a7a..b8feecc72fe 100755 --- a/bin/fm-test-run.sh +++ b/bin/fm-test-run.sh @@ -220,8 +220,7 @@ refuse_primary_checkout_for_task() { && common_dir=$(cd "$common_dir" 2>/dev/null && pwd -P) || common_dir= [ -n "$git_dir" ] && [ -n "$common_dir" ] || return 0 [ "$git_dir" = "$common_dir" ] || return 0 - top=$(git -C "$ROOT" rev-parse --show-toplevel 2>/dev/null) \ - && top=$(cd "$top" 2>/dev/null && pwd -P) || top=$ROOT + top=$(cd "$ROOT" && pwd -P) die "refusing to run in the repository primary checkout $top while FM_TASK_ID=$task_id is set; run from the assigned task worktree instead" } diff --git a/tests/lib.sh b/tests/lib.sh index 6167cb6df25..2227976086e 100644 --- a/tests/lib.sh +++ b/tests/lib.sh @@ -42,6 +42,12 @@ umask 022 # strips this to verify real refusal. export FM_GATE_REFUSE_BYPASS=1 +# Clear the task-worker marker bin/fm-spawn.sh exports into ship and scout +# panes. This suite builds git-init fixture repositories whose primary checkout +# it runs a copied bin/fm-test-run.sh in, and that runner refuses the primary +# under the marker. A case that verifies the refusal sets FM_TASK_ID itself. +unset FM_TASK_ID + # Resolve the repo root from this library's own location. Consumed by sourcing # test files, not by this library, so it reads as "unused" here. # shellcheck disable=SC2034 From 34a5d083c37ae706c8710a075b675bc048b3d6e8 Mon Sep 17 00:00:00 2001 From: Talon Stark Date: Sun, 6 Sep 2026 20:31:12 -0700 Subject: [PATCH 3/3] no-mistakes(document): docs: record FM_TASK_ID marker and runner placement refusal --- bin/fm-spawn.sh | 9 ++++++++- docs/architecture.md | 1 + docs/configuration.md | 3 ++- docs/verification/trace-context.md | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index 4988df01301..1c27df82883 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -182,6 +182,12 @@ # itself a linked worktree of the project repository still launches. A pane # that never reaches an isolated worktree refuses at the end of that wait, # naming the last path seen and why it was rejected. +# That placement is proven only at launch. Every ship or scout pane therefore +# also receives `export FM_TASK_ID=` before the launch command, on +# the same channel as GOTMPDIR, and bin/fm-test-run.sh refuses to execute the +# behavior suite from the repository primary checkout while that marker is +# set (its header owns the refusal). A secondmate runs in its own home and is +# not marked. # Only after this isolation check, every fresh ship or scout requires a clean # task worktree. When an origin configuration is detected, spawn fetches it, # resolves the current remote default branch, and resets to its tip. When none @@ -224,7 +230,8 @@ # LANG LC_ALL LC_CTYPE TMPDIR TMP TEMP GOTMPDIR, plus backend identity/routing: # TMUX TMUX_PANE HERDR_ENV HERDR_SESSION HERDR_SOCKET_PATH HERDR_PANE_ID # CMUX_WORKSPACE_ID CMUX_SURFACE_ID CMUX_TAB_ID CMUX_PANEL_ID CMUX_SOCKET_PATH -# ZELLIJ ZELLIJ_SESSION_NAME ZELLIJ_PANE_ID FM_ZELLIJ_SESSION. +# ZELLIJ ZELLIJ_SESSION_NAME ZELLIJ_PANE_ID FM_ZELLIJ_SESSION, plus the task +# marker FM_TASK_ID that ship and scout panes receive above. # An enabled task trace also retains TRACEPARENT. Explicit Firstmate launch # assignments still apply inside the filtered environment. Raw commands must # be POSIX sh compatible under this opt-in; the absent-file path is unchanged. diff --git a/docs/architecture.md b/docs/architecture.md index eb0a31697f9..cff000d5c8b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -214,6 +214,7 @@ Only a named non-default branch checked out in `FM_ROOT` is a worktree tangle. `fm-guard.sh` prints the repair command on the next mutable fleet action, while `bin/fm-session-start.sh` reports the same condition through bootstrap as a `TANGLE:` line at session start. If another live session holds the fleet lock, both surfaces keep the alarm but switch to read-only wording with no repair command. Ship briefs also tell the crewmate to verify `pwd -P` and `git rev-parse --show-toplevel` before creating `fm/`, then stop with a blocked status if it landed in the primary checkout. +Placement is proven only at launch, so `bin/fm-spawn.sh` also exports the task id as `FM_TASK_ID` into every ship and scout pane, and `bin/fm-test-run.sh` refuses to execute the behavior suite from the primary checkout while that marker is set; the runner's header owns the predicate and [`tests/fm-test-run.test.sh`](../tests/fm-test-run.test.sh) pins it. ## No-mistakes gate authority boundary diff --git a/docs/configuration.md b/docs/configuration.md index 6eb700331a0..2ee007121d2 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -359,7 +359,7 @@ OPENAI_API_KEY SSH_AUTH_SOCK ``` -Firstmate retains basic home, executable search, terminal, locale, temporary-directory, and backend routing variables, plus its explicit launch assignments and enabled task trace. +Firstmate retains basic home, executable search, terminal, locale, temporary-directory, and backend routing variables, plus its explicit launch assignments, its ship and scout task marker, and enabled task trace. [`fm-spawn.sh --help`](../bin/fm-spawn.sh) owns the exact retained names and parsing mechanics. Other ambient names must be listed explicitly, including custom credential-store locations, proxy settings, and certificate overrides when required by the selected tools. The command shell and worker may still create their own variables. @@ -852,6 +852,7 @@ FM_CONFIG_OVERRIDE= # alternate config dir, mainly for tests FM_PROC_ROOT_OVERRIDE= # alternate /proc root for Linux process-identity reads in fm-wake-lib.sh and fm-teardown.sh, mainly for tests FM_BACKEND= # optional runtime backend override for new spawns; tmux/herdr/zellij/orca/cmux support ship/scout spawns, codex-app is not accepted FM_TRACE_CONTEXT= # optional trace-context override; see "Trace context propagation" +FM_TASK_ID= # internal task-worker marker fm-spawn.sh exports into ship and scout panes, never set by hand; bin/fm-test-run.sh refuses to execute in the repository primary checkout while it is set HERDR_SESSION=default # herdr-only: named session for normal backend ops; not enough for destructive cleanup (docs/herdr-backend.md) FM_BACKEND_HERDR_SUBMIT_POLLS=6 # herdr-only: agent-state samples spread across each Enter attempt's budget when confirming a submit (docs/herdr-backend.md "Current transport behavior") FM_BACKEND_HERDR_SUBMIT_MIN_SLEEP=0.6 # herdr-only: minimum per-Enter confirmation budget before polling agent-state after an idle baseline diff --git a/docs/verification/trace-context.md b/docs/verification/trace-context.md index c6af19f8d41..f7b1edaa743 100644 --- a/docs/verification/trace-context.md +++ b/docs/verification/trace-context.md @@ -9,7 +9,7 @@ Comparison base: `main` at `976d97f`. The colocated unit suite `tests/fm-trace-context-lib.test.sh` (26 assertions) exercises validation (valid accepted; malformed, wrong-length, uppercase, all-zero, `ff` version, and shell-metacharacter values rejected), root minting with every mint a distinct sampled root and no parent-adoption input, the recovery reuse path with the recorded carrier winning over the ambient environment, default-off omission, the enable precedence of `FM_TRACE_CONTEXT` over `config/trace-context` with unset or empty deferring to the file, normalized home-session state, atomic replacement of a read-only prior record, stale-session rejection after failed publication, missing or invalid state defaulting off, the Secondmate home-session boundary with later file state plus the per-task trace boundary (two resolves under one persistent ambient `TRACEPARENT` root two distinct traces and adopt neither), forced entropy failure omitting safely, and the minted-root fixed-shape check. -The spawn-path integration suite `tests/fm-trace-context-spawn.test.sh` (12 assertions), hermetic against an ambient `FM_TRACE_CONTEXT`, drives `bin/fm-spawn.sh` end to end with a fake tmux pane and a real isolated git worktree: enabled, one resolved carrier is recorded as `traceparent=` in the meta only after the identical `TRACEPARENT` export is sent before the launch literal; disabled, neither is written nor sent (only `GOTMPDIR` is); a failed carrier delivery leaves no `traceparent=` claim while the source task still launches; an unsafe delivery whose partial input cannot be cleared stops before appending the launch command; a failed metadata append removes the carrier from the launched task without aborting it; duplicate Secondmate preflight leaves inherited trace configuration unchanged; a relaunch reuses the recorded carrier verbatim; and spawns ignore later config and environment edits in favor of the frozen home-session decision. +The spawn-path integration suite `tests/fm-trace-context-spawn.test.sh` (12 assertions), hermetic against an ambient `FM_TRACE_CONTEXT`, drives `bin/fm-spawn.sh` end to end with a fake tmux pane and a real isolated git worktree: enabled, one resolved carrier is recorded as `traceparent=` in the meta only after the identical `TRACEPARENT` export is sent before the launch literal; disabled, neither is written nor sent (`GOTMPDIR` still is); a failed carrier delivery leaves no `traceparent=` claim while the source task still launches; an unsafe delivery whose partial input cannot be cleared stops before appending the launch command; a failed metadata append removes the carrier from the launched task without aborting it; duplicate Secondmate preflight leaves inherited trace configuration unchanged; a relaunch reuses the recorded carrier verbatim; and spawns ignore later config and environment edits in favor of the frozen home-session decision. The per-task boundary regression models the reviewed Secondmate scenario exactly: two unrelated tasks spawned sequentially from one home while the same fixed `TRACEPARENT` sits in the spawning environment (a persistent Secondmate's launch-time carrier) record and inject valid carriers whose trace ids differ from each other and from the ambient carrier, and a relaunch of the first task reuses its original carrier verbatim for both the meta record and the injected export. Two further assertions drive a genuine two-level primary -> Secondmate -> worker chain, running `bin/fm-spawn.sh` twice with the exact environment the primary injects into the Secondmate, and prove the primary's effective override governs the nested worker both ways: env-on with no config file keeps the nested worker enabled while it roots its own per-task trace distinct from the Secondmate's carrier, and env-off with the file present keeps the nested worker disabled even though the `config/trace-context` file was copied into the Secondmate home. A final assertion drives the file-decided path (`FM_TRACE_CONTEXT` unset) and proves the Secondmate's recorded/injected carrier and its delivered `FM_TRACE_CONTEXT=on|off` snapshot are always derived from one frozen decision, so a carrier is never paired with the opposite enable state.