diff --git a/.agents/skills/project-management/SKILL.md b/.agents/skills/project-management/SKILL.md index 86e37422d17..665b53f3425 100644 --- a/.agents/skills/project-management/SKILL.md +++ b/.agents/skills/project-management/SKILL.md @@ -38,15 +38,14 @@ Do not overwrite or repurpose an existing path. The registry records the project's standing posture, which is the captain's default for the work rather than any task's answer; `AGENTS.md` section 7 owns how each task's concrete mode and yolo are resolved at intake and passed explicitly to the brief, the spawn, and any promotion. Choose that posture when adding or creating the project: -- `no-mistakes` runs the full validation pipeline before a PR. - `direct-PR` pushes and opens a PR without the no-mistakes pipeline. - `local-only` has no required remote or PR and lands only through the approved local fast-forward path. -- `no-mistakes-prod-only` is a conditional policy rather than one flat mode: genuinely internal-only tooling, automation, contributor or operator process, and release or submission work ships `direct-PR`, while product-facing, mixed, and uncertain work ships `no-mistakes`. +- `no-mistakes` runs the full validation pipeline before a PR, and is registered only when the captain asks for it. -`no-mistakes-prod-only` is the default for a newly added or created remote-backed project when the captain specifies nothing, and a project with no remote defaults to `local-only`. -State that resolved default while confirming the source, local name, and posture instead of asking the captain to choose from scratch, and record a flat mode instead whenever they ask for one. -Existing registry entries keep the meaning they already have and are never migrated or reinterpreted, so a legacy entry with no bracket stays `no-mistakes`. -Registering a conditional policy is a one-time choice and never requires classifying any change; the per-task surface classification happens at each task's intake, and internal-only is never inferred from file location or project name. +For now, `direct-PR` is the default for a newly added or created remote-backed project, and a project with no remote defaults to `local-only`. +State that resolved default while confirming the source, local name, and posture instead of asking the captain to choose from scratch. +Never register a project as `no-mistakes` or `no-mistakes-prod-only` unless the captain asks for that posture. +The conditional `no-mistakes-prod-only` posture is retired: existing entries were converted to `direct-PR`, and a legacy entry that still carries it, or has no bracket, now reads as `direct-PR`. The optional `+yolo` posture changes merge authority only and does not change the delivery mode. Default it off for every project and every posture, and enable it only on the captain's explicit instruction. @@ -56,14 +55,14 @@ Default it off for every project and every posture, and enable it only on the ca Confirm the source URL, local project name, delivery posture, and autonomy posture, stating the resolved default for each rather than asking the captain to invent one. Clone into `projects/` and add the registry entry only after the destination is known to be unused. -A `no-mistakes` or `no-mistakes-prod-only` project must have an `origin` remote and must complete the initialization procedure below, because a conditional policy's product-facing work runs the pipeline while its internal-only work still takes the direct PR. A `direct-PR` project needs an `origin` remote but skips no-mistakes initialization. +A `no-mistakes` project, registered only at the captain's request, must have an `origin` remote and must complete the initialization procedure below. A `local-only` project may have no remote and skips no-mistakes initialization. ## Create a project Creating a GitHub repository is outward-facing. -Before making that remote change, propose the repository name, owner or organization, visibility, and delivery posture, defaulting visibility to private and the posture to `no-mistakes-prod-only`, then obtain the captain's explicit consent for those exact values; a stated default never replaces that consent. +Before making that remote change, propose the repository name, owner or organization, visibility, and delivery posture, defaulting visibility to private and the posture to `direct-PR`, then obtain the captain's explicit consent for those exact values; a stated default never replaces that consent. Use `gh-axi` for the approved GitHub operation and consult its current help rather than relying on remembered flags. After remote creation succeeds, clone it locally, add the registry entry, and initialize it according to its delivery posture. @@ -72,7 +71,7 @@ The captain's request to create that local project authorizes this local initial ## Initialize -Run no-mistakes initialization only for `no-mistakes` and `no-mistakes-prod-only` projects: +Run no-mistakes initialization only when the captain has asked for it, either for a project registered as `no-mistakes` at the captain's request or on a direct request to initialize one: ```sh cd projects/ && no-mistakes init && no-mistakes doctor diff --git a/AGENTS.md b/AGENTS.md index c4f62af7dbc..025aa6a667a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -45,7 +45,7 @@ You may maintain this repo's private operational state directly. Shared tracked material is `AGENTS.md`, `README.md`, `CONTRIBUTING.md`, `.tasks.toml`, `.github/workflows/`, `bin/`, `.agents/skills/`, and public `skills/`. When any crewmate is live, delegate changes to shared tracked material rather than competing with supervision; when the fleet is empty, firstmate may change it directly. This repo is a shared template, while `.env`, `data/`, `state/`, `config/`, `projects/`, and `.no-mistakes/` are captain-private and gitignored. -Ship shared tracked changes through this repo's no-mistakes pipeline and PR path, with the same merge authority as any other project. +Ship shared tracked changes through a PR on the delivery path section 7 selects, with the same merge authority as any other project. Never add an agent name as a commit co-author. ## 2. Layout and state @@ -311,8 +311,8 @@ Load `diagnostic-reasoning` before scoping a reported bug and before acting on a Resolve every ship task's concrete delivery mode and `yolo` merge posture at intake. Pass the mode explicitly to the brief, and pass both values explicitly to the spawn and any scout promotion; each command refuses to guess the values it consumes. A current explicit captain instruction wins; otherwise the project's registry entry is the captain's standing posture, and dropping below its rigor needs a reason you can state. -On a `no-mistakes-prod-only` project, classify the task's surface: internal-only tooling, automation, contributor or operator process, and release or submission work ships `direct-PR`, while product-facing, mixed, and uncertain work ships `no-mistakes`; never infer internal-only from file location or project name. -An unregistered project or absent registry resolves to `no-mistakes` with yolo off, and the registration gap goes to the captain. +For now, firstmate never selects `no-mistakes` itself, for any project or kind of change: a task ships `no-mistakes` only when the captain explicitly requests it for that task or asked to register it as the project's posture, and otherwise ships `direct-PR`, or `local-only` for a project with no remote. +An unregistered project or absent registry resolves to that same default with yolo off, and the registration gap goes to the captain. Record the resulting mode, `yolo` merge posture, and the one-line reason for any deviation in the backlog item note. Treat file or subsystem overlap as a risk signal rather than an automatic reason to wait, and dispatch isolated work immediately with no concurrency cap when each change can be independently implemented and validated and the selected delivery path can reconcile ordinary rebases or conflicts. @@ -343,7 +343,7 @@ The selected delivery path owns its own rigor. When no-mistakes is selected, no-mistakes alone owns review, fixes, tests, documentation, push, PR, and CI; otherwise follow the faster path without adding an independent reviewer. Never hold work outside no-mistakes for a manual clean verdict, stack serial manual reviews, or infer authority for one from security, architecture, or risk alone. A separate review or audit is allowed only when the captain explicitly requests that deliverable or the authorized task is a knowledge-only review; one named question remains scoped to that question. -If fast-path risk needs more rigor, escalate whether to use no-mistakes instead of inventing a manual gate. +If fast-path risk needs more rigor, ask the captain whether to request no-mistakes instead of inventing a manual gate. The path's worker, automated gates, and captain approval remain authoritative: - **no-mistakes** runs the full pipeline through a PR, then waits for the configured merge authority. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e2fd860cafd..b1ab2ed3e17 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -82,7 +82,7 @@ Coordinate any workflow rollback with its required-check names so a retired chec ## Development -Tracked changes to firstmate itself - `AGENTS.md`, `README.md`, `CONTRIBUTING.md`, `.tasks.toml`, `.github/workflows/`, `bin/`, `.agents/skills/`, and `skills/` - ship through the `no-mistakes` pipeline on a feature branch and require an explicit merge approval. +Tracked changes to firstmate itself - `AGENTS.md`, `README.md`, `CONTRIBUTING.md`, `.tasks.toml`, `.github/workflows/`, `bin/`, `.agents/skills/`, and `skills/` - ship through a PR from a feature branch on the delivery path `AGENTS.md` section 7 selects - `direct-PR` unless the captain requests the `no-mistakes` pipeline - and require an explicit merge approval. Before making any such change, load the agent-only `firstmate-coding-guidelines` skill (`.agents/skills/firstmate-coding-guidelines/SKILL.md`). It has the knowledge-placement rules that keep `AGENTS.md` from regrowing after each diet pass. There is no reliable way for `bin/fm-brief.sh`'s scaffold to detect that a task's repo is firstmate itself, so firstmate adds this skill's load line to firstmate-repo briefs by hand. diff --git a/bin/fm-brief.sh b/bin/fm-brief.sh index 264126f6d99..5b3464d11b5 100755 --- a/bin/fm-brief.sh +++ b/bin/fm-brief.sh @@ -45,8 +45,8 @@ # direct-PR implement -> push + open PR via gh-axi (no pipeline) -> configured merge authority # local-only implement on branch, stop and report "ready in branch" (no push/PR); # the configured merge authority approves, firstmate merges to local main -# no-mistakes-prod-only is a registry policy, not a task mode; resolve it to one of -# the three concrete modes at intake before calling this script. +# no-mistakes-prod-only is a retired registry value, not a task mode; pass one of +# the three concrete modes resolved at intake. # The generated ship brief records the chosen mode as a fixed machine-readable # "Delivery contract: mode=" line. bin/fm-spawn.sh reads that line and refuses # to launch a ship task whose explicit --mode disagrees, so an adjusted brief and the @@ -166,7 +166,7 @@ if [ "$KIND" = ship ]; then case "$MODE" in no-mistakes|direct-PR|local-only) ;; no-mistakes-prod-only) - echo "error: no-mistakes-prod-only is a registry policy, not a task mode; classify this task's surface and resolve it to no-mistakes or direct-PR at intake" >&2 + echo "error: no-mistakes-prod-only is a retired registry value, not a task mode; ship direct-PR unless the captain requested no-mistakes for this task at intake" >&2 exit 1 ;; *) echo "error: --mode must be one of no-mistakes, direct-PR, local-only (got '$MODE')" >&2; exit 1 ;; esac diff --git a/bin/fm-fleet-sync.sh b/bin/fm-fleet-sync.sh index dd00be86baa..bc942b25f16 100755 --- a/bin/fm-fleet-sync.sh +++ b/bin/fm-fleet-sync.sh @@ -324,7 +324,7 @@ sync_project() { echo "$label: skipped: not a clone root (git would act on $proj_top)" return 0 fi - mode_line=$("$FM_ROOT/bin/fm-project-mode.sh" "$label" 2>/dev/null || echo "no-mistakes off") + mode_line=$("$FM_ROOT/bin/fm-project-mode.sh" "$label" 2>/dev/null || echo "direct-PR off") mode=${mode_line%% *} if [ "$mode" = "local-only" ]; then echo "$label: skipped: local-only project" diff --git a/bin/fm-project-mode.sh b/bin/fm-project-mode.sh index 3046202f23f..3af327ef773 100755 --- a/bin/fm-project-mode.sh +++ b/bin/fm-project-mode.sh @@ -12,28 +12,28 @@ # bin/fm-spawn.sh's advisory registry-deviation notice. # # Registry line format (data/projects.md): -# - - (added ) -> no-mistakes off (legacy default) +# - - (added ) -> direct-PR off (legacy, no bracket) # - [] - (added ) -> off # - [ +yolo] - (added ) -> on # # Registered modes: -# no-mistakes full pipeline -> PR -> configured merge authority (default) -# direct-PR push + PR via gh-axi, no pipeline +# direct-PR push + PR via gh-axi, no pipeline (default) # local-only local branch, no remote/PR, guarded local merge -# no-mistakes-prod-only a conditional policy, not a task mode: firstmate -# classifies each task's surface at intake (the -# project-management skill owns that classification). -# Mechanical output maps it to its most rigorous leg, -# no-mistakes, so sync, seeding, and init treat such a -# project as the remote-backed pipeline project it is. +# no-mistakes full pipeline -> PR -> configured merge authority; +# registered only at the captain's request +# no-mistakes-prod-only retired conditional posture (the project-management +# skill owns its retirement); still parsed so an +# unconverted entry is not a typo, and mapped to +# direct-PR, the posture it was converted to. # yolo (orthogonal) = merge authority only: when on, firstmate merges green, # in-scope work itself (AGENTS.md section 7). # -# --raw prints the registered annotation unmapped, so a caller that must tell a -# conditional policy apart from a flat mode sees "no-mistakes-prod-only" itself. +# --raw prints the registered annotation unmapped, so a caller that must tell the +# retired value apart from a flat mode sees "no-mistakes-prod-only" itself. # -# An unknown/missing project or unknown mode falls back to "no-mistakes off" and warns -# to stderr, so a typo never silently drops the gate. +# An unknown/missing project or unknown mode falls back to the "direct-PR off" +# default and warns to stderr, so a typo is visible. Firstmate never selects the +# no-mistakes pipeline on its own (AGENTS.md section 7), so no fallback does either. # Usage: fm-project-mode.sh [--raw] set -eu @@ -50,15 +50,15 @@ fi NAME=${1:?usage: fm-project-mode.sh [--raw] } if [ ! -f "$REG" ]; then - echo "warn: no registry at $REG; defaulting $NAME to no-mistakes off" >&2 - echo "no-mistakes off" + echo "warn: no registry at $REG; defaulting $NAME to direct-PR off" >&2 + echo "direct-PR off" exit 0 fi # awk emits " " (one line) or nothing if the project is absent. parsed=$(awk -v n="$NAME" ' $1=="-" && $2==n { - mode="no-mistakes"; yolo="off"; + mode="direct-PR"; yolo="off"; if ($3 ~ /^\[/) { s=""; for (i=3; i<=NF; i++) { s = s (s==""?"":" ") $i; if ($i ~ /\]$/) break } @@ -72,8 +72,8 @@ parsed=$(awk -v n="$NAME" ' ' "$REG") if [ -z "$parsed" ]; then - echo "warn: project \"$NAME\" not in registry; defaulting to no-mistakes off" >&2 - echo "no-mistakes off" + echo "warn: project \"$NAME\" not in registry; defaulting to direct-PR off" >&2 + echo "direct-PR off" exit 0 fi @@ -81,12 +81,12 @@ mode=${parsed%% *} yolo=${parsed##* } case "$mode" in no-mistakes|direct-PR|local-only|no-mistakes-prod-only) ;; - *) echo "warn: unknown mode \"$mode\" for $NAME; defaulting to no-mistakes off" >&2; mode=no-mistakes; yolo=off ;; + *) echo "warn: unknown mode \"$mode\" for $NAME; defaulting to direct-PR off" >&2; mode=direct-PR; yolo=off ;; esac case "$yolo" in on|off) ;; *) yolo=off ;; esac -# A conditional policy is not a task mode. Mechanical callers get its most -# rigorous leg; --raw callers get the annotation itself (see the header). +# The retired conditional posture reads as the direct-PR it was converted to; +# --raw callers get the annotation itself (see the header). if [ "$RAW" -eq 0 ] && [ "$mode" = no-mistakes-prod-only ]; then - mode=no-mistakes + mode=direct-PR fi echo "$mode $yolo" diff --git a/bin/fm-promote.sh b/bin/fm-promote.sh index 1f53b8a50d3..4b4e4295990 100755 --- a/bin/fm-promote.sh +++ b/bin/fm-promote.sh @@ -23,7 +23,7 @@ # alongside the kind= flip. Firstmate resolves both at promotion time, having just # read the scout's report (AGENTS.md section 7); data/projects.md holds the # captain's standing posture as context, and this script never looks it up. -# no-mistakes-prod-only is a registry policy rather than a task mode and is refused. +# no-mistakes-prod-only is a retired registry value rather than a task mode and is refused. # Usage: fm-promote.sh --mode --yolo set -eu @@ -89,7 +89,7 @@ done case "$MODE" in no-mistakes|direct-PR|local-only) ;; no-mistakes-prod-only) - echo "error: no-mistakes-prod-only is a registry policy, not a task mode; classify this task's surface and resolve it to no-mistakes or direct-PR" >&2 + echo "error: no-mistakes-prod-only is a retired registry value, not a task mode; ship direct-PR unless the captain requested no-mistakes for this task" >&2 exit 1 ;; *) echo "error: --mode must be one of no-mistakes, direct-PR, local-only (got '$MODE')" >&2; exit 1 ;; esac diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index fa8da51d9ea..d3e2ce4e2d0 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -22,8 +22,8 @@ # than becoming intent. That library owns the parsing and intent rules. When # the explicit mode carries less rigor than the project's standing posture, a # loud one-line deviation notice is printed and the spawn continues. -# no-mistakes-prod-only is a registry policy rather than a task mode and is -# refused as a flag value. +# no-mistakes-prod-only is a retired registry value rather than a task mode and +# is refused as a flag value. # Ship/scout launches always put fm-dod-lib.sh's current worker role scope # first in the private launch-brief overlay, including the exact task-owned # steering inbox. This never rewrites a project's instruction files or a @@ -718,7 +718,7 @@ else case "$MODE" in no-mistakes | direct-PR | local-only) ;; no-mistakes-prod-only) - echo "error: no-mistakes-prod-only is a registry policy, not a task mode; classify this task's surface and resolve it to no-mistakes or direct-PR at intake" >&2 + echo "error: no-mistakes-prod-only is a retired registry value, not a task mode; ship direct-PR unless the captain requested no-mistakes for this task at intake" >&2 exit 1 ;; *) @@ -2629,9 +2629,9 @@ if [ "$KIND" = ship ]; then fi # The registry holds the captain's standing posture, so dropping below it is # allowed (a current explicit captain instruction wins) but never silent. An - # unregistered project resolves to the same no-mistakes standing default, which - # is why the notice names the standing posture rather than the registry line. A - # conditional policy is excluded: both of its legs are legitimate classifications. + # unregistered project resolves to the same direct-PR standing default, which + # is why the notice names the standing posture rather than the registry line. + # The retired conditional value is excluded: it now reads as direct-PR. STANDING_MODE=$("$FM_ROOT/bin/fm-project-mode.sh" --raw "$PROJ_NAME" 2>/dev/null | cut -d' ' -f1) || STANDING_MODE= if [ -n "$STANDING_MODE" ] && [ "$STANDING_MODE" != no-mistakes-prod-only ] && [ "$(delivery_rigor_rank "$MODE")" -lt "$(delivery_rigor_rank "$STANDING_MODE")" ]; then diff --git a/docs/architecture.md b/docs/architecture.md index 7cdc3ff6d5e..b06b5ca526f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -327,7 +327,7 @@ The mode is passed explicitly to `bin/fm-brief.sh`, and both values are passed e A ship brief records its mode as a fixed machine-readable line and the spawn refuses to launch on a different one, so the worker's instructions and the recorded task delivery cannot diverge. `bin/fm-dod-lib.sh` is the one owner of that mode's definition of done, rendered into a generated ship brief, the ship instructions a promoted scout receives, and that scout's own `brief.md` so a later relaunch reads the same contract, so a promoted worker cannot be handed a weaker contract than a briefed one. It is also the one owner of the no-mistakes `--intent` contract those workers follow. -`data/projects.md` records each project's standing posture and optional `+yolo` merge flag as the captain's default and as context for that decision, including the conditional `no-mistakes-prod-only` policy; a ship spawn that drops below the registered rigor prints a deviation notice and continues. +`data/projects.md` records each project's standing posture and optional `+yolo` merge flag as the captain's default and as context for that decision, where `no-mistakes` appears only at the captain's request and the retired `no-mistakes-prod-only` value reads as `direct-PR`; a ship spawn that drops below the registered rigor prints a deviation notice and continues. `bin/fm-project-mode.sh` remains the one registry parser for the mechanical consumers that have no task in hand: fleet sync's `local-only` skip and home seeding's refusal and no-mistakes initialization. When a selected delivery path calls for a diff, `bin/fm-review-diff.sh` refreshes the authoritative base and, when task meta records `pr=`, always fetches and compares against `refs/pull//head` by default (recorded `pr_head=` is only an offline fallback) before falling back to the local branch with a warning. Where a no-mistakes pipeline stores evidence in the repo, it publishes that PR-viewable validation evidence to an orphan evidence branch that shares no history with code branches, so it never enters the crew branch or the default branch. diff --git a/tests/fm-brief.test.sh b/tests/fm-brief.test.sh index 88f4e566ff0..8a10f48af7f 100755 --- a/tests/fm-brief.test.sh +++ b/tests/fm-brief.test.sh @@ -223,8 +223,8 @@ test_ship_modes_generate_clean_briefs() { # A ship task's delivery mode is firstmate's per-task decision, so a missing or # unusable value must stop the scaffold instead of silently defaulting. The -# no-mistakes-prod-only row is the conditional registry policy: it is never a task -# mode, and its refusal must say to classify the task's surface first. +# no-mistakes-prod-only row is the retired conditional registry value: it is never +# a task mode, and its refusal must say so. test_ship_mode_is_required_and_closed_set() { local home id out status label flag expect home="$TMP_ROOT/mode-required-home" @@ -243,7 +243,7 @@ test_ship_mode_is_required_and_closed_set() { missing --mode||ship briefs require --mode empty --mode value|--mode|requires a value unknown mode value|--mode nope|must be one of no-mistakes, direct-PR, local-only -conditional policy is not a task mode|--mode no-mistakes-prod-only|classify this task's surface +retired conditional value is not a task mode|--mode no-mistakes-prod-only|retired registry value ROWS pass "fm-brief.sh: ship --mode is required and closed-set validated" } diff --git a/tests/fm-secondmate-lifecycle-e2e.test.sh b/tests/fm-secondmate-lifecycle-e2e.test.sh index 56b7ac3f1f5..cd3dd67bf25 100755 --- a/tests/fm-secondmate-lifecycle-e2e.test.sh +++ b/tests/fm-secondmate-lifecycle-e2e.test.sh @@ -51,7 +51,7 @@ setup_world() { cat > "$HOME_DIR/data/projects.md" </dev/null) - [ "$out" = "no-mistakes off" ] || fail "fm-project-mode did not isolate missing registry by home" + [ "$out" = "direct-PR off" ] || fail "fm-project-mode did not isolate missing registry by home" FM_HOME="$home_one" "$ROOT/bin/fm-brief.sh" task-a app --mode no-mistakes >/dev/null || fail "brief scaffold failed under FM_HOME" brief="$home_one/data/task-a/brief.md" @@ -1158,7 +1158,7 @@ test_home_seed_skips_initialized_existing_no_mistakes_projects() { origin=$(git -C "$home/projects/alpha" remote get-url origin) git clone --quiet "$origin" "$subhome/projects/alpha" git -C "$subhome/projects/alpha" remote add no-mistakes "$TMP_ROOT/no-mistakes-alpha.git" - printf '%s\n' '- alpha - alpha project (added 2026-06-22)' '- beta - beta project (added 2026-06-22)' > "$home/data/projects.md" + printf '%s\n' '- alpha [no-mistakes] - alpha project (added 2026-06-22)' '- beta [no-mistakes] - beta project (added 2026-06-22)' > "$home/data/projects.md" fakebin=$(make_recording_no_mistakes "$TMP_ROOT/existing-initialized-fake") : > "$log" @@ -1190,7 +1190,7 @@ test_home_seed_refuses_uninitialized_existing_no_mistakes_project() { mkdir -p "$subhome/projects" origin=$(git -C "$home/projects/alpha" remote get-url origin) git clone --quiet "$origin" "$subhome/projects/alpha" - printf '%s\n' '- alpha - alpha project (added 2026-06-22)' > "$home/data/projects.md" + printf '%s\n' '- alpha [no-mistakes] - alpha project (added 2026-06-22)' > "$home/data/projects.md" fakebin=$(make_recording_no_mistakes "$TMP_ROOT/existing-uninitialized-fake") : > "$log" diff --git a/tests/fm-task-delivery.test.sh b/tests/fm-task-delivery.test.sh index 7457a5d87b5..ab89d57c566 100755 --- a/tests/fm-task-delivery.test.sh +++ b/tests/fm-task-delivery.test.sh @@ -93,7 +93,7 @@ missing --yolo|--mode no-mistakes|ship spawns require --yolo missing --mode|--yolo off|ship spawns require --mode unknown mode|--mode nope --yolo off|must be one of no-mistakes, direct-PR, local-only unknown yolo|--mode no-mistakes --yolo maybe|--yolo must be on or off -conditional policy as a task mode|--mode no-mistakes-prod-only --yolo off|classify this task's surface +retired conditional value as a task mode|--mode no-mistakes-prod-only --yolo off|retired registry value ROWS pass "fm-spawn: a ship spawn requires a valid explicit mode and yolo before anything is created" } @@ -158,9 +158,9 @@ EOF # The registry is the captain's standing posture, so dropping below its rigor is # allowed but never silent, while matching or exceeding it stays quiet. An -# unregistered project resolves to the same no-mistakes standing default -# (AGENTS.md section 7), so a downgrade there is announced too. A conditional -# policy is excluded because both of its legs are legitimate classifications. +# unregistered project resolves to the direct-PR standing default (AGENTS.md +# section 7), so shipping it direct-PR is quiet while local-only is announced. +# The retired conditional value reads as direct-PR and is never announced. test_spawn_notices_a_rigor_downgrade_against_the_registry() { local rec home proj fakebin out label mode registry expect registered n=0 while IFS='|' read -r label registry mode expect registered; do @@ -187,8 +187,10 @@ no-mistakes project shipped direct-PR|- proj [no-mistakes] - fixture (added 2026 no-mistakes project shipped local-only|- proj [no-mistakes] - fixture (added 2026-01-01)|local-only|notice|no-mistakes no-mistakes project shipped no-mistakes|- proj [no-mistakes] - fixture (added 2026-01-01)|no-mistakes|quiet|no-mistakes local-only project shipped no-mistakes|- proj [local-only] - fixture (added 2026-01-01)|no-mistakes|quiet|local-only -conditional policy shipped direct-PR|- proj [no-mistakes-prod-only] - fixture (added 2026-01-01)|direct-PR|quiet|no-mistakes-prod-only -unregistered project resolves to the no-mistakes standing default|- other [no-mistakes] - fixture (added 2026-01-01)|direct-PR|notice|no-mistakes +retired conditional value shipped direct-PR|- proj [no-mistakes-prod-only] - fixture (added 2026-01-01)|direct-PR|quiet|no-mistakes-prod-only +unregistered project shipped direct-PR is quiet|- other [no-mistakes] - fixture (added 2026-01-01)|direct-PR|quiet|direct-PR +unregistered project resolves to the direct-PR standing default|- other [no-mistakes] - fixture (added 2026-01-01)|local-only|notice|direct-PR +unbracketed legacy entry reads as direct-PR|- proj - fixture (added 2026-01-01)|direct-PR|quiet|direct-PR ROWS pass "fm-spawn: a rigor downgrade against the registered posture is announced, never blocked" } @@ -235,8 +237,8 @@ test_promote_requires_and_records_the_delivery_contract() { out=$(FM_HOME="$home" FM_STATE_OVERRIDE="$home/state" "$PROMOTE" promote-d1 --mode no-mistakes-prod-only --yolo off 2>&1) status=$? - [ "$status" -ne 0 ] || fail "promotion on a conditional policy should exit non-zero" - assert_contains "$out" "classify this task's surface" "promote did not refuse the conditional policy as a task mode" + [ "$status" -ne 0 ] || fail "promotion on the retired conditional value should exit non-zero" + assert_contains "$out" "retired registry value" "promote did not refuse the retired conditional value as a task mode" blocked_data="$home/data-blocked" printf 'not a directory\n' > "$blocked_data" @@ -397,9 +399,10 @@ STUB pass "fm-promote: a promoted worker receives the same mode-specific delivery contract a briefed one does" } -# The registry parser survives for the mechanical consumers only. It accepts the -# conditional policy, maps it to its most rigorous leg for them, and exposes the -# raw annotation for the one caller that must tell a policy from a flat mode. +# The registry parser survives for the mechanical consumers only. It still accepts +# the retired conditional value, maps it to the direct-PR it was converted to, and +# exposes the raw annotation. No default or fallback ever yields no-mistakes: only +# an explicit (captain-requested) no-mistakes entry does. test_project_mode_maps_the_conditional_policy() { local home out err home="$TMP_ROOT/project-mode/home" @@ -409,14 +412,16 @@ test_project_mode_maps_the_conditional_policy() { - yoloproj [no-mistakes-prod-only +yolo] - fixture (added 2026-01-01) - flatproj [direct-PR] - fixture (added 2026-01-01) - typoproj [no-mistakez] - fixture (added 2026-01-01) +- legacyproj - fixture (added 2026-01-01) +- nmproj [no-mistakes] - fixture (added 2026-01-01) EOF out=$(FM_HOME="$home" "$PROJECT_MODE" prodproj 2>/dev/null) - [ "$out" = "no-mistakes off" ] || fail "conditional policy did not map to its most rigorous leg (got '$out')" + [ "$out" = "direct-PR off" ] || fail "retired conditional value did not map to direct-PR (got '$out')" err=$(FM_HOME="$home" "$PROJECT_MODE" prodproj 2>&1 >/dev/null) [ -z "$err" ] || fail "a registered conditional policy still warned as unknown: $err" out=$(FM_HOME="$home" "$PROJECT_MODE" yoloproj 2>/dev/null) - [ "$out" = "no-mistakes on" ] || fail "conditional policy dropped its +yolo posture (got '$out')" + [ "$out" = "direct-PR on" ] || fail "retired conditional value dropped its +yolo posture (got '$out')" out=$(FM_HOME="$home" "$PROJECT_MODE" --raw prodproj 2>/dev/null) [ "$out" = "no-mistakes-prod-only off" ] || fail "--raw did not expose the registered annotation (got '$out')" @@ -425,10 +430,19 @@ EOF [ "$out" = "direct-PR off" ] || fail "--raw altered a flat registered mode (got '$out')" out=$(FM_HOME="$home" "$PROJECT_MODE" typoproj 2>/dev/null) - [ "$out" = "no-mistakes off" ] || fail "a typo'd mode no longer falls back to the most rigorous default" + [ "$out" = "direct-PR off" ] || fail "a typo'd mode did not fall back to the direct-PR default (got '$out')" err=$(FM_HOME="$home" "$PROJECT_MODE" typoproj 2>&1 >/dev/null) assert_contains "$err" "unknown mode" "a typo'd registry mode stopped warning" - pass "fm-project-mode: the conditional policy is accepted, mapped for mechanical callers, and readable raw" + + out=$(FM_HOME="$home" "$PROJECT_MODE" legacyproj 2>/dev/null) + [ "$out" = "direct-PR off" ] || fail "an unbracketed legacy entry did not read as direct-PR (got '$out')" + out=$(FM_HOME="$home" "$PROJECT_MODE" never-registered 2>/dev/null) + [ "$out" = "direct-PR off" ] || fail "an unregistered project did not default to direct-PR (got '$out')" + out=$(FM_HOME="$home/absent" "$PROJECT_MODE" prodproj 2>/dev/null) + [ "$out" = "direct-PR off" ] || fail "an absent registry did not default to direct-PR (got '$out')" + out=$(FM_HOME="$home" "$PROJECT_MODE" nmproj 2>/dev/null) + [ "$out" = "no-mistakes off" ] || fail "an explicit no-mistakes entry no longer reads as no-mistakes (got '$out')" + pass "fm-project-mode: only an explicit no-mistakes entry yields no-mistakes; defaults and the retired value read as direct-PR" } # Spawn and promotion refuse leftover Task-subsection placeholders through the