Skip to content

harden: fail-closed PR gate, origin/HEAD refresh, worktree isolation - #10

Merged
DereKk8 merged 3 commits into
mainfrom
fm/fleet-hardening-f2
Jul 14, 2026
Merged

DereKk8 merged 3 commits into
mainfrom
fm/fleet-hardening-f2

Conversation

@DereKk8

@DereKk8 DereKk8 commented Jul 14, 2026

Copy link
Copy Markdown
Owner

Summary

Three hardening fixes derived from the nm-orchestration incident (2026-07-14, PR #64), where a PR whose own body admitted "Test skipped / 1 error still open / 🚨 High risk" was relayed to the captain as ready because fm-pr-check.sh never read the PR body before arming the merge poll.

Fix 1.1 — bin/fm-fleet-sync.sh: calls git remote set-head origin --auto (best-effort) after every successful fetch so origin/HEAD stays accurate when the upstream repo renames its default branch.
default_branch() emits an actionable warning with the repair command when it falls back to the hardcoded main/master scan.
The STUCK report for a clone on a non-default branch includes the one git checkout command needed to restore it.

Fix 1.2 — bin/fm-pr-check.sh (highest value): the gate is fully fail-closed.
Seven distinct refuse paths cover every unverifiable condition: gh not on PATH, any gh pr view call fails (auth error, network, rate limit), PR body contains a no-mistakes skip marker / unresolved error / high-risk label, GitHub reports merge state DIRTY, project= absent from task meta or directory not found, ls-remote fails, ls-remote returns no symbolic ref.
--force-ready is the only bypass and records pr_check_override=1 in meta.
19 tests cover all refuse paths plus false-positive boundaries and bookkeeping; shellcheck clean.

Fix 1.3 — bin/fm-spawn.sh: validate_spawn_worktree asserts the acquired worktree's git-common-dir resolves inside the project directory.
Both unverifiable paths are now fail-closed: an empty result from git rev-parse --git-common-dir and an unresolvable path from cd each refuse with a distinct message naming the exact failure.
Two new tests in tests/fm-backend.test.sh pin each refuse path; shellcheck clean.

Test plan

  • bash tests/fm-pr-check.test.sh — 19/19 pass
  • bash tests/fm-fleet-sync.test.sh — all pass (including 2 new tests for Fix 1.1)
  • bash tests/fm-backend.test.sh — all pass (including 2 new tests for Fix 1.3)
  • shellcheck -x bin/fm-pr-check.sh bin/fm-fleet-sync.sh bin/fm-spawn.sh tests/fm-pr-check.test.sh tests/fm-fleet-sync.test.sh tests/fm-backend.test.sh — clean

DereKk8 added 3 commits July 14, 2026 13:10
…D, assert worktree clone ownership

Fix 1.2 (highest value): fm-pr-check.sh now fetches the PR before arming
the merge poll and refuses (exit non-zero, naming the exact condition) when
the PR body contains a skipped pipeline gate, an unresolved error, a high-risk
marker, or when GitHub reports the merge state as DIRTY, or the PR targets a
branch that differs from the project's true remote default.  --force-ready
bypasses the checks and records pr_check_override=1 in meta.  Absence of
no-mistakes markers (hand-written PRs, direct-PR mode) does not trip the check.
Tests pin the false-positive/false-negative boundary across 13 cases.

Fix 1.1: fm-fleet-sync.sh calls 'git remote set-head origin --auto'
(best-effort) after every successful fetch, so origin/HEAD stays accurate even
when the upstream repo changes its default branch.  default_branch() now emits
an actionable warning when it falls back to the hardcoded main/master scan.
The STUCK report for a clone on a named non-default branch includes the one
checkout command needed to restore it.  Tests pin both behaviours.

Fix 1.3: validate_spawn_worktree in fm-spawn.sh asserts that the acquired
worktree's git-common-dir resolves inside the project directory firstmate
spawned from.  Treehouse keys its pool by basename+remote-url, so two local
clones of the same remote share one pool; without this assertion a treehouse
get can silently hand out a worktree backed by a different clone's .git.
…iable path

Previously the gate failed open in three paths: gh absent from PATH skipped
the check entirely, any gh call failure left REFUSE=0 via || VAR="", and a
missing project= in meta silently skipped the base-branch comparison.  A gate
that passes when it cannot verify is the same defect that caused the
nm-orchestration incident.

Every unverifiable path now calls pr_check_refuse() with a distinct message:
  - gh not on PATH
  - gh pr view body/merge-state/base call fails
  - project= absent from task meta or directory not found
  - ls-remote fails or returns no symbolic ref

--force-ready remains the only bypass and records pr_check_override=1.

Six new tests cover each refused path (19 total, all passing, shellcheck clean).
…able paths

The cross-clone pool-leak check in validate_spawn_worktree previously failed
open in two paths: an empty result from git rev-parse --git-common-dir (rev-parse
fails) silently skipped the whole block, and an unresolvable path (cd fails)
skipped the comparison.  Either miss could accept a worktree backed by a different
clone's .git objects.

Both paths now call explicit refuses:
  - empty wt_common: "cannot verify worktree isolation: 'git rev-parse
    --git-common-dir' failed or returned empty for '$WT'"
  - unresolvable wt_common_real: "cannot verify worktree isolation:
    git-common-dir path '$wt_common' cannot be resolved to an absolute path"

Two new tests in tests/fm-backend.test.sh pin each refuse path using a git stub
that intercepts --git-common-dir while delegating all other git calls to the real
binary.  Both tests pass; shellcheck clean.
@DereKk8
DereKk8 merged commit 8b58dc4 into main Jul 14, 2026
2 of 4 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.

1 participant