Skip to content

fix(spawn): refuse a push-mode brief whose task text forbids the push or PR - #10

Merged
doitdigital0495 merged 12 commits into
mainfrom
fm/fm-brief-no-pr-contradiction
Aug 24, 2026
Merged

fix(spawn): refuse a push-mode brief whose task text forbids the push or PR#10
doitdigital0495 merged 12 commits into
mainfrom
fm/fm-brief-no-pr-contradiction

Conversation

@doitdigital0495

Copy link
Copy Markdown
Owner

Intent

ADDENDUM 2 - four further accepted review rounds, all already applied in the four commits b2e5ba0, a8aaa25, 33a3559, bf28d89 that this run is validating. Each round was escalated as an ask-user finding rather than self-answered, each was ruled a correction required by accepted intent, and each was applied by the pipeline itself. The previous run then died on a pipeline-agent crash ("step review failed: agent review: claude exited: exit status 1") after committing round 5, custody was returned with no-mistakes axi sync --recover exactly as branch_sync.next_action directed, and no hand edit was made to the recovered work.

Round 2 - the permit rule must require a delivery object. The first permit rule blanked the whole line on any then <verb>, which silently disarmed the refusal on the literal 2026-08-22 incident line whenever an ordinary trailing sentence followed it ("- Do NOT push and do NOT open a PR. Run the tests, then create a summary in the status file." and "... Start the dev server, then open the preview at 390px." both launched). The permitting verb must now take the delivery itself as its object, so ordinary sequencing continues the task instead of permitting the push. The same round closed three verified recall holes: "- Do not push anything.", "- No pushing and no PR.", and the yet phrasings.

Round 3 - a bare comma must not terminate a delivery object. Treating every comma as a clause end hard-blocked six lines that refuse nothing about delivery ("- No user-visible push, the sync is silent.", "- No push, no pull, no fetch in the test harness.", "- Do not add a command that can push, it is unsafe.", "- Do not worry about the push; the captain handles it.", "- Do not let the release push, or the deploy, run twice.", "- Do not name the file PR.md."). A comma now counts only with explicit following negative delivery context, which preserves the two load-bearing comma true positives ("no push, no PR." and "... no-mistakes, push, or open a PR without captain approval."). The file verb now requires an article, yet was added to both object lists, and the --relaunch applicability wording in the fm-spawn.sh header was corrected: the refusals CREATE no worktree, endpoint, or task metadata, but on the relaunch path an existing metadata record and live endpoint are read before the delivery block, so the earlier "before any ... exists" phrasing was wrong.

Round 4 - the push object must carry explicit Git delivery meaning. Accepting any determiner meant ordinary programming senses of push hard-blocked a spawn ("- Do not push to the analytics queue on every keystroke.", "- Never push to npm from CI.", "- Do not push it to the array; use concat.", "- Do not push this event to the dataLayer.", "- Do not push and pop the same stack twice."). The object vocabulary was narrowed to explicit Git delivery nouns and the generic continuations that proved nothing were dropped. This applied the same standard the original work used when it deleted its own unexercised routing strip: the review's mutation check showed 14 accepted tokens were exercised by no regression at all, and to - the token that fired on push to npm - was one of them.

Round 5 - close the anything/until bypass. Those two words were accepted as the object regardless of what followed, which re-admitted the exact class round 4 had just released ("- Do not push anything to the CDN.", "- Do not push anything into the global store.", "- Do not push until the animation finishes.", "- Do not push until the queue drains."), plus "- Do not push to the repo cache on read." through a bare-prefix target match and "- Do not push or pop the PR badge." through the second-mention rule. The smallest targeted correction was applied rather than another word-list widening.

Verified state of the head this run is validating, re-measured rather than assumed, using the real GNU grep the script runs under (note that an ad-hoc probe through ugrep silently fails open on a pattern this size, which is why every measurement here used /usr/bin/grep):

  • All 11 verified programming-sense and generic-continuation false positives launch.
  • All 8 accepted false positives from earlier rounds still launch.
  • All 10 true prohibitions still refuse, including both comma-dependent ones, the subject-carrying "you cannot push until the captain signs off", and the yet phrasings.
  • Recall on the live corpus is unchanged after five rounds of tightening: across the 50 ship briefs this fleet has scaffolded the detector fires on exactly the same 7 that carry the contradiction and on nothing else.
  • bin/fm-lint.sh, bin/fm-doc-audience-check.sh, check-agents-e2e on AGENTS.md, and tests/fm-task-delivery.test.sh, tests/fm-brief.test.sh and tests/fm-documentation-audiences.test.sh through bin/fm-test-run.sh are all green at this head.

Ceilings deliberately left documented rather than grown into machinery, all of which fail in the launch direction (the pre-change status quo) and none of which affect any live brief: a refusal written mid sentence ("Implement it, but do not push and do not open a PR."); a genuine refusal followed on the same line by a permitting verb over a PR-shaped noun ("Do NOT push. Then create a PR template file."); push targets with an adjective or an unlisted determiner ("Never push the feature branch.", "Do not push our changes.", "Do not push commits."); and a refusal written below a line starting with "# " inside a fenced code block in the task text, which the section-scoped read stops at. The accepted instruction in each case was explicitly not to add an override, a general natural-language parser, or broader section extraction.

ADDENDUM - accepted corrections from the previous run's review gate, now implemented on top of the original work described below. The previous run (01M0R7M0EDYN2H4VKPA9NVY2VK) parked at the review gate with two ask-user findings, both were escalated rather than self-answered, both were ruled corrections required by accepted intent, the pipeline's own fix commit was then rejected by the fork's pre-commit hook, and that fix round was discarded. Everything in this addendum was therefore reapplied by hand on top of 305f0b0d between runs, with branch custody released (branch_sync state user_owned), never while a run was active.

Correction 1 - detector tightened rather than given an override. The reviewer proved five phrasings that do not refuse delivery at all were being read as stop points and hard-blocked, with no escape: "- Do not push secrets or .env files to the remote.", "- No push notifications in this milestone; skip the service worker.", "- Do not push until the tests are green, then open the PR as usual.", "Do not open a PR against upstream; open it against the fork.", and "- Do not create a PR template file." All five were independently re-verified as firing before the fix. The accepted resolution was explicitly to tighten detection rather than add an override, so that only an actual prohibition on the selected delivery action refuses, while scoped objects, temporary conditions that explicitly permit the push afterwards, and target-specific wording that still allows the configured fork are all allowed.

  • Both objects must now end the clause or continue into a word that keeps the delivery action itself as the object. That is what separates "do not push secrets" and "no push notifications" from "do not push and do not open a PR", and "do not create a PR template file" from "do not create a pull request for this work".
  • A line that also permits the delivery later ("do not push until the tests are green, then open the PR as usual") sequences the mandate rather than refusing it, and is dropped before matching.
  • A refusal may carry its own subject, so "you cannot push until the captain signs off" is read as an instruction.
  • The PR-routing case ("do not open a PR against upstream; open it against the fork") is handled by the tightened PR object alone. A separate routing-target strip was written first, then deliberately deleted after a mutation check proved no regression depended on it: an unexercised strip in a safety classifier is exactly the kind of thing that rots.
  • Recall is unchanged and was re-measured, not assumed: across the 49 ship briefs this fleet has scaffolded the detector still fires on exactly the 7 that carry the contradiction (cosmic-sets-import, cosmic-brand-accents, cosmic-catalogue-threshold, cosmic-homepage-pin, agentic-portability-impl, fm-config-symlinked, fm-autocompact-175k-b8) and on nothing else.

Correction 2 - operator-facing rule added to AGENTS.md section 7. One concise line now routes a stop point before the remote (a running preview, or the captain's own review ahead of any push) to --mode local-only instead of contradicting a push-mode generated brief, with the authoritative detail left in bin/fm-spawn.sh, bin/fm-brief.sh, and docs/architecture.md rather than restated. This was deliberately NOT relocated elsewhere to avoid editing AGENTS.md.

Correction 3 - the required "Validate end-to-end" section. Firstmate's AGENTS.md never had one, and the fork's pre-commit hook (check-agents-e2e) blocks any commit touching AGENTS.md until it does, which is what killed the previous run's fix commit. The section was added because every project AGENTS.md is required to own its runnable end-to-end proof, not to get past the hook: the hook was neither weakened, bypassed, nor worked around, and --no-verify was explicitly rejected. It is three sentences that point at bin/fm-lint.sh, bin/fm-test-run.sh, the scaffold/spawn/teardown scripts, docs/tmux-backend.md, and docs/verification/, rather than copying any contract.

Correction 4 - regression strength. Regressions now cover all five reported false positives, plus true prohibitions that name only the push and only the PR, plus a refusal carrying its own subject. Each new bound is pinned by mutation check: removing the permit rule, loosening the push object, or loosening the PR object each fails its own row. Every launching row now asserts it reached a marker the backend prints after the delivery-agreement check, so a row can no longer pass vacuously on a spawn that stopped earlier, and every refusing row asserts that marker is absent - which also proves a refused spawn never reaches the backend at all.

The one review finding marked no-op (launch-rows-weak-assert) is the same weakness Correction 4 closes.

=== ORIGINAL INTENT (unchanged, still the acceptance context) ===

Fix the generated ship-brief contract that causes a worker to push and open a PR even when the task text explicitly says to stop at a running preview and wait for captain approval.

Required approach, as specified:

  • Reproduce the real failure through public commands: scaffold a task whose requested stop point is a running preview with no push or PR, then show how the generated delivery definition wins and drives a PR anyway.
  • Use the three 2026-08-22 incidents (cosmic-sets-import, cosmic-audit-fixes, cosmic-hero-video) as evidence, but verify current post-upstream behavior rather than assuming it is unchanged.
  • Trace bin/fm-brief.sh, bin/fm-spawn.sh, task-mode parsing, delivery definitions, and every caller that chooses a mode.
  • Compare the contradictory path with the direct-PR and local-only paths that behave as documented.
  • Identify the earliest contract divergence and test the smallest counterfactual that prevents it.
  • Implement the minimum one-owner fix that makes the preview-before-approval stop point structurally expressible OR makes contradictory input refuse before dispatch.
  • Explicitly ruled out by the user: prose precedence, worker judgment, source-string assertions, and any warning that still launches an unsafe brief. The fix must refuse, not warn.
  • Preserve existing direct-PR, no-mistakes, and local-only behavior when their requested stop points match their delivery contracts.
  • If a delivery mode or command contract changes, update its script header/help, generated brief text, spawn-time validation, authoritative operator documentation, and every supported harness/backend applicability surface without duplicating the contract.
  • Add executable regressions proving that preview-only work cannot push or open a PR before approval and that ordinary direct-PR still does.
  • Run the targeted tests, bin/fm-doc-audience-check.sh for maintained prose changes, bin/fm-lint.sh, the relevant portable suite through bin/fm-test-run.sh, and the full no-mistakes path.
  • Open the PR against doitdigital0495/firstmate, never upstream.

What the investigation established (context a reviewer reading only the diff would not have):

  • Root cause and earliest machine-checkable divergence: bin/fm-brief.sh scaffolds the task section as a {TASK} placeholder and can never see the task text, which firstmate fills in afterwards by hand. bin/fm-spawn.sh already validated the brief's recorded "Delivery contract: mode=" line against the explicit --mode flag, but it never validated the brief against ITSELF. So a direct-PR or no-mistakes brief whose task text says "Do NOT push and do NOT open a PR" launched normally, and the generated definition of done - the machine-readable contract at the end of the brief - is what the worker follows.
  • Reproduced through public commands only: scaffolded with fm-brief.sh --mode direct-PR, filled {TASK} with the real cosmic-sets-import constraint text, and fm-spawn.sh ran all the way through to backend window creation with both the prohibition (task line) and the mandate ("push your branch and open a PR with gh-axi") present in one file.
  • Incident evidence was verified rather than assumed. Of the three named 2026-08-22 incidents only cosmic-sets-import actually carried the contradiction; cosmic-audit-fixes and cosmic-hero-video had no push/PR prohibition in their task text and behaved exactly as direct-PR documents. Those two are the control cases and are used as such in the regressions.
  • Scanning every ship brief in the live firstmate home found 7 push-mode briefs carrying the contradiction, not 3.
  • Deliberate design decision: a fourth "preview" delivery mode was considered and rejected. It would not prevent the failure, because firstmate would still have to choose it, and the incidents happened precisely because firstmate chose a push mode while writing contradictory prose. The refusal is the only option that structurally prevents the failure regardless of which mode is chosen, and the backlog already records the captain's own workaround for this stop point: scaffold as local-only.

What was implemented (deliberate choices a reviewer should not flag as mistakes):

  • One owner: the refusal lives in the existing delivery-agreement block of bin/fm-spawn.sh, which already owns "the brief and the delivery record must agree". It extends that to "the brief must also agree with itself". No new script, no new flag, no new mode, no override flag - an override would be exactly the warning-that-still-launches the user ruled out, and the accepted correction was to tighten detection rather than add one.
  • On mode=no-mistakes or mode=direct-PR the spawn reads ONLY the "# Task" section of the brief and refuses when that text forbids the push or opening the PR, naming --mode local-only as the mode whose contract already stops at a branch the captain reviews first. The refusal runs before any worktree, endpoint, window, or task metadata exists, so a refused spawn mutates nothing; regressions assert both that no metadata is written and that the backend is never reached.
  • Scoping the read to the "# Task" section is deliberate: the generated Rules and Definition of done deliberately carry their own prohibitions ("Never push to the default branch", "Never merge a PR") which bound HOW the mandate is carried out rather than refusing it, and must not be read as contradictions. A force-push ban is the same kind of bound and is stripped for the same reason.
  • No delivery mode contract changed. All three generated definitions of done are byte-identical to before, which is asserted by a regression, so the generated brief text deliberately needed no edit; adding a warning line to every generated brief would cost tokens on every task while the structural gate already prevents the failure.
  • Documentation was updated at its existing owners only, without duplicating the contract: the bin/fm-spawn.sh header (mechanics owner), a one-line cross-reference in the bin/fm-brief.sh header so the scaffolder knows the spawn will refuse, one sentence in docs/architecture.md's existing "Delivery modes are explicit per task" section (the maintainer-architecture owner), and now one line in AGENTS.md section 7 (the operator surface where the mode is chosen at intake).
  • Harness/backend applicability was inspected rather than assumed and recorded once, in the fm-spawn.sh header, rather than per adapter: both refusals read only local files and run before any worktree, endpoint, or task metadata exists, so they apply identically on every supported harness and runtime backend, on a --relaunch (whose mode comes from the task's recorded metadata and which reaches the same block), and on each pair of a batch spawn (which re-invokes the script per pair with the shared --mode). The check is not harness-dependent in the firstmate-coding-guidelines sense - no vendor process name, rendered output, banner, or keybind glyph is involved - so no per-harness live evidence record was added.
  • Tests follow the repo rule that they must exercise behavior through an executable or public interface and never assert implementation-source bytes: they scaffold real briefs through bin/fm-brief.sh, fill the placeholder, and drive bin/fm-spawn.sh, extending the existing tests/fm-task-delivery.test.sh which already owns delivery-contract behavior rather than inventing a new runner.

Verification evidence:

  • Counterfactual proven: with bin/fm-spawn.sh reverted to the base commit the new test fails and with the fix it passes.
  • bin/fm-lint.sh clean (ShellCheck 0.11.0 pinned, actionlint 1.7.12 pinned), bin/fm-doc-audience-check.sh clean (73 surfaces, 268 local links), check-agents-e2e clean on AGENTS.md, and tests/fm-task-delivery.test.sh, tests/fm-brief.test.sh and tests/fm-documentation-audiences.test.sh all pass through bin/fm-test-run.sh.

Baseline evidence that must be preserved and not hidden: four test scripts fail in this local environment and are pre-existing and unrelated to this change - tests/fm-arm-pretool-check.test.sh, tests/fm-calm-pi-extension.test.sh, tests/fm-kimi-harness.test.sh, and tests/fm-test-run.test.sh. They were verified to fail identically on the base commit ba5f4c4 with every file from this branch reverted, they live in four unrelated subsystems (the watcher pretool guard, the Pi calm extension, Kimi hook install, and a missing local ruby needed to parse a workflow as YAML), and they were deliberately left alone under this repo's blocking scope-discipline rule that a change touches only the files its task names. They are reported as baseline, not fixed here and not concealed.

What Changed

  • bin/fm-spawn.sh extends its existing delivery-agreement block from "the brief and the task record must agree" to "the brief must also agree with itself": on --mode no-mistakes or direct-PR it reads only the brief's # Task section and refuses the spawn when that text forbids the push or opening the PR that the generated definition of done mandates, naming --mode local-only in the error. A new brief_task_forbids_delivery helper does the matching with clause-scoped extended regular expressions that require a Git delivery object (so ordinary senses like "push to the analytics queue", "no push notifications", or "do not create a PR template file" still launch), strips bounded phrasings (force-push, push-to-default-branch), drops lines that permit the delivery later ("... then open the PR as usual"), and fails closed - each awk/tr/sed/grep stage runs into its own variable and any non-1 exit refuses instead of reading its own failure as agreement. The check runs before any worktree, endpoint, or task metadata is created.
  • Documentation updated at its existing owners without restating the contract: the bin/fm-spawn.sh header (mechanics, including --relaunch and batch-spawn applicability), a cross-reference in the bin/fm-brief.sh header, one sentence in docs/architecture.md's delivery-modes section, and one operator line in AGENTS.md section 7 routing a pre-remote stop point to local-only. AGENTS.md also gains the required ## Validate end-to-end section pointing at bin/fm-lint.sh, bin/fm-test-run.sh, the scaffold/spawn/teardown scripts, docs/tmux-backend.md, and docs/verification/.
  • tests/fm-task-delivery.test.sh grows regressions that scaffold real briefs through bin/fm-brief.sh, fill the {TASK} placeholder, and drive bin/fm-spawn.sh: true prohibitions (push-only, PR-only, comma-joined, subject-carrying, yet phrasings) must refuse and write no task metadata, while scoped/programming-sense/permitting phrasings and the direct-PR control cases must launch and reach a backend marker printed after the delivery check. The three generated definitions of done are asserted byte-identical to before.

Risk Assessment

✅ Low: The fix round closes the reported fail-open across every classifier stage with correct status capture, its regressions genuinely discriminate rather than pass vacuously, and I independently re-verified that classification on the documented corpus is unchanged; the only residual is an unexercised guard on two stages, which fails in the refusing direction.

Testing

Ran the smallest relevant automated set (tests/fm-task-delivery.test.sh, plus tests/fm-brief.test.sh and tests/fm-documentation-audiences.test.sh through bin/fm-test-run.sh) - all pass - and then proved the intent at the product level with two reproducible CLI transcripts driven only through bin/fm-brief.sh and bin/fm-spawn.sh: the 2026-08-22 cosmic-sets-import brief is refused with its own task line quoted and local-only named, nothing is created and the backend is never reached, while the same text under local-only and an ordinary direct-PR brief both launch; the identical brief still launches on base commit 69e445c, and a sweep of the 49 real push-mode ship briefs in the live firstmate home refuses exactly the 7 that carry the contradiction and nothing else. This change is CLI-only with no rendered UI surface, so the reviewer-visible evidence is the CLI transcripts rather than screenshots. Worktree is clean; evidence lives only under the evidence directory.

Evidence: End-to-end CLI transcript: contradictory brief refused, local-only and ordinary direct-PR launch, false/true positive matrix

Source: End-to-end CLI transcript: contradictory brief refused, local-only and ordinary direct-PR launch, false/true positive matrix

==============================================================
1. The real 2026-08-22 incident (cosmic-sets-import), scaffolded
   --mode direct-PR, whose generated definition of done says:
   | # Definition of done
   | Delivery contract: mode=direct-PR
   | This task ships **direct-PR**: you raise the PR yourself, without the no-mistakes pipeline.
   | The task is complete only when committed on your branch.
   | When it is implemented and committed, push your branch and open a PR with `gh-axi`, then append `done: PR {url}` to the status file and stop.
   | Do NOT run /no-mistakes. The configured merge authority decides whether to merge the PR; firstmate relays the outcome.

   ...while the task text the captain wrote says:
   | Rebuild the product sets import screen and verify it in the running preview at 390px.
   | 
   | - Do NOT push and do NOT open a PR. The captain approves that himself after his own QA.
   | 
   | # Herdr lifecycle declaration - NOT ENABLED

   $ fm-spawn.sh cosmic-sets-import <proj> claude --mode direct-PR --yolo off
   error: contradictory brief for cosmic-sets-import: mode=direct-PR ends in a pushed branch and an open PR, but the task text forbids it: "- Do NOT push and do NOT open a PR. The captain approves that himself after his own QA."
          scaffold this task --mode local-only so the worker stops at a branch the captain reviews first, or drop the prohibition from the task text
   [exit 1]
   metadata written? no - nothing was created

==============================================================
2. Same task text through the mode whose contract fits the stop point
   $ fm-spawn.sh cosmic-sets-import-local <proj> claude --mode local-only --yolo off
   notice: cosmic-sets-import-local ships mode=local-only while the standing posture for proj is no-mistakes - less rigor than the captain's standing posture; proceed only on a current explicit captain instruction or an intake judgment you can state
   [backend] tmux reached - delivery checks cleared
   [backend] tmux reached - delivery checks cleared
   [backend] tmux reached - delivery checks cleared
   [exit 1]

==============================================================
3. Ordinary direct-PR work still launches (control: cosmic-hero-video,
   the 2026-08-22 incident that carried no prohibition)
   $ fm-spawn.sh cosmic-hero-video <proj> claude --mode direct-PR --yolo off
   notice: cosmic-hero-video ships mode=direct-PR while the standing posture for proj is no-mistakes - less rigor than the captain's standing posture; proceed only on a current explicit captain instruction or an intake judgment you can state
   [backend] tmux reached - delivery checks cleared
   [backend] tmux reached - delivery checks cleared
   [backend] tmux reached - delivery checks cleared
   [exit 1]

==============================================================
4. Ordinary programming senses of push are NOT delivery refusals
   launched - Do not push to the analytics queue on every keystroke.
   launched - Never push to npm from CI.
   launched - Do not push it to the array; use concat.
   launched - Do not push this event to the dataLayer.
   launched - Do not push and pop the same stack twice.
   launched - Do not push anything to the CDN.
   launched - Do not push until the animation finishes.
   launched - No push notifications in this milestone; skip the service worker.
   launched - Do not push secrets or .env files to the remote.
   launched - Do not create a PR template file.
   launched - Do not push until the tests are green, then open the PR as usual.
   launched Do not open a PR against upstream; open it against the fork.

==============================================================
5. Real delivery prohibitions still refuse
   refused         - Do NOT push and do NOT open a PR. The captain approves that himself after his own QA.
   refused         - Do not push, do not open a PR. The captain approves after his own QA.
   refused         5. Commit the branch and stop. Do not invoke no-mistakes, push, or open a PR without captain approval.
   refused         - Never push this branch; leave the dev server running for the captain's QA.
   refused         - Never open a pull request; the captain reviews the running preview first.
   refused         - Do not push anything.
   refused         - No pushing and no PR until the captain approves.
   refused         - Do not push yet.
   refused         - No PR yet; the captain reviews first.
   refused         You cannot push until the captain signs off on the preview.
Evidence: Counterfactual against base 69e445c plus live-fleet recall sweep (49 push-mode briefs, 7 refused)

Source: Counterfactual against base 69e445c plus live-fleet recall sweep (49 push-mode briefs, 7 refused)

==============================================================
COUNTERFACTUAL - the identical contradictory brief, two fm-spawn.sh

  base commit 69e445c:
    [backend] tmux reached - delivery checks cleared
    [backend] tmux reached - delivery checks cleared
    [backend] tmux reached - delivery checks cleared

  this branch:
    error: contradictory brief for cosmic-sets-import: mode=direct-PR ends in a pushed branch and an open PR, but the task text forbids it: "- Do NOT push and do NOT open a PR. The captain approves that himself after his own QA."
           scaffold this task --mode local-only so the worker stops at a branch the captain reviews first, or drop the prohibition from the task text

==============================================================
LIVE FLEET RECALL - every real ship brief in ~/.firstmate/data,
driven through this branch's fm-spawn.sh with its own recorded mode

  REFUSED  agentic-portability-impl           mode=direct-PR   "- Do NOT push and do NOT open a PR. The captain approves that himself."
  REFUSED  cosmic-brand-accents               mode=direct-PR   "- Do NOT push and do NOT open a PR. Start the dev server, leave it running and report the
  REFUSED  cosmic-catalogue-threshold         mode=direct-PR   "- Do NOT push and do NOT open a PR. Start the dev server, verify at 390x844 and 1440px, l
  REFUSED  cosmic-homepage-pin                mode=direct-PR   "- Do NOT push and do NOT open a PR. Start the dev server in this worktree, verify in a re
  REFUSED  cosmic-sets-import                 mode=direct-PR   "- Do NOT push and do NOT open a PR. The captain approves that himself after his own QA."
  REFUSED  fm-autocompact-175k-b8             mode=no-mistakes "5. Run targeted checks, `bin/fm-lint.sh`, and repository-required validation. Commit the 
  REFUSED  fm-config-symlinked                mode=direct-PR   "- Do NOT push and do NOT open a PR; the captain approves that himself."

  ship briefs with a recorded contract : 53
  of those in a push mode              : 49
  refused as self-contradictory        : 7
Evidence: Reproducible demo driver used for the end-to-end transcript

Source: Reproducible demo driver used for the end-to-end transcript

#!/usr/bin/env bash
# End-to-end demo of the ship-brief self-contradiction refusal, driven only
# through the public commands bin/fm-brief.sh and bin/fm-spawn.sh.
set -u
ROOT=$1
W=$(mktemp -d)
HOME_DIR="$W/home"; PROJ="$W/projects/proj"; FAKEBIN="$W/bin"
mkdir -p "$HOME_DIR/data" "$HOME_DIR/state" "$HOME_DIR/config" "$PROJ" "$FAKEBIN"
# The backend a cleared spawn would reach; it announces itself and then refuses,
# so nothing is ever created and "launched" is visible in the transcript.
printf '#!/bin/sh\necho "[backend] tmux reached - delivery checks cleared" >&2\nexit 1\n' > "$FAKEBIN/tmux"
chmod +x "$FAKEBIN/tmux"

scaffold() { # <id> <mode> <task>
  rm -rf "$HOME_DIR/data/$1"
  FM_GATE_REFUSE_BYPASS=1 FM_ROOT_OVERRIDE='' FM_HOME="$HOME_DIR" FM_DATA_OVERRIDE="$HOME_DIR/data" FM_STATE_OVERRIDE="$HOME_DIR/state" \
    "$ROOT/bin/fm-brief.sh" "$1" proj --mode "$2" >/dev/null || exit 1
  awk -v repl="$3" '$0 == "{TASK}" { print repl; next } { print }' \
    "$HOME_DIR/data/$1/brief.md" > "$HOME_DIR/data/$1/b" && mv "$HOME_DIR/data/$1/b" "$HOME_DIR/data/$1/brief.md"
}
spawn() { # <id> <mode>
  FM_ROOT_OVERRIDE='' FM_HOME="$HOME_DIR" FM_STATE_OVERRIDE="$HOME_DIR/state" FM_DATA_OVERRIDE="$HOME_DIR/data" \
    FM_PROJECTS_OVERRIDE="$W/unused" FM_CONFIG_OVERRIDE="$HOME_DIR/config" \
    FM_SPAWN_NO_GUARD=1 FM_GATE_REFUSE_BYPASS=1 FM_BACKEND=tmux PATH="$FAKEBIN:$PATH" \
    "$ROOT/bin/fm-spawn.sh" "$1" "$PROJ" claude --mode "$2" --yolo off 2>&1
  echo "[exit $?]"
}

TASK='Rebuild the product sets import screen and verify it in the running preview at 390px.

- Do NOT push and do NOT open a PR. The captain approves that himself after his own QA.'

echo "=============================================================="
echo "1. The real 2026-08-22 incident (cosmic-sets-import), scaffolded"
echo "   --mode direct-PR, whose generated definition of done says:"
scaffold cosmic-sets-import direct-PR "$TASK"
sed -n '/^# Definition of done/,$p' "$HOME_DIR/data/cosmic-sets-import/brief.md" | sed -n '1,12p' | sed 's/^/   | /'
echo
echo "   ...while the task text the captain wrote says:"
sed -n '/^# Task/,/^# /p' "$HOME_DIR/data/cosmic-sets-import/brief.md" | sed -n '2,6p' | sed 's/^/   | /'
echo
echo "   $ fm-spawn.sh cosmic-sets-import <proj> claude --mode direct-PR --yolo off"
spawn cosmic-sets-import direct-PR | sed 's/^/   /'
echo "   metadata written? $( [ -e "$HOME_DIR/state/cosmic-sets-import.meta" ] && echo YES || echo 'no - nothing was created' )"
echo
echo "=============================================================="
echo "2. Same task text through the mode whose contract fits the stop point"
scaffold cosmic-sets-import-local local-only "$TASK"
echo "   $ fm-spawn.sh cosmic-sets-import-local <proj> claude --mode local-only --yolo off"
spawn cosmic-sets-import-local local-only | sed 's/^/   /'
echo
echo "=============================================================="
echo "3. Ordinary direct-PR work still launches (control: cosmic-hero-video,"
echo "   the 2026-08-22 incident that carried no prohibition)"
scaffold cosmic-hero-video direct-PR 'Re-encode the hero video to AV1 and check the LCP in the preview.

- If the re-encode misses the budget, fall back to option A and say so plainly in the PR body.'
echo "   $ fm-spawn.sh cosmic-hero-video <proj> claude --mode direct-PR --yolo off"
spawn cosmic-hero-video direct-PR | sed 's/^/   /'
echo
echo "=============================================================="
echo "4. Ordinary programming senses of push are NOT delivery refusals"
i=0
while IFS= read -r line; do
  [ -n "$line" ] || continue
  i=$((i+1))
  scaffold "prog-$i" direct-PR "Do the work.

$line"
  out=$(spawn "prog-$i" direct-PR)
  case "$out" in
    *"contradictory brief"*) verdict="REFUSED (false positive!)" ;;
    *"delivery checks cleared"*) verdict="launched" ;;
    *) verdict="?? $out" ;;
  esac
  printf '   %-8s %s\n' "$verdict" "$line"
done <<'ROWS'
- Do not push to the analytics queue on every keystroke.
- Never push to npm from CI.
- Do not push it to the array; use concat.
- Do not push this event to the dataLayer.
- Do not push and pop the same stack twice.
- Do not push anything to the CDN.
- Do not push until the animation finishes.
- No push notifications in this milestone; skip the service worker.
- Do not push secrets or .env files to the remote.
- Do not create a PR template file.
- Do not push until the tests are green, then open the PR as usual.
Do not open a PR against upstream; open it against the fork.
ROWS
echo
echo "=============================================================="
echo "5. Real delivery prohibitions still refuse"
i=0
while IFS= read -r line; do
  [ -n "$line" ] || continue
  i=$((i+1))
  scaffold "true-$i" direct-PR "Do the work.

$line"
  out=$(spawn "true-$i" direct-PR)
  case "$out" in
    *"contradictory brief"*) verdict="refused" ;;
    *"delivery checks cleared"*) verdict="LAUNCHED (miss!)" ;;
    *) verdict="?? $out" ;;
  esac
  printf '   %-15s %s\n' "$verdict" "$line"
done <<'ROWS'
- Do NOT push and do NOT open a PR. The captain approves that himself after his own QA.
- Do not push, do not open a PR. The captain approves after his own QA.
5. Commit the branch and stop. Do not invoke no-mistakes, push, or open a PR without captain approval.
- Never push this branch; leave the dev server running for the captain's QA.
- Never open a pull request; the captain reviews the running preview first.
- Do not push anything.
- No pushing and no PR until the captain approves.
- Do not push yet.
- No PR yet; the captain reviews first.
You cannot push until the captain signs off on the preview.
ROWS
rm -rf "$W"
Evidence: Reproducible driver for the counterfactual and live-fleet recall sweep

Source: Reproducible driver for the counterfactual and live-fleet recall sweep

#!/usr/bin/env bash
# (a) Counterfactual: the same brief on the base commit's fm-spawn.sh.
# (b) Recall on the live fleet: every real ship brief in ~/.firstmate/data driven
#     through fm-spawn.sh with the mode the brief itself records.
set -u
ROOT=$1; BASE=$2
W=$(mktemp -d)
HOME_DIR="$W/home"; PROJ="$W/projects/proj"; FAKEBIN="$W/bin"
mkdir -p "$HOME_DIR/data" "$HOME_DIR/state" "$HOME_DIR/config" "$PROJ" "$FAKEBIN"
printf '#!/bin/sh\necho "[backend] tmux reached - delivery checks cleared" >&2\nexit 1\n' > "$FAKEBIN/tmux"
chmod +x "$FAKEBIN/tmux"

spawn() { # <spawn-bin> <id> <mode>
  FM_ROOT_OVERRIDE='' FM_HOME="$HOME_DIR" FM_STATE_OVERRIDE="$HOME_DIR/state" FM_DATA_OVERRIDE="$HOME_DIR/data" \
    FM_PROJECTS_OVERRIDE="$W/unused" FM_CONFIG_OVERRIDE="$HOME_DIR/config" \
    FM_SPAWN_NO_GUARD=1 FM_GATE_REFUSE_BYPASS=1 FM_BACKEND=tmux PATH="$FAKEBIN:$PATH" \
    "$1" "$2" "$PROJ" claude --mode "$3" --yolo off 2>&1
}

# --- (a) counterfactual --------------------------------------------------
cp -r "$ROOT/bin" "$W/basebin"
git -C "$ROOT" show "$BASE:bin/fm-spawn.sh" > "$W/basebin/fm-spawn.sh"
chmod +x "$W/basebin/fm-spawn.sh"
mkdir -p "$HOME_DIR/data/cosmic-sets-import"
FM_GATE_REFUSE_BYPASS=1 FM_ROOT_OVERRIDE='' FM_HOME="$HOME_DIR" FM_DATA_OVERRIDE="$HOME_DIR/data" \
  FM_STATE_OVERRIDE="$HOME_DIR/state" "$ROOT/bin/fm-brief.sh" cosmic-sets-import proj --mode direct-PR >/dev/null
awk '$0 == "{TASK}" { print "Rebuild the product sets import screen and verify it in the running preview at 390px.\n\n- Do NOT push and do NOT open a PR. The captain approves that himself after his own QA."; next } { print }' \
  "$HOME_DIR/data/cosmic-sets-import/brief.md" > "$W/b" && mv "$W/b" "$HOME_DIR/data/cosmic-sets-import/brief.md"

echo "=============================================================="
echo "COUNTERFACTUAL - the identical contradictory brief, two fm-spawn.sh"
echo
echo "  base commit $BASE:"
spawn "$W/basebin/fm-spawn.sh" cosmic-sets-import direct-PR | sed 's/^/    /'
echo
echo "  this branch:"
spawn "$ROOT/bin/fm-spawn.sh" cosmic-sets-import direct-PR | sed 's/^/    /'

# --- (b) live-fleet recall ------------------------------------------------
echo
echo "=============================================================="
echo "LIVE FLEET RECALL - every real ship brief in ~/.firstmate/data,"
echo "driven through this branch's fm-spawn.sh with its own recorded mode"
echo
total=0; push=0; refused=0
for b in "$HOME/.firstmate"/data/*/brief.md; do
  id=$(basename "$(dirname "$b")")
  mode=$(sed -n 's/^Delivery contract: mode=//p' "$b" | head -n1)
  [ -n "$mode" ] || continue
  total=$((total+1))
  case "$mode" in no-mistakes|direct-PR) ;; *) continue ;; esac
  push=$((push+1))
  rm -rf "$HOME_DIR/data/live"; mkdir -p "$HOME_DIR/data/live"
  cp "$b" "$HOME_DIR/data/live/brief.md"
  out=$(spawn "$ROOT/bin/fm-spawn.sh" live "$mode")
  case "$out" in
    *"contradictory brief"*)
      refused=$((refused+1))
      printf '  REFUSED  %-34s mode=%-11s %s\n' "$id" "$mode" \
        "$(printf '%s\n' "$out" | sed -n 's/.*forbids it: //p' | cut -c1-90)" ;;
  esac
done
echo
echo "  ship briefs with a recorded contract : $total"
echo "  of those in a push mode              : $push"
echo "  refused as self-contradictory        : $refused"
rm -rf "$W"
Evidence: Key excerpt: the refusal a captain actually sees
$ fm-spawn.sh cosmic-sets-import <proj> claude --mode direct-PR --yolo off
error: contradictory brief for cosmic-sets-import: mode=direct-PR ends in a pushed branch and an open PR, but the task text forbids it: "- Do NOT push and do NOT open a PR. The captain approves that himself after his own QA."
scaffold this task --mode local-only so the worker stops at a branch the captain reviews first, or drop the prohibition from the task text
[exit 1]
metadata written? no - nothing was created

(base commit 69e445c, same brief: "[backend] tmux reached - delivery checks cleared")

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ⚠️ bin/fm-spawn.sh:1751 - The refusal gate discards its matcher's exit status: n=$(printf ... | sed ... | grep -nE &#34;$DELIVERY_NO_PUSH_RE&#34; | head -n 1 | cut -d: -f1) takes the status of cut, so grep exit 1 (no match) and grep exit >1 (regex engine error) are indistinguishable, and brief_task_forbids_delivery returns 1 in both cases. Any grep that cannot compile this ~2020-character ERE therefore launches the contradictory brief silently - the exact 2026-08-22 failure this change exists to stop, with no diagnostic. This is not hypothetical: the intent itself records that ugrep fails open on this pattern, and I reproduced it (ugrep -nE &#34;$DELIVERY_NO_PUSH_RE&#34; on the canonical "- Do NOT push and do NOT open a PR." line exits 2 while /usr/bin/grep exits 0). A safety classifier should fail closed. Suggested minimal fix: capture grep's status via PIPESTATUS or run grep into a variable first, and on status >1 print an error and exit 1 rather than treating it as "no contradiction found".
  • ℹ️ bin/fm-spawn.sh:1716 - Several accepted alternatives in the new vocabulary are covered by no regression row: raise/submit in DELIVERY_REFUSE_OPEN (line 1722), now/here/yourself and the for|unless|before|without|at|on hold prefixes in DELIVERY_PR_OBJECT (line 1720), permission/green light/go-ahead in DELIVERY_HOLD (line 1716), unless|without|for in DELIVERY_CONDITION (line 1718), and the we subject in DELIVERY_REFUSE_TOKEN. Round 4 of this same work deleted accepted tokens on exactly this ground ("an unexercised strip in a safety classifier is exactly the kind of thing that rots"), so the standard is applied unevenly. No concrete failure follows - these widen refusal rather than launch - so this is a note, not a blocker.

🔧 Fix: fail closed when delivery-refusal matcher errors
1 warning still open:

  • ⚠️ bin/fm-spawn.sh:1752 - The fail-closed fix only covers the grep stage of the classifier pipeline. hits=$(printf &#39;%s\n&#39; &#34;$lc&#34; | sed -E &#34;/$DELIVERY_PERMIT_RE/s/.*//&#34; | grep -nE &#34;$DELIVERY_NO_PUSH_RE&#34;) || st=$? takes the pipeline status, which without pipefail is grep's status only. If the sed stage cannot run (a sed without -E, sed missing from PATH, a permit pattern the engine will not compile) it prints nothing and exits non-zero, grep then exits 1 on empty input, st=1 is classified as a genuine no-match, and the contradictory brief launches - exactly the fail-open the round exists to close. Verified: printf &#39;do not push and do not open a pr.\n&#39; | sed --bogus-flag -E &#34;/x/s/.*//&#34; | grep -nE &#39;push&#39; yields st=1 with no hits. Two sibling stages have no status handling at all: lc=$(printf ... | tr ... | sed -E &#39;...&#39;) at line 1746 and the awk task extraction at line 1745 (set -e is suppressed inside the function because the call site is the left operand of ||), so a failure in either produces empty text and the same silent launch. Note set -o pipefail does NOT fix this - pipefail returns the rightmost non-zero status, so grep's 1 still masks sed's failure. Minimal fix: run the permit strip into a variable and check its status (and the tr/awk stages') before greping, treating any stage failure as the same >1 refusal path already added at line 1788. The new header sentence at line 18-20 ("That check fails closed: a matcher that cannot run its pattern refuses the spawn rather than reading its own failure as agreement") currently overstates the behavior.

🔧 Fix: fail closed on every delivery-classifier stage
1 info still open:

  • ℹ️ tests/fm-task-delivery.test.sh:447 - The fix round added fail-closed guards for four classifier stages (awk task extraction, tr case-fold, sed bounded-push strip, sed permit strip) plus grep, but the regression ROWS table covers only three: grep, and the two sed stages. The rm -f &#34;$fakebin/grep&#34; &#34;$fakebin/sed&#34; &#34;$fakebin/tr&#34; &#34;$fakebin/awk&#34; line shows all four were intended. The awk and tr guards at bin/fm-spawn.sh:1758 and 1760 are therefore new, unexercised safety code - exactly the standard this work applied to itself when it deleted an unexercised routing strip ("an unexercised strip in a safety classifier is exactly the kind of thing that rots"). Adding two ROWS entries (awk|/^# Task and tr|[:upper:]) reuses the existing stub_stage helper and needs no new machinery. No concrete failure follows - both guards widen refusal rather than launch - so this is a note, not a blocker.
✅ **Test** - passed

✅ No issues found.

  • ./bin/fm-test-run.sh tests/fm-task-delivery.test.sh - 10 cases pass, including the 57-row contradiction matrix, the fail-closed classifier-stage rows, and the byte-identical definitions-of-done assertions
  • ./bin/fm-test-run.sh tests/fm-brief.test.sh tests/fm-documentation-audiences.test.sh - scaffolder and maintained-prose suites pass at this head
  • Manual end-to-end CLI transcript: bin/fm-brief.sh &lt;id&gt; proj --mode direct-PR -> fill {TASK} with the real cosmic-sets-import prohibition -> bin/fm-spawn.sh &lt;id&gt; &lt;proj&gt; claude --mode direct-PR --yolo off (refuses, no metadata, backend never reached) vs --mode local-only (launches)
  • Manual counterfactual: identical filled brief run against git show 69e445c:bin/fm-spawn.sh (launches, reaches the fake backend) vs this branch (refuses)
  • Manual live-fleet recall sweep: every ~/.firstmate/data brief with a recorded Delivery contract: mode= driven through this branch's fm-spawn.sh with its own mode - 53 total, 49 push mode, 7 refused (agentic-portability-impl, cosmic-brand-accents, cosmic-catalogue-threshold, cosmic-homepage-pin, cosmic-sets-import, fm-autocompact-175k-b8, fm-config-symlinked)
  • Manual false-positive/true-positive matrix through the real spawn: 12 programming-sense and scoped-object lines launch, 10 delivery prohibitions refuse
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…ndates

fm-brief.sh's no-mistakes and direct-PR definitions of done both end in a
pushed branch and an open PR. The scaffold cannot see the {TASK} text
firstmate fills in afterwards, so a task text that forbids exactly that
shipped as a self-contradicting brief, and the generated section is what the
worker follows: on 2026-08-22 three workers opened PRs the captain had not
approved after their own task text said not to.

A ship spawn already refuses when the brief's recorded mode disagrees with
the flag. Extend that same check so the brief must also agree with itself: on
a push mode, read only the "# Task" section and refuse when it forbids the
push or opening the PR, naming local-only as the mode whose contract already
stops at a branch the captain reviews first.

The default-branch qualifier is stripped before matching and only the opening
verbs count, so "never push to the default branch" and "do not merge the PR"
stay compatible with direct-PR. The check runs before any worktree, endpoint,
or task metadata exists and reads only local files, so it is independent of
the selected harness and runtime backend.
Run against every ship brief this fleet has scaffolded, the first form of the
check read three lines it should not have: a force-push ban, a history-rewrite
ban, and prose that merely describes a no-push stop point.

Strip a force-push ban the same way the default-branch qualifier is stripped -
both bound how the mandate is carried out rather than refusing it - and require
the refusing token to open a line or a clause, so a refusal written mid sentence
reads as prose about pushing rather than an instruction not to.

On the same corpus the bounded check now fires on exactly the push-mode briefs
that carry the contradiction and on nothing else. Each bound is pinned by a
regression row that fails when it is removed.
…bility

Both refusals read only local files and run before any worktree, endpoint, or
task metadata exists, which is what makes them uniform across every supported
harness and runtime backend, on a relaunch, and on each pair of a batch spawn.
Record that once in the script header rather than per adapter.
The first detector read five phrasings as stop points that do not refuse
delivery at all: a ban scoped to what may be pushed, an unrelated product
feature that happens to be called push, a sequencing condition that explicitly
permits the PR afterwards, a PR routed at a named target, and a ban on a
PR-related file. With no override, each of those hard-blocked a legitimate
spawn.

Both objects must now end the clause or continue into a word that keeps the
delivery action itself as the object, so "do not push secrets", "no push
notifications", and "do not create a PR template file" no longer read as
refusals. A line that also permits the delivery later ("do not push until the
tests are green, then open the PR as usual") sequences the mandate rather than
refusing it and is dropped before matching, and a refusal may carry its own
subject ("you cannot push until the captain signs off").

Recall is unchanged: across the 49 ship briefs this fleet has scaffolded the
detector still fires on exactly the 7 carrying the contradiction and on nothing
else.

Regressions cover all five reported false positives, add true prohibitions that
name only the push and only the PR, and pin each new bound - removing any one of
them fails its row. Every launching row now asserts it reached a marker the
backend prints, so a row can no longer pass on a spawn that stopped earlier,
and every refusing row asserts that marker is absent.

AGENTS.md gains one section 7 line routing a stop point before the remote to
local-only, and the "Validate end-to-end" section every AGENTS.md owes, both
pointing at the authoritative scripts and docs rather than restating them.
@doitdigital0495
doitdigital0495 merged commit fc6a4e1 into main Aug 24, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants