feat: strengthen fleet safety and decision handling - #34
Merged
Merged
Conversation
Port upstream kunchenguid#518 (6556882): gate agents must not spawn/send/teardown. Sourced refuse lib on fleet entrypoints; FM_GATE_REFUSE_BYPASS for suite. Cherry-picked-from: 6556882 (kunchenguid#518)
Port upstream kunchenguid#854 (50cc24a) plus primary-scope-lib dependency. Compose subagent PreToolUse matcher with fork arm-pretool seatbelt and claim-guard Stop path; do not track Claude permissions.deny. Composition covered by tests/fm-pretool-stack-composition.test.sh. Cherry-picked-from: 50cc24a (kunchenguid#854)
B2: upstream kunchenguid#483 (88e38ea) + kunchenguid#505 (2364817). - cd-guard PreToolUse seatbelt composed with arm-pretool and subagent stacks - export arm shell classifier for reuse; do not replace private PreToolUse rails - turnend force-includes marked secondmate homes via primary-scope-lib - keep fork physical-identity matching, queue-pending, and claim-guard Stop path Cherry-picked-from: 88e38ea (kunchenguid#483), 2364817 (kunchenguid#505)
B3: replace deferred kunchenguid#435 with tip lock-lib ownership. Teardown sources fm_lock_is_provably_stale for index.lock cleanup while keeping fork path-spelling retry (7c0ad63 family) and landed-work refusals. Cherry-picked-from: tip fm-lock-lib.sh (replaces deferred kunchenguid#435)
B4: upstream kunchenguid#593 (cd218f2) + kunchenguid#654 (024b96b). Adds fm-decision-hold.sh, decision-hold-lifecycle skill/docs, classify status_open_decisions fold, scout teardown verify gate, and unstubs bearings/fleet-snapshot decisions_open for structured captain holds. Cherry-picked-from: cd218f2 (kunchenguid#593), 024b96b (kunchenguid#654)
B5 partial: upstream kunchenguid#752 (a26b37c). Prefer Linux /proc starttime+cmdline over ps lstart so WSL2 btime drift cannot false-evict a live watcher. Cherry-picked-from: a26b37c (kunchenguid#752)
B5 partial: upstream kunchenguid#747 (4ab61fa). After raw queue commit, best-effort annotations add home-local status context without blocking appends or reordering authoritative drain rows. No watch-arm topology changes. Cherry-picked-from: 4ab61fa (kunchenguid#747)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Port firstmate upstream Batch B (safety take-soon) onto Keigyoku/firstmate as one big-wave PR with bisectable per-item commits.
B1: gate-refuse kunchenguid#518 + subagent guard kunchenguid#854, composing with fork PreToolUse stacks (arm-pretool, kill-guard, TDD, claim-guard) — proven by composition tests.
B2: cd-guard kunchenguid#483 + secondmate turnend scope kunchenguid#505 without regressing fork physical-identity matching / claim-guard Stop path.
B3: tip fm-lock-lib + teardown reconciliation, keeping /home<->/var/home path-spelling retry and landed-work refusals.
B4: decision-hold kunchenguid#593+kunchenguid#654 with bearings/fleet-snapshot decisions_open unstubbed for structured captain holds.
B5 partial: kunchenguid#752 wall-clock identity + kunchenguid#747 wake enrichment. SKIPPED (PR notes): kunchenguid#637 guard banners, kunchenguid#743 parked-stale, kunchenguid#744 ordinary-vs-recovery — fork private watch/guard evolution; avoid re-introducing double-fork/orphan watcher topology (fork #23 arm-as-child is law).
TDD red-first for behavior; RED evidence belongs in PR body. PR must target Keigyoku/firstmate base main (not kunchenguid). Do not merge.
What Changed
Risk Assessment
✅ Low: Captain, the latest scope fix correctly uses the shared marker-aware predicate, and all earlier decision-durability, deduplication, scoping, path-identity, teardown, and watcher-topology requirements remain intact.
Testing
The previously successful full-suite baseline was supplemented with 14 focused behavioral scripts and five end-to-end CLI/plugin checks; everything passed, the worktree remained clean, and no screenshots were needed because the changed end-user surfaces are CLI output.
Evidence: Guard composition and scope
Evidence: Teardown reconciliation and refusal
Evidence: Structured decision lifecycle
Evidence: Enriched wake and wall-clock identity
Evidence: OpenCode external-healthy-arm
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed (3) ✅
bin/fm-decision-hold.sh:150- Captain,origin_open_decisionssuppresses still-open keyedneeds-decision/blockedevents when a non-secondmate’s final status is merelydoneorfailed. Consequently,complete --none,verify, and scout teardown can succeed without an explicitresolvedorcaptain-heldclose. This contradicts required B4 behavior: “decision-hold feat: add durable captain decision holds kunchenguid/firstmate#593+fix(bin): strip quoted blocked_by values during decision hold resolve kunchenguid/firstmate#654 … unstubbed for structured captain holds.” Confirm whether terminal status should override an unresolved structured decision; otherwise remove this suppression.bin/fm-bearings-snapshot.sh:361-bearings_decisionsincludes secondmate captain holds, whiledecisions_alladds those holds separately before addingbearings_decisions. Each hold is emitted twice and consumes the bounded decision budget twice, potentially crowding out distinct decisions. Keep the reason-bearing explicit projection and makebearings_decisionsstatus-only.🔧 Fix: Captain: enforce durable decisions and deduplicate holds
1 warning still open:
AGENTS.md:803- Captain, the operational instructions sayfm-turnend-guard.shnever fires in a secondmate home, contradicting the newly widened marker-aware scope described two lines earlier.docs/turnend-guard.md:177similarly equates claim-guard and turn-end scoping even though claim-guard intentionally retains the narrower Stop path. Update both statements to distinguish secondmate turn-end coverage from claim-guard’s unchanged scope.🔧 Fix: Captain: clarify secondmate turn-end and claim-guard scope
1 warning still open:
docs/subagent-guard.md:128- Captain, the new scope contract says nonexistentfm-sessionstart-nudge.shandfm-turnend-guard.shboth usefm_primary_scope_matches, but turn-end instead duplicates the checks without calling the predicate. Remove the nonexistent consumer reference and have turn-end call the shared predicate—or document its separate implementation—so the promised no-drift invariant is accurate.🔧 Fix: Captain: unify marker-aware primary scope enforcement
✅ Re-checked - no issues remain.
🔧 **Test** - 1 issue found → auto-fixed (2) ✅
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"🔧 Fix: Captain: complete scout decision inventories in teardown fixtures
1 error still open:
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"🔧 Fix: Captain, stabilize teardown fixtures and OpenCode guard
✅ Re-checked - no issues remain.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"Configured baseline:command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"(previously passed)for t in tests/fm-pretool-stack-composition.test.sh tests/fm-gate-refuse.test.sh tests/fm-subagent-pretool-check.test.sh tests/fm-cd-pretool-check.test.sh tests/fm-turnend-guard.test.sh tests/fm-lock-lib.test.sh tests/fm-backend.test.sh tests/fm-backend-orca.test.sh tests/fm-backend-zellij.test.sh tests/fm-decision-hold-lifecycle.test.sh tests/fm-bearings-snapshot.test.sh tests/fm-wake-queue.test.sh tests/fm-watcher-lock.test.sh tests/fm-pi-watch-extension.test.sh; do bash "$t" || rc=1; done; exit "$rc"Manual composed PreToolUse refusal/allow and cd physical-scope CLI flowsManual unlanded-work teardown refusal and stale-lock reconciliation flowsManual decision hold, Bearings/fleet snapshot, routing, and resolution lifecycleManual enriched wake drain and simulated wall-clock/PID-reuse identity checksOpenCode watch-arm and turn-end plugin integration with external healthy outputgit status --shortcleanup verification✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.