From 0fb2b168947b129f030aca6d5a68134e3b67af36 Mon Sep 17 00:00:00 2001 From: QuinnBot Date: Sun, 2 Aug 2026 11:22:26 -0700 Subject: [PATCH 1/6] fix(brief): give the worktree-isolation assertion real paths The ship brief tells a worker to stop unless its top level is "the disposable task worktree you were launched in" rather than "the primary checkout firstmate operates from", and states neither path. Both operands are unbound, and the rule's default on any failure to confirm is STOP, so the check is a judgment call rather than a comparison. It refused a correctly isolated worker in the live fleet: a firstmate-repo crewmate reads the primary checkout's absolute path four times in its own brief and its own worktree path nowhere, and git rev-parse --git-common-dir - the one command that names another checkout - points at the primary checkout too. fm-brief.sh now emits the two paths as {FM_WORKTREE} and {FM_PRIMARY_CHECKOUT} isolation facts, and fm-spawn.sh fills them in from the worktree it has already verified, on both the treehouse and Orca paths, immediately after validate_spawn_worktree. The values are the physically resolved paths because git rev-parse --show-toplevel always reports the resolved one. The rewrite matches on the fact-line prefix rather than swapping the placeholder once, so a relaunch into a different worktree corrects a stale path instead of pinning the brief to the first slot it used. A brief holding a placeholder the fill cannot reach refuses to launch: a worker reading a literal {FM_WORKTREE} has no isolation check at all. Briefs scaffolded before this contract carry neither placeholder nor fact line and are left untouched, so in-flight tasks still relaunch. The safety property is preserved and strengthened: a worker genuinely in the primary checkout compares equal to a named path and stops, and a worker in an unexpected third tree now reports what it actually read instead of being pushed onto the same refusal. --- bin/fm-brief.sh | 16 +- bin/fm-spawn.sh | 45 +++++- tests/fm-brief.test.sh | 51 +++++++ tests/fm-spawn-isolation-facts.test.sh | 196 +++++++++++++++++++++++++ 4 files changed, 304 insertions(+), 4 deletions(-) create mode 100755 tests/fm-spawn-isolation-facts.test.sh diff --git a/bin/fm-brief.sh b/bin/fm-brief.sh index 9ce35ebdc5d..eea4eb3a2ba 100755 --- a/bin/fm-brief.sh +++ b/bin/fm-brief.sh @@ -34,6 +34,10 @@ # local-only implement on branch, stop and report "ready in branch" (no push/PR); # captain approves, firstmate merges to local main # Ship briefs begin with a worktree-isolation assertion before the branch step. +# That assertion carries {FM_WORKTREE} and {FM_PRIMARY_CHECKOUT} placeholders +# because neither path exists yet at scaffold time; bin/fm-spawn.sh fills both in +# at launch, from the worktree it has already verified, and refuses to launch a +# brief left holding either placeholder. # Scout tasks ignore mode - their deliverable is a report, not a merge. # Every scaffold's status protocol distinguishes the configured # declared-external-wait verb (FM_CLASSIFY_PAUSED_VERB, default "paused") from @@ -369,9 +373,15 @@ $HERDR_SECTION # Setup You are in a disposable git worktree of $REPO, at a detached HEAD on a clean default branch. -**Verify isolation before anything else.** Run \`pwd -P\` and \`git rev-parse --show-toplevel\`; both must resolve to the disposable task worktree you were launched in, such as a treehouse pool path or an Orca-managed worktree, not the primary checkout firstmate operates from. -The path check is authoritative: \`git rev-parse --git-dir\` and \`git rev-parse --git-common-dir\` can help inspect the repo, but they do not prove you are outside the primary checkout. -If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append \`blocked: launched in primary checkout, not an isolated worktree\` to the status file and stop. +**Verify isolation before anything else.** Run \`cd "\$(git rev-parse --show-toplevel)" && pwd -P\` and compare what it prints against these two paths, which firstmate resolved and verified at launch: + +- your isolated task worktree: {FM_WORKTREE} +- the primary checkout: {FM_PRIMARY_CHECKOUT} + +If it equals the primary checkout, STOP - do not branch or commit here - append \`blocked: launched in primary checkout, not an isolated worktree\` to the status file and stop. +If it equals your task worktree, you are isolated: proceed. +If it is any third path, or the command fails, append \`blocked: isolation check read {the exact path or error}\` to the status file and stop. +\`git rev-parse --git-dir\` and \`git rev-parse --git-common-dir\` point into the primary checkout's \`.git\` for every linked worktree, including yours; that is expected and is not evidence that you are in the primary checkout. 1. First action: create your branch: \`git checkout -b fm/$ID\`$SETUP2 diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index 439a99b74b9..a3c4c7e7e18 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -106,7 +106,10 @@ # Before a secondmate launch, the home is locally fast-forwarded to the primary # default-branch commit when safe; skipped syncs warn and launch unchanged. # Ship/scout spawns refuse to launch unless the resolved task path is a real -# git worktree root distinct from the primary project checkout. +# git worktree root distinct from the primary project checkout. Once verified, +# both paths are written into the brief's isolation facts +# ({FM_WORKTREE}/{FM_PRIMARY_CHECKOUT} from bin/fm-brief.sh), and a brief still +# holding either placeholder refuses to launch. # Batch dispatch: pass one or more `id=repo` pairs instead of a single , e.g. # fm-spawn.sh fix-a-k3=projects/foo add-b-q7=projects/bar [--scout] # Each pair re-execs this script in single-task mode, so the single path stays the only @@ -929,6 +932,44 @@ validate_spawn_worktree() { # echo "error: $source did not yield an isolated worktree (resolved '$WT'; worktree root '${wt_top:-none}'; primary '$PROJ_ABS'); refusing to launch to avoid tangling the primary checkout. Inspect target $inspect_target" >&2 exit 1 fi + # The verified physical worktree root, for fill_isolation_facts below. Physical + # because the worker compares against `git rev-parse --show-toplevel`, which + # reports the resolved path even when the shell reached the worktree through a + # symlink, while $WT keeps whatever raw path the backend reported. + WT_REAL=$wt_real +} + +# Fill the ship brief's isolation facts with the two paths the worker cannot +# derive for itself: its own task worktree and the primary checkout. +# bin/fm-brief.sh writes {FM_WORKTREE}/{FM_PRIMARY_CHECKOUT} placeholders because +# neither path exists when the brief is scaffolded, and a worker asked to judge +# in prose whether it is "in the primary checkout" can refuse correct isolation - +# a firstmate-repo crewmate sees the primary checkout's path several times in its +# own brief and its own worktree path nowhere. +# Rewriting by line prefix rather than a one-shot placeholder swap keeps a +# relaunch into a different worktree correct instead of pinning the brief to the +# first slot it ever used. A brief carrying neither placeholder nor fact line - +# every brief scaffolded before this contract, and every secondmate charter - is +# left untouched. A placeholder surviving the rewrite is fatal: a worker reading a +# literal {FM_WORKTREE} has no isolation check at all. +fill_isolation_facts() { # + local brief=$1 worktree=$2 primary=$3 tmp + if grep -q -e '^- your isolated task worktree: ' -e '^- the primary checkout: ' "$brief" 2>/dev/null; then + tmp="$brief.fm-isolation-facts.$$" + if ! { FM_FILL_WORKTREE="$worktree" FM_FILL_PRIMARY="$primary" awk ' + /^- your isolated task worktree: / { print "- your isolated task worktree: " ENVIRON["FM_FILL_WORKTREE"]; next } + /^- the primary checkout: / { print "- the primary checkout: " ENVIRON["FM_FILL_PRIMARY"]; next } + { print } + ' "$brief" > "$tmp" && mv "$tmp" "$brief"; }; then + rm -f "$tmp" + echo "error: could not write the isolation facts into $brief; refusing to launch a brief whose isolation check has no paths to compare" >&2 + exit 1 + fi + fi + if grep -q -e '{FM_WORKTREE}' -e '{FM_PRIMARY_CHECKOUT}' "$brief" 2>/dev/null; then + echo "error: $brief still contains an unfilled isolation placeholder; refusing to launch a worker whose isolation check cannot be evaluated" >&2 + exit 1 + fi } herdr_projection_meta_field_exact() { # @@ -1230,6 +1271,7 @@ EOF exit 1 fi validate_spawn_worktree "orca worktree create" "$W" + fill_isolation_facts "$BRIEF" "$WT_REAL" "$PROJ_ABS_REAL" if [ -z "$ORCA_TERMINAL" ]; then ORCA_TERMINAL=$(fm_backend_orca_terminal_create "$ORCA_WORKTREE_ID" "$W") || exit 1 fi @@ -1456,6 +1498,7 @@ if [ "$KIND" != secondmate ] && [ "$BACKEND" != orca ]; then fi validate_spawn_worktree "treehouse get" "$T" + fill_isolation_facts "$BRIEF" "$WT_REAL" "$PROJ_ABS_REAL" fi # Per-task temp root: /tmp/fm-/ with Go's build temp nested at gotmp/. Go won't diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index b59e17ad67c..86b930cd254 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -295,6 +295,56 @@ test_ship_project_memory_wording() { pass "fm-brief.sh: ship project-memory wording carries the AGENTS.md authoring bar" } +# The isolation assertion must ship as an evaluable comparison against two named +# paths, never as prose the worker has to interpret. A worker cannot derive +# either path itself - a firstmate-repo crewmate sees the primary checkout's path +# repeatedly in its own brief and its worktree path nowhere - so the scaffold +# emits placeholders that bin/fm-spawn.sh fills in from the worktree it has +# already verified. Placeholders belong only where something fills them: the +# scout scaffold and the secondmate charter carry no isolation assertion, so a +# leaked placeholder there would be a permanently unfillable literal. +test_ship_isolation_assertion_names_both_paths() { + local home id brief scout charter + home="$TMP_ROOT/isolation-facts-home" + mkdir -p "$home/data" + id="brief-isolation-d1" + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" "$id" some-proj >/dev/null 2>&1 + brief="$home/data/$id/brief.md" + assert_present "$brief" "brief was not scaffolded" + assert_grep "- your isolated task worktree: {FM_WORKTREE}" "$brief" \ + "ship brief lost the fillable task-worktree isolation fact" + assert_grep "- the primary checkout: {FM_PRIMARY_CHECKOUT}" "$brief" \ + "ship brief lost the fillable primary-checkout isolation fact" + assert_grep "If it equals the primary checkout, STOP" "$brief" \ + "ship brief lost the stop branch for the primary checkout" + assert_grep "If it equals your task worktree, you are isolated: proceed." "$brief" \ + "ship brief lost the proceed branch for a correctly isolated worker" + assert_grep "blocked: launched in primary checkout, not an isolated worktree" "$brief" \ + "ship brief lost the isolation refusal status line" + assert_grep "If it is any third path, or the command fails" "$brief" \ + "ship brief lost the third-path branch that reports what was actually read" + assert_no_grep "not the worktree you were launched in, STOP" "$brief" \ + "ship brief kept the unverifiable launched-in referent that refused correct isolation" + + FM_HOME="$home" "$ROOT/bin/fm-brief.sh" brief-isolation-d2 some-proj --scout >/dev/null 2>&1 + scout="$home/data/brief-isolation-d2/brief.md" + assert_present "$scout" "scout brief was not scaffolded" + assert_no_grep "{FM_WORKTREE}" "$scout" \ + "scout brief leaked an isolation placeholder nothing fills" + assert_no_grep "{FM_PRIMARY_CHECKOUT}" "$scout" \ + "scout brief leaked an isolation placeholder nothing fills" + + FM_SECONDMATE_CHARTER="fixture charter" FM_HOME="$home" \ + "$ROOT/bin/fm-brief.sh" brief-isolation-d3 --secondmate --no-projects >/dev/null 2>&1 + charter="$home/data/brief-isolation-d3/brief.md" + assert_present "$charter" "secondmate charter was not scaffolded" + assert_no_grep "{FM_WORKTREE}" "$charter" \ + "secondmate charter leaked an isolation placeholder nothing fills" + assert_no_grep "{FM_PRIMARY_CHECKOUT}" "$charter" \ + "secondmate charter leaked an isolation placeholder nothing fills" + pass "fm-brief.sh: the ship isolation assertion names both paths and placeholders stay where they are filled" +} + test_herdr_lab_contract_is_explicit_and_complete() { local home id brief home="$TMP_ROOT/herdr-lab-home" @@ -659,6 +709,7 @@ test_ship_modes_generate_clean_briefs test_faster_paths_use_configured_authority_without_stacked_review test_no_mistakes_dod_wording test_ship_project_memory_wording +test_ship_isolation_assertion_names_both_paths test_herdr_lab_contract_is_explicit_and_complete test_herdr_lab_contract_quotes_foreign_firstmate_path test_herdr_lab_omission_is_loud_for_ship_and_scout diff --git a/tests/fm-spawn-isolation-facts.test.sh b/tests/fm-spawn-isolation-facts.test.sh new file mode 100755 index 00000000000..fcff4bd2b8c --- /dev/null +++ b/tests/fm-spawn-isolation-facts.test.sh @@ -0,0 +1,196 @@ +#!/usr/bin/env bash +# Behavior tests for the isolation facts bin/fm-spawn.sh writes into a ship +# brief (the fill_isolation_facts step after validate_spawn_worktree). +# +# The generated ship brief tells the worker to stop unless its own top level is +# the isolated task worktree rather than the primary checkout, but bin/fm-brief.sh +# knows neither path when it scaffolds - the worktree does not exist yet - so it +# emits {FM_WORKTREE}/{FM_PRIMARY_CHECKOUT} placeholders. Left unfilled, that +# check is a judgment call with no operands, and it has refused a correctly +# isolated worker in the live fleet: a firstmate-repo crewmate reads the primary +# checkout's absolute path several times in its own brief and its worktree path +# nowhere. fm-spawn.sh fills both in from the worktree it has already verified, +# rewrites a stale value on a later launch instead of pinning the brief to the +# first slot it used, leaves pre-contract briefs untouched, and refuses to launch +# a worker whose isolation check still holds a literal placeholder. +set -u + +# shellcheck source=tests/lib.sh +. "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +SPAWN="$ROOT/bin/fm-spawn.sh" +TMP_ROOT=$(fm_test_tmproot fm-spawn-isolation-facts) + +# A fake tmux whose pane_current_path always reports the settled worktree, plus +# a no-op treehouse: the settle loop itself is covered by +# tests/fm-spawn-worktree-settle.test.sh and is not the subject here. +make_fakebin() { + local dir=$1 fakebin + fakebin=$(fm_fakebin "$dir") + cat > "$fakebin/tmux" <<'SH' +#!/usr/bin/env bash +set -u +case "$*" in + *"#{pane_current_path}"*) printf '%s\n' "${FM_FAKE_PANE_PATH:-}"; exit 0 ;; +esac +case "${1:-}" in + display-message) printf 'firstmate\n'; exit 0 ;; + list-windows) exit 0 ;; + has-session|new-session|new-window|kill-window) exit 0 ;; + send-keys) exit 0 ;; +esac +exit 0 +SH + chmod +x "$fakebin/tmux" + fm_fake_exit0 "$fakebin" treehouse + printf '%s\n' "$fakebin" +} + +# make_case builds a home, a primary project checkout and a real +# linked worktree of it, and returns the paths. The brief body is written by +# each test so it can carry placeholders, stale values, or neither. +make_case() { + local name=$1 id=$2 case_dir + case_dir="$TMP_ROOT/$name" + CASE_HOME="$case_dir/home" + CASE_PROJ="$case_dir/project" + CASE_WT="$case_dir/wt" + CASE_FAKEBIN=$(make_fakebin "$case_dir/fake") + mkdir -p "$CASE_HOME/data/$id" "$CASE_HOME/projects" "$CASE_HOME/state" "$CASE_HOME/config" + printf 'codex\n' > "$CASE_HOME/config/crew-harness" + fm_git_worktree "$CASE_PROJ" "$CASE_WT" "wt-$name" + touch "$CASE_HOME/state/.last-watcher-beat" + CASE_BRIEF="$CASE_HOME/data/$id/brief.md" +} + +# The ship-brief isolation block as bin/fm-brief.sh renders it, with +# and supplied by the caller so a test can seed placeholders or a +# stale already-filled value. +write_brief() { + local brief=$1 worktree=$2 primary=$3 + cat > "$brief" <&1 +} + +# The paths the worker will compare against must be the physically resolved ones, +# because `git rev-parse --show-toplevel` always reports the resolved path. +resolved() { + (cd "$1" && pwd -P) +} + +test_placeholders_are_filled_with_the_verified_paths() { + local id out status wt_real proj_real + id=isolation-facts-fill-z1 + make_case fill "$id" + write_brief "$CASE_BRIEF" '{FM_WORKTREE}' '{FM_PRIMARY_CHECKOUT}' + wt_real=$(resolved "$CASE_WT") + proj_real=$(resolved "$CASE_PROJ") + + out=$(run_spawn "$id") + status=$? + expect_code 0 "$status" "spawn should succeed and fill the isolation facts (got: $out)" + assert_contains "$out" "spawned $id" "spawn did not report success" + assert_grep "- your isolated task worktree: $wt_real" "$CASE_BRIEF" \ + "brief did not receive the verified task worktree" + assert_grep "- the primary checkout: $proj_real" "$CASE_BRIEF" \ + "brief did not receive the primary checkout" + assert_no_grep "{FM_WORKTREE}" "$CASE_BRIEF" "brief kept an unfilled worktree placeholder" + assert_no_grep "{FM_PRIMARY_CHECKOUT}" "$CASE_BRIEF" "brief kept an unfilled primary placeholder" + pass "fm-spawn.sh: isolation placeholders are filled with the verified worktree and primary checkout" +} + +# A brief that already carries paths - a relaunch, or a task respawned into a +# different pool slot - must be corrected to the worktree this launch verified. +# A stale path would refuse a correctly isolated worker exactly like an unfilled +# placeholder does. +test_stale_isolation_facts_are_rewritten() { + local id out status wt_real + id=isolation-facts-stale-z2 + make_case stale "$id" + write_brief "$CASE_BRIEF" "$TMP_ROOT/stale/some-other-pool-slot" "$TMP_ROOT/stale/some-other-primary" + wt_real=$(resolved "$CASE_WT") + + out=$(run_spawn "$id") + status=$? + expect_code 0 "$status" "spawn should succeed on an already-filled brief (got: $out)" + assert_grep "- your isolated task worktree: $wt_real" "$CASE_BRIEF" \ + "brief kept a stale worktree path from an earlier launch" + assert_no_grep "some-other-pool-slot" "$CASE_BRIEF" \ + "brief still names the worktree from an earlier launch" + assert_no_grep "some-other-primary" "$CASE_BRIEF" \ + "brief still names the primary checkout from an earlier launch" + pass "fm-spawn.sh: a relaunch rewrites stale isolation facts instead of pinning the first slot" +} + +# A placeholder the fill cannot reach - here the fact lines were removed but the +# placeholder text survives elsewhere - must stop the launch. A worker reading a +# literal {FM_WORKTREE} has no isolation check at all, so proceeding would be +# strictly worse than refusing. +test_unfillable_placeholder_refuses_to_launch() { + local id out status + id=isolation-facts-unfillable-z3 + make_case unfillable "$id" + cat > "$CASE_BRIEF" <<'EOF' +task fixture + +Compare your top level against {FM_WORKTREE} before starting. +EOF + + out=$(run_spawn "$id") + status=$? + [ "$status" -ne 0 ] || fail "spawn exited 0 with an unfilled isolation placeholder in the brief" + assert_contains "$out" "unfilled isolation placeholder" \ + "refusal did not name the unfilled isolation placeholder" + assert_not_contains "$out" "spawned $id" "spawn reported success despite refusing" + assert_absent "$CASE_HOME/state/$id.meta" "a refused spawn still recorded task metadata" + pass "fm-spawn.sh: a brief with an unfillable isolation placeholder refuses to launch" +} + +# Briefs scaffolded before this contract carry the old prose assertion and no +# placeholders. They must keep launching unchanged, so an in-flight task can +# still be relaunched after the fleet updates. +test_pre_contract_brief_launches_unchanged() { + local id out status before after + id=isolation-facts-legacy-z4 + make_case legacy "$id" + cat > "$CASE_BRIEF" <<'EOF' +task fixture + +**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`. +EOF + before=$(cat "$CASE_BRIEF") + + out=$(run_spawn "$id") + status=$? + after=$(cat "$CASE_BRIEF") + expect_code 0 "$status" "spawn should still launch a pre-contract brief (got: $out)" + assert_contains "$out" "spawned $id" "spawn did not report success for a pre-contract brief" + [ "$before" = "$after" ] || fail "spawn rewrote a pre-contract brief that carries no isolation facts" + pass "fm-spawn.sh: a pre-contract brief launches unchanged" +} + +test_placeholders_are_filled_with_the_verified_paths +test_stale_isolation_facts_are_rewritten +test_unfillable_placeholder_refuses_to_launch +test_pre_contract_brief_launches_unchanged + +echo "# all fm-spawn-isolation-facts tests passed" From b811f176758264af8110d16578d5c638d36a799d Mon Sep 17 00:00:00 2001 From: QuinnBot Date: Sun, 2 Aug 2026 11:29:14 -0700 Subject: [PATCH 2/6] no-mistakes(review): Reject partial isolation fact blocks before spawn --- bin/fm-spawn.sh | 14 ++++++++-- tests/fm-spawn-isolation-facts.test.sh | 38 ++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index a3c4c7e7e18..4229403b170 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -953,8 +953,18 @@ validate_spawn_worktree() { # # left untouched. A placeholder surviving the rewrite is fatal: a worker reading a # literal {FM_WORKTREE} has no isolation check at all. fill_isolation_facts() { # - local brief=$1 worktree=$2 primary=$3 tmp - if grep -q -e '^- your isolated task worktree: ' -e '^- the primary checkout: ' "$brief" 2>/dev/null; then + local brief=$1 worktree=$2 primary=$3 tmp worktree_fact=0 primary_fact=0 + grep -q '^- your isolated task worktree: ' "$brief" 2>/dev/null && worktree_fact=1 + grep -q '^- the primary checkout: ' "$brief" 2>/dev/null && primary_fact=1 + if [ "$worktree_fact" = 1 ] && [ "$primary_fact" = 0 ]; then + echo "error: $brief is missing the '- the primary checkout:' isolation fact line; refusing to launch a worker whose isolation check is missing an operand" >&2 + exit 1 + fi + if [ "$worktree_fact" = 0 ] && [ "$primary_fact" = 1 ]; then + echo "error: $brief is missing the '- your isolated task worktree:' isolation fact line; refusing to launch a worker whose isolation check is missing an operand" >&2 + exit 1 + fi + if [ "$worktree_fact" = 1 ]; then tmp="$brief.fm-isolation-facts.$$" if ! { FM_FILL_WORKTREE="$worktree" FM_FILL_PRIMARY="$primary" awk ' /^- your isolated task worktree: / { print "- your isolated task worktree: " ENVIRON["FM_FILL_WORKTREE"]; next } diff --git a/tests/fm-spawn-isolation-facts.test.sh b/tests/fm-spawn-isolation-facts.test.sh index fcff4bd2b8c..a86e7f66202 100755 --- a/tests/fm-spawn-isolation-facts.test.sh +++ b/tests/fm-spawn-isolation-facts.test.sh @@ -188,9 +188,47 @@ EOF pass "fm-spawn.sh: a pre-contract brief launches unchanged" } +test_missing_worktree_fact_refuses_to_launch() { + local id out status + id=isolation-facts-missing-worktree-z5 + make_case missing-worktree "$id" + write_brief "$CASE_BRIEF" '{FM_WORKTREE}' '{FM_PRIMARY_CHECKOUT}' + grep -v '^- your isolated task worktree: ' "$CASE_BRIEF" > "$CASE_BRIEF.tmp" + mv "$CASE_BRIEF.tmp" "$CASE_BRIEF" + + out=$(run_spawn "$id") + status=$? + [ "$status" -ne 0 ] || fail "spawn exited 0 with the worktree isolation fact missing" + assert_contains "$out" "missing the '- your isolated task worktree:' isolation fact line" \ + "refusal did not name the missing worktree isolation fact" + assert_not_contains "$out" "spawned $id" "spawn reported success despite the missing worktree fact" + assert_absent "$CASE_HOME/state/$id.meta" "a malformed isolation block still recorded task metadata" + pass "fm-spawn.sh: a brief missing the worktree isolation fact refuses to launch" +} + +test_missing_primary_fact_refuses_to_launch() { + local id out status + id=isolation-facts-missing-primary-z6 + make_case missing-primary "$id" + write_brief "$CASE_BRIEF" '{FM_WORKTREE}' '{FM_PRIMARY_CHECKOUT}' + grep -v '^- the primary checkout: ' "$CASE_BRIEF" > "$CASE_BRIEF.tmp" + mv "$CASE_BRIEF.tmp" "$CASE_BRIEF" + + out=$(run_spawn "$id") + status=$? + [ "$status" -ne 0 ] || fail "spawn exited 0 with the primary-checkout isolation fact missing" + assert_contains "$out" "missing the '- the primary checkout:' isolation fact line" \ + "refusal did not name the missing primary-checkout isolation fact" + assert_not_contains "$out" "spawned $id" "spawn reported success despite the missing primary-checkout fact" + assert_absent "$CASE_HOME/state/$id.meta" "a malformed isolation block still recorded task metadata" + pass "fm-spawn.sh: a brief missing the primary-checkout isolation fact refuses to launch" +} + test_placeholders_are_filled_with_the_verified_paths test_stale_isolation_facts_are_rewritten test_unfillable_placeholder_refuses_to_launch test_pre_contract_brief_launches_unchanged +test_missing_worktree_fact_refuses_to_launch +test_missing_primary_fact_refuses_to_launch echo "# all fm-spawn-isolation-facts tests passed" From ac9e1bafc24b39a13f2a27090530401542b2560a Mon Sep 17 00:00:00 2001 From: QuinnBot Date: Sun, 2 Aug 2026 11:39:34 -0700 Subject: [PATCH 3/6] no-mistakes(document): Document named isolation operands --- docs/architecture.md | 2 +- tests/fm-backend-orca.test.sh | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/docs/architecture.md b/docs/architecture.md index 07c2ff5cd2d..0068c01f3f4 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -144,7 +144,7 @@ Only a named non-default branch checked out in `FM_ROOT` is a worktree tangle. `fm-tangle-lib.sh` resolves the default branch from `origin/HEAD`, then local `main` or `master`, and classifies that named non-default primary branch as the 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. +After validating the task root, `fm-spawn.sh` writes the physically resolved task-worktree and primary-checkout paths into newly scaffolded ship briefs so the crewmate can compare its physical Git top level against named operands before creating `fm/` and stop if it is not in the verified worktree. ## No-mistakes gate authority boundary diff --git a/tests/fm-backend-orca.test.sh b/tests/fm-backend-orca.test.sh index 17a20971daa..cd9b14a52b4 100755 --- a/tests/fm-backend-orca.test.sh +++ b/tests/fm-backend-orca.test.sh @@ -509,7 +509,7 @@ test_spawn_preserves_orca_metadata_when_pathless_worktree_cleanup_fails() { } test_spawn_writes_orca_metadata_and_launches_harness() { - local proj wt data state config id out log + local proj wt data state config id out log proj_real wt_real id="orcaspawnz1" proj="$TMP_ROOT/spawn-project" wt="$TMP_ROOT/spawn-wt" @@ -518,7 +518,12 @@ test_spawn_writes_orca_metadata_and_launches_harness() { config="$TMP_ROOT/spawn-config" fm_git_worktree "$proj" "$wt" "fm/$id" mkdir -p "$data/$id" "$state" "$config" - printf 'brief\n' > "$data/$id/brief.md" + cat > "$data/$id/brief.md" <<'EOF' +brief + +- your isolated task worktree: {FM_WORKTREE} +- the primary checkout: {FM_PRIMARY_CHECKOUT} +EOF touch "$state/.last-watcher-beat" orca_case spawn log="$LOG" @@ -541,6 +546,16 @@ test_spawn_writes_orca_metadata_and_launches_harness() { assert_grep "terminal=term-spawn" "$state/$id.meta" "meta missing terminal handle" assert_grep "orca_worktree_id=wt-spawn" "$state/$id.meta" "meta missing Orca worktree id" assert_grep "worktree=$wt" "$state/$id.meta" "meta missing Orca worktree path" + proj_real=$(cd "$proj" && pwd -P) + wt_real=$(cd "$wt" && pwd -P) + assert_grep "- your isolated task worktree: $wt_real" "$data/$id/brief.md" \ + "Orca spawn did not fill the verified physical worktree into the brief" + assert_grep "- the primary checkout: $proj_real" "$data/$id/brief.md" \ + "Orca spawn did not fill the physical primary checkout into the brief" + assert_no_grep "{FM_WORKTREE}" "$data/$id/brief.md" \ + "Orca spawn left the worktree isolation placeholder unfilled" + assert_no_grep "{FM_PRIMARY_CHECKOUT}" "$data/$id/brief.md" \ + "Orca spawn left the primary-checkout isolation placeholder unfilled" assert_not_contains "$(cat "$log")" $'orca\x1f''terminal'$'\x1f''create' \ "spawn should reuse the implicit terminal returned by Orca worktree creation" assert_contains "$(cat "$log")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-spawn'$'\x1f''--text'$'\x1f''export GOTMPDIR=/tmp/fm-orcaspawnz1/gotmp'$'\x1f''--enter'$'\x1f''--json' \ From 6295ceb366f0dae796ccf913bc425198fd8f85be Mon Sep 17 00:00:00 2001 From: QuinnBot Date: Sun, 2 Aug 2026 12:45:50 -0700 Subject: [PATCH 4/6] chore: trigger CI From c40d7e36e3ef1f58596987fb38ef4e89990408af Mon Sep 17 00:00:00 2001 From: QuinnBot Date: Sun, 2 Aug 2026 12:57:34 -0700 Subject: [PATCH 5/6] test(spawn): drive isolation-facts spawns through the shared pane owner The isolation-facts suite was written against a base without the launch-delivery verification fm-spawn now performs, so its fake tmux answered send-keys with a bare exit 0 and every spawn in the suite failed with "launch command delivery could not be verified after 3 attempts". Source the shared pane shell from tests/lib.sh and answer capture-pane and send-keys through fm_fake_pane_capture and fm_fake_pane_send, the same owner tests/fm-spawn-worktree-settle.test.sh uses. That owner executes the submitted checksum against the bytes the pane actually accumulated, so this suite exercises the real delivery contract instead of a local imitation that could report success on corrupt staging. --- tests/fm-spawn-isolation-facts.test.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/fm-spawn-isolation-facts.test.sh b/tests/fm-spawn-isolation-facts.test.sh index a86e7f66202..31cf2677792 100755 --- a/tests/fm-spawn-isolation-facts.test.sh +++ b/tests/fm-spawn-isolation-facts.test.sh @@ -23,13 +23,18 @@ TMP_ROOT=$(fm_test_tmproot fm-spawn-isolation-facts) # A fake tmux whose pane_current_path always reports the settled worktree, plus # a no-op treehouse: the settle loop itself is covered by -# tests/fm-spawn-worktree-settle.test.sh and is not the subject here. +# tests/fm-spawn-worktree-settle.test.sh and is not the subject here. The pane +# answers fm-spawn's launch-delivery protocol through the shared owner in +# tests/lib.sh, so this suite verifies real delivery rather than a local +# imitation of it. make_fakebin() { local dir=$1 fakebin fakebin=$(fm_fakebin "$dir") cat > "$fakebin/tmux" <<'SH' #!/usr/bin/env bash set -u +# shellcheck source=/dev/null +. "$(dirname "$0")/pane-shell.sh" case "$*" in *"#{pane_current_path}"*) printf '%s\n' "${FM_FAKE_PANE_PATH:-}"; exit 0 ;; esac @@ -37,11 +42,16 @@ case "${1:-}" in display-message) printf 'firstmate\n'; exit 0 ;; list-windows) exit 0 ;; has-session|new-session|new-window|kill-window) exit 0 ;; - send-keys) exit 0 ;; + capture-pane) fm_fake_pane_capture; exit 0 ;; + send-keys) + fm_fake_pane_send "$@" + exit 0 + ;; esac exit 0 SH chmod +x "$fakebin/tmux" + fm_fake_pane_shell "$fakebin" fm_fake_exit0 "$fakebin" treehouse printf '%s\n' "$fakebin" } From 629ff187fcf7adb824514e493410f4c608f6358d Mon Sep 17 00:00:00 2001 From: QuinnBot Date: Sun, 2 Aug 2026 13:19:27 -0700 Subject: [PATCH 6/6] test(brief): guard the isolation assertion by its named operands The tangle guard pinned the literal sentence "The path check is authoritative", which the isolation assertion no longer contains: the check is now an exact comparison against two paths fm-spawn.sh fills in at launch, rather than prose the crewmate has to interpret. Assert the property instead of the removed phrasing - both operands present, and the git-dir/common-dir output explicitly not evidence of being in the primary checkout. That is strictly more than the old assertion checked, since it now requires the operands the comparison needs, and it keeps the existing guards against presenting the git-dir shortcut as decisive. --- tests/fm-tangle-guard.test.sh | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/fm-tangle-guard.test.sh b/tests/fm-tangle-guard.test.sh index 9a87f3711d2..34b4b486c3a 100755 --- a/tests/fm-tangle-guard.test.sh +++ b/tests/fm-tangle-guard.test.sh @@ -123,6 +123,12 @@ test_bootstrap_line() { # The generated ship brief must carry the isolation assertion AHEAD of the # `git checkout -b` step, so the crewmate verifies its worktree before branching. +# The assertion compares the crewmate's resolved top level against two named +# paths that bin/fm-spawn.sh fills in at launch, so both operands must be present +# and the git-dir/common-dir output must stay explicitly non-decisive: those +# commands point into the primary checkout's `.git` for every linked worktree, +# and a brief that let a crewmate read that as membership refused a correctly +# isolated worker in the live fleet. test_brief_assertion_precedes_branch() { local home brief iso br home="$TMP_ROOT/brief-home" @@ -132,8 +138,12 @@ test_brief_assertion_precedes_branch() { assert_present "$brief" "brief was not scaffolded" assert_grep "blocked: launched in primary checkout, not an isolated worktree" "$brief" \ "brief is missing the isolation blocked-status contract" - assert_grep "The path check is authoritative" "$brief" \ - "brief must make the path check authoritative" + assert_grep "- your isolated task worktree:" "$brief" \ + "brief must name the task worktree the path check compares against" + assert_grep "- the primary checkout:" "$brief" \ + "brief must name the primary checkout the path check compares against" + assert_grep "is not evidence that you are in the primary checkout" "$brief" \ + "brief must keep the path check authoritative over git-dir/common-dir output" assert_no_grep "A reliable test that you are in a linked worktree" "$brief" \ "brief must not present git-dir/common-dir as decisive" assert_no_grep "they are identical in the primary checkout" "$brief" \