Skip to content

feat(ci): add bounded test failure receipts - #129

Open
quinnbot-ai wants to merge 76 commits into
mainfrom
fm/fm-ci-flake-failure-receipt
Open

feat(ci): add bounded test failure receipts#129
quinnbot-ai wants to merge 76 commits into
mainfrom
fm/fm-ci-flake-failure-receipt

Conversation

@quinnbot-ai

Copy link
Copy Markdown
Owner

Problem

CI test failures lack durable, bounded lane diagnosis receipts.

Outcome

Add runner-owned typed receipts, always-uploaded artifacts, and aggregate validation.

Tests

Focused runner and workflow suites; changed suite; lint; docs-audience; diff checks.

Worker provenance

  • harness: codex
  • model: gpt-5.6-terra
  • effort: high

QuinnBot and others added 30 commits July 28, 2026 09:29
* fix(herdr): serialize concurrent presentation recovery

* no-mistakes(review): Captain, serialize recovery waits across lock-owner waves

* no-mistakes(document): Clarify Herdr recovery lock documentation

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(teardown): recognize content-landed local work

Add the conservative zero-context proof for histories that preserve the
complete task outcome without preserving ancestry or exact patch identity,
and take the branch ref as an argument so the exact-tree proof runs first.
When later default-branch edits make that 3-way merge conflict, the fallback
accepts only when the task delta cannot still be applied forward and can be
applied completely in reverse against the default tree.

Dirty and incomplete task content stay refused. Ported from the pure-Kun
reconciliation onto the upstream teardown owner.

* no-mistakes(review): Harden teardown content-landed proof boundaries

* no-mistakes(review): Reject relocated teardown content proof hunks

* no-mistakes(document): Document hardened teardown content-landed proof

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(watch): fence watcher arm and cycles to the session-lock owner

An orphaned watcher from a dead harness session retained the supervision
singleton (fresh beacon, held watch.lock) while a new harness session held
state/.lock, so completed-crewmate wakes were absorbed into a dead stdout
and the active session was never woken.

Add fm_session_owner_fence to bin/fm-session-lock-lib.sh, the one owner of
the may-this-process-supervise decision, and enforce it at watcher startup,
every watcher cycle, arm start, and every arm attach iteration. A live
foreign harness holding state/.lock fences supervision with a typed
watcher: FAILED line; no lock, a malformed lock, a dead or non-harness
holder, the owning session's own descendants, and away mode all pass, so
recovery arming, adapter arms, and the afk daemon are unchanged.

tests/fm-session-owner-fence.test.sh reproduces the cross-harness takeover
end to end and covers the refusal, restart, dead-owner, and afk paths.

* no-mistakes(review): Captain, harden session-owner fencing against handoff races

* no-mistakes(document): Document supervision session-owner fence

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* feat(pi): add Firstmate presentation parity

* no-mistakes(review): Fix Pi footer telemetry and responsive field preservation

* no-mistakes(review): Preserve complete Pi footer values at narrow widths

* no-mistakes(test): Captain, fix Pi footer runtime colors and startup title

* no-mistakes(document): Document Pi presentation defaults

* no-mistakes(lint): Captain: fix Pi footer test lint

* fix(pi): prioritize subscription models in picker

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
Co-authored-by: QuinnBot <quinnbot@proton.me>
…resolved by the incoming branch (#61)

* fix: restore stock macOS Bash 3.2 brief scaffolding (kunchenguid#1093)

* fix(brief): make DOD scaffolding parse-safe on stock macOS Bash 3.2

fm-brief.sh built each Definition-of-done block and the not-enabled
Herdr declaration with `VAR=$(cat <<EOF ... EOF)`. On Bash 3.2 (macOS
/bin/bash) the lexer scans for the command substitution's closing `)`
textually and tracks quote state through the heredoc body, so a single
apostrophe, unbalanced quote, or unbalanced paren in that prose breaks
parsing of the whole script. Every ship-brief scaffold (no-mistakes,
direct-PR, local-only) failed with `unexpected EOF while looking for
matching )`. Bash 4+ parses it fine, so the breakage stayed invisible
everywhere except stock macOS.

Replace all four command-substitution heredocs with
`IFS= read -r -d '' VAR <<EOF || true`. That removes the `$(...)`
wrapper and the entire defect class regardless of future prose, and
preserves the variable expansion the direct-PR and local-only bodies
need. `read` keeps the heredoc's trailing newline that `$(...)` used to
strip, so trim one newline to keep every generated brief byte-identical
to prior output.

Guard the structure, not one historical phrase: a new test rejects any
heredoc nested in a command substitution anywhere in fm-brief.sh, where
the old assertion pinned a single apostrophe phrase and so missed the
reintroduction. Extend the stock-macOS Bash CI job from parsing one
script to the whole maintained shell surface (bin/*.sh,
bin/backends/*.sh, tests/*.sh), matching bin/fm-lint.sh's canonical file
set so parse scope and lint scope cannot drift apart.

* no-mistakes(review): Captain: harden Bash structure and inventory guards

* no-mistakes(document): Align stock macOS Bash contributor checks

* no-mistakes(lint): Suppress deliberate SC2016 literal fixture warnings

* test: stabilize tmux teardown conformance baseline (kunchenguid#1209)

* fix(test): pin teardown tmux baseline to historical kill selectors

merge-base HEAD main collapses to HEAD after the exact-selector change
lands on the default branch, so the old teardown fixture was accidentally
exercising current exact targets. Resolve a content-historical permissive
tmux adapter from first-parent history and force that post-squash topology
inside the conformance case so main and feature branches keep the same
old-vs-new contract.

* no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings

* docs: slim quota-array-dispatch to the pace selection core (kunchenguid#1197)

Cut the runtime skill to the compact pace-aware selection procedure plus
minimum owner pointers. Keep every distinct decision rule and move expanded
acceptance scenarios to deterministic fixture ownership assertions.

Size: 170/1374/10187 -> 63/544/4068 (about 63%/60%/60% reduction).

* fix: allow resolved dirt in local merges

* no-mistakes(review): Harden dirty merge preflight validation

* no-mistakes(review): Reject divergent staged state during ignored conversion

* no-mistakes(document): Clarify dirty local merge safety contract

---------

Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com>
Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: QuinnBot <quinnbot@proton.me>
)

* feat: add durable link intake

* no-mistakes(review): Fix intake atomicity and composer safety contracts

* no-mistakes(review): Make link intake publication crash-safe

* no-mistakes(review): Harden portable process-crash atomic link intake

* no-mistakes(review): Harden lock recovery and retrieval date safety

* no-mistakes(review): Stabilize lock identity across upgrades

* no-mistakes(document): Correct link intake verification output

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…#62)

* fix: recover from degraded no-mistakes reviewers

* no-mistakes(review): Disambiguate crew and reviewer run identifiers

* no-mistakes(document): Reviewer recovery documentation verified current

* no-mistakes(lint): Fix ShellCheck quoting in reviewer recovery assertions

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…il-fast classification, bounded enumeration (#63)

* fix: preserve ignored directories in local merges

* no-mistakes(review): Harden ignored-directory merge preservation

* no-mistakes(review): Align target ignore checks with project case semantics

* no-mistakes(document): Document ignored-directory local merge safeguards

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* test(herdr): guard projected spawn against macOS focus theft

* no-mistakes(review): Enforce macOS spawn activation regression in CI

* no-mistakes(document): Document macOS Herdr focus regression

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
… bounded refusal diagnostics (#65)

* fix: preserve ignored non-file paths in local merges

* no-mistakes(review): fix staged ignored symlink preservation

* no-mistakes(review): fix index-flag-safe staged symlink proof

* no-mistakes(review): fix staged symlink type-drift proof

* no-mistakes(review): bypass replacement refs in symlink proof

* no-mistakes(document): Document bounded local-merge preservation diagnostics

* no-mistakes(review): Harden symlink and ignored-directory merge proofs

* no-mistakes(review): Reclassify ignored parents against target rules

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…nd base comparisons (#66)

* fix: resolve the publish remote for syncs and landed-work checks

Every self-update reported "skipped: diverged from origin/main" once `origin`
became the upstream and `fork` the publish target, so running homes never
received landed updates while their local main was cleanly behind fork/main.

bin/fm-remote-lib.sh is now the single owner of that resolution: `fork` when
that remote exists, else `origin`. A single-remote clone keeps today's behavior
exactly. Fast-forward-only semantics are unchanged - nothing is forced,
stashed, or discarded, and divergence from the publish remote is still refused.

The sweep covers every other place that assumed landed work lives on origin:
the self-update and secondmate sync (bin/fm-ff-lib.sh base mode, renamed
"origin" to "publish"), project clone refresh (bin/fm-fleet-sync.sh plus
bootstrap's remote-backed clone count), teardown's landed-work test (PR head
fetch and default-branch content check), the review base and PR-head fetch
(bin/fm-review-diff.sh), and the changed-test base ref (bin/fm-test-run.sh).

bin/fm-review-diff.sh also passes --no-ext-diff so review reads the real patch
instead of an operator's configured diff.external renderer, which was making
its PR-head assertion fail locally.

* no-mistakes(document): Document publish-remote resolution accurately

* test: stage fm-remote-lib.sh in the fixtures that build a synthetic bin

CI's serial lane failed because fm-teardown.sh now sources fm-remote-lib.sh,
while two fixtures build a synthetic bin/ from a hand-listed subset of libs and
did not stage it, so teardown aborted under set -eu:

  - tests/fm-backend.test.sh's old-vs-new conformance old bin (sibling list)
  - tests/fm-gotmp.test.sh's two fake FM_ROOT builders (symlinked libs)

Both now stage the lib, exactly as they already do for fm-pr-lib.sh.

bin/fm-test-run.sh's changed-file map also selects backend-dispatch for
bin/fm-remote-lib.sh, since that family's fixtures stage fm-teardown.sh; without
it a change to this lib never selects the suite that just caught this.

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…ered standing check (#67)

* feat(watch): wake firstmate on an unreviewed operational alert backlog

A machine that records critical alerts into a durable inbox instead of a
chat transport silently disarms every deadman and tripwire feeding it once
nobody reads that inbox: each check still fires and still records its alert
while no one is told.

Wire that inbox into the existing wake path as the reserved standing check
state/ops-watch.check.sh, registered through the ordinary custom-check trust
binding, so an unreviewed backlog arrives as a normal check: wake carrying a
compact triage digest - total, oldest age, and the three busiest classes.

- bin/fm-ops-inbox-lib.sh resolves the watch settings, streams the spool and
  acknowledgement log through a bounded read, and owns the dedupe record.
- bin/fm-ops-inbox-poll.sh wakes on a count or age threshold, on a stale,
  missing, or unreadable review receipt (a dead receipt generator is the
  failure that hides every other one), on a spool past the read cap, and on
  a malformed configuration; a standing backlog then stays quiet until it
  grows materially, gains a new class, changes receipt state, or the
  re-remind interval passes.
- fm-bootstrap.sh arms and disarms the watch at a locked session boundary,
  only where there is an inbox to watch, never in a secondmate home by
  default, and never over a live task holding the reserved id.
- fm-supervision-lib.sh treats an armed standing poll as a supervision need,
  since a standing poll only reaches firstmate through a live watcher.

Firstmate only reads the inbox: it never writes, rotates, or acknowledges
alerts, and alerts route to the first mate rather than straight to the
captain. docs/ops-inbox-wake.md owns the contract and reconciles the second,
unrelated path that shares the ops-inbox name.

* no-mistakes(review): Harden ops inbox fail-closed wake handling

* no-mistakes(review): Close ops inbox fail-open paths

* no-mistakes(review): Close ops inbox fail-open paths

* no-mistakes(review): Canonicalize ops inbox numbers and classify missing jq

* no-mistakes(document): Align ops-inbox documentation with wake behavior

* no-mistakes(document): Suppress intentional fixture-template SC2016 notes

* fix(watch): satisfy new base invariants for the ops-inbox watch

The rebase onto the advanced publish base brought two repository
invariants that postdate this branch's validation.

- tests/fm-lint.test.sh pins an exact allowlist of suites permitted to
  keep production source context. This suite sources
  bin/fm-supervision-lib.sh to read FM_SUP_STANDING_DESC and call
  fm_supervision_needed, which is the callback/variable interop the
  allowlist exists to track, so record it there rather than hiding the
  dependency behind a source=/dev/null directive.
- Tracked files may not contain user-specific absolute paths. The
  configuration example now uses an OS-neutral absolute placeholder
  while still showing that state_dir must be an absolute path.

No behavior change: the watch, its poll, its thresholds, and its
fail-closed dispositions are untouched.

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* feat(bin): implemented the first deterministic, read-only Pure-Kun convergence scoreboard slice with focused verification and maintained remeasurement guidance.

* feat(bin): corrected the scoreboard’s trailing-newline TOON conformance gap and added byte-level regression coverage.

* feat(bin): the scoreboard now rejects ambiguous branch/tag ref names while accepting fully qualified corrections.

* feat(bin): hardened the scoreboard so failed diff measurements cannot silently report zero changes.

* feat(bin): isolated scoreboard measurement from ambient Git attributes and configuration so identical explicit refs now produce stable metrics.

* feat(bin): hardened deterministic TOON error output against raw ASCII control characters.

* no-mistakes(review): fix(scoreboard): sanitize preflight Git environment

* no-mistakes(review): fix(scoreboard): bind isolated repository object format

* no-mistakes(test): Fix scoreboard help when HOME is unset

* no-mistakes(document): Document scoreboard hardening contracts

* no-mistakes(document): Correct scoreboard help remote refs

* no-mistakes(review): Captain, fix scoreboard isolation and edge-case parsing

* no-mistakes(review): Captain, isolate scoreboard Git calls with environment allowlist

* no-mistakes(review): Captain, document scoreboard preflight ambient-input limitations

* no-mistakes(document): Document scoreboard hardening contracts

* no-mistakes(lint): Escape fixture variables for ShellCheck compliance

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…and answer each request once (#69)

* fix(x-mode): commit the offer marker only after its wake is emitted

The poll claimed state/x-context/<request_id>.offered.json before printing
the x-mention wake line, and the marker is what makes every later relay
re-offer silent. A poll that stopped in that gap left a marker with no wake:
the mention stayed pending in the inbox, every re-offer was discarded, and it
was never surfaced again for the rest of the retention window.

Claim, emit, then commit. The claim still atomically gates concurrent polls
but now records wake_emitted:false, only an emitted marker suppresses a later
offer, and reaching the claim with an existing marker means an interrupted
offer to recover. An interruption now costs at most one repeated wake instead
of a lost mention. A marker with no wake_emitted field counts as emitted, so
an upgrade cannot replay an answered mention into a public reply.

* fix(x-mode): post one public answer per request_id

The poll offers a mention at least once: an offer interrupted before its wake
is re-offered rather than silenced, because a repeated wake is recoverable and
a silenced mention is not. That makes a repeated wake reachable, and nothing
local stopped it from becoming a second public reply - only the relay's own
409 stood between a duplicate wake and a duplicate post.

Enforce exactly-once where the public action happens. The initial answer path
atomically claims state/x-context/<request_id>.answered.json before posting and
refuses with exit 10 when the request was already answered or an earlier
attempt's outcome is unknown. The claim is released whenever the answer
definitely did not land, so an ordinary failure stays retryable, and follow-ups
keep their relay-side cap instead.

* no-mistakes(review): Hold ambiguous X answer claims against duplicate posts

* no-mistakes(review): Confirm X answers and surface unresolved claims

* no-mistakes(review): Align X responder guidance with 409 handling

* no-mistakes(document): Align X-mode durability documentation

* no-mistakes(lint): Suppress intentional inner-shell expansion warnings

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…oposal (#68)

* feat(bin): inherit backend config into secondmate homes (kunchenguid#1219)

* Inherit config/backend into secondmate homes with deliberate-override preservation

Add backend to the shared inheritable config allowlist so launch, locked
bootstrap, and config-push converge a primary pin into secondmate homes as each
home local future-spawn default. Track last-inherited bytes in a private state
provenance marker so deliberate per-home overrides survive present and absent
primary convergence, keep --backend and FM_BACKEND stronger, and extend the
existing inheritance tests plus docs and skill claims.

* no-mistakes(review): Preserve equal unprovenanced backend overrides

* no-mistakes(review): Preserve symlink overrides and verify spawn precedence

* no-mistakes(review): Snapshot backend inheritance for consistent provenance

* no-mistakes(review): Simplify backend inheritance to primary-authoritative convergence

* no-mistakes(document): Document inherited backend override preservation

* fix: restore primary-authoritative backend inheritance after document regression

The document step reintroduced provenance and deliberate per-home override
semantics after review had simplified config/backend to plain primary-authoritative
allowlist membership. Restore the primary-always-wins path: present overwrites,
absent removes, no provenance marker, and docs/tests match that contract.

* no-mistakes(review): Add divergent backend precedence regression fixtures

* no-mistakes(document): Document backend inheritance contract

* fix(pi): remove Calm's upper version ceiling (kunchenguid#1226)

* fix(pi): remove Calm's exclusive Pi upper-version ceiling

tests/fm-calm-pi-extension.test.sh gated on a closed PI_COMPAT_VERSIONS
allowlist ("0.81.1 0.82.0") that refused any other installed Pi, and docs
described that range as "supported" rather than verified evidence. The
Calm CHANGELOG shows no API introduced at either version, so there is no
evidence for a real minimum; the presentation adapters already probe the
exact method they patch rather than checking a version.

Replace the allowlist with dated version evidence that never rejects a
newer Pi, and make each presentation adapter degrade independently with
a diagnostic if a future Pi removes its API, instead of the whole Calm
extension failing to load. Rewrite the feasibility doc's "Pi 0.81.1
through 0.82.0" phrasing to state it as verified evidence, not a
ceiling.

* no-mistakes(review): Probe missing Calm adapter exports safely

* no-mistakes(document): Document Calm's unbounded Pi compatibility

* fix(bin): allow session-local todo tools in the subagent guard (kunchenguid#1204)

* fix(guard): allow session-local todo tools in the primary

The delegation-shape guard denied TaskCreate and TaskUpdate because their
normalized names contain the `task` stem. Those tools write only the harness's
session-local todo list, which has no executor: it spawns no agent, allocates
no worktree, registers no schedule, and starts nothing that outlives the
session. That is not the unaccounted work the guard exists to stop, so the stem
match was a false positive, and the deny text told the primary to run
bin/fm-brief.sh and bin/fm-spawn.sh to create a todo entry.

Add a separately-reasoned PLAN_ONLY_TOOLS exact-name exclusion rather than
widening OBSERVE_ONLY_TOOLS, whose documented contract is tools that only
observe or stop existing work. Both lists stay exact-name so neither can widen
by substring.

Tests cover the two allowed names and six near-miss names that a substring or
shortened-stem widening would release; both mutations were watched red.

* no-mistakes(review): drop session-local todo tools from recommended deny list

* no-mistakes: apply CI fixes

* fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid (kunchenguid#1206)

* fix(session-lock): resolve Claude bg-spare ancestry to the outermost claude pid

fm_harness_ancestry_pid() previously returned the first ancestor process
whose command matched a verified harness name. Claude Code's Stop hook
fires as a bg-spare worker several levels below the session's actual
lock-owning claude process (hook shell -> claude bg-spare ->
claude bg-pty-host -> claude -> claude(lock)), so the first match was
the bg-spare worker, not the lock owner. fm_session_lock_owned_by_self()
then never matched state/.lock, and the Claude Stop auto-arm silently
treated its own primary session as an unrelated live owner and never
armed the watcher.

The walk now keeps going past a claude-named match, looking for a still
more ancestral claude-named match, and stops the instant a non-match
follows an already-found match (bounding it to a contiguous run rather
than the literal ancestry top, so an unrelated claude-named process
further up the real process tree is never mistaken for part of this
session's own nested chain). Every other harness keeps the original
first-match-wins behavior, since e.g. Pi's shared signed-wrapper
ancestry actually holds the session at the inner engine pid, not an
outer wrapper pid. Hop limit raised from 8 to 16 to cover the deeper
bg-spare chain.

* no-mistakes(review): Add nested-claude-ancestry regression test; fix nudge doc depth claim

* no-mistakes: apply CI fixes

* fix: conferma l'avvio del watcher su Windows/MSYS (kunchenguid#1212)

* fix: confirm watcher startup on MSYS

* no-mistakes(review): gate MSYS arm ready timeout, cache uname, harden locale test

* no-mistakes(review): validate OpenCode ready timeout, make uname cache internal

* fix(spawn): forward CLAUDE_CONFIG_DIR to claude crewmates (kunchenguid#1195)

* fix(spawn): forward firstmate's CLAUDE_CONFIG_DIR to claude crewmates

Crewmate panes are created by a long-lived tmux/herdr daemon that does not
inherit firstmate's current environment. When firstmate runs under a non-default
CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split), a bare
`claude` in the crewmate pane fell back to the default ~/.claude store and
launched unauthenticated, blocking the crewmate before it could do any work.

fm-spawn now prefixes the claude launch with firstmate's own resolved
CLAUDE_CONFIG_DIR when set, so the crewmate uses the same credential/config
store firstmate is authenticated with. An unset value is the single-store
default and adds no prefix; non-claude harnesses are unaffected.

Adds three tests in fm-spawn-dispatch-profile.test.sh (forwarded-when-set,
omitted-when-unset, non-claude-ignored) and pins CLAUDE_CONFIG_DIR in the test
helper so launch assertions no longer depend on the developer's environment.

* no-mistakes: apply CI fixes

* fix: preserve dispatch identity across authentication checks (kunchenguid#1233)

* fix: preserve dispatch harness identity

* no-mistakes(review): Fix Grok counterfactual tuple validation

* no-mistakes(document): Scope dispatch authentication to selected tuple

* fix: restore dispatch instruction budget

* no-mistakes(review): Scope dispatch authentication after candidate selection

* fix(bin): normalize relative durable paths (kunchenguid#1256)

* fix(bin): handle dash-leading harness process names (#2)

* fix: handle dash-leading harness process names

* no-mistakes(review): Make dash-leading harness regression hermetic

* fix: preserve secondmate reply routes across relative homes

Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved.

Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory.

* no-mistakes(review): Preserve absolute overrides and normalize relative durable paths

* no-mistakes(review): Normalize relative home before deriving durable paths

* no-mistakes(document): Document relative durable-path normalization

* no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization

* no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck

* refactor(skills): make Bearings chat-only by default (kunchenguid#1136)

* Add internal status skill

* no-mistakes(document): register /status skill in documentation-audiences inventory

* no-mistakes(lint): replace grep|wc -l with grep -c in status skill test

* test: silence literal status skill patterns

* Refactor bearings default to chat-only

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>

* Clarify follow-up routing during validation (kunchenguid#1277)

* fix: honor concrete approval for project operations (kunchenguid#1272)

* docs: add captain-approved project operation exception to hard rule 1

Firstmate stays read-only over projects by default, but when the captain
clearly approves a concrete project operation and scope in the moment,
firstmate may perform exactly that approved operation with its own tools.
The approval is never inferred, broadened, or standing, and it does not
relax the existing force, discard, unlanded-work, or merge-authority
boundaries.

* no-mistakes(review): Clarify captain-approved project operation boundaries

* no-mistakes(document): Clarify captain-approved project operation scope

* docs: cover directories and preserve the operation-or-scope alternative

Widen the captain-approved project operation exception in AGENTS.md to
files or directories, and restore the explicit operation-or-scope
alternative that a prior pipeline auto-fix had collapsed into "and".

Rework project-management SKILL.md's Remove section, which previously
told firstmate to refuse project removal until a guarded helper existed;
that helper was never built, so the text directly contradicted the new
instruction-only exception. It now points at the exception plus the
existing removal preflight it still requires unchanged.

Update the one instruction-owners test assertion that hard-coded the
sentence removed above, so the suite tracks current, not obsolete, text.

* docs: add captain-approved project operation exception to hard rule 1

Firstmate stays read-only over projects by default, but when the captain
clearly approves a concrete project operation and scope in the moment,
firstmate may perform exactly that approved operation with its own tools.
The approval is never inferred, broadened, or standing, and it does not
relax the existing force, discard, unlanded-work, or merge-authority
boundaries.

* no-mistakes(review): Clarify captain-approved project operation boundaries

* no-mistakes(document): Clarify captain-approved project operation scope

* docs: cover directories and preserve the operation-or-scope alternative

Widen the captain-approved project operation exception in AGENTS.md to
files or directories, and restore the explicit operation-or-scope
alternative that a prior pipeline auto-fix had collapsed into "and".

Rework project-management SKILL.md's Remove section, which previously
told firstmate to refuse project removal until a guarded helper existed;
that helper was never built, so the text directly contradicted the new
instruction-only exception. It now points at the exception plus the
existing removal preflight it still requires unchanged.

Update the one instruction-owners test assertion that hard-coded the
sentence removed above, so the suite tracks current, not obsolete, text.

* no-mistakes(review): Align project removal preflight with approved exception

* no-mistakes(document): Align project removal documentation with approved exception

* fix: restore removal test byte-for-byte and preserve the default sentence

tests/fm-instruction-owners.test.sh had been changed to assert different
text; restore it byte-for-byte to origin/main. project-management SKILL.md's
Remove section now keeps the exact default "Never issue a raw removal
command from Firstmate." sentence that test still asserts, immediately
followed by the already-approved captain-operation-or-scope exception, so
the default and the exception both stay explicit and consistent.

* no-mistakes(document): Align project-write boundary documentation

* fix(skills): route new project intake through secondmate scopes (kunchenguid#1275)

* Route project intake through secondmate scopes

* no-mistakes(test): Guard all main-home project registry mutations

* no-mistakes(document): Consolidate secondmate routing documentation

* no-mistakes: apply CI fixes

* Restore new-project routing scope

* no-mistakes(document): Clarify secondmate routing for new-project intake

* no-mistakes: apply CI fixes

* fix: scope validation corrections by accepted behavior (kunchenguid#1281)

* fix: scope validation corrections by accepted behavior

* no-mistakes(review): Classify stale delivery evidence as an autonomous correction

* test: replace source assertions with behavioral coverage (kunchenguid#1282)

* test: remove source-content assertions

* no-mistakes(review): Replace source assertions with runtime behavior coverage

* no-mistakes(review): Isolate Kimi task temp runtime coverage

* no-mistakes(document): Refresh test cleanup documentation

* no-mistakes: apply CI fixes

* fix(watch): escalate busy workers with no completed turn (kunchenguid#1286)

* fix(watch): bound how long a busy pane may run with no completed turn

A busy pane (backend busy state or the harness's rendered footer) was
unconditional, unbounded proof of liveness in every escalation path, so a
hung foreground tool call behind a busy signature could run for hours
undetected (2026-07 hibit-agent-focus-nonsteal-r1 incident: a catastrophic-
backtracking regex hung one bash call for 25h behind an unchanging
"Working..." footer).

FM_BUSY_TURN_MAX_SECS (default 3600s) now bounds how long a busy pane may
run with no completed turn (state/<id>.turn-ended, or its spawn record
before any turn has completed). Past the bound, busy_turn_over_age routes
the pane through the existing wedge_timer_check, reusing the identical
stale reason, escalation counter, and demand-deep-inspection marker for
human inspection only - never an automatic interrupt, signal, or restart
of the worker or its tool process. A completed turn resets the age.

Reproduced end-to-end against the real installed Pi TUI: a foreground
`sleep 999999` bash call with no timeout renders the actual busy footer,
and two captures ~15s apart show the elapsed counter changing the pane
hash while the same turn stays unfinished. Running the pre-fix watcher
against the real captures showed it never starts a wedge timer no matter
how long the pane stays busy; the fixed watcher starts and escalates the
timer through the same mechanism, while the real hung process remained
untouched and alive throughout.

* no-mistakes(review): fix: parse enriched AFK stale reasons

* no-mistakes(review): fix: preserve enriched wedges during AFK supervision

* no-mistakes(review): fix: route all enriched AFK wedges

* no-mistakes(document): Clarify busy-turn age supervision documentation

* fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261)

A name-by-name list of config/ entries silently stops ignoring any new or
home-local file placed there, which makes the working tree read as dirty and
blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md
already documents config/ as captain-private and gitignored as a category;
this makes .gitignore match that contract.

* fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304)

The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps
.gitignore for a specific spelling of the config/ ignore pattern. It fails
on a semantically equivalent pattern like config/** and does not prove Git
actually ignores anything, per the completed source-content-test audit.

Replace it with a real git check-ignore control test on a generated
unrelated path, and strengthen the existing directory-coverage test with
generated unpredictable direct and nested config/ paths.

* feat(decision-board): render the captain's daily decision board from the fleet snapshot

Adds bin/fm-decision-board.sh, a thin renderer over bin/fm-fleet-snapshot.sh in
the same shape as fm-fleet-view.sh and fm-bearings-snapshot.sh. It selects the
captain-owned open items the snapshot already normalizes - open backlog entries
with hold_kind=captain plus tasks with open ask-user decisions - merges an id
present in both into a single entry, splits them by whether his answer alone
releases the work, and orders longest-waiting first.

Markdown is the drop-in section for the morning packet, HTML is a standalone
on-demand surface, and JSON exposes the same model. An unreadable decision queue
exits 3 so it can never be mistaken for an empty board.

Colocated behavior tests cover source selection, the merge, the ready split
including the parked override, ordering, bounding with disclosure, all three
formats, HTML escaping of backlog text, and argument validation.

* docs(decision-board): state the verbatim-relay boundary in the script header

The board relays a durable record's own wording rather than paraphrasing it, so
the header now says why: rewording the question a captain is answering would be
inventing it. Section 9 translation is a duty of whoever writes the entry; the
renderer enforces only what it can carry honestly - project names instead of
paths, and plain group wording.

* no-mistakes(review): Fix decision numbering and malformed date handling

* no-mistakes(review): Fix config ignores, decision filtering, and date validation

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Daniel Kuykendall IV <danielkuykendall23@gmail.com>
Co-authored-by: Trillium Smith <Spiteless@gmail.com>
Co-authored-by: Unknownzed <45267749+Unknownzed@users.noreply.github.com>
Co-authored-by: lhalbert <lucashalbert@users.noreply.github.com>
Co-authored-by: AG <ag@agw3.org>
Co-authored-by: deeto15 <92119640+deeto15@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com>
Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(spawn): verify long launch delivery

* no-mistakes(review): Fix bounded launch verification and submission

* no-mistakes(document): Document verified fm-spawn launch delivery

* no-mistakes(lint): Fix launch delivery shell lint findings

* no-mistakes(document): Refresh spawn launch delivery documentation

* no-mistakes(document): Document Herdr launch delivery transport

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
)

* test: stabilize tmux teardown conformance baseline (kunchenguid#1209)

* fix(test): pin teardown tmux baseline to historical kill selectors

merge-base HEAD main collapses to HEAD after the exact-selector change
lands on the default branch, so the old teardown fixture was accidentally
exercising current exact targets. Resolve a content-historical permissive
tmux adapter from first-parent history and force that post-squash topology
inside the conformance case so main and feature branches keep the same
old-vs-new contract.

* no-mistakes(lint): Suppress intentional literal-pattern ShellCheck warnings

* fix(bin): normalize relative durable paths (kunchenguid#1256)

* fix(bin): handle dash-leading harness process names (#2)

* fix: handle dash-leading harness process names

* no-mistakes(review): Make dash-leading harness regression hermetic

* fix: preserve secondmate reply routes across relative homes

Resolve relative home, data, and state inputs before durable charter generation, and fail when caller-relative directories cannot be resolved.

Use absolute paths at the related spawn, AFK daemon, and X-mode cross-process handoffs so later processes cannot reinterpret them from another working directory.

* no-mistakes(review): Preserve absolute overrides and normalize relative durable paths

* no-mistakes(review): Normalize relative home before deriving durable paths

* no-mistakes(document): Document relative durable-path normalization

* no-mistakes(review): Captain: Ignore inherited CDPATH during relative path normalization

* no-mistakes(lint): Fix empty CDPATH assignments for ShellCheck

* refactor(skills): make Bearings chat-only by default (kunchenguid#1136)

* Add internal status skill

* no-mistakes(document): register /status skill in documentation-audiences inventory

* no-mistakes(lint): replace grep|wc -l with grep -c in status skill test

* test: silence literal status skill patterns

* Refactor bearings default to chat-only

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>

* test: replace source assertions with behavioral coverage (kunchenguid#1282)

* test: remove source-content assertions

* no-mistakes(review): Replace source assertions with runtime behavior coverage

* no-mistakes(review): Isolate Kimi task temp runtime coverage

* no-mistakes(document): Refresh test cleanup documentation

* no-mistakes: apply CI fixes

* fix(gitignore): ignore config/ as a directory, not by exact filename (kunchenguid#1261)

A name-by-name list of config/ entries silently stops ignoring any new or
home-local file placed there, which makes the working tree read as dirty and
blocks guarded sync paths that refuse to touch a dirty home. AGENTS.md
already documents config/ as captain-private and gitignored as a category;
this makes .gitignore match that contract.

* fix(tests): replace source-content .gitignore assertion with behavioral coverage (kunchenguid#1304)

The second assertion in fm-gitignore-config.test.sh (added by kunchenguid#1261) greps
.gitignore for a specific spelling of the config/ ignore pattern. It fails
on a semantically equivalent pattern like config/** and does not prove Git
actually ignores anything, per the completed source-content-test audit.

Replace it with a real git check-ignore control test on a generated
unrelated path, and strengthen the existing directory-coverage test with
generated unpredictable direct and nested config/ paths.

* feat: bound and consolidate startup memory during stow (kunchenguid#1303)

* Add bounded startup memory curation

* no-mistakes(review): Record reproducible stow verification evidence

* no-mistakes(review): Validate inherited secondmate stow evidence

* no-mistakes(document): Document editable startup-memory budget propagation

* fix(herdr): place workers in the launching workspace (kunchenguid#1328)

* fix(herdr): place workers in the launching agent's exact workspace

Herdr enforces no workspace-label uniqueness, and spawn resolved its
container by taking the FIRST workspace whose label matched the home
label. With two workspaces both labeled "firstmate", a worker launched
from the second one was created in the first, so it appeared in a
different space than the Firstmate the captain was watching.

Reproduced end to end on Herdr 0.7.5 protocol 17 by running the real
bin/fm-spawn.sh inside a launcher pane in the second "firstmate"
workspace: the worker landed in w1 while its launcher was in w2, with an
unrelated third workspace focused throughout, which also rules out any
dependence on the focused workspace.

Placement now binds to the launching process's own Herdr identity. Herdr
injects HERDR_PANE_ID, HERDR_SESSION, and HERDR_SOCKET_PATH into every
process it manages a pane for, and fm_backend_herdr_launcher_identity
resolves that pane's current owning tab and workspace live from Herdr,
cross-checking the pane against its tab and confirming the workspace
exists exactly once in the session. The injected HERDR_TAB_ID and
HERDR_WORKSPACE_ID are creation-time snapshots and are deliberately not
read as current identity. Labels are no longer placement authority.

A claimed parent identity that is unreadable, contradictory, stale, or
from another named session or Herdr server stops the spawn before any
worker endpoint exists, rather than degrading to a label search. A
launcher with no Herdr ancestry has no workspace to inherit and keeps
the per-home labeled container, which must now resolve to exactly one
workspace; two same-labeled candidates refuse instead of adopting
either. A --secondmate launch keeps standing up that home's own
workspace by design.

With presentation spaces enabled, the projected child is created and
bound under that same exact parent and anchors its ordering on it, so a
duplicated home label no longer makes the layout ambiguous. Projection,
focus restoration, restart binding, and quarantine rules are unchanged,
and children are never collapsed into the parent. tmux, Zellij, cmux,
Orca, and the away-mode daemon terminal were each inspected and are not
affected: none resolves a container by searching mutable labels.

tests/fm-backend-herdr-launcher-workspace-e2e.test.sh drives the real
spawn and teardown against an isolated Herdr lab, with its headline case
running fm-spawn.sh inside a real Herdr pane so the identity comes from
Herdr's own injection. The refusal matrix and the ordering anchor are
covered deterministically in tests/fm-backend-herdr.test.sh.

Eight existing real-Herdr suites inherited the developer terminal's own
Herdr pane into their isolated lab sessions, which the new cross-session
check correctly refuses. tests/herdr-test-safety.sh now owns
herdr_forget_inherited_pane and those suites call it, so what they assert
no longer depends on where they were launched from.

Two unrelated fixes found along the way. tests/fm-secondmate-harness.test.sh
had the same class of environment leak through CLAUDECODE, which outranks
PI_CODING_AGENT in bin/fm-harness.sh and made its pi-signed ancestry case
resolve "claude" whenever the suite ran inside Claude Code. And
fm-spawn.sh's usage() printed a fixed line range that had already been
truncating its own help mid-sentence.

* no-mistakes(review): Enforce exact Herdr launcher and projection identity

* no-mistakes(document): Document exact Herdr launcher workspace placement

* fix(calm): refine Calm working boat animation (kunchenguid#1339)

* feat(calm): replace Pi's working row with an animated ship while Calm is on

While Calm is active and one logical agent run is under way, Calm now hides
Pi's built-in working row and renders a small two-row SSHHIP-derived boat in
its place. When Calm is off, Pi's stock working row is left untouched.

The presentation uses only public Pi extension API: setWorkingVisible(false)
plus a temporary setWidget() component whose render(width) owns the responsive
geometry and whose timer requests a TUI render. Visibility follows agent_start
through agent_settled, so the boat does not flicker between tool calls,
automatic continuations, retries, or compaction inside the same run, and
settle, abort, and failure all reach the same cleanup.

fm-calm.ts stays the sole owner of the presentation choice and the only caller
of setWorkingVisible(); the new lib owns the sprite geometry and widget.

* no-mistakes(review): Guarded Calm-off lifecycle visibility writes; focused tests pass

* no-mistakes(test): Fixed Calm E2E wait to include tmux scrollback

* no-mistakes(document): Document Calm working boat behavior

* no-mistakes: apply CI fixes

* feat(calm): slow the Calm boat, animate blue water, and make the sail directional

The boat now moves one column every 880ms while a bounded fixed-cell water phase
advances every 220ms, so the water ripples several times between boat steps and
the presentation reads as calm. One scheduler drives both clocks and disposing
the widget stops them together; ticks rather than wall-clock timestamps drive
every state change, so tests seek animation time exactly.

Colors are standard ANSI foreground codes instead of theme lookups: blue for
every water cell and yellow for the complete boat, each run closed with a
default-foreground reset so nothing bleeds into padding or later frames. ANSI
bytes never enter geometry, so visible width stays exact.

The mainsail is directional and trails aft of the mast: <| travelling right and
|> travelling left. Direction reverses the moment the boat lands on an endpoint,
so the endpoint frame already shows the new heading and no frame at or after a
bounce shows the previous sail.

* test(calm): wait for the Ctrl+O expansion redraw this block asserts

* docs(calm): record the revised working-presentation verification evidence

* no-mistakes(document): Fix Calm feasibility document EOF whitespace

* fix(dispatch): preflight candidate auth before quota escalation (kunchenguid#1349)

* fix(dispatch): scope candidate authentication to its own surface

A locally expired timestamp in one credential store was reported to the
captain as a sign-out, including for dispatch candidates that never read
that store. A `harness=pi, model=xai/grok-*` candidate authenticates
through Pi's own xAI credential, but the only Grok quota reading
available was gated on the standalone Grok CLI's separate token, whose
expiry clock drifts independently. The always-loaded intake rule then
turned that unreadable quota into a mandatory captain escalation.

Add `bin/fm-auth-preflight.sh` as the deterministic owner of the parts
that must not depend on agent memory: it resolves a tuple's
authentication surface from quota-axi's own emitted auth sources rather
than from a harness or model name, so another harness's CLI can never
gate a candidate that does not use it. A vendor CLI is launched only
when the tuple's own harness owns the credential store under test and a
non-destructive discovery command is registered for it, which today is
`grok models` alone. That probe runs at most once with stdin closed and
a hard timeout, reads its verdict from the first stdout line because the
command exits 0 either way, treats unrecognized output as indeterminate,
and never invokes login, logout, or the interactive TUI. Quota is read
at most twice, and unknown headroom never makes a candidate ineligible
on its own.

Update the dispatch procedure to match: usable authentication with
unmeasurable headroom stays eligible at lower preference with the
unknown disclosed, and stop-and-report is reserved for unresolved
authentication, an unresolved relationship, or malformed configuration.
Record that Grok's `credits.remaining` is a prepaid balance rather than
window headroom.

Gate quota-axi at 0.1.16 in bootstrap, the first build reporting
per-credential auth sources. A stale install previously passed the
presence check silently, which is why a fix published two days earlier
was still not in effect.

Replace the orphaned quota-array-dispatch fixtures, which encoded a
`provider: "xai"` shape the tool never emits and had no consumer, with
fixtures shaped like real 0.1.16 output that the new suite drives the
script against. The suite asserts the verdict and, separately, which
vendor CLIs were launched, so a Pi/xAI candidate reaching the Grok CLI
fails. Map `tests/fixtures/<dir>` to its consuming suite so a fixture
change selects the right tests instead of refusing.

* refactor(bootstrap): give the quota-axi floor one owner

The floor was stated twice - once in bootstrap's gate and once inline in
the auth preflight - so bumping it needed two edits that could drift.
Move it to bin/fm-quota-axi-lib.sh alongside its rationale, matching the
existing tasks-axi library, and derive the comparison from the constant
so the number appears exactly once. Bootstrap turns a failing check into
the operator diagnostic; the preflight refuses to emit an unscoped
verdict. Map the new library to both consuming suites so a bump re-runs
them, and record that any usable source means the surface authenticates.

* no-mistakes(review): Captain: bound quota checks and removed Python dependency

* no-mistakes(review): Captain: enforce conservative headroom and exact preflight retry

* no-mistakes(review): Captain: preserve OpenCode eligibility without auth-surface guessing

* no-mistakes(review): Captain: reject malformed OpenCode model relationships

* no-mistakes(review): Captain: exempt verified unmodeled tuples from intake escalation

* no-mistakes(document): Updated dispatch authentication documentation

* no-mistakes: apply CI fixes

* feat(x-mode): reconcile promised public replies deterministically (kunchenguid#1350)

* feat(x-mode): reconcile promised public replies deterministically

A promised final reply in an X or Discord thread was only kept while the
primary remembered it. Compaction or restart erased that memory, so a typed
public-followup obligation could sit at pending-work after its PR merged and
the original thread never got its reply.

Make the promise durable state instead:

- bin/fm-public-followup-emit.sh reports a typed terminal work result (source
  home, work id, generation, outcome, safe deliverables, bounded public-safe
  text) into the owning home's private inbox. The event id is derived from
  that identity tuple, so duplicate reports and restart replay converge with
  no coordination, and nothing ever parses a free-form done: sentence.
- bin/fm-public-followup.sh registers a commitment, reconciles events through
  tasks-axi public-followup, and runs the idempotent delivery sequence
  (begin-delivery with the payload hash, post, record the posted receipt or a
  typed error) against the stored platform and opaque thread binding. A
  delivery interrupted between post and receipt refuses rather than risk a
  second public reply.
- Session start surfaces unresolved commitments from disk, the existing relay
  poll surfaces a new terminal-result set once, and teardown refuses while
  this home still owes a public reply for that exact work.

tasks-axi public-followup remains the only owner of the obligation state
machine, state/x-context/ the only owner of the private request context, and
fm-x-reply.sh the only thing that posts. Its new optional --receipt-file is
the one addition there, so a caller can record how many messages were sent.

A home that never opted into the myfirstmate relay gates out on a single
[ -f "$FM_HOME/.env" ] test: no tasks-axi call, no backlog or context scan,
no output, and no artifact. Evidence in docs/verification/public-followup.md.

* no-mistakes(review): Hardened public-followup reconciliation and ownership guards

* no-mistakes(review): Hardened typed terminal cleanup and receipt reconciliation

* no-mistakes(review): Automated typed-delivery cleanup and strict backlog validation

* no-mistakes(review): Fail-closed parent resolution and registration-safe delivery

* no-mistakes(review): Harden relay gating and validate secondmate bindings

* no-mistakes(review): Use owner-aware single-gate teardown protection

* no-mistakes(document): Correct public-followup documentation drift

* no-mistakes(lint): Quote done literals to fix ShellCheck warnings

* no-mistakes: apply CI fixes

* feat(bin): replace busy heuristics with semantic lifecycle state (kunchenguid#1327)

* feat: add semantic busy-state contract owner and event writer

One owner (bin/fm-busy-lib.sh) for the captain-approved semantic
busy-state redesign: a per-task gen-bound record written only by
bin/fm-busy-event.sh, per-harness trusted-source classification with
explicit source attribution, busy/idle/unknown/dead semantics where
missing, malformed, stale, or untrusted semantic data is unknown -
never idle - and endpoint death is the only process-level override.
The Grok-only rendered-tail fallback and the standalone-Kimi
verification gate live behind the same classifier.

* feat: arm busy-state at spawn and convert Pi to the semantic extension path

fm-spawn arms the busy-state contract for converted adapters and seeds
busy/fm-spawn (the launch brief is a submitted turn). The Pi/pi-signed
per-task extension now reports agent_start -> busy and agent_settled ->
idle confirmed by ctx.isIdle(), covering auto-retries, compaction
retries, tool loops, and queued continuations, while turn_end stays a
wake notification touch. Teardown removes the new record, gen sidecar,
and lock. Live-verified on Pi 0.82.0: seed -> agent-start busy ->
agent-settled idle with the marker still touched.

* feat: convert OpenCode to the semantic session.status plugin path

The per-task plugin (renamed .opencode/plugins/fm-busy-state.js) now
classifies from OpenCode's semantic session.status events - busy and
retry are active, idle is inactive - latched to the worker's own
session so a subagent child session can never clear the worker's busy
state. The session.idle marker touch stays a wake notification.
Teardown removes both the new and the legacy plugin filenames.
Live-verified on OpenCode 1.17.18 in a real TUI pane: seed ->
session-busy -> session-status-idle.

* feat: convert Claude to the full lifecycle hooks path

The per-task settings.local.json now wires UserPromptSubmit -> busy
and Stop, StopFailure, and SessionEnd -> idle, so API-error and
shutdown turn ends can never strand a busy record; Stop keeps the
turn-ended notification touch. A refused (stale-gen) event exits 0 and
stays silent so Claude's own lifecycle is never broken. Live-verified
on Claude Code 2.1.220: UserPromptSubmit fires for the argv launch
prompt, Stop closes each turn, a mid-stream Escape interrupt fires no
closing hook, and the firstmate-controlled idle/fm-interrupt clear
resolves it.

* feat: gate Codex busy state behind verified semantic sources

The approved contract prefers Codex's app-server turn lifecycle with
capability negotiation and sanctions its lifecycle hooks as the
intermediate. Live probes on codex-cli 0.145.0 show neither is usable
for a pane worker: the app-server daemon is unreachable for a TUI
thread and refuses to start outside the managed standalone install,
and firstmate-written project hooks never fired (interactive with
directory trust granted, and exec, both with
--dangerously-bypass-hook-trust) while global hooks fired in the same
runs. Codex therefore classifies unknown codex-unverified behind an
explicit probe rather than falling back to idle or footer text, and
fm-spawn installs no unverified Codex wiring.

* feat: gate standalone Kimi busy state on live verification

Standalone Kimi has no installed binary here, so per the approved
contract its semantic path stays guarded and it classifies unknown
kimi-unverified rather than idle - and never from its locale-sensitive
moon-phase spinner, which the redesign forbids inventing as a state
source. The gate records the preferred source order (Wire prompt
request lifetime, which brackets a turn and reports cancellation, then
the documented hooks including Interrupt because Stop does not fire on
interrupts) and the exact evidence required to open it. Arming without
wiring would seed a busy record nothing could clear, so both land
together behind the same gate.

* feat: route busy consumers through the contract and drop the global OR

The watcher, crew-state reader, and away-mode daemon now decide busy
state through bin/fm-busy-lib.sh: only an exact busy verdict counts as
working, and unknown never becomes working or a silent idle, so a crew
whose semantic state is missing, malformed, stale, or unverified
surfaces instead of being absorbed. Crew-state reports the producing
source in its detail. The watcher's global OR regex default is gone;
Grok keeps its isolated fallback inside the contract. The daemon's
supervisor-pane reader stays rendered-text - that pane is not a
recorded task - but is now scoped to firstmate's own detected harness
instead of every vendor signature. Secondmate pending-reply
observation is deliberately unchanged and documented as a
delivery-confirmation signal, not task state.

* docs: point busy-state documentation at the single contract owner

Adds a maintainer-architecture section naming bin/fm-busy-lib.sh as
the owner of what busy means, with per-adapter sources, the
unknown-never-idle rule, the endpoint-death override, and the two
rendered-text readers that deliberately stay outside the contract.
Replaces the stale regex-first prose in architecture, tmux-backend,
herdr-backend, and configuration; converts the harness-adapters
per-harness rows from UI signatures to the semantic source each
harness uses; and records the live verification evidence, including
why Codex and standalone Kimi stay unknown.

* fix: arm away-launch signal handlers before acquiring the lifecycle lock

fm_afk_launch_main acquired its lock and only then installed the EXIT,
INT, and TERM traps. A signal arriving in that window terminated the
process by default action and left the lock directory behind, which
blocks the next away-mode launch until the stale-owner reclaim path
clears it. The release helper only removes a lock this process owns,
so the handlers are now armed first. The accompanying test also killed
the child whether or not the lock had appeared and sampled cleanup the
instant wait returned; it now requires the lock, then allows a bounded
settle, so it proves the guarantee instead of racing it.

* test: align fleet, Kimi, lifecycle, and detection suites with the contract

The fleet snapshot and wake-daemon lifecycle fixtures now prove a
working crew through its own semantic busy-state record instead of
rendered pane text, which is what those consumers read. The Kimi
watcher test asserts the approved contract directly: a standalone Kimi
task classifies unknown rather than matching its moon-phase spinner,
while Grok's isolated fallback still classifies only Grok. The
pi-signed detection cases clear ambient harness markers, fixing a
pre-existing failure where the running session's own CLAUDECODE
outranked the fixture's marker.

* fix: stop teardown from deleting a project's own Codex hooks file

An intermediate revision wired Codex through a firstmate-written
<worktree>/.codex/hooks.json, and teardown removed it alongside the
other generated wiring. The Codex wiring was dropped when its probes
came back unverified, so that removal now targets a file firstmate
never creates - and a project may legitimately track its own
.codex/hooks.json, which teardown would then delete from a pooled
worktree.

* fix: keep busy-record parsing from disturbing its sourcing caller

The record parser split fields with set -- under a temporary noglob,
which clobbers a sourcing caller's positional parameters and restores
glob expansion even when the caller had disabled it. The watcher, the
daemon, and the crew-state reader all source this library, so it now
reads fields with read -a, which never globs and never touches caller
state.

* docs: state exactly which Claude hook paths were reproduced live

The busy-state record listed all four wired Claude hooks in the source
column, which could read as a claim that every one fired during the
pass. UserPromptSubmit and Stop did; StopFailure and SessionEnd are
wired from hook names confirmed present in the installed binary, but
the abnormal turn ends they cover were not reproduced.

* test: let reset_fakes own the crew-state busy-text fixture lifecycle

The Grok fallback case set FM_FAKE_BUSY_TEXT and cleared it inline, so
the variable's lifetime was owned by one test rather than by the
shared reset that every other fake already uses.

* no-mistakes(review): Fix semantic busy-state lifecycle races

* no-mistakes(review): Make busy-state retirement idempotent

* no-mistakes(review): Enforce semantic state boundaries for status and injection

* no-mistakes(review): Restore harness-scoped away-mode busy guard

* no-mistakes(document): Refresh semantic busy-state documentation

* no-mistakes: apply CI fixes

* fix: preserve Calm boat continuity across working periods (kunchenguid#1356)

* fix(calm): resume working boat from frozen column across runs

Keep one extension-owned boat animation for the Pi session so settling
freezes column and direction, the next working period resumes there
without hidden-time jumps, and only a fresh session resets to the left edge.

* no-mistakes(review): Freeze Calm boat from last rendered state

* no-mistakes(document): Document Calm boat continuity contract

* fix: restore evidence-based dispatch eligibility (kunchenguid#1358)

* fix(dispatch): judge candidate provider relations instead of rejecting them

Firstmate deterministically dropped supported Pi candidates in the
openai-codex family. bin/fm-auth-preflight.sh resolved a harness=pi tuple's
credential surface by constructing the source id `pi:<model-prefix>`, so
`pi + openai-codex/gpt-5.6-terra` looked for a `pi:openai-codex` source. That
source does not exist, because Pi's Codex family authenticates through the
Codex store quota-axi already lists as `auth-json`/`cli-rpc`. The tuple
returned `eligible=no reason=surface-unresolved` while the Pi catalog listed
the model and the Codex provider reported fresh, usable credentials with 64
effective percent remaining on its all-model scope.

The prefix construction was only ever valid where Pi holds its own credential
(`pi:xai`, `pi:kimi-coding`), which is why every previously configured Pi tuple
resolved and the defect stayed hidden until a Codex-family Pi model was
configured.

Retire dispatch eligibility from deterministic shell. The dispatching first
mate now establishes model support and provider family from each harness's
authoritative catalog, applies quota at the granularity the vendor supplies,
and shows that reasoning. Provider-level and all-model evidence bounds every
model established in that family; a named-model window bounds only its own
model. Missing model-level quota, a missing auth source, unmeasurable headroom,
and unmodeled authentication are disclosed uncertainty. Only concrete
contradictory evidence blocks a candidate.

Replace the preflight with bin/fm-vendor-auth-probe.sh, which keeps the
captain's approved bounded probe envelope without any routing knowledge: it
takes no harness, model, or provider, reads no quota, renders no verdict, and
holds only a fixed-argv safety allowlist. Its behavior suite proves the absent
identity surface, the untouched quota, the uniform exit status, the fixed argv
with stdin closed, and a real bound even when the configured bound is zero.

Also fixed along the way: a zero FM_*_TIMEOUT silently removed the hard bound,
the pinned Grok version had drifted to 0.2.117, and --changed selection refused
outright on any deleted bin/ script.

AGENTS.md section 4 and quota-array-dispatch own the corrected policy,
harness-adapters gets the catalog-responsibility correction, and
docs/verification/dispatch-auth.md records the 2026-07-30 evidence on
Pi 0.82.0, quota-axi 0.1.16, and grok 0.2.117.

* no-mistakes(review): Reject all-zero vendor probe timeouts

* docs: define captain instruction precedence (kunchenguid#1362)

* docs: add captain-authorized inherent red-check merge exception

Keep the default red-PR ban and own one always-loaded exception in the
merge-authority section: captain-explicit PR or bounded batch plus exact
check, only when the failure is inherent to the selected delivery path.
Yolo cannot activate it; final head and the full current check suite must
be verified; other substantive failures remain non-waivable.

* docs: replace narrow red-check exception with captain precedence

Supersede the inherent failing-check merge exception with one always-loaded
Firstmate-local rule: a current explicit concrete captain instruction
overrides a conflicting Firstmate-written standing rule only within exact
scope, never above platform/system/developer instructions. Keep the ordinary
red-PR default and yolo boundary; point section 7 at the section 1 owner.

* docs: define validation supersession sequence (kunchenguid#1407)

* fix: give validation-time captain overrides a supersession sequence

The Validate section let a captain instruction that completely
invalidates the work being validated keep the same task and worker, but
never said how: the adjacent rule flatly bans hand-editing, committing,
aborting, or restarting during an active run with no carve-out, so a
worker facing full invalidation had no sanctioned path forward.

Add the missing sequence: cancel through no-mistakes axi's abort
command, confirm the run has stopped through axi status, recover branch
ownership through axi sync's guarded recovery, only then replace the
obsolete work, and validate once against the final head. The existing
ban on hand-editing an active run now cross-references this sequence
instead of contradicting it.

* no-mistakes(review): Make validation custody recovery conditional

* no-mistakes(document): Clarify validation supersession abort exception

* fix: keep obsolete pipeline commits out of the superseded deliverable

The review-applied fix made custody recovery conditional on
branch_sync.next_action.code, but left an open gap: recovering custody
settles who owns the branch, not what content ships. As written, a
worker could recover an obsolete run's branch and build the
replacement on top of its now-irrelevant commits instead of from the
correct pre-invalidation base, carrying obsolete content into the
final deliverable.

Make that explicit: custody recovery settles ownership, not content,
so the worker replaces obsolete work from the correct base and keeps
the obsolete run's commits out of what gets validated and shipped.

* no-mistakes(test): Restore minimal pre-invalidation replacement instruction

* fix: dedupe redundant "replace the obsolete work" restatement

Line 309 already says the worker replaces the obsolete work from the
correct pre-invalidation base, excluding the obsolete commits. The
closing sentence restated "replace the obsolete work" again before
gating the final validation run, layering the same fact twice instead
of stating it once.

Trim the closing sentence to just the ownership gate and the
single-run-against-final-head requirement it uniquely adds.

* fix: bind backend overrides to exact-task authority (kunchenguid#1413)

* fix: bind explicit --backend to exact-task authority

A Herdr-backed second mate carried a prior one-task --backend tmux
exception forward by analogy, so its child landed in tmux and never
appeared under the second mate in Herdr. Runtime detection was correct;
the authority surface was not.

docs/configuration.md now owns that an explicit --backend is authorized
only for that exact task. AGENTS.md and fm-spawn help point there.

* no-mistakes(document): Consolidate backend selection authorization documentation

* fix(herdr): prevent focus flashes during projected workspace cleanup (kunchenguid#1229)

* fix: remove projected workspaces through Herdr's focus-preserving pane-death path

Herdr 0.7.5's explicit close of a workspace-emptying last pane moves the
attached client's focus to a neighbor workspace, flashing the captain's
whole window and routing in-flight keystrokes to the wrong pane until
Firstmate's exact-tab restore masks it 56-197 ms later.

Teardown and cleanup now plan a workspace-emptying close as a focus-safe
removal: verify the close empties the workspace, reposition the doomed
workspace behind the focused one through the verified workspace.move
transport when it sits before a non-last focused workspace, prove the pane
holds one lone idle shell, and end that shell so Herdr removes the emptied
workspace through its focus-preserving pane-death path. Any ambiguity or
failure falls back to the plain close behind the existing restore backstop,
and fm_backend_herdr_kill applies the same plan for non-projected removals.

Two conditions proven on real hardware are encoded in the adapter: BSD ps
reports a login shell's comm as "-zsh", and an idle shell transiently
hosts a prompt helper right after a workspace.move relayout, absorbed by a
bounded strict-sample settle window in the idle-shell proof, now the single
owner shared with session-start cleanup.

An isolated-lab regression reproduces the raw steal on 0.7.5 and proves the
plan removes a doomed workspace with zero wrong-focus samples and no
corrective focus; unit fixtures cover the position, edge, ambiguity, move
and kill failure, escalation, and transient-helper cases. Upstream fixes
(kunchenguid#1877 explicit close, kunchenguid#1912 pane death) are merged but unreleased; once
released the plan degrades to a harmless reorder-then-remove.

* no-mistakes(review): Confirm pane death from structured not-found responses

* no-mistakes(review): Serialize Herdr kills and sample focus continuously

* no-mistakes(review): Synchronize Herdr focus evidence output

* no-mistakes(review): Refuse unlocked Herdr pane closes

* no-mistakes(document): Correct Herdr focus-safety documentation

* no-mistakes: apply CI fixes

* fix: never erase a Herdr task's records while its pane survives a refused close

A transient presentation-lock contention could produce a completed teardown
while the exact Herdr pane stayed alive as an unowned restored shell: the
kill refused the unlocked close (correctly), returned success, the warning
was suppressed, and cleanup erased the task's status, turn-end, and
metadata records after the isolated copy had already been returned.

Teardown now acquires the named-session presentation lock before anything
destructive: a contended lock refuses up front while the isolated copy, the
task branch, every durable record, and the endpoint are all intact for a
plain rerun, and the projected and flat close paths both run under that one
held lock instead of acquiring their own. Durable records are erased only
once the exact pane is confirmed gone through its structured presence; a
refused, skipped, or failed close retains every record with a visible,
retryable error, and after a skipped close (unresolvable lock path) only a
structured pane_not_found counts as gone - unknown never does.

The teardown regression drives a live contending lock holder end to end:
the refusal touches nothing (no worktree return, no branch drop, no close
attempt), and the retry after release returns the copy, closes the pane
under the lock, and removes the records. The unconfirmed projected close
now refuses with records retained, and the structured-presence gate has a
strict/default unit matrix.

* no-mistakes(review): Require structured pane-not-found before Herdr record removal

* no-mistakes(document): Correct Herdr record-retention verification date

* fix: refuse ambiguity, revalidate SIGKILL ownership, and roll back failed removals

Three accepted-contract corrections from the post-CI personal review of the
Herdr keep-spaces focus-flash mitigation.

Ambiguous endpoint identity no longer counts as a confirmed-gone pane: a
missing or malformed target refuses record removal in the structured
presence gate, and teardown treats missing confirmation machinery as a
refusal instead of skipping the gate, so only an exact structured
pane_not_found ever erases durable task records.

The pane-death SIGKILL escalation re-reads the exact pane's process
information and refuses to signal unless the same shell pid still passes
the strict bare-idle ownership proof, so a pid that exited and was reused
by an unrelated process is never signaled; the refused escalation falls
back to the plain close with the unrelated process untouched.

A reposition whose removal is not confirmed no longer outlives the attempt:
the emptying-close plan records the verified pre-move order and original
index whenever it invokes the mover, and both close owners restore the
exact original workspace order through a second verified move, under the
same held session lock, before reporting the close as failed.

Each defect was reproduced first: the unit matrix documented malformed
identity as gone, the PID-reuse regression showed SIGKILL reaching a
disowned pid, and the rollback regression showed a single unrestored move.
Teardown-level regressions cover unparseable presence retention alongside
the strict identity matrix.

* no-mistakes(review): Require confirmed Herdr removal and resolvable teardown locks

* no-mistakes(review): Enforce structured Herdr closes and teardown preflight

* no-mistakes(review): Preflight explicit Herdr close confirmation helper

* no-mistakes(document): Document Herdr rollback failure semantics

* no-mistakes(review): Captain, harden recursive Herdr teardown safety

* no-mistakes(document): Document recursive Herdr teardown evidence

* fix: retain nested secondmate home when a recursive child cleanup fails

Captain-decided Option A correction for nm-askuser-flash-r6, found during
complete-diff rereview of the merged head.

cleanup_firstmate_home_children's recursive secondmate branch called
itself for a nested child's home without checking the result, then
unconditionally removed that home right after. remove_firstmate_home
ends in an unconditional recursive delete with no check for leftover
records, so a nested secondmate whose own Herdr grandchild failed its
confirmed-gone check would have its entire home - retained grandchild
records included - erased by the very next line.

Guard the recursive call the same way every other fallible call in this
function already is: || return 1, skipping remove_firstmate_home and
leaving the nested home and its records for a safe rerun.

Empirically, fm-teardown.sh's set -eu already halted the script on the
prior unguarded call before reaching removal (verified by hand with the
guard reverted, under both this session's bash and stock macOS bash
3.2) - the reachable behavior was already correct. The explicit guard
is still applied exactly as decided: it matches every sibling call site
in the function, and it stops the correctness of this path depending on
errexit's well-known fragility under refactors (a wrapping if/&&, or a
future subshell) rather than on an explicit check.

Adds a teardown-level regression building on the existing direct-child
Herdr fixtures: a top-level secondmate contains a nested secondmate,
whose own Herdr child's close goes unconfirmed. Proves through the
public fm-teardown.sh interface that the nested home, the nested
secondmate's own record, and the grandchild's metadata and status all
survive, and that the top-level secondmate's record survives too.

* no-mistakes(document): Document nested Herdr teardown retention

* fix: prioritize completion runway in quota-aware dispatch (kunchenguid#1431)

* fix(dispatch): prioritize quota completion runway

* no-mistakes(document): Document completion-aware quota runway selection

* fix(bin): preserve full task contract in no-mistakes intent (kunchenguid#1447)

* Preserve task contract in no-mistakes intent

* no-mistakes(review): Preserve complete current task contract in no-mistakes intent

* fix(bin): parse punctuated secondmate registry entries safely (kunchenguid#1452)

* fix: centralize secondmate registry parsing

* no-mistakes(review): Centralize secondmate registry binding validation

* no-mistakes(review): Harden registry EOF and symlink validation

* no-mistakes(review): Reject unreadable registries before parsing

* no-mistakes(document): Document punctuation-safe secondmate registry validation

* no-mistakes: apply CI fixes

* feat: schedule validation lane releases

* no-mistakes(review): Bind validation releases to reservations and serialize delivery

* no-mistakes(review): Bind validation completion to reservation-scoped run starts

* no-mistakes(review): Fail closed on unavailable reservation evidence

* no-mistakes(review): Retain holders on unavailable terminal evidence

* no-mistakes(document): Document fail-closed validation lane reservations

* no-mistakes(lint): Fix ShellCheck empty assignments and terminal-state comparisons

* no-mistakes(review): Harden validation reservation evidence handling

* no-mistakes(review): Harden validation lane event and run evidence

* no-mistakes(review): Surface and retry validation lane check failures

* no-mistakes(document): Clarify fail-closed validation lane evidence

* test: mock verified Grok launch delivery

* test: mock verified tmux launch delivery

* test: mock verified Herdr launch delivery

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: AG <ag@agw3.org>
Co-authored-by: deeto15 <92119640+deeto15@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@users.noreply.github.com>
Co-authored-by: QuinnBot <quinnbot@proton.me>
* refactor(tests): give the launch-delivery pane one verifying owner

Seventeen fake terminals across thirteen suites each carried their own copy
of fm-spawn's launch-delivery protocol. Every copy answered the staged-launch
check by echoing back the marker it parsed out of the submitted line, so it
reported success without ever running the checksum it was meant to verify:
corrupt staging would have passed unnoticed in each of them.

Add fm_fake_pane_shell to tests/lib.sh as the single owner of a healthy pane's
answer, and compose it into every one of those fakes. It executes the submitted
check against the bytes the pane actually accumulated, so all thirteen suites
now run the real verification. Each fake keeps ownership of everything else it
models - its own logging, window inventory, kimi readiness state, and pane
content - and a fake for another backend feeds the shared shell directly, the
way the herdr fixture answers `pane run` and `pane read`.

tests/fm-spawn-launch-delivery.test.sh keeps its own purpose-built fake: it
owns the truncation, wrapping, and retry-injection contract, which a healthy
pane deliberately does not model. tests/fm-backend-orca.test.sh already ran the
real check and is left alone.

Net 171 fewer lines of duplicated emulation.

* ci: size the serial lane timeout to its measured wall

The portable serial cap was a hang tripwire set at 20 minutes against a stale
"~13 min wall" note. The lane now measures 16.1 minutes over 78 scripts on one
hosted runner and 17.9 over 69 on another, while two same-revision runs
exceeded 20 and were killed: runner speed moves this lane by more than a third,
so a cap near the typical wall sits inside the variance and reports a healthy
suite as red. Both main and every PR were red for that reason, with no failing
assertion.

Raise it to 40 minutes, roughly 2.5x the measured wall and the value the Herdr
lane already uses, and replace the stale comment with the real measurements.

Record those measurements in docs/fm-test-portable-shards.md under "Measured
serial wall", with the script count and run id behind each one. The lane grows
whenever a new script is neither proven-isolated nor Herdr-gated, so the next
person to grow it can see the current wall and the runner spread instead of
rediscovering both from a cancelled job.

No test is weakened, skipped, or reassigned to another lane.

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…nual-arm collision (#75)

* fix(bin): converge watcher arming on one owner per home

A Claude primary parks its watcher for the whole handling turn because the
Stop-owned auto-arm is its arming owner, but the pull-based mid-turn guard
still carried the manual-arm repair line. The model complied, and that second
arm became a second relay: when the shared cycle closed on a real wake, the
owning arm returned the reason while the following arm reported
"watcher: FAILED - cycle ended without an actionable reason", which the Stop
hook translated into a supervision-is-down alarm that demanded yet another
manual arm.

- fm-supervision-instructions.sh gains --owner-absent, so only a caller that
  has established the arming owner absent asks for a manual arm.
  fm-turnend-guard.sh is that caller; a mid-turn caller now gets the parked
  line naming the Stop-owned auto-arm. Model-owned arming harnesses are
  unchanged.
- fm-watch-arm.sh separates a followed cycle's close (watcher: cycle-ended,
  still typed and nonzero) from an owned child's unexplained close
  (watcher: FAILED), because only the arm that forked a watcher can see that
  watcher's reason line.
- fm-claude-stop-autoarm.sh rewakes for a drain on a followed close and
  reserves the supervision-down alarm for the typed failure.

No blind turn end, no lost wakes, home-scoped repair, and one watcher per home
are unchanged; no backend code is touched.

* no-mistakes(review): Captain, align watcher architecture status contract

* no-mistakes(review): Deduplicate watcher status contract documentation

* no-mistakes(document): Document followed-cycle status across supervision protocols

* test(x-mode): track the harness-aware repair line in opt-out remediation

The opt-out remediation assertion expected the Claude manual-arm wording, but
bootstrap is a mid-turn caller that never establishes the arming owner absent,
so on a Claude primary the renderer now names the Stop-owned auto-arm instead.

Assert that owner for Claude, and add a Grok round-trip so the case still
proves the line tracks the harness rather than being one hardcoded constant -
which is what the assertion was always for.

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
#74)

* fix(brief): give the worktree-isolation assertion real paths

The ship brief tells a worker to stop unless its top level is "the
disposable task worktree you were launched in" rather than "the primary
checkout firstmate operates from", and states neither path. Both operands
are unbound, and the rule's default on any failure to confirm is STOP, so
the check is a judgment call rather than a comparison. It refused a
correctly isolated worker in the live fleet: a firstmate-repo crewmate
reads the primary checkout's absolute path four times in its own brief and
its own worktree path nowhere, and git rev-parse --git-common-dir - the one
command that names another checkout - points at the primary checkout too.

fm-brief.sh now emits the two paths as {FM_WORKTREE} and
{FM_PRIMARY_CHECKOUT} isolation facts, and fm-spawn.sh fills them in from
the worktree it has already verified, on both the treehouse and Orca paths,
immediately after validate_spawn_worktree. The values are the physically
resolved paths because git rev-parse --show-toplevel always reports the
resolved one. The rewrite matches on the fact-line prefix rather than
swapping the placeholder once, so a relaunch into a different worktree
corrects a stale path instead of pinning the brief to the first slot it
used. A brief holding a placeholder the fill cannot reach refuses to
launch: a worker reading a literal {FM_WORKTREE} has no isolation check at
all. Briefs scaffolded before this contract carry neither placeholder nor
fact line and are left untouched, so in-flight tasks still relaunch.

The safety property is preserved and strengthened: a worker genuinely in
the primary checkout compares equal to a named path and stops, and a worker
in an unexpected third tree now reports what it actually read instead of
being pushed onto the same refusal.

* no-mistakes(review): Reject partial isolation fact blocks before spawn

* no-mistakes(document): Document named isolation operands

* chore: trigger CI

* test(spawn): drive isolation-facts spawns through the shared pane owner

The isolation-facts suite was written against a base without the
launch-delivery verification fm-spawn now performs, so its fake tmux
answered send-keys with a bare exit 0 and every spawn in the suite failed
with "launch command delivery could not be verified after 3 attempts".

Source the shared pane shell from tests/lib.sh and answer capture-pane and
send-keys through fm_fake_pane_capture and fm_fake_pane_send, the same owner
tests/fm-spawn-worktree-settle.test.sh uses. That owner executes the
submitted checksum against the bytes the pane actually accumulated, so this
suite exercises the real delivery contract instead of a local imitation that
could report success on corrupt staging.

* test(brief): guard the isolation assertion by its named operands

The tangle guard pinned the literal sentence "The path check is
authoritative", which the isolation assertion no longer contains: the check
is now an exact comparison against two paths fm-spawn.sh fills in at launch,
rather than prose the crewmate has to interpret.

Assert the property instead of the removed phrasing - both operands present,
and the git-dir/common-dir output explicitly not evidence of being in the
primary checkout. That is strictly more than the old assertion checked,
since it now requires the operands the comparison needs, and it keeps the
existing guards against presenting the git-dir shortcut as decisive.

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…ry capacity (#76)

* refactor(dispatch): return crew routing to agent judgment with advisory capacity

Dispatch had accumulated a mechanical control plane in front of the routing
decision: a required quota-axi with a version floor, a 113-line selection
procedure, and a pre-selection vendor credential probe. Together they gated the
start path before any judgment happened, and a capacity or credential reading
could refuse a dispatch outright.

Section 4 now states the routing decision as agent judgment over task fit and
reasoning class. Capacity is advisory: quota-axi is read when installed and may
separate otherwise comparable candidates, but headroom, runway, pace, an
unmeasurable window, or a missing reading never refuses a dispatch or downgrades
the captain's strongest-reasoning class. Selection is followed by exactly one
preflight - fm-spawn.sh validating the concrete selected profile at launch - and
work stops only when the selected capability actually fails or every remaining
fallback would cross an identity boundary.

Retires the quota-array-dispatch skill, the pre-selection vendor auth probe, the
quota-axi compatibility floor, and their verification record. quota-axi leaves
the required universal toolchain, so an absent or older capacity tool no longer
emits a start-path blocker; bootstrap tests pin that guarantee directly.

Preserved unchanged: fm-spawn.sh's fail-closed launch validation and worktree
isolation assertion, secondmate home identity, malformed dispatch config as an
actionable error, and the explicit-harness consultation backstop.

* no-mistakes(review): Remove residual preselection dispatch gates

* no-mistakes(document): Correct stale session-start quota documentation

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(bin): classify ignored runtime churn as info

* no-mistakes(review): Preserve index on pre-merge preservation failures

* no-mistakes(document): Clarify ignored-path preservation semantics

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* feat(bin): prepare a queued ingest scout for every retrievable captain link

Recording a retrievable link now also prepares one ingest scout for it: a scout
brief scaffolded through fm-brief.sh and a queued backlog item filed through the
configured backlog backend. The scout's instructions have it ingest the source
with the existing browser or media tool, keep the ingested text as a durable
artifact beside its report, report what the fleet can build from it along with
out-of-the-box ideas, blindspots, and other angles mapped to current lanes, and
file promising ideas as further queued backlog items.

Preparation stops there. Nothing is spawned, started, or dispatched, so
firstmate keeps dispatch authority, spawn safety, dispatch-profile consultation,
and capacity judgment.

The scout task id derives from the canonical URL alone, so re-recording the same
link - including the scout's own transcript-retaining upsert - converges on the
one brief and backlog item instead of queueing a second scout under a changed
title. An inaccessible record prepares no scout, --no-scout skips preparation for
one invocation, and prepare-scout prepares or repairs one for a stored record.
Preparation runs after publication, so a failure leaves the record stored and
exits 3 with the repair command.

* no-mistakes(review): Fix link-scout contracts, quoting, routing, and backend failures

* no-mistakes(review): Route promising ideas to their mapped lanes

* no-mistakes(document): Document link-scout project write boundary

* no-mistakes(lint): Suppress intentional SC2016 warnings in link intake tests

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…er read empty (#81)

* test(review-diff): cover host external diff viewers in the regression matrix

bin/fm-review-diff.sh already passes --no-ext-diff on both its change
predicate and its patch emission, landed in c059bd0, but nothing pinned that
behavior. A host diff.external= or GIT_EXTERNAL_DIFF= viewer would otherwise
be free to replace the reviewed patch with its own rendering, or to fail where
the configured viewer is not installed, and no test would notice.

Add case (f) to the matrix: configure both a repository-level diff.external and
the environment equivalent to a fake viewer, then assert the review still
prints a unified patch and never the viewer's output.

Record the flag as a repo style rule so future commands that read git patch
output do not repeat the assumption.

* no-mistakes(review): extend the external-diff case to trusted exit codes

The viewer in case (f) returned a non-zero status, so it only exercised patch
emission. With diff.external= plus diff.trustExitCode=true, or the environment
equivalents, Git trusts the viewer's exit code, so `git diff --quiet` can
report no changes for a branch that genuinely changed - the review then prints
"no changes" and ends, hiding the whole diff. That silent-empty-review path was
reachable and untested.

Have the fake viewer exit 0 and set diff.trustExitCode=true so the case covers
the predicate as well as the patch, and widen the style rule from patch output
to any git diff whose exit status controls behavior.

* test(review-diff): make the trusted-exit coverage actually reach the predicate

Case (f) set diff.trustExitCode=true but ran the review with GIT_EXTERNAL_DIFF
set, and that environment variable overrides diff.external while
diff.trustExitCode does not govern it. Git therefore never trusted the fake
viewer's exit code, so the predicate was never falsified and the case only ever
proved patch substitution - the silent "no changes" path it was added for went
untested.

Exercise both mechanisms separately: repository config alone, then
GIT_EXTERNAL_DIFF with GIT_EXTERNAL_DIFF_TRUST_EXIT_CODE. Verified on git
2.50.1 that removing --no-ext-diff from the predicate now fails the case with
"no changes", and removing it from patch emission fails on the viewer output.

Check patch substitution before change detection so each failure names its own
cause; a falsified predicate emits no viewer output to match against.

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
quinnbot-ai and others added 29 commits August 8, 2026 18:50
* feat: enforce literal test inventory receipts

* no-mistakes(review): Harden exact-SHA test receipt merge enforcement

* no-mistakes(review): Restore atomic exact-base local landing

* no-mistakes(review): Harden atomic merge and PR metadata boundaries

* no-mistakes(review): Narrow landing contract and reject wrong-branch transactions

* no-mistakes(document): Document literal inventory merge contract

* no-mistakes(lint): Fix merge script ShellCheck warnings

* test: make merge receipt fixture identity explicit

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix: support exact merges for unprotected repositories

* no-mistakes(review): Captain, protection parser hardened; atomic base binding unavailable

* no-mistakes(review): Harden unprotected GitHub merge validation and containment

* no-mistakes(review): Verify unprotected merge transition attribution

* no-mistakes(review): Reject empty commits in unprotected rebases

* no-mistakes(document): Clarify unprotected merge confirmation contract

* no-mistakes(review): Captain, preserve protected merges and validate raw null envelopes

* no-mistakes(review): Remove unused decoder and make GitHub fixtures portable

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* feat(test-run): add durable progress journal

* no-mistakes(review): Finalize journal lifecycle and preserve worker outcomes

* no-mistakes(review): Close journal signal windows and zero-worker lifecycle

* no-mistakes(document): Document durable test-run progress journals

* no-mistakes(lint): Fix ShellCheck syntax in test runner

* fix(test-run): support empty selections on Bash 3.2

* no-mistakes(review): Captain, fix durable journal transition invariants

* no-mistakes(review): Captain, persist terminal outcomes before bookkeeping

* no-mistakes(test): Stabilize stock Bash signal cleanup timeout

* no-mistakes(lint): Fix progress journal ShellCheck warnings

* fix(ci): align stock Bash runner count

* no-mistakes(review): Enforce durable journal publication barriers

* no-mistakes(review): Sync every created journal ancestor

* no-mistakes(document): Correct stale progress journal invariant

* no-mistakes(review): Defer journal signals until durable initialization completes

* no-mistakes(review): Shield journal initialization from process-group signals

* no-mistakes(review): Reap timed-out journal test process groups

* no-mistakes(document): Correct journal metadata documentation

* no-mistakes(lint): Captain, suppress trap-reachability ShellCheck false positives

* fix(ci): update stock Bash test count

* no-mistakes(review): Shield durable worker-start journal transitions from signals

* no-mistakes(document): Document durable journal signal ordering

* no-mistakes(lint): Captain, fix progress journal ShellCheck findings

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…103)

* feat(watch): carry refill evidence on the heartbeat wake

A bare `heartbeat` wake said nothing about fleet capacity, so refilling a
fleet that drained overnight depended on the supervisor deciding to go
look. Twice this week that left finished work parked and a ready queue
undispatched until morning.

An emitted heartbeat now carries `refill: ready=<n> live=<m> ids=<...>`
on its durable queue payload: how many queued items the configured
backlog backend reports as dispatchable, and how many ship and scout
endpoints are still live. Acting on it becomes part of the ordinary
wake-handling contract rather than a judgment call.

bin/fm-refill.sh owns the probe and the line format, and fails open in
every direction: a manual backlog backend, an absent or incompatible
tasks-axi, an unreadable backlog, a probe past its hard time bound, or
any unexpected error emits nothing and the heartbeat goes out exactly as
before. The probe runs as a bounded child that reports raw fields, so
neither a truncated run nor a crafted backlog id can shape the payload.

The evidence rides the queue payload rather than the printed reason line
because the arm, checkpoint, and Stop auto-arm layers pattern-match that
line as `heartbeat($|:)`; it stays exactly `heartbeat`, and every
supervision protocol already drains the queue before handling a wake, so
the change is harness-agnostic.

* no-mistakes(review): Fix autonomous heartbeat refill routing and probe validation

* no-mistakes(document): Clarify heartbeat refill documentation

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(merge): bypass gh shim in GraphQL capture

* no-mistakes(document): Document merge capture regression evidence

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(gh): pin shim PR target to origin

* no-mistakes(review): Fix gh shim repository option parsing

* no-mistakes(review): Fix long option repository flag confusion

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
…llback (#106)

GitHub now reports the fine-grained personal-token denial for gh pr checks
with a deeper GraphQL error path (.contexts.nodes.0 appended), so the exact
literal signature match never activated the bounded CI fallback and an active
no-mistakes run looped on the denial even with a green exact-head workflow.

Match the denial sentence plus a whole statusCheckRollup path component at any
depth instead. Denials for other APIs, and paths that only begin with those
characters, still replay unchanged. Supported argument shapes and exact-head
workflow verification are untouched.

Co-authored-by: QuinnBot <quinnbot@proton.me>
…rp flag (#107)

GitHub CLI 2.92.0 refuses `--slurp` whenever `--jq` is present, so the CI
fallback's workflow-runs read failed before it could terminalize a green
exact-head run, and its stderr was discarded behind a generic message.

Filter each page on its own and join the per-run objects into the single
JSON check array instead, keeping the exact-head recheck, all-page
aggregation, the pending placeholder for zero runs, and the denial-shape
restrictions unchanged. A failed read now prints gh's own error too.

The gh double now reproduces the 2.92.0 flag refusal, per-page evaluation,
and gh's sorted result keys, with a case that proves the refusal is real
before asserting the fallback still reaches a verdict against it.

Co-authored-by: QuinnBot <quinnbot@proton.me>
…f-recursion (#108)

* fix(gh): resolve shim PR target from registration

* fix(merge): bound cross-home gh recursion

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(ci): harden Actions fallback verification

* no-mistakes(review): Harden PR-run selection and normalizer failure handling

* no-mistakes(review): Type terminal state cleanup failures

* no-mistakes(review): Unify deadline state failure handling

* no-mistakes(test): Complete upstream CI fallback proposal contract

* no-mistakes(review): Harden Actions fallback failure and completeness handling

* no-mistakes(review): Close CI state and consumer boundary gaps

* no-mistakes(document): Document canonical CI fallback state handling

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
Co-authored-by: QuinnBot <quinnbot@proton.me>
Co-authored-by: QuinnBot <quinnbot@proton.me>
* feat(bootstrap): remind about credential metadata expiry

* fix(bootstrap): satisfy reminder shell lint

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(afk): dedupe unchanged refill escalations

* no-mistakes(review): Fingerprint complete refill state before display capping

* no-mistakes(document): Documentation already reflects refill escalation dedupe

* no-mistakes(lint): Fix refill daemon lint warnings

* no-mistakes(review): Process queued refill transitions durably before deduping

* no-mistakes(review): Unify durable refill observation acknowledgement and suppression

* no-mistakes(review): Fail open heartbeat state and preserve crash replay

* no-mistakes(review): Quarantine malformed heartbeat state without starving transitions

* no-mistakes(lint): Captain, resolve scoped ShellCheck diagnostics

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
Co-authored-by: QuinnBot <quinnbot@proton.me>
Co-authored-by: QuinnBot <quinnbot@proton.me>
Co-authored-by: QuinnBot <quinnbot@proton.me>
* ci: gate native backend coverage

* test: map zellij installer to native gate

* fix: refresh zellij release checksum

* test: cover native backend workflow scheduling

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(status): dedupe unchanged keyed pause reports

* test(brief): cover status reporter path quoting

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(test-run): enforce semantic family ownership

* test(ci): update focused runner count

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* feat: add bounded agent retrospective

* fix(test): classify agent retrospective suite

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* feat(pr): add concise direct PR narratives

* fix(pr): avoid nested credential wrappers

* fix(pr): bypass the credential routing shim

* fix(test): classify direct PR narrative coverage

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
* fix(teardown): retire proven detached task metadata

* no-mistakes(review): Harden detached retirement identity and recovery

* no-mistakes(review): Bind detached teardown repository identities

* no-mistakes(review): Bind fork and canonical target identities

* no-mistakes(document): Document detached teardown retirement guarantees

* no-mistakes(lint): Fix teardown ShellCheck diagnostics

* fix(teardown): preserve branches through return recheck

* fix(teardown): recheck stale locks at return boundary

---------

Co-authored-by: QuinnBot <quinnbot@proton.me>
Co-authored-by: QuinnBot <quinnbot@proton.me>
Co-authored-by: QuinnBot <quinnbot@proton.me>
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