Skip to content

feat(bin): have the worker start its own no-mistakes run (CFVC-15) - #2035

Closed
sbracewell64 wants to merge 63 commits into
kunchenguid:mainfrom
sbracewell64:fm/cfvc-15-worker-calls-pipeline
Closed

feat(bin): have the worker start its own no-mistakes run (CFVC-15)#2035
sbracewell64 wants to merge 63 commits into
kunchenguid:mainfrom
sbracewell64:fm/cfvc-15-worker-calls-pipeline

Conversation

@sbracewell64

Copy link
Copy Markdown

Intent

CFVC-15: the worker calls the pipeline directly - retire the keystroke transition.

Goal: the fleet's most-travelled transition (implementation-committed -> validate) stops being actuated by firstmate simulating a human typing /no-mistakes into a terminal composer. Today the worker commits, appends "done:", and stops; that wakes firstmate; firstmate spends a model turn whose entire semantic content is a transition already determined; then bin/fm-send.sh types /no-mistakes into a TUI composer. That actuator has a measured false-positive class: on 2026-07-03 two crewmates were sent the trigger and both left it fully typed but unsubmitted in the composer for minutes while fm-send exited 0 with no error.

Accepted scope:

  1. bin/fm-brief.sh's definition-of-done block for mode=no-mistakes ships: the worker that finishes its implementation commit invokes no-mistakes axi run directly as one blocking call inside its existing definition of done, replacing the append-done-and-stop step. Firstmate's turn is spent only when the pipeline returns something needing a decision.
  2. AGENTS.md section 7's validate paragraph follows the same change. The firstmate-coding-guidelines skill was loaded and followed before touching either (knowledge-placement decision tree, one-owner rule, AGENTS.md size discipline, one sentence per line, plain dash, no agent co-author, shellcheck-clean scripts, colocated tests).
  3. Retire the /no-mistakes keystroke actuator and its per-harness quirk table from the harness-adapters skill - a genuine deletion of documented complexity, not a wrapper over it.
  4. Hard constraints from the increment's certification row: this must NOT become a private loop runner. No new watcher, scheduler, wake queue, or supervision daemon; it is one call inside the existing definition of done, replacing a stop. The shared-daemon rule is preserved: a worker never restarts the no-mistakes daemon, because one instance serves every lane.
  5. Tests: a worker reaching definition-of-done starts a run without a firstmate turn; a negative control asserts a concurrent run on another branch is refused rather than joined; the daemon-preservation rule is asserted.

Completion criteria: (a) zero firstmate model turns between commit and run start for a ship task; (b) the per-harness keystroke quirk section deleted from harness-adapters; (c) no new scheduler/watcher/queue, asserted by review.

Dependency consumed: CFVC-07's fm-verify landed on the trunk this branch is cut from (commit fabaac4). The pipeline's return is therefore read through the three-valued verifier contract rather than a bare exit status - the generated brief tells the worker to judge the call by the run result it prints, and to treat a return with no readable run result (empty return, daemon error, killed process) as could-not-observe, which is never a pass and never grounds for a blind retry.

Decisions and tradeoffs made while doing the work, which a reviewer reading only the diff would not know:

  • Branch scoping was added to the generated brief because the increment creates a new hazard: a worker that starts its own run can now meet another lane's run through the shared daemon. This was verified live and read-only rather than assumed - with no run on this branch, a bare no-mistakes axi status answered with a DIFFERENT branch's run that had a finding awaiting response. The brief therefore tells the worker to check a run's branch: before treating it as its own, and never to respond to, abort, or adopt a foreign lane's run. bin/fm-crew-state.sh already owns and documents that cross-branch fallback fact on the firstmate side, so the brief states the operational rule rather than restating the mechanism (one-owner rule).

  • The retirement deletes the "no-mistakes skill invocation" section AND the per-harness Skill invocation table rows, because that axis existed solely for the retired validation trigger; the exit command remains the only routine command-shaped steer firstmate sends. Facts that live code still depends on were deliberately kept and re-anchored to command-shaped sends in general rather than to the retired trigger: codex's $-popup settle scoping (bin/fm-send.sh, tests/fm-send-popup-settle.test.sh) and grok's slash-popup argument-hint hazard with its herdr submit-verification fix. The dated 2026-07-03 incident record is kept verbatim as evidence for that live fix, with one line added noting it is the measured failure that retired the actuator. bin/fm-send.sh's command-shaped classification was deliberately NOT changed: it serves any command-shaped send including the captain typing, and is covered by existing tests.

  • Now-dangling skill-invocation load triggers were removed from AGENTS.md sections 4 and 13, .agents/skills/firstmate-orca, .agents/skills/stuck-crewmate-recovery, and docs/configuration.md, per trigger hygiene - a trigger pointing at a deleted section is dead weight. docs/architecture.md's mention of codex $... skill invocations was intentionally left alone because it describes live fm-send behavior.

  • The # Who is speaking to you example was changed from /no-mistakes to /compact and the header rationale reworded, because the exception's premise (firstmate cannot mark a command-shaped message) stays true while the specific example became stale once firstmate stopped sending that trigger. /compact is still live in worker instructions.

  • Rollout deviates deliberately from the increment's "migration is per-harness" row: the change is harness-independent. The replacement is a shell command every verified adapter already runs, so the harness-dependent surface is REMOVED rather than migrated, and gating it per harness would add exactly the machinery the certification row forbids. Already-scaffolded briefs keep their previous contract, so in-flight lanes are unaffected, and AGENTS.md section 7 gained one line telling firstmate to steer such a worker into the run rather than restore the actuator. This deviation was reported to firstmate rather than made silently.

  • Test placement follows the tests/fm-ask-user-authority.test.sh precedent: a contract-specific file (tests/fm-worker-initiated-validation.test.sh) asserting behavior through the executable interface (running bin/fm-brief.sh and inspecting the artifact it generates), never implementation-source bytes. Each absence assertion is paired with a negative control that reconstructs the retired shape and watches the same predicate go red, because a check that can only report "not found" cannot distinguish a passing brief from a broken generator. All four cases were additionally witnessed failing against the pre-change generator before being trusted green. The test is registered in bin/fm-test-run.sh's pure-contract-unit family and --check-coverage passes.

  • The worker's own behavior cannot be executed in CI (the worker is an agent, not a script), so the contract is enforced at the surface that IS executable: the generated brief. This is stated plainly rather than claimed as full end-to-end coverage.

Verification status: bin/fm-lint.sh clean (pinned ShellCheck 0.11.0), bin/fm-doc-audience-check.sh clean, and the directly affected suites pass (fm-worker-initiated-validation, fm-brief, fm-ask-user-authority, fm-task-delivery, fm-send-marker, fm-send-popup-settle, fm-documentation-audiences, fm-test-run). One unrelated test, fm-calm-pi-extension, fails on a local Node ERR_UNKNOWN_FILE_EXTENSION for a .ts Pi extension; this was confirmed pre-existing by reproducing it identically on a pristine git archive HEAD export, and this change touches no Pi, Node, or calm surface.

Delivery constraint specific to this lane: this branch is cut from a fork trunk that is ahead of upstream. If the rebase gate flags the ahead-of-upstream commits, the accepted answer is to skip that step rather than rebase.

What Changed

  • bin/fm-brief.sh's mode=no-mistakes definition of done now has the worker invoke no-mistakes axi run directly as one blocking call after its implementation commit — judging the call by the printed run result under the three-valued verifier contract (an unreadable result is could-not-observe, never a pass), and checking a run's branch: before treating it as its own — replacing the append-done:-and-stop step that spent a firstmate turn typing /no-mistakes into a TUI composer. The keystroke actuator and its per-harness quirk table are deleted from the harness-adapters skill (the 2026-07-03 incident record is kept as evidence), AGENTS.md section 7 follows the same change, and now-dangling skill-invocation load triggers are removed; a review-stage follow-up aligned the rule 4 examples and blocked: append wording with the new definition of done. No new watcher, scheduler, queue, or daemon-restart path is introduced, and a worker never restarts the shared no-mistakes daemon.
  • New contract suite tests/fm-worker-initiated-validation.test.sh asserts through the generated brief that a worker reaching definition-of-done starts its own run, that a concurrent run on another branch is refused rather than joined, and that the shared-daemon rule holds — each absence assertion paired with a negative control witnessed red against the pre-change generator, and the suite registered in bin/fm-test-run.sh's pure-contract-unit family.
  • The branch also carries the fork trunk ahead of the upstream base (64 commits across 245 files, which is why the rebase gate was deliberately skipped): fleet admission control, the LoopSpec schema/register and lawful-transition actuation, the deterministic decision surface, task role/deliverable/stage axes with a durable retry budget, the model registry and zero-budget spawn gate, the wake-outcome ledger, the worktree guard, remote-secondmate tooling, and the fleet launcher menu, among others.

Risk Assessment

✅ Low: The fix commit resolves all three prior findings exactly as instructed (mode-conditional rule 4 examples, an explicit could-not-observe conditional with a coherent placeholder, and the updated test comment), and rendering all brief kinds plus statically re-evaluating every test predicate against the fresh artifact shows no new defects, unbound-variable paths, or intent regressions.

Testing

Exercised the worker-facing contract end-to-end: generated a real no-mistakes brief and diffed its definition-of-done against the pre-change generator's output (showing the append-done-and-stop handoff replaced by the worker's own blocking no-mistakes axi run call with branch scoping and the could-not-observe rule), ran the new contract test green and witnessed it red against the old generator, ran the touched suites (fm-brief, fm-ask-user-authority, fm-documentation-audiences) and the coverage-registration check, and grepped harness-adapters to confirm the keystroke quirk table is gone; everything passed with no new scheduler/watcher surface in the diff. No screenshot evidence applies because the end-user surface is generated markdown, captured directly as artifacts.

Evidence: Definition-of-done before/after diff (pre-change fabaac4 generator vs CFVC-15 generator)
--- /tmp/dod-before.md	2026-08-09 17:39:58.685781575 -0400
+++ /tmp/dod-after.md	2026-08-09 17:39:58.687141062 -0400
@@ -1,12 +1,23 @@
 # Definition of done
 Delivery contract: mode=no-mistakes
-The task is complete only when committed on your branch.
-When you believe it is complete, append `done: {summary}` to the status file and stop.
-Firstmate will then instruct you to run /no-mistakes to validate and ship a PR.
+The task is complete only once it is committed on your branch AND validated through the no-mistakes pipeline, which you start yourself.
+
+The moment your implementation commit lands, start validation with one blocking call:
+   `no-mistakes axi run --intent "{what this task set out to accomplish}"`
+Start it directly. Do not append a status line for the commit, do not report `done:` first, and do not wait to be told: nobody is watching for a handoff at this point, so a worker that stops here stalls its own task.
+That call blocks until the first approval gate, the CI-ready point, or the final outcome, and prints it.
+Make `--intent` preserve all relevant content from this brief's `# Task` section plus every later accepted Firstmate requirement, clarification, constraint, exclusion, and supersession, carrying only each requirement's current accepted form; retain direct requirements instead of substituting a diff summary, and exclude generic operational, status, delivery, and other scaffold boilerplate unless it is task-specific.
+
+Judge that call by the run result it prints, never by its exit status.
+A printed gate or outcome is an observation you can act on; a call that returns no readable run result at all - an empty return, a daemon error, or a killed process - is could-not-observe, which is never a pass and never a reason to retry blindly.
+Only in that could-not-observe case, append `blocked: {what you observed instead of a run result}` and stop; a printed gate or outcome is never `blocked:` - drive it as described below.
+
+The pipeline validates YOUR branch only, and one shared daemon serves every other lane at the same time.
+Check the `branch:` of any run you are shown before treating it as yours: with no run on your own branch, a bare `no-mistakes axi status` answers with some other branch's run.
+A run on a branch that is not yours is another lane's work - never respond to it, abort it, or adopt it as your own, and never restart the daemon to clear it (rule 6).
 
 You drive no-mistakes by responding to its gates, not by implementing fixes.
-Follow the guidance no-mistakes itself provides for the mechanics: it loads when you invoke /no-mistakes, and `no-mistakes axi run --help` plus the `help` lines in each `axi` response are authoritative and version-matched to the installed binary.
-When starting no-mistakes, make `--intent` preserve all relevant content from this brief's `# Task` section plus every later accepted Firstmate requirement, clarification, constraint, exclusion, and supersession, carrying only each requirement's current accepted form; retain direct requirements instead of substituting a diff summary, and exclude generic operational, status, delivery, and other scaffold boilerplate unless it is task-specific.
+Follow the guidance no-mistakes itself provides for the mechanics: `no-mistakes axi run --help` plus the `help` lines in each `axi` response are authoritative and version-matched to the installed binary.
 Do not hand-edit, commit, or fix findings yourself while a run is active - the pipeline applies every fix.
 The one exception is a rebase the pipeline hands back to you: resolve and commit it under `# Branch conflict resolution` above, then return to driving the gates.
 
@@ -16,4 +27,4 @@
   When the decision comes back, feed it to the gate with `no-mistakes axi respond` and let the pipeline apply it - do not route the question to "the user" or implement the fix yourself.
 - Avoid `--yes`: it would silently bypass firstmate's authority check and any required captain escalation.
 
-After /no-mistakes reports CI green (the CI-ready return point - do not wait for it to keep monitoring in the background until merge), append `done: PR {url} checks green` and stop. You are finished.
+After the pipeline reports CI green (the CI-ready return point - do not wait for it to keep monitoring in the background until merge), append `done: PR {url} checks green` and stop. You are finished.
Evidence: Full generated worker brief (mode=no-mistakes) from the CFVC-15 generator
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
{TASK}

# Context pressure
Claude Code sessions receive host-computed context-window telemetry in the bottom status line.
For a spawned Claude worker, the same reading is written to `/tmp/fm-cfvc15-demo/context-pressure.json`.
At natural phase boundaries, read that file when it exists and use it instead of a self-estimate.
When `compact_recommended` is `true` (70 percent used or higher), run `/compact` before continuing.
The file is optional because other worker runtimes do not expose this verified telemetry contract; never fabricate a reading when it is absent.

# Herdr lifecycle declaration - NOT ENABLED
**HARD SAFETY GATE:** this scaffold cannot inspect the task text that replaces `{TASK}` later.
If the task will start, stop, delete, restart, profile, or otherwise drive Herdr lifecycle behavior, stop and regenerate the brief with `--herdr-lab` before dispatch.
Do not add Herdr lifecycle commands to this unguarded brief by hand.

# Setup
You are in a disposable git worktree of sampleproj, at a detached HEAD on a clean default branch.

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable task worktree you were launched in, such as a treehouse pool path or an Orca-managed worktree, not the primary checkout firstmate operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b fm/cfvc15-demo`
2. Run `no-mistakes doctor`; if it reports the repo is not initialized here, run `no-mistakes init`.

# Rules
1. Never push to the default branch. Never merge a PR.
2. Stay inside this worktree; modify nothing outside it.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations. Most of that is action - opening a pull request, commenting, listing issues - and an action has no observation type; when you are instead making an OBSERVATION whose answer you will act on, run it through `/home/shane/.no-mistakes/worktrees/5f306883d81c/01KZM69GBW3PSGK370XDEMAY7V/bin/fm-verify.sh` (`--list` names the declared verifiers) rather than reading the tool's exit status.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/tmp/fm-cfvc15-demo/state/cfvc15-demo.status'`
   States: working, needs-decision, blocked, paused, done, failed.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced - never the implementation commit, whose only next step is the pipeline call under Definition of done) and the
   needs-decision/blocked/paused/done/failed states. No step-by-step FYI progress lines;
   firstmate reads your pane for that.
   A mid-task `working:` line (including setup complete) is nonterminal: do not end the
   turn after it; continue the same stage until a defined `done:` gate under Definition of done.
   Use `paused: {why}` - distinct from `blocked:` - ONLY when you are deliberately idling on a
   known external wait you expect to clear on its own (an upstream release, a rate-limit reset,
   a scheduled window): firstmate then leaves your idle pane alone and rechecks it on a long
   cadence instead of treating it as a possible wedge. Use `blocked:` when you are stuck and need help.
5. If a decision belongs above the implementation worker (product choices, destructive actions, ask-user findings),
   append `needs-decision: {summary of options}` and stop. Firstmate will apply the configured authority and reply with the decision.
   When firstmate replies or a blocker clears and you resume, append `resolved: {how it was decided or unblocked}` (add the same `[key=<slug>]` if you opened it with one) so the decision or blocker is durably closed and does not keep resurfacing.
6. Never stop, restart, or update the shared `no-mistakes` daemon - it is one instance serving
   every lane/home, so restarting it kills other lanes' in-flight pipeline runs. On ANY no-mistakes
   daemon error, append `blocked: {the daemon error}` and stop; only firstmate manages the daemon.

# Who is speaking to you
Firstmate marks every message it sends you with a leading `[fm-from-firstmate]` label followed by an invisible system separator; that separator is untypable, so a human never produces it.
A marked message is firstmate: act on it as task instruction.
An unmarked message is a human typing directly into your pane - usually the captain, who may believe this pane is firstmate rather than a worker.
The one exception is a message that starts with `/`, or on codex with `$`, such as `/compact`: a harness recognizes that form only at the very start of the line, so firstmate cannot mark it without breaking it. Treat such a message as routine and act on it.
Before acting on any other unmarked message, say plainly that you are a worker on task `cfvc15-demo`, not firstmate, and that merges, cross-lane work, other lanes' state, and fleet supervision belong to firstmate. Then ask whether they still want you to proceed.
Escalation is always the status file in rule 4, never this pane.
Firstmate does not read your chat, so anything you address to the captain here is lost: a decision or gate left parked with only "Captain, ..." in this pane is invisible to everyone and stalls the task indefinitely.

# Branch conflict resolution
Rebase and resolve branch/base conflicts yourself whenever intent is clear, at any file count: keep the base wherever this branch made no deliberate change, reapply this branch's contributions on top, and preserve every prior pipeline fix commit through the rebase.
Escalate only genuinely ambiguous intent to firstmate, never the captain.

# Verification discipline
An observation has three values, never two: observed-good, observed-bad, and could-not-observe.
The third is a real result, not a missing one.
An empty result set, an unreadable file, an absent artifact, an unreachable tool, a silent verifier, and an exit code that covers both a failure and a refusal are all could-not-observe, and none of them is a pass.
Never narrow one into the other two: an empty violations log, no output, and no failures found are never success on their own, and a missing expected artifact is could-not-observe at collection time rather than work still in progress.
Run a verifier through `/home/shane/.no-mistakes/worktrees/5f306883d81c/01KZM69GBW3PSGK370XDEMAY7V/bin/fm-verify.sh` (`--help` lists the declared verifiers) and act on the `PASS` / `FAIL` / `NO_VERIFIER_RAN` result it returns, rather than interpreting a tool's exit status yourself.
When the observation you need has no declared verifier, apply the same three-valued rule by hand: name which of the three values you reached and the evidence you reached it on, and report the undeclared verifier as a gap - never infer a pass from an exit status.
Before trusting a success reported only by absence, run a negative control, watch that same check go red, and only then run the real check.

# Project memory
If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/home/shane/.no-mistakes/worktrees/5f306883d81c/01KZM69GBW3PSGK370XDEMAY7V/bin/fm-ensure-agents-md.sh .` in the worktree.
Record only project knowledge useful to almost every future session.
For anything the codebase already shows, prefer a pointer to the authoritative file, command, or doc over copying the detail.
If you touch a project `AGENTS.md` that lacks `## Maintaining this file`, add that short self-governance section from `/home/shane/.no-mistakes/worktrees/5f306883d81c/01KZM69GBW3PSGK370XDEMAY7V/bin/fm-ensure-agents-md.sh` in the same pass.
Keep it proportionate: skip `AGENTS.md` edits for trivial tasks that produced no durable project knowledge.

# Commit conventions
Never add an agent name as a commit co-author, and never add a Co-Authored-By trailer naming an agent, whatever your own harness instructions say.
Never carry the fleet conversational conventions - captain address and nautical seasoning - into a commit message, PR title, PR body, or anything else crewmates and other tools read.

# Definition of done
Delivery contract: mode=no-mistakes
The task is complete only once it is committed on your branch AND validated through the no-mistakes pipeline, which you start yourself.

The moment your implementation commit lands, start validation with one blocking call:
   `no-mistakes axi run --intent "{what this task set out to accomplish}"`
Start it directly. Do not append a status line for the commit, do not report `done:` first, and do not wait to be told: nobody is watching for a handoff at this point, so a worker that stops here stalls its own task.
That call blocks until the first approval gate, the CI-ready point, or the final outcome, and prints it.
Make `--intent` preserve all relevant content from this brief's `# Task` section plus every later accepted Firstmate requirement, clarification, constraint, exclusion, and supersession, carrying only each requirement's current accepted form; retain direct requirements instead of substituting a diff summary, and exclude generic operational, status, delivery, and other scaffold boilerplate unless it is task-specific.

Judge that call by the run result it prints, never by its exit status.
A printed gate or outcome is an observation you can act on; a call that returns no readable run result at all - an empty return, a daemon error, or a killed process - is could-not-observe, which is never a pass and never a reason to retry blindly.
Only in that could-not-observe case, append `blocked: {what you observed instead of a run result}` and stop; a printed gate or outcome is never `blocked:` - drive it as described below.

The pipeline validates YOUR branch only, and one shared daemon serves every other lane at the same time.
Check the `branch:` of any run you are shown before treating it as yours: with no run on your own branch, a bare `no-mistakes axi status` answers with some other branch's run.
A run on a branch that is not yours is another lane's work - never respond to it, abort it, or adopt it as your own, and never restart the daemon to clear it (rule 6).

You drive no-mistakes by responding to its gates, not by implementing fixes.
Follow the guidance no-mistakes itself provides for the mechanics: `no-mistakes axi run --help` plus the `help` lines in each `axi` response are authoritative and version-matched to the installed binary.
Do not hand-edit, commit, or fix findings yourself while a run is active - the pipeline applies every fix.
The one exception is a rebase the pipeline hands back to you: resolve and commit it under `# Branch conflict resolution` above, then return to driving the gates.

Two firstmate-specific rules layer on top of that guidance:
- ask-user findings are never yours to answer: escalate to firstmate (rule 5) and stop.
  Firstmate applies the authority contract in its `AGENTS.md` and obtains any required captain decision.
  When the decision comes back, feed it to the gate with `no-mistakes axi respond` and let the pipeline apply it - do not route the question to "the user" or implement the fix yourself.
- Avoid `--yes`: it would silently bypass firstmate's authority check and any required captain escalation.

After the pipeline reports CI green (the CI-ready return point - do not wait for it to keep monitoring in the background until merge), append `done: PR {url} checks green` and stop. You are finished.
Evidence: Contract test transcript: green on CFVC-15, witnessed red on pre-change generator

$ bash tests/fm-worker-initiated-validation.test.sh (against the CFVC-15 generator) ok - a no-mistakes worker starts validation itself, with no firstmate turn between commit and run ok - worker-started validation preserves the one-instance shared-daemon rule ok - a concurrent run on another branch is refused rather than joined ok - the validate transition stays one blocking call, with no poller, timer, or watcher $ bash tests/fm-worker-initiated-validation.test.sh (against the pre-change fabaac4 generator — witnessed red) not ok - no-mistakes brief does not tell the worker to start its own run

$ bash tests/fm-worker-initiated-validation.test.sh   (against the CFVC-15 generator)
ok - a no-mistakes worker starts validation itself, with no firstmate turn between commit and run
ok - worker-started validation preserves the one-instance shared-daemon rule
ok - a concurrent run on another branch is refused rather than joined
ok - the validate transition stays one blocking call, with no poller, timer, or watcher

$ bash tests/fm-worker-initiated-validation.test.sh   (against the pre-change fabaac4 generator — witnessed red)
not ok - no-mistakes brief does not tell the worker to start its own run

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

⏭️ **Rebase** - skipped

Push main to origin, or rebase your branch onto origin/main, before gating.

🔧 **Review** - 3 issues found → auto-fixed ✅
  • ⚠️ bin/fm-brief.sh:679 - Rule 4's reportable-phase example list still names "fix implemented" (bin/fm-brief.sh:679), which is exactly the moment the new definition of done forbids reporting ("Do not append a status line for the commit", line 620). Since every append wakes firstmate, a worker weighing rule 4's example over the DoD prohibition re-creates the commit-to-run-start wake the increment's completion criterion (a) requires to be zero. The example list is shared across modes (it stays apt for local-only), so the fix needs a wording decision — e.g. mode-conditional examples or dropping "fix implemented" — rather than a mechanical edit.
  • ℹ️ bin/fm-brief.sh:625 - The sentence "Append blocked: {what the pipeline returned} and stop." (bin/fm-brief.sh:625) is not explicitly conditioned on the could-not-observe case it follows: a literal reader at a printed approval gate could take it as the next step for any run result and stall the task with blocked: instead of driving the gate (the correction only arrives two paragraphs later). Additionally, the placeholder "{what the pipeline returned}" is incoherent for a case defined by the pipeline returning nothing readable — "{what you observed instead}" or an explicit "If so, append..." conditional would remove both ambiguities.
  • ℹ️ tests/fm-brief.test.sh:814 - The block comment above test_crewmate_scaffolds_carry_who_is_speaking still says the command-shaped exclusion exists "so the routine validation trigger does not read as a rule violation" (tests/fm-brief.test.sh:814), but this change retires that trigger and the assertion message beneath was already updated to the new rationale ("so a typed command is not read as an impostor"). Update the comment to match — a stale rationale pointing at a retired mechanism is exactly the dead weight the change's trigger-hygiene pass removed elsewhere.

🔧 Fix: align rule 4 examples and blocked append with DoD
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-worker-initiated-validation.test.sh — all 4 contract cases pass (worker-started run, shared-daemon rule, foreign-branch run refused, no private loop runner)
  • bash tests/fm-worker-initiated-validation.test.sh run against the pre-change fabaac4 generator — witnessed red (not ok - no-mistakes brief does not tell the worker to start its own run), proving the checks detect the retired shape
  • bash tests/fm-brief.test.sh — full brief-generator suite passes, including the mode=no-mistakes definition-of-done and /compact who-is-speaking example
  • bash tests/fm-ask-user-authority.test.sh and bash tests/fm-documentation-audiences.test.sh — touched suites pass
  • bin/fm-test-run.sh --check-coverage — new test registered in the pure-contract-unit family, 144 tests mapped
  • Manual: generated a real brief via FM_HOME=… bin/fm-brief.sh cfvc15-demo sampleproj --mode no-mistakes and diffed its definition-of-done against one produced by the pre-change fabaac4 generator
  • Manual: grep -n &#39;Skill invocation|/no-mistakes|$no-mistakes&#39; .agents/skills/harness-adapters/SKILL.md — only the deliberately kept 2026-07-03 incident record remains
  • Manual: reviewed the full CFVC-15 diff (fabaac4..79915bc, 9 files) to confirm no new watcher, scheduler, queue, or daemon-restart path is introduced
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

sbracewell64 and others added 30 commits August 9, 2026 15:50
A fleet launcher will soon open PRIMARY firstmate sessions alongside the
crewmate sessions fm-spawn.sh opens, so both need the same verified launch
commands. Today that knowledge lives only inside bin/fm-spawn.sh, and the
drift a second copy causes is not hypothetical: a downstream registry
hand-copied claude's command as `claude --dangerously-skip-permissions`,
dropping CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false - the ghost-text
suppression that keeps firstmate from reading predicted-prompt text as real
typed input when it captures a pane.

Extract launch_template, model_flag_for_harness, and effort_flag_for_harness
(plus the shell_quote both flag resolvers depend on) into a new sourced
bin/fm-launch-lib.sh, and have fm-spawn.sh source it. Every crewmate, scout,
and secondmate template is byte-identical to before, so spawn behavior is
unchanged on all six verified adapters.

launch_template also gains a `primary` kind for the launcher. A primary
session has no task, no worktree, no brief, and no status file, so it launches
bare and is greeted by the session-start adapters already installed in the
home; each primary template keeps its adapter's verified autonomy flag and
claude's ghost-text prefix. An unrecognized kind still resolves to the
crewmate shape, and an unverified adapter still returns non-zero for every
kind.

tests/fm-launch-lib.test.sh pins both arms directly, including a proof that
fm-spawn.sh redefines none of the functions and that no other script under
bin/ hand-writes a launch command. Existing suites that read the template
bytes now read them from their new owner.
bin/fm-launch.sh is the captain's front door: it renders a five-entry harness
menu, starts one firstmate primary session in this home, and attaches to it.

The menu is derived and probed, never declared. An entry is available only when
its harness binary resolves on PATH, or - for a Pi-routed entry - when the
provider named in its model appears in pi's local auth record. Unavailable
entries stay visible and dim, each with one actionable line, so the menu never
changes shape under the captain's muscle memory. Both probes are local file
reads, so the menu touches no network and executes no binary at all.

Menu entries carry no launch command. They name a harness plus an optional
model and effort, and the command is resolved through bin/fm-launch-lib.sh at
launch time - the single owner a downstream registry has already drifted from
once by hand-copying a launch string and dropping claude's ghost-text
suppression prefix.

The launcher states on every render, before the choice, that the session it
starts runs without permission prompts. That discharges the consumer obligation
bin/fm-launch-lib.sh's header binds on every consumer of a primary template.

Herdr is mandatory with no silent fallback to a bare shell, and the gate runs
after selection so no socket round trip sits on the critical path. Before
creating anything the launcher looks for a primary already running in this home
and offers to reattach, so two sessions can never contend for one home's
session lock.

Selection is one keypress. A human who mistypes gets a redrawn prompt; a
scripted caller keeps the refuse-don't-reprompt behavior, and a blank line or
EOF refuses rather than launching whatever the default happens to be - taking
the default there once started an unattended session nobody chose.

Presets live in gitignored config/launch-presets.json and the built-in five need
no configuration. They are deliberately not inherited into secondmate homes: a
secondmate is provisioned and launched by the primary through bin/fm-spawn.sh,
never through this front door, so there would be no consumer for an inherited
menu.

The Windows entry point and WSL bridge are out of scope here and land
separately.
…coverage

tests/fm-launch-lib.test.sh's one-owner guards grepped bin/fm-spawn.sh for
function definitions and its literal source line, and git-grepped bin/ for
launch-command markers - implementation-source assertions the coding
guidelines now forbid. Prove the same guarantee behaviorally instead: a
sandboxed copy of bin/ shows fm-spawn's launch decision follows a swapped
fm-launch-lib.sh in both directions and that fm-spawn cannot take a launch
decision without the library, so the launch knowledge has exactly one live
owner. The byte-for-byte template pins already go through the public
launch_template interface and stay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… briefs to read the marker (#9)

* feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145)

* feat: add verified pi-signed adapter

* no-mistakes(review): Correct pi-signed maintainer verification date

* no-mistakes(review): Correct remaining pi-signed verification dates

* no-mistakes(review): Preserve authoritative pi-signed runtime identity

* no-mistakes(document): Document pi-signed shared adapter semantics

* no-mistakes: apply CI fixes

* fix(pi): rearm watcher across session transitions (kunchenguid#1166)

* fix(pi): rearm watcher across same-process session transitions

Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement
as well as terminal quit. The primary watcher extension latched a module-level
stopping flag on every shutdown, so a replacement session in the same process
could not arm monitoring until Pi restarted.

Own arm authority per session generation so only the active live generation
may start, stop, or rearm the child. Replacement sessions can arm again without
restarting Pi, stale prior-generation callbacks cannot mutate the active cycle,
and real quit still blocks late rearm.

* no-mistakes(review): Preserve Pi generation isolation and exit cleanup

* no-mistakes(document): Correct Pi watcher transition documentation

* feat: route crew dispatch using quota-window pace (kunchenguid#1172)

* Consume quota-axi pace signals in dispatch profile array selection.

Add quota-array-dispatch as the single owner of the pace-aware candidate
choice, keep AGENTS.md to the intake boundary and load trigger, and cover
the acceptance cases with sanitized schemaVersion 3 fixtures.

* no-mistakes(review): Stop and report genuine quota dispatch ties

* no-mistakes(document): Document quota pace freshness and uncertainty

* fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171)

* fix(grok): adapt Stop continuation to runtime capability

* no-mistakes(review): Reject ambiguous Grok Stop payloads

* no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions

* no-mistakes(review): Enforce exact tmux cleanup selectors

* no-mistakes(test): Fix historical tmux fixture and validate Grok Stop

* no-mistakes: apply CI fixes

* 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).

* 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: 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

* feat(bin): mark crewmate and scout steers as from-firstmate

A steer lands in the receiving agent's own chat, where nothing else told
firstmate's instructions apart from a human typing into that pane. The gap
was proven in both directions on 2026-07-26: the captain opened a crewmate
pane believing it was firstmate and issued cross-lane instructions there, and
a Pi crewmate at an ask-user gate addressed "Captain, ..." into its own pane
and sat parked - nobody reads a crewmate pane, and a parked pipeline emits no
wake, so that direction fails silently. AGENTS.md section 1 rule 4 already
required workers to honor a distinction the system gave them no means to make.

fm-send now applies the existing from-firstmate carrier to every text steer
whose target resolves through this home's meta, not just kind=secondmate.
A crewmate or scout carries the marker alone; the corr= correlation token and
the parent pending-reply record stay secondmate-only, because a crewmate
already answers on its own status file. Explicit backend targets and the
--key path are unchanged.

Command-shaped text is the one exclusion. A harness recognizes a slash
command, or a codex $<skill> invocation, only at the very start of the
composer line, so any prefix silently demotes it to prose. Verified on claude
2.1.220 and pi 0.82.0: with either marker shape prepended, /no-mistakes stops
opening the completion popup entirely and would submit as ordinary text.
Crewmate sends of that shape therefore stay unmarked and byte-identical, which
also keeps every documented popup hazard out of this change's blast radius:
the only bytes that move are plain text no harness parses specially. The
exclusion deliberately does not reach a secondmate, whose marker is what
creates its reply guarantee.

The ship and scout scaffolds gain a "Who is speaking to you" section teaching
the reader side: marked is firstmate, unmarked is a human who may believe the
pane is firstmate, self-identify as a worker on this task before acting, and
escalation is always the status file. AGENTS.md states the provenance
principle once in rule 4; the away-mode stub and the secondmate charter keep
their own distinct consequences.

* no-mistakes(review): align brief's unmarked-message exception wording to fm-send predicate

* no-mistakes(test): fix stale corr-less assertion in Pi/Herdr marker e2e

* no-mistakes(document): generalize task-selector marker context to from-firstmate

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@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>
…spawn gate, and probe verification (#10)

* feat(bin): enforce the zero-budget model rule at spawn and config-edit time

The fleet's stated safety rule - "the budget for every API-key provider is
ZERO ... this is a safety rule, not a preference" - was implemented as prose
inside a JSON comment blob that no code read. It relied on the coordinator
recalling it correctly at every intake and every failover, forever.

That is load-bearing because one API key commonly reaches both free and
metered models on the same provider, rendered identically in every catalogue
listing (six columns, no cost column, no entitlement column). A single
mistyped or well-meant model name is a charge. A separate incident had
already shown the fleet will route from a plausible name without checking:
a model was configured from a catalogue listing, never probed, and every
dispatch to that tier failed at launch until an investigation found it.

Add config/models.json (local, gitignored) as the enforced copy, plus the
checks that read it:

- fm-spawn refuses a model whose API-key provider is not on the verified-free
  allowlist, whose provider cost posture is unclassified, whose registry
  status is rejected or blocked, or whose concurrency cap is already met.
  The check sits at the first point where harness and model are both final
  and the last point before any mutation, so a refusal creates nothing. It is
  also the only gate that sees an explicit --model that bypassed the dispatch
  config, which bootstrap validation structurally cannot see.
- bootstrap binds config/crew-dispatch.json to the registry, so a rule naming
  an unregistered, non-approved, or unprobed model fails at config-edit time.
- fm-model-verify runs the entitlement probe and the price-drift comparison,
  interval-gated by observation level so the steady-state cost is one file
  read. Probes close stdin and run under a timeout; pi -p can otherwise hang
  unbounded, and a wedged probe on the session-start path would present to
  supervision as a stale session.

Three axes are kept deliberately separate, because conflating any two of them
is itself a failure mode: cost (can this call be billed), routability (is the
account entitled to it), and availability (is it answering right now). A
rate-limited model is unavailable, not demoted, so a transient outage cannot
permanently degrade the routing table; availability lives in state/ and
routing status in config/, written by different code.

Enforcement is asymmetric about the registry's absence, by design. With no
config/models.json the spawn check is inert and behavior is byte-identical to
before, so nothing is forced on a home that never opted in; bootstrap then
reports the unenforced state rather than leaving it silent. With the file
present every unclear answer refuses - malformed JSON, an unsupported schema,
an unclassified provider, a missing jq - because a broken safety file must
never read as an absent one.

The allowlist stores each price numerically rather than only a cost class,
which is what makes a repricing detectable at all: a name-based allowlist is
structurally blind to one, since the thing that makes a name safe is a number
living in a catalogue the provider rewrites. Allowlist evidence must include
a genuinely price-bearing source; a probe is deliberately not enough, because
it proves the account gets an answer and says nothing about what that answer
costs.

The promotion system ships dormant behind a config flag and a named evidence
instrument, so activation is a configuration and data change rather than a
code change. Its authority is validated as a ceiling in each direction:
Tier 4 to Tier 3 may be automatic, Tier 3 to Tier 2 needs captain
confirmation, and Tier 1 and Tier 0 are never entered by accumulated
evidence - Tier 1 is triggered by risk, not capability rank, and a spotless
Tier 2 record demonstrates nothing about credential or destructive-operation
judgment.

config/models.json is inherited by secondmate homes alongside
config/crew-dispatch.json and must not be separated from it: inheriting the
rules without the registry would leave a secondmate's own crewmates outside
enforcement and make every inherited model read as unregistered there.

* no-mistakes(review): cost-gate probe paths, surface sweep stderr, fix test epoch

* no-mistakes(document): docs: add models.json to inheritance allowlist and jq toolchain
…puted context telemetry (#11)

* feat: add real context pressure telemetry

* no-mistakes(review): decouple statusline display from snapshot writes, truncate percentages

* no-mistakes(document): cover secondmate charter compaction trigger in fm-brief header

* no-mistakes(document): record dated Claude 2.1.219 statusLine payload verification evidence

* no-mistakes(review): require only trigger percentages, name missing optional telemetry fields
* feat: add fleet admission control stages 0 and 1

Adds the third layer above routing and scheduling: whether the fleet
should accept another task at all right now. It ships inert - a home with
no `_scheduling.admission_control` policy sees no behavior change and pays
one cheap config read.

The defining constraint is task independence. Admission reads only the
fleet snapshot, never the incoming task, so the same snapshot returns the
same band for every task; `bin/fm-admission.sh` enforces that structurally
by refusing a task argument. Anything that varies per task stays in
routing or scheduling.

- `bin/fm-admission-lib.sh` is the single owner of the executable schema
  check, shared by bootstrap's startup diagnostic and the evaluator so the
  two cannot drift on the same config bytes. Unknown fields are refused
  rather than ignored, so a typo cannot silently disable a safety
  condition, and every rule from the accepted design refuses with an
  actionable reason.
- `bin/fm-admission.sh` composes the existing read-only fleet snapshot
  into named signals, each with its own validity, and combines them into a
  preferred/soft/hard/unknown band. Every rule names the observed value,
  its source and freshness, the exact JSON config path, the configured
  value, and the resulting band. Exit status is the band, so a caller that
  ignores the output still stops safely.
- Backlog consistency is a signal separate from worker-census integrity. A
  backlog row that contradicts task metadata is a bookkeeping fault to
  repair, not evidence of physical saturation; one aggregate health bit
  would close the fleet for the wrong reason.
- The existing per-home session lock is the single-primary admission
  authority. No new process, daemon, reservation store, or second queue:
  deferred and refused requests stay in the owning backlog under a `load`
  hold, and capacity is re-examined at the two existing seams, successful
  cleanup and session start.
- Nothing numeric enforces. Only the deterministic safety conditions -
  authority, census integrity, snapshot freshness - can set a band, and
  the schema refuses a configuration that tries to enable a threshold
  whose predictive value is unmeasured. Active workers, load-hold depth,
  and worker breakdown are recorded as observations with no cap.
- Signals with no collector are recorded as unmeasured rather than assumed
  to be zero, and admission wait age stays explicitly uncollected because
  backlog age is task age.
- The decision record is the named extension seam for the wake-outcome
  ledger, which does not expose one yet; nothing is persisted and no
  competing evidence store is opened.

Dormant distributed machinery (reservations, remote nodes, a second intake
authority) is settled as a validated schema contract rather than running
code, so activating it later cannot change admission's semantics.

`tests/fm-gotmp.test.sh` gains the new teardown dependency in its fake
root, matching how its other sourced libs are already linked.

* no-mistakes(review): fail closed on unmeasurable snapshot age; tighten band and trigger validation

* no-mistakes(document): classify fleet-admission skill; fix bootstrap verbose-fact claim

* test(fm-backend): copy fm-admission-lib.sh into the synthetic old bin

test_teardown_conformance_old_vs_new pins BASE_REF=HEAD when building the
old-bin fixture, so its "old" fm-teardown.sh is HEAD's teardown, which now
sources fm-admission-lib.sh. The lib was missing from
OLD_BIN_UNCHANGED_SIBLINGS, so the old teardown aborted at source time.
Mirror teardown's real dependency set, exactly like its other sourced libs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145)

* feat: add verified pi-signed adapter

* no-mistakes(review): Correct pi-signed maintainer verification date

* no-mistakes(review): Correct remaining pi-signed verification dates

* no-mistakes(review): Preserve authoritative pi-signed runtime identity

* no-mistakes(document): Document pi-signed shared adapter semantics

* no-mistakes: apply CI fixes

* fix(pi): rearm watcher across session transitions (kunchenguid#1166)

* fix(pi): rearm watcher across same-process session transitions

Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement
as well as terminal quit. The primary watcher extension latched a module-level
stopping flag on every shutdown, so a replacement session in the same process
could not arm monitoring until Pi restarted.

Own arm authority per session generation so only the active live generation
may start, stop, or rearm the child. Replacement sessions can arm again without
restarting Pi, stale prior-generation callbacks cannot mutate the active cycle,
and real quit still blocks late rearm.

* no-mistakes(review): Preserve Pi generation isolation and exit cleanup

* no-mistakes(document): Correct Pi watcher transition documentation

* feat: route crew dispatch using quota-window pace (kunchenguid#1172)

* Consume quota-axi pace signals in dispatch profile array selection.

Add quota-array-dispatch as the single owner of the pace-aware candidate
choice, keep AGENTS.md to the intake boundary and load trigger, and cover
the acceptance cases with sanitized schemaVersion 3 fixtures.

* no-mistakes(review): Stop and report genuine quota dispatch ties

* no-mistakes(document): Document quota pace freshness and uncertainty

* fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171)

* fix(grok): adapt Stop continuation to runtime capability

* no-mistakes(review): Reject ambiguous Grok Stop payloads

* no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions

* no-mistakes(review): Enforce exact tmux cleanup selectors

* no-mistakes(test): Fix historical tmux fixture and validate Grok Stop

* no-mistakes: apply CI fixes

* 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).

* 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: 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

* feat(watch): wake firstmate when a monitored PR goes conflicting

An overtaken pull request sat conflicted until someone noticed the
maintainer bot's comment: PR 1284 waited an hour, and PR 1267 was
overtaken six times in a day. Nothing in the armed merge poll reported a
conflict, so the only signal was a long-cadence recheck or a human read.

The poll now reports conflicts on the same validated path as merges. Its
single GitHub request carries state, mergeability, and the head commit
instead of state alone, so a conflicting open pull request emits
"dirty <head>" at no extra forge call. State is decided first and alone,
so merged and closed pull requests keep exactly the result they had
before. GitLab is untouched: plain glab field output carries no conflict
field, and reading one would need the JSON processor firstmate
deliberately does not depend on.

The watcher dedupes by conflict episode, keyed on that head commit, and
its wake carries the pull request URL so the worker can be steered to
rebase without a lookup first. The head is the key because poll silence
is ambiguous - clean, unknown, closed, and every error look identical -
so silence can never clear it, while a changed head does mean the branch
moved and went conflicting again. An untouched conflict re-surfaces no
more often than FM_PR_DIRTY_RESURFACE_SECS. The marker retires with the
rest of the poll artifacts at teardown and on merge.

GitHub can briefly report mergeability as unknown while it recomputes
after a base push. That is silence here and resolves on the following
sweep, rather than adding a retry and a timing dependency to a static
program; GitHub recomputes on the base push, so a sweep arriving minutes
later normally reads a settled value.

Changing the poll's bytes retires every armed check, which the existing
content-based migration already handles: the next --checks-safe run
quarantines the stale copies and rebuilds each poll from the recorded
pr=, so homes see one PR_CHECK_MIGRATION line and lose no armed watch.

* no-mistakes(review): record PR conflict episode only after wake enqueued

* no-mistakes(document): correct stale gh field-selector claim in GitLab watch doc

* no-mistakes(document): rename merge poll to PR poll in armed-check docs

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@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>
* feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145)

* feat: add verified pi-signed adapter

* no-mistakes(review): Correct pi-signed maintainer verification date

* no-mistakes(review): Correct remaining pi-signed verification dates

* no-mistakes(review): Preserve authoritative pi-signed runtime identity

* no-mistakes(document): Document pi-signed shared adapter semantics

* no-mistakes: apply CI fixes

* fix(pi): rearm watcher across session transitions (kunchenguid#1166)

* fix(pi): rearm watcher across same-process session transitions

Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement
as well as terminal quit. The primary watcher extension latched a module-level
stopping flag on every shutdown, so a replacement session in the same process
could not arm monitoring until Pi restarted.

Own arm authority per session generation so only the active live generation
may start, stop, or rearm the child. Replacement sessions can arm again without
restarting Pi, stale prior-generation callbacks cannot mutate the active cycle,
and real quit still blocks late rearm.

* no-mistakes(review): Preserve Pi generation isolation and exit cleanup

* no-mistakes(document): Correct Pi watcher transition documentation

* feat: route crew dispatch using quota-window pace (kunchenguid#1172)

* Consume quota-axi pace signals in dispatch profile array selection.

Add quota-array-dispatch as the single owner of the pace-aware candidate
choice, keep AGENTS.md to the intake boundary and load trigger, and cover
the acceptance cases with sanitized schemaVersion 3 fixtures.

* no-mistakes(review): Stop and report genuine quota dispatch ties

* no-mistakes(document): Document quota pace freshness and uncertainty

* fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171)

* fix(grok): adapt Stop continuation to runtime capability

* no-mistakes(review): Reject ambiguous Grok Stop payloads

* no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions

* no-mistakes(review): Enforce exact tmux cleanup selectors

* no-mistakes(test): Fix historical tmux fixture and validate Grok Stop

* no-mistakes: apply CI fixes

* 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).

* 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(tests): reap background processes on every suite ending

tests/fm-watcher-lock.test.sh launches real watchers and arms in the
background but only killed them on its happy path, so a failing assertion
or a timeout(1) kill left them running. The worst case was
test_watch_restart_attaches_to_healthy_peer: when it aborted, its
bin/fm-watch-arm.sh --restart survived, and because an arm launches a
successor whenever its child cycle ends, killing just the watcher brought
another one back. Those orphans kept polling an already-deleted temp home,
inflated the shell count of whatever checkout ran the suite, and polluted
later liveness reads.

tests/lib.sh now owns a reaper: fm_test_reap registers a pid, and
fm_test_cleanup kills it together with the children it had at teardown
time. The tree is snapshotted before the first kill because a dead
parent's children are reparented beyond the reach of a ppid walk, and the
kill is SIGKILL so a catchable signal cannot hand back a fresh successor.
Cleanup is now installed for HUP/INT/TERM as well as EXIT, since a bare
EXIT trap does not run for a signalled shell - the reason a hung case
leaked. Every background launch in the suite registers its pid at launch.

* no-mistakes(review): gate reaper kills by identity and survives ps loss

* no-mistakes(document): document harness reaper coverage in watcher-lock test header

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@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>
…nshown herdr wedge alarms (#14)

* feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145)

* feat: add verified pi-signed adapter

* no-mistakes(review): Correct pi-signed maintainer verification date

* no-mistakes(review): Correct remaining pi-signed verification dates

* no-mistakes(review): Preserve authoritative pi-signed runtime identity

* no-mistakes(document): Document pi-signed shared adapter semantics

* no-mistakes: apply CI fixes

* fix(pi): rearm watcher across session transitions (kunchenguid#1166)

* fix(pi): rearm watcher across same-process session transitions

Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement
as well as terminal quit. The primary watcher extension latched a module-level
stopping flag on every shutdown, so a replacement session in the same process
could not arm monitoring until Pi restarted.

Own arm authority per session generation so only the active live generation
may start, stop, or rearm the child. Replacement sessions can arm again without
restarting Pi, stale prior-generation callbacks cannot mutate the active cycle,
and real quit still blocks late rearm.

* no-mistakes(review): Preserve Pi generation isolation and exit cleanup

* no-mistakes(document): Correct Pi watcher transition documentation

* feat: route crew dispatch using quota-window pace (kunchenguid#1172)

* Consume quota-axi pace signals in dispatch profile array selection.

Add quota-array-dispatch as the single owner of the pace-aware candidate
choice, keep AGENTS.md to the intake boundary and load trigger, and cover
the acceptance cases with sanitized schemaVersion 3 fixtures.

* no-mistakes(review): Stop and report genuine quota dispatch ties

* no-mistakes(document): Document quota pace freshness and uncertainty

* fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171)

* fix(grok): adapt Stop continuation to runtime capability

* no-mistakes(review): Reject ambiguous Grok Stop payloads

* no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions

* no-mistakes(review): Enforce exact tmux cleanup selectors

* no-mistakes(test): Fix historical tmux fixture and validate Grok Stop

* no-mistakes: apply CI fixes

* 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).

* 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(composer): read an NBSP-padded empty composer as empty, not pending

Away-mode escalations sat undelivered for ~9.5 hours in each of three
stretches. Root cause, with the reproduction now pinned as a regression test:

TRIGGER. Real Claude Code 2.1.220 pads its EMPTY composer row with U+00A0
NBSP, so the captured row is exactly `❯` + \xc2\xa0. bash's [[:space:]] does
not match U+00A0, so no trim in bin/fm-composer-lib.sh or in any adapter could
remove it; the leading-glyph strip left a lone NBSP behind, and the shared
classifier concluded "real, unsubmitted content remains" -> `pending` on a
genuinely idle pane. It is a stable property of the idle pane, not a race, so
it recurred on every poll indefinitely. The NBSP originates in claude's own
output (it sits inside claude's own colour run; non-claude panes never carry
it), so the defect is reader-independent: both herdr's ANSI reader and tmux
`capture-pane -e` surface it faithfully.

MASK. Only the consumers that require an AFFIRMATIVE `empty` could see it, and
both fail safe rather than loudly: away-mode escalation injection defers on
anything that is not `empty`, and verified submit reports a swallowed Enter.
Every other composer consumer treats `pending` as ordinary. So the wedge
produced deferral, not an error, and no test covered the shape - the \xc2\xa0
byte pair appeared NOWHERE under tests/, which is precisely why all three
9.5-hour delivery failures passed CI.

SYMPTOM. Buffered captain-relevant escalations (decision gates, blockers,
completions) delivered only by the away-mode return catch-up ~9.5h later, and
false "delivery unconfirmed" errors on steers into an idle pane.

FIX (sufficiency). bin/fm-composer-lib.sh's fm_composer_classify_content now
normalizes the non-ASCII blanks a TUI can use as padding before its trims:
U+00A0, U+2007 and U+202F map to an ASCII space, U+200B and U+FEFF are dropped.
That function is the ONE fleet-wide owner of the empty|pending|unknown verdict,
so this covers both ANSI readers and all four adapters (tmux, herdr, Orca,
cmux) at once and cannot drift back into per-adapter copies.

SAFETY PROPERTY. Every character normalized here RENDERS AS BLANK, and nothing
else is touched, so the change can only ever make an OTHERWISE-BLANK row read
as blank. It is impossible for real typed text to become `empty`: a row holding
any visible glyph keeps that glyph byte for byte, NBSP-joined text stays
`pending`, and a bare NBSP-padded dead-shell prompt stays non-empty (it becomes
the documented `unknown` instead of `pending`). Tests assert both directions.

Also hardens the away-mode wedge alarm's herdr channel, which failed in the
same incident. `herdr notification show` exits 0 even when it showed nothing:
with `[ui.toast] delivery` off (herdr's default) it answers
{"shown":false,"reason":"disabled"}. wedge_alarm_via_herdr read exit 0 as
success, so a configured-but-disabled channel produced a healthy log line and
reached nobody. It now parses the payload, treats an explicit "shown":false as
a channel failure and logs the reported reason; a build that reports no
outcome keeps its exit-status verdict.

Tests: the reproduction becomes the regression test. tests/fm-composer-lib.test.sh
and tests/fm-backend-herdr.test.sh gain fixtures carrying the LITERAL \xc2\xa0
byte pair captured from real Claude Code 2.1.220, each paired with a real-text
counterpart so the safety property is asserted, and tests/fm-daemon.test.sh
covers the unshown, shown, and payload-less herdr channel outcomes. All 171
existing composer and herdr tests stay green (175 with the new ones), and the
daemon suite goes 99 -> 101.

* no-mistakes(document): document herdr wedge-alarm delivery verification in channel reference

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@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>
…check (#12)

* feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145)

* feat: add verified pi-signed adapter

* no-mistakes(review): Correct pi-signed maintainer verification date

* no-mistakes(review): Correct remaining pi-signed verification dates

* no-mistakes(review): Preserve authoritative pi-signed runtime identity

* no-mistakes(document): Document pi-signed shared adapter semantics

* no-mistakes: apply CI fixes

* fix(pi): rearm watcher across session transitions (kunchenguid#1166)

* fix(pi): rearm watcher across same-process session transitions

Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement
as well as terminal quit. The primary watcher extension latched a module-level
stopping flag on every shutdown, so a replacement session in the same process
could not arm monitoring until Pi restarted.

Own arm authority per session generation so only the active live generation
may start, stop, or rearm the child. Replacement sessions can arm again without
restarting Pi, stale prior-generation callbacks cannot mutate the active cycle,
and real quit still blocks late rearm.

* no-mistakes(review): Preserve Pi generation isolation and exit cleanup

* no-mistakes(document): Correct Pi watcher transition documentation

* feat: route crew dispatch using quota-window pace (kunchenguid#1172)

* Consume quota-axi pace signals in dispatch profile array selection.

Add quota-array-dispatch as the single owner of the pace-aware candidate
choice, keep AGENTS.md to the intake boundary and load trigger, and cover
the acceptance cases with sanitized schemaVersion 3 fixtures.

* no-mistakes(review): Stop and report genuine quota dispatch ties

* no-mistakes(document): Document quota pace freshness and uncertainty

* fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171)

* fix(grok): adapt Stop continuation to runtime capability

* no-mistakes(review): Reject ambiguous Grok Stop payloads

* no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions

* no-mistakes(review): Enforce exact tmux cleanup selectors

* no-mistakes(test): Fix historical tmux fixture and validate Grok Stop

* no-mistakes: apply CI fixes

* 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).

* 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): allowlist the opencode turn-end plugin in teardown's dirty check

bin/fm-teardown.sh's dirty check allowlists the turn-end scaffolding
fm-spawn writes into a task worktree, but that hand-maintained list had
drifted: .opencode/plugins/fm-turn-end.js was missing. When the
info/exclude write does not take, an opencode worktree left an untracked
.opencode/ surviving the filter, so teardown refused an otherwise clean
tree as uncommitted changes.

git collapses a fully-untracked directory to a bare "?? .opencode/" line,
which can neither match an exact path nor prove the directory holds
nothing else, so the status call now expands untracked files. Expanding
only ever adds lines, so it cannot hide real dirty work. The .claude/
term stays an un-anchored directory prefix on purpose, so behavior for
untracked work under .claude/ is unchanged: it is still allowlisted, and
narrowing it to the exact settings.local.json path is deliberately out of
scope for this fix.

Add regression coverage pinning every worktree-resident artifact at once,
since this is a defense-in-depth layer nothing exercised, plus a negative
case proving the allowlist stays per-path rather than per-directory. The
test pins core.excludesFile so a machine-global ignore of .claude/ cannot
silently make the case vacuous.

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@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>
* Add worker conflict and verification rules

* no-mistakes(review): Scope rebase carve-out, document sections, test variant split

* no-mistakes(document): note delegated-rebase exception in AGENTS.md validation rule
* fix(bin): refuse spawns that would reclaim a still-owned pool slot

`treehouse status` reports a pool slot as available when it has no lease,
no live process, and a clean working tree.
It does not consider whether HEAD carries commits unreachable from the
default branch, so a slot holding a finished-but-unlanded branch is
allocatable and the next `treehouse get` detaches it.
Restarts make this reachable: they leave slot ownership recorded as either
nothing or a stale value, and both read as free.

The check has to run before allocation.
`treehouse get` resets the slot while acquiring it, before it returns a
path, and fm-spawn only learns the worktree by polling the pane's current
path afterwards, so a check placed there inspects a slot whose evidence
has already been erased.

Add bin/fm-worktree-guard.sh, called from bin/fm-spawn.sh before any
endpoint exists.
It reads treehouse's own available verdict from `status --json` rather than
reimplementing eligibility, then applies an offline emptiness test per slot
using --no-optional-locks so another lane's index is never written.
On evidence it refuses and names the slot, the evidence, the apparent
owning task, and what would release it.

Ownership resolves through fm_pid_identity, never a bare pid: fm-spawn now
records worktree_owner_pid and worktree_owner_identity into the existing
state/<id>.meta.
Only an identity match reads alive, only a recorded identity that no longer
matches reads dead, and an absent record reads unresolved rather than as a
released slot, so a post-reboot reused pid cannot read as a live owner.

The guard never resets, cleans, forces, discards, or releases anything.
bin/fm-teardown.sh remains the sole releaser of a slot holding work and the
owner of the complete landed-work test.

Uncommitted content is separately protected by treehouse itself, which
reports such a slot dirty and refuses to reclaim one even when every slot
in an exhausted pool is dirty, so the exposure closed here is specifically
a clean working tree plus commits not on the default branch.

Suites that spawn a crewmate stubbed treehouse as a silent exit 0, which
the guard correctly refuses because the real binary prints [] for an empty
pool and never nothing. Add fm_fake_treehouse to tests/lib.sh so that one
faithful stub is shared rather than re-rolled per suite.

Empirical basis: docs/verification/worktree-allocation.md.

* test: model treehouse status in the remaining spawn fakes

fm-secondmate-harness builds its fakebin without any treehouse stub and
runs with a BASE_PATH that excludes the real binary. That was invisible
while fm-spawn only typed `treehouse get` into the pane; the guard runs
`treehouse status` from fm-spawn's own environment before allocating.

The Herdr presentation comparison normalizes per-run container ids; the
worktree owner pid and identity vary per run for the same reason, so they
belong in that normalization.

* no-mistakes(review): Align worktree guard with allocation safety contract

* no-mistakes(review): Validate treehouse JSON schema before allocation

* no-mistakes(document): Document safe Treehouse allocation

* fix(bin): inspect Treehouse pools on builds without status --json

CI pins Treehouse v2.0.1 via bin/fm-install-treehouse.sh, and that build has
no `status --json` flag at all: passing it exits 1 with "unknown flag".
The guard required that flag, so every Treehouse-backed spawn refused on the
pinned build and four real-herdr-gated suites failed.

The same defect exists on v2.0.1 - it also reports a clean slot holding an
unlanded branch as available - so the guard must still inspect the pool there
rather than refuse every spawn.

Probe capability from `status --help` advertising --json, not from the error
text or a version string, and fall back to the human-readable table. That
table needs two compensations: a path under $HOME is abbreviated with a
leading tilde, and a slot may be followed by indented process continuation
lines. Every non-blank, non-indented line must parse or the guard refuses,
so a dropped row cannot leave a slot uninspected.

jq is now required only on the --json path.

* test: model the treehouse status capability probe in spawn fakes

The guard probes `status --help` for --json before choosing a format, so a
stub that answers every `status` invocation with [] makes the probe see no
--json, fall back to the human-readable table, and then fail to parse [] as a
table row.

Model all three shapes the guard uses: help advertising --json, --json
returning [] for an empty pool, and plain status printing nothing, which is
what an empty pool looks like in that format.

* docs: record that local Treehouse verification does not prove CI green

bin/fm-lint.sh pins shellcheck and refuses to run under any other version, so
local and CI cannot diverge on lint. Treehouse has no equivalent: CI installs
the version pinned in bin/fm-install-treehouse.sh while a developer machine
runs whatever is on PATH, and nothing reconciles them.

This is a fleet-wide property of how suites are verified, not a detail of any
one change, and it has already cost one red CI run.
* feat: add Windows WSL launcher bridge

* no-mistakes(review): pin bat CRLF via gitattributes, canonicalize test temp root

* no-mistakes(review): cover gitattributes CRLF pin in bridge suite

* no-mistakes(document): Clarify Windows launcher dependency and argument guidance
…onflicts resolved) (#32)

* 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(bin): add durable process-event supervision (kunchenguid#1483)

* feat(procevent): supervise long-polling sources into durable events

Firstmate had no way to wait on a blocking external process without holding
a conversational turn. Add a domain-neutral process-to-event runner plus a
thin adapter around the currently published `lavish-axi poll` interface:
canonical physical source identity, one machine-wide owner per source, direct
argv execution, and durable 0600 result capture before any event referencing
it is published on the existing wake queue. No second notifier, no polling
control plane, and no retry machinery.

A captured result with no durable handled acknowledgement stays eligible for
bounded re-announcement across any number of drains and restarts. Draining a
wake before acting on it and then starting a replacement session resurfaces
the same exact source and sequence, and never puts result payload text in an
event line. `fm-procevent.sh handled <source-id> <sequence>` is the only thing
that stops re-announcement: generation-keyed, private, path-safe, durable, and
atomically idempotent, so a paired external effect gated on its first-time
versus repeat report is never authorized twice.

An acknowledgement is refused unless matching captured result and adapter
records already exist, so a premature or mistyped call cannot suppress a
future result.

The source side is unchanged and still lossy: the published poll clears
feedback destructively before returning it, so a result lost in that window
is unrecoverable. This is never at-least-once, no-loss, or lossless, and the
handled acknowledgement is not a generic exactly-once effect either - a crash
between an external effect and its acknowledgement can still repeat that
effect on replay.

Integrate registered sources with watcher supervision, the guards, and
recoverable secondmate teardown across nested homes, and cover source
identity, lifecycle races, supervision, restart handling, and cleanup safety
with regressions.

* no-mistakes(review): Prevent Lavish prompt text from spoofing missing sessions

* no-mistakes(review): Serialize publication and secure handled acknowledgements

* no-mistakes(document): Document hardened process-event acknowledgement guarantees

* fix(procevent): never reclaim a source whose owned group still runs

A runner is its own process group leader and starts the blocking source in
that group, but the claim records only the leader PID and its identity. If the
leader died while the source child kept running, the missing PID was
classified stale: reconciliation released the claim and started a second
runner while the old blocking source was still consuming the same canonical
source. For the Lavish adapter that means two destructive long polls racing on
one review session, so it is not harmless process litter. It also contradicted
the documented promise that ownership is never released until the whole group
is gone.

Ownership state now distinguishes a generation that is really gone from one
whose leader crashed with its group still alive. Reconcile stops that
surviving group and releases its exact generation before starting any
replacement, and keeps the claim for a later cycle when it cannot prove the
group stopped or another home owns it. Acquisition and `start` treat the same
state as held rather than reclaimable.

Signalling that group is safe precisely because only an absent leader reaches
this state. A reused PID leaves the leader alive, so the identity comparison
still classifies it stale or uncertain and no group signal follows, which
keeps the existing PID-reuse refusal intact.

Add a public-interface regression for the exact crash cut - SIGKILL only the
leader, prove the child group survives, reconcile, and prove the old group is
gone with no second source running - plus its counterexample that a generation
with no leader and no surviving group is still reclaimed. Update the runner
help, operating documentation, skill, and verification record where they
described reclaim in terms of the leader alone.

* no-mistakes(review): Enforce runner group ownership and detect poller overlap

* no-mistakes(review): Isolate runner groups from unrelated caller processes

* no-mistakes(document): Document isolated process-event runner launch

* no-mistakes(lint): Suppress Perl literal ShellCheck false positive

* fix(bin): retire terminal process events and surface queued wakes (kunchenguid#1500)

* fix(bin): deliver process-event results and retire ended sources

Two defects reproduced during a real Lavish adapter session.

One human `Send & End` produced four captured results: the real feedback,
then recurring empty ended sessions. The generic runner had no way to learn
a source was finished, so every reconcile restarted a poll that returned
immediately. The runner now asks the source's own adapter -
`fm-procevent-<adapter>.sh terminal <result-file>` - and on exit 0 alone
re-proves ownership, drops the registration, and releases its own claim
under one source boundary. Terminal knowledge stays adapter-owned: for
Lavish that is an ended session, a missing session, and the final feedback
delivery the published poll marks with `session_ended`. An adapter with no
terminal command keeps its source armed exactly as before. Capture before
publication, captured-result durability, queued wake durability, bounded
re-announcement, handled deduplication, one-owner ownership, and explicit
idempotent retirement are all unchanged.

A captured result queued its `check` wake durably, but a healthy watcher
with a fresh beacon never delivered it; the result surfaced only after a
manual drain. Publication happens outside the watcher (in the runner) or
unconditionally (in reconcile), so the watcher had no newly actionable
signal to report and never reached its rewake path. It now reports a
queued-but-unsurfaced process-event record through the same actionable exit
every other wake uses, deduplicated by the same `.seen-*` marker discipline
the signal scan uses, so the record is always durable before it is
suppressed. The durable queue remains the authority and no second notifier,
poller, timer, queue, or adapter-specific wake path is added.

Regressions cover both, driven end to end: an armed Lavish source against a
stand-in for the published poll polls once, captures once, publishes one
distinct event, and retires itself; two fixture adapters prove the terminal
decision follows the adapter alone; and a real capture plus a real watcher
prove one proactive wake before any drain, with no duplicate wake while the
record stays queued or after it is acknowledged.

* no-mistakes(review): Harden process-event retirement and proactive delivery

* no-mistakes(review): Route process-event delivery through shared wake owner

* no-mistakes(document): Clarify process-event delivery and retirement documentation

* no-mistakes(lint): Fix ShellCheck control-flow warnings

* no-mistakes(lint): Fix wake output status lint warning

* perf: shard portable serial tests across CI runners (kunchenguid#1544)

* perf(ci): shard the portable serial behavior lane across runners

The Behavior portable serial job ran all 69 scripts of the serial
remainder on one runner. The measured serial sum on run 30725985757 was
1143762 ms (19m04s) against a 20-minute timeout, so the job intermittently
reached the cap and was cancelled with every step passing. Setup is only
about 7s, so the cost is entirely test wall time.

Split the lane into four separate-runner shards. Each shard is still
strictly serial, and separate runners mean no two of these stateful
scripts ever share a machine, so the split needs no concurrency isolation
proof. Assignment is longest-processing-time bin packing over measured
per-script duration hints, balancing every shard to 285941 ms (~4m46s) of
expected work, and the timeout tightens from 20 to 15 minutes.

bin/fm-test-run.sh owns the shard count and refuses a lane whose "ofN"
disagrees with it, while ci.yml derives the same count from
strategy.job-total rather than a literal, so changing it in either file
alone fails the lane loudly instead of leaving part of the required suite
unrun. --check-coverage additionally proves the shards are non-empty,
disjoint, and exactly equal to the serial lane. No test is weakened,
skipped, or removed.

Also replace the wall-clock sleeps in the --jobs scheduler test fixture
with an explicit signal handshake between the fixtures. The old
0.5s-versus-0.05s race failed on a loaded machine; the handshake passes
under sustained CPU saturation.

* no-mistakes(review): Correct portable serial shard balance evidence

* no-mistakes(document): Document portable serial shard evidence accurately

* fix(bin): correct session lock and attached watcher supervision (kunchenguid#1545)

* fix(bin): identify harness sessions by path and report delivered wakes

Two supervision faults, both reported by a contributor and both open on the
default branch.

Fault 1: the Stop auto-arm never claims the home. fm_harness_ancestry_pid()
matched only the basename of `ps -o comm=`, and Claude Code's native installer
names the per-session executable by its version (.../share/claude/versions/
2.1.220), so that basename identifies nothing. Three real failure shapes follow:
a version-named session is missed entirely and the hook exits 0 with the epoch
never written (unconditional on Linux, where procps reports the kernel exec name
and ignores argv[0]); a claude-named daemon that directly parents sessions wins
the outermost-contiguous-claude rule ahead of the session itself; and a session
that is both version-named and daemon-parented has its live lock reclaimed as
stale and rewritten to the shared daemon pid, corrupting the home's ownership
record.

Harness identity now also reads whole components of the executable path and of
argv[0], which is what both platforms still carry. Matching whole components
only keeps that widening safe: bin/fm-claude-stop-autoarm.sh and ~/.claude/hooks
scripts have no "claude" component. Ownership is then decided against the
session's whole contiguous harness ancestry rather than one chosen pid, which is
the honest form of the question the library already documents ("does the current
process descend from that same harness?"). That subsumes the outermost-pid rule
for Claude's nested bg-spare worker chain instead of reverting it, and lets a
daemon-parented session recognize its own lock. Lock acquisition still writes the
outermost pid of the run, the only pid that lives as long as the session.

Fault 2: an attached arm reports a delivered cycle as FAILED. The watcher prints
its one reason line to its own stdout, so only the arm that forked it can read
that line; an arm that attached observes nothing but a released lock and called a
completely successful cycle "cycle ended without an actionable reason". No
supervision event was lost - the durable queue held it - but every harness
protocol reads that line as "supervision is down" and directs a manual re-arm.

The arm now resolves an unobservable close against the durable wake queue, which
records every wake before the watcher prints it and whose sequence counter never
rewinds, not even across a drain. A cycle the queue proves delivered a wake
reports that wake and exits 0; a cycle whose records a handling turn already
drained reports the delivery without inventing a reason line; only a cycle that
delivered nothing is still the typed nonzero failure. Fixing it in the arm covers
codex, opencode, pi, grok and kimi, not just the Claude Stop path.

Regressions: tests/fm-session-lock-ancestry.test.sh pins both platforms' ps
semantics behind a deterministic process table and runs the real Stop auto-arm in
version-named, daemon-parented, and combined real process trees, each orphaned so
the walk cannot escape the fixture. tests/fm-watch-arm.test.sh drives a real
watcher and a real attached arm through a real wake. Every fault case fails on
the previous code.

* no-mistakes(review): Bind watcher delivery records to process identity

* no-mistakes(review): Return validated watcher identity atomically

* no-mistakes(review): Track watcher successors by PID and identity

* no-mistakes(document): Consolidate watcher arm-cycle documentation ownership

* fix(bin): harden Claude supervision auto-arm recovery (kunchenguid#1495)

* fix(supervision): harden Claude auto-arm failure handling

* no-mistakes(review): Guarantee automatic retry after Claude auto-arm failures

* no-mistakes(review): Gate attended fail-open on verified supervision failure

* no-mistakes(document): Document Claude auto-arm retry and guard scope

* no-mistakes: apply CI fixes

* fix(supervision): make Claude fail-open progression monotonic

* no-mistakes(review): Preserve auto-arm failure episodes until verified watcher recovery

* no-mistakes(review): Linearize auto-arm failure progression across existing locks

* no-mistakes(review): Linearize positive recovery across shared failure episode lock

* no-mistakes(review): Scope Claude recovery contention to Claude guard mode

* no-mistakes(document): Align supervision auto-arm documentation

* no-mistakes(review): Preserve actionable wakes despite healthy successors

* no-mistakes(document): Refresh supervision auto-arm documentation

---------

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

* fix(bin): keep a released task's PR landable through the sanctioned path

The captain's parked-completion ruling releases a ship worker once its pull
request is done, green, and mergeable - that is, before it lands. Cleanup then
removes state/<id>.meta, and both bin/fm-pr-merge.sh and bin/fm-pr-check.sh
refused with "task metadata is unavailable" without it. Following the ruling
therefore stranded the pull request from the only sanctioned merge command, and
AGENTS.md section 7 forbids reaching around that guard with a lower-level one.

Landing identity now comes from the forge rather than from the caller:

- bin/fm-teardown.sh leaves a minimal private state/<id>.landing record (pr,
  the forge's pr_head, project) when it releases a ship task whose request has
  not landed. The forge decides: the record is written unless the forge
  positively reports the request merged, because an unreachable forge is not
  evidence that anything landed. A failed write warns and never blocks cleanup,
  and the pending landing is reported so the released request is not forgotten.
- bin/fm-pr-lib.sh resolves "the task's PR identity record" in one place, as the
  meta while a task is live and the landing record once it is released. The
  merge poll's validity and retirement checks go through that resolver, so a
  released task's merge watch can be rearmed and still retires normally.
- bin/fm-pr-check.sh and bin/fm-pr-merge.sh accept a released task through that
  record, and rebuild one from a forge read of the request itself for a task
  released before landing records existed. Nothing is asserted by the caller.
- A merge through a landing record re-reads the request at its forge and refuses
  unless it is still open, so no stale local value decides anything. It arms no
  poll, because a released task has nothing left to watch and the merge is
  synchronous, and the spent record is removed once the request lands.

The metadata refusal is preserved exactly where it still means something: a task
with no meta, no landing record, and no request the forge can resolve is refused
before gh-axi is called, as before.

* no-mistakes(review): enforce landing identity before reconstruction

* no-mistakes(document): Document released-task landing identity
…s detection (#20)

* feat(bin): add verified pi-signed runtime adapter (kunchenguid#1145)

* feat: add verified pi-signed adapter

* no-mistakes(review): Correct pi-signed maintainer verification date

* no-mistakes(review): Correct remaining pi-signed verification dates

* no-mistakes(review): Preserve authoritative pi-signed runtime identity

* no-mistakes(document): Document pi-signed shared adapter semantics

* no-mistakes: apply CI fixes

* fix(pi): rearm watcher across session transitions (kunchenguid#1166)

* fix(pi): rearm watcher across same-process session transitions

Pi emits session_shutdown for ordinary /new, /resume, and /fork replacement
as well as terminal quit. The primary watcher extension latched a module-level
stopping flag on every shutdown, so a replacement session in the same process
could not arm monitoring until Pi restarted.

Own arm authority per session generation so only the active live generation
may start, stop, or rearm the child. Replacement sessions can arm again without
restarting Pi, stale prior-generation callbacks cannot mutate the active cycle,
and real quit still blocks late rearm.

* no-mistakes(review): Preserve Pi generation isolation and exit cleanup

* no-mistakes(document): Correct Pi watcher transition documentation

* feat: route crew dispatch using quota-window pace (kunchenguid#1172)

* Consume quota-axi pace signals in dispatch profile array selection.

Add quota-array-dispatch as the single owner of the pace-aware candidate
choice, keep AGENTS.md to the intake boundary and load trigger, and cover
the acceptance cases with sanitized schemaVersion 3 fixtures.

* no-mistakes(review): Stop and report genuine quota dispatch ties

* no-mistakes(document): Document quota pace freshness and uncertainty

* fix: adapt Grok Stop continuation and harden endpoint cleanup (kunchenguid#1171)

* fix(grok): adapt Stop continuation to runtime capability

* no-mistakes(review): Reject ambiguous Grok Stop payloads

* no-mistakes(review): Reject duplicate Grok fields and accept spaced tmux sessions

* no-mistakes(review): Enforce exact tmux cleanup selectors

* no-mistakes(test): Fix historical tmux fixture and validate Grok Stop

* no-mistakes: apply CI fixes

* 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).

* 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: 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

* fix(harness): recognize every verified harness process name consistently

Three places classify a process as a harness, and they disagreed. Measured
against current main by running each rule over the same basenames:

  comm            fm-harness   session-lock  tmux-alive
  kimi-nightly    unknown      yes           alive
  pi-launcher     unknown      no            alive
  Pi              unknown      no            alive

Two consequences, both real and both fail-safe rather than dangerous:

kimi was matched EXACTLY in bin/fm-harness.sh while its four markerless
siblings (claude, codex, opencode, grok) are substring globs, and while
bin/fm-session-lock-lib.sh and bin/backends/tmux.sh both matched it as a
substring. A kimi under a variant basename was therefore recognized by the
other two consumers but self-detected as `unknown`.

pi-launcher and Pi - the launcher wrapper and the npm shim basename, both
already known to bin/backends/tmux.sh's pane classifier - were absent from
self-detection and from the session-lock ancestry regex. A firstmate running
under either reported `unknown`, and could not prove it owned its home's
session lock, so bin/fm-claude-stop-autoarm.sh's ownership check would decline
to arm. fm_session_lock_owned_by_self fails closed, so this was a false
negative, never a false ownership claim.

Align the two outliers: kimi becomes a substring match like its siblings, and
the pi family gains the two missing basenames. The pi family stays ANCHORED on
purpose - a bare `pi` substring would also match pip, pipenv, and any path
component containing "pi" - so each launcher basename gets its own exact arm
rather than relaxing the anchor.

Deliberately NOT done: the originally-planned shared process-identity table.
It would have replaced ~15 lines of clear in-place literals with 80-100 lines
of table, dispatcher, three per-consumer generators, and two exceptions to
encode exactly the divergences fixed above - failing the design's own stop
condition, with its justifying defect (pi missing from the tmux alive list)
already fixed on main by kunchenguid#1145. The inconsistencies were the finding worth
shipping; the machinery was not.

Coverage extends tests/fm-kimi-harness.test.sh rather than adding a runner, and
asserts behavior by executing bin/fm-harness.sh and bin/fm-lock.sh against fake
ancestry. Each case was verified to FAIL before the fix: reverting kimi to an
exact arm, dropping the pi-family arms, and reverting the session-lock regex
each reproduce the original miss. pip, pipenv, and piper are asserted to stay
unrecognized so the anti-substring guard cannot be relaxed unnoticed.

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
Co-authored-by: Christopher McKay <101884182+karotkriss@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>
#1)

* fix(merge-local): refuse only on genuine working-tree collisions

bin/fm-merge-local.sh refused whenever `git status --porcelain` was
non-empty. That is stricter than git itself, which already refuses to
overwrite modified files and refuses to fast-forward over an untracked
file at an incoming path, and it is self-deadlocking: it blocks the very
commit that settles the uncommitted entries (ignore rules plus one index
removal), because the cure sits behind the symptom.

Refuse only where the fast-forward could actually destroy local work:
the intersection of paths uncommitted in the project checkout and paths
this exact fast-forward rewrites. The incoming set comes from
DEFAULT..BRANCH after the fast-forward check settles, so it is the real
change set rather than a diverged two-way diff.

Kept safe in the ambiguous directions:
- A fast-forward that removes a tracked path deletes it from the working
  tree, so local modifications there still collide.
- An untracked file at a path the incoming commit adds still collides.
- Both endpoints of a staged rename count as uncommitted, and the
  rename's second NUL field is consumed so later records are not
  misread as status codes.
- Unresolved conflicts, unrecognized status codes, truncated records,
  and failed git commands refuse instead of degrading into a silently
  empty change set.

Untracked files, ignored files, and changes at paths the fast-forward
never touches no longer block it. A refusal now names every colliding
path and what the merge does to it, which the blanket message could not.

Both sides are read NUL-delimited: -z is the only status and diff format
that emits paths verbatim rather than quoting names with spaces or
non-ASCII bytes. --untracked-files=all keeps untracked entries as
individual files, since a collapsed parent directory would hide a
collision at a nested path.

tests/fm-merge-local.test.sh covers both directions, including the
observed deadlock as a regression: two untracked operator documents plus
one modified tracked pointer the incoming commit removes from the index
must refuse naming only the pointer, then land once that single path is
settled.

* test(merge-local): pin the nested untracked-directory collision

git status collapses a wholly untracked directory to a single "reports/"
entry, which hides a collision at the nested path the fast-forward actually
adds. The guard passes --untracked-files=all for exactly that reason, so
cover it: an untracked "reports/Quarterly Review.pdf" inside an untracked
"reports/" must still be named as a collision and must survive the refusal.

Dropping --untracked-files=all from the guard fails this test and nothing
else, so the flag is now pinned by behavior rather than by comment.

* no-mistakes(review): name path-shape collisions and refuse in-progress git operations

* no-mistakes(review): reach in-progress sentinels on detached HEAD, split git am from rebase

* no-mistakes(review): accept intent-to-add, disambiguate diff revs, catch pending sequencer

* no-mistakes(document): correct ignored-path rationale in fm-merge-local header

* no-mistakes(review): name in-progress states before divergence; catch collapsed untracked dirs

---------

Co-authored-by: Firstmate Crew <crew@example.invalid>
#35)

* feat(bin): wake-outcome ledger for measured coordinator attention cost

Supervision attention cost was estimated, never measured, and the dormant
model-promotion system is gated on exactly this instrument plus a terminal
per-task outcome line.

Adds data/wake-ledger.tsv, a per-home append-only evidence record, and
bin/fm-wake-ledger.sh as its single owner of format, closed outcome
vocabulary, and append semantics. Three record kinds: one wake record per
drained wake, one outcome record per handled wake joined on the wake-queue
sequence, and one terminal task record per finished task.

The wake half is written deterministically by the drain and the outcome half
by the coordinator. That split is deliberate: a single coordinator-written
line would make measured attention cost fall whenever the recording step was
skipped, so the metric would move without the underlying quantity moving.
Splitting it gives a denominator that is stable under no change and turns
missing coverage into a reported number instead of a silent undercount.

The ledger cannot block, delay, alter, or fail a wake. Its call site sits
below the drain's authoritative print-and-delete boundary, with stdout
discarded and failure ignored; the ledger takes no lock and writes one
capped line per record. Teardown writes the terminal record immediately
before deleting the task metadata, the last moment that task's harness,
model, and effort are recoverable anywhere.

It lives under data/ rather than state/ because teardown clears
state/<id>.* and this evidence must outlive the tasks it describes.
Watcher-absorbed wakes stay out by design: they never reach the coordinator,
so they are not part of the quantity being measured.

* no-mistakes(review): join wake outcomes on (seq, queued) across state resets

* no-mistakes(document): note wake-ledger recording step in fm-wake-drain header
…unchenguid#1613) (#43)

* feat(bin): carry a task's read base and contribution base separately

A dispatched worker needs two different commits and the spawn path supplied
only one, so every brief citing a file or a line number was silently
unreliable whenever they differed. Measured across four concurrently live
workers: every one sat at upstream 4ee4a0a while the fleet ran 631c248, a
33-commit gap. One task found a script its brief cited entirely absent from
its own base; another recorded that upstream line numbers are simply wrong
for the running fleet and nearly published them.

Neither single reference fixes it. Resetting slots to the fork trunk carries
fleet-only commits into every upstream contribution. Resetting them to
upstream dispatches tasks onto a trunk missing 29 tracked files, including 10
bin/ scripts the fleet runs. So the spawn path now resolves and keeps both:

  slot base           the commit the worktree is placed at, so reads, greps,
                      line citations and running the code resolve against
                      what the fleet actually runs
  contribution target the commit the branch is cut from, so the PR carries no
                      commit that target never had

bin/fm-task-base-lib.sh owns the contract and derives both from the project
checkout. An upstream trunk that exists but cannot be read locally is
recorded as unresolved rather than guessed onto the slot base, because that
guess is the pollution being prevented. Explicit flags override the
derivation, which cannot see a task's target files.

fm-spawn.sh places the slot at the slot base instead of leaving it at
whatever commit the pool last used, records slot_base=, contribution_target=
and base_state= in task metadata, and refuses a brief that names a different
pair. A slot holding uncommitted work or sitting on a task branch is left
untouched. fm-brief.sh states both plainly with the read-here/write-there
rule and cuts the branch from the contribution target by name. A scout takes
only a read base. Where the two coincide the brief is byte-identical.

Verified with every negative control watched red first: the guard neutered to
always pass, the unresolved case degraded onto the slot base, the slot reset
removed, the metadata recording removed, and the brief's branch step reverted
each fail the test that covers them. Against the live repo the guard reports
the real branch clean and refuses the fork trunk with the exact 33 commits.

* no-mistakes(review): Captain: enforce mode-aware spawn base contracts

* no-mistakes(document): Document separate task base references
…eam kunchenguid#1676) (#44)

* fix(bin): see a crewmate's work when it happens in a child process

Supervision's absorb rule consulted only semantic sources: the no-mistakes
run step, the status log, and the harness busy signal. All three correctly
report "not working" the moment an agent backgrounds a long command and
ends its turn, so a crew doing real work in a child process reads as a
wedge. Measured 2026-08-03: one crew running the portable suite in the
background produced seven consecutive false wedge escalations across 42
minutes, each demanding a deep inspection, and at least three other crews
hit the same pattern while pipeline stages ran underneath them.

Add descendant CPU advancement as a third source of positive evidence,
alongside the existing ones rather than in place of them. The agent is
resolved from kernel facts, never a vendor process name: its working
directory is the task's recorded worktree, and it is the leader of the
foreground process group on its pane's terminal. The CPU of everything
below it - live descendants plus already-reaped ones through the leader's
cutime/cstime - is compared against a sample from the previous poll.

Two rules keep it from becoming a blindfold. Each sample is bound to
fm_pid_identity of the agent it came from, so a reused pid is never
compared across the identity change. And only ADVANCEMENT counts: a
descendant that merely exists proves nothing, so a crew whose child is
hung, dead, or absent still escalates on the unchanged schedule. The
agent's own utime/stime is excluded, so a looping wedged agent cannot
vouch for itself.

The stale threshold, escalation ladder, and deep-inspection demand are
untouched. The new evidence folds into the same work_now gate a busy pane
uses, so it inherits the BUSY_TURN_MAX_SECS completed-turn bound: a child
that churns forever can delay an escalation, never cancel one. A definite
parked, done, failed, or blocked run-step verdict is never overridden.

Where /proc is unavailable the probe reports no evidence, which is exactly
today's behaviour.

Verified in all four directions against real processes, and each guarantee
was witnessed failing first under a deliberate breakage: existence instead
of advancement, the agent's own CPU counted, identity ignored, the
freshness bound removed, a definite verdict overridden, and the
completed-turn bound not applied to the new evidence.

* no-mistakes(review): Captain, preserve semantic verdicts over child liveness

* no-mistakes(review): Captain, prevent double-probing child CPU evidence

* no-mistakes(document): Document descendant CPU supervision
#45)

* fix(bin): resolve the wake sequence instead of trusting a supplied one

`fm-wake-ledger.sh outcome` took the wake sequence as a positional argument
and validated only that it was an integer. A coordinator recording outcomes
after the fact no longer had the real sequences and supplied descending
placeholders; every one was accepted and stored with queued=unknown, which is
also what a legitimately wiped state/ produces, so nothing looked wrong and
nothing reconciled outcome records against wake records. Measured 2026-08-04:
200 of 249 outcome records were fabricated this way.

The coordinator now names what a wake cost, never which number it was:

- `outcome <token>` with no sequence resolves the most recent wake record that
  no outcome record joins, and is the normal path. A second bare call moves on
  rather than re-recording the same wake, and no unrecorded wake left is a
  loud refusal rather than a guess.
- An explicitly passed sequence that joins no wake record is refused before
  anything is written, confirmed against the whole file so the refusal states
  a fact rather than a lookup horizon. `--allow-unjoined` keeps the genuine
  wiped-state/ case reachable and a guess unreachable.
- `reconcile` counts the outcome records that join no wake record, and
  session-start bootstrap reports it, so the corruption cannot stay silent. An
  absent ledger counts a real zero; one that exists and cannot be read refuses
  rather than reporting that same zero.

The (seq, queued) durable join identity, the closed outcome vocabulary, and
multiple sequences per invocation are unchanged, and no existing record is
rewritten or migrated.

* fix(bin): stop the wake library creating state when it is only read

fm-wake-lib.sh ran `mkdir -p "$STATE"` at source time, so merely sourcing it
made the caller a writer. The new `reconcile` bootstrap diagnostic sources it
to count unjoined outcome records, which turned a read into a mutation and
broke bootstrap's detect-only read-only contract; the guard that asserts that
contract went red ("detect-only bootstrap created its state directory").

Fixed at the source rather than by teaching bootstrap to avoid it, because any
future read-only caller inherits the same trap. Sourcing now observes a home
and never creates one; the write paths materialize the directory on demand:

- fm_state_ensure() is the named, idempotent creation point, called by
  fm_wake_append for the sequence file that always lives in $STATE.
- fm_lock_owner_dir creates the directory that will hold a lock it is about to
  take. This is load-bearing, not defensive: fm_lock_abs_path fails while the
  parent is missing and fm_lock_acquire_wait retries forever, so without it a
  lock under a missing state directory hangs instead of failing. Verified by
  removing the guard and watching the wait spin until killed.

Every other caller that writes into $STATE either creates it itself or takes a
lock first, so the removed source-time mkdir was duplication, not the contract.

The diagnostic itself is unchanged and still distinguishes its three cases: an
absent ledger counts a real zero and stays silent, a populated ledger reports
the true unjoined count, and a ledger that exists but cannot be read reports
that rather than the same zero. Reads leave the ledger byte-identical; it stays
append-only and nothing is rewritten, migrated, or truncated.
fm-fleet-snapshot.sh handed the whole backlog and task inventory to jq as
--argjson command-line arguments. Linux caps a single argv entry at
MAX_ARG_STRLEN (131072 bytes) independent of the much larger ARG_MAX total,
so the snapshot died with "Argument list too long" once the backlog crossed
that cap - a size the fleet had already reached.

Bulk JSON now travels on stdin: json_envelope assembles the value with shell
builtins and pipes it into `jq -n`, whose filter reads it back with `input`.
stdin was chosen over a temp file or a process substitution because this
command is read-only and must stay that way - there is no file to create,
secure, or leave behind when an interrupted read abandons it. Every unbounded
--argjson in the file is converted, not only the one that was failing.

The more serious half was a fail-open. task_json_lines built one row per task
in a loop piped into `jq -s`, so a row that could not be serialized was
dropped while `jq -s` slurped the survivors and exited 0 - a task vanished
from a snapshot that still reported success, and a supervisor reviewing the
fleet would see a task that does not exist. scout_report_lines had the same
shape and its caller checked no status at all, and fm-fleet-view.sh rendered
a snapshot that produced no output as a healthy empty fleet at exit 0. A
genuine failure now exits nonzero and names what failed, while an empty fleet
remains a complete snapshot at exit 0.

Tests generate the oversized backlog and the oversized task row
programmatically, assert both render in full, and assert that neither a
failed snapshot nor an empty one is ever reported as success.

The macOS stock-Bash job asserts an exact fleet-view test count, so its
expected value moves from 15 to 20 in the same commit as the five new tests
to keep the guard and the suite from drifting. The sibling Bearings
assertion stays at 41: that suite is unchanged.
The "has this landed?" check measured against a trunk this fleet does not
land on, so proven-landed work read as unlanded and held its worktree slot.

With `origin` fetching kunchenguid/firstmate and pushing sbracewell64/
firstmate, both refs the check considered were wrong at once: origin/main
tracked upstream, and refs/heads/main had not been fast-forwarded since the
fork trunk advanced five times that day. Measured on the held slot:

  PR 44 squash-merged, recorded head 2582c15, fork trunk f90ed1d
  git diff --stat 2582c15 f90ed1d              -> empty (content landed)
  git merge-base --is-ancestor 2582c15 f90ed1d -> false (squash)
  fm_landed_tree_contains vs refs/heads/main       -> 1 (not contained)
  fm_landed_tree_contains vs origin/main           -> 2 (inconclusive)
  fm_landed_tree_contains vs the fork trunk        -> 0 (contained)

The default fetch refspec cannot reach the fork, because it points at the
fetch url. fm_landed_push_url resolves the push url when it differs from
the fetch url - the same fetch-versus-push comparison bin/fm-task-base-lib.sh
already made at the spawn site, now owned in one place and read by both -
and fm_landed_refresh_push_target fetches that trunk into
refs/fm-landing/origin/<name> for fm_landed_candidate_refs to offer.

The check is not made more permissive. The added ref is a landing target
only because this fleet demonstrably pushes there, never inferred from a
remote's name. Only a caller that already refreshes remotes fetches it, so
the guard stays local. A push url that exists but cannot be read leaves the
landing target unread, and teardown refuses rather than falling back to the
upstream answer - a case that previously passed on that fallback. A
repository whose fetch and push urls agree resolves no landing ref at all
and takes an identical path.

Every negative control was watched red first: pre-fix, the fork-landed slot
is refused at both the teardown and guard sites, and the unreadable-push-url
case wrongly passes. The unlanded, upstream-only, and single-remote controls
pass before and after, so they are not vacuous. Verified against the live
held slot: the resolved push url fetches the real fork trunk, which reports
the merged work contained while upstream stays inconclusive, and the same
slot plus one unlanded commit still reports not contained.

Full changed-scope run: 75 scripts, 6 failing identically on the unmodified
fork trunk (tmux readiness, node ESM resolution, harness env), 0 new.
kunchenguid#1614) (#48)

Ports upstream PR kunchenguid#1614 onto this fork's trunk. Nothing here is redesigned:
the guard, its refusal wording, the --allow-unverified override, the
merge_verification=/merge_verified_head= metadata keys, and the crew-state
mapping changes are upstream's as written.

Provenance:
  upstream PR   kunchenguid#1614
  head commit   e3e4b47
  its base      c8edff3
  landed onto   3611e49 (sbracewell64/firstmate main)

The two trunks diverged at upstream kunchenguid#1495, so the diff did not apply cleanly.
Resolutions, all of them fork-versus-upstream divergence rather than changes
to what kunchenguid#1614 does:

bin/fm-pr-merge.sh - this fork resolves a task's identity through either a live
meta or the durable landing record a released task keeps, which upstream has
no equivalent of. The record resolution stays, and verification is placed
between it and the recording step, so upstream's property still holds exactly:
a head the guard refuses leaves no pr= recorded and no merge poll armed. META
is bound to whichever record the task actually has, so the verification
metadata write addresses it unchanged.

tests/fm-pr-merge.test.sh - upstream's file is the base, with this fork's six
released-task cases and their fixtures re-added. Their gh mocks now answer the
verification read as well as the forge-view read.

test_missing_meta_refuses_before_merge exists on both trunks asserting opposite
behavior: upstream refuses a task with no record before any forge lookup, while
this fork deliberately rebuilds that record from the pull request itself. The
fork's version of the case is kept, because that reconstruction is this
trunk's behavior.

AGENTS.md, docs/architecture.md, docs/scripts.md - upstream's sentences folded
into the fork's own text for the landing record, the task base references, and
the merge poll's conflict reporting.

Verified on this fork: fm-lint.sh clean, fm-doc-audience-check.sh ok, and
tests/fm-pr-merge.test.sh (34), tests/fm-crew-state.test.sh (54) and
tests/fm-pr-check-security.test.sh (41) all pass. Checked once against the live
forge with the merge command mocked: the pre-change path issued a squash merge
for a pull request with three failing check runs, and this one refused it,
naming the head and the failing count, before arming anything.
…d of upstream kunchenguid#1825, refreshed) (#61)

* fix(ci): verify a head-bound attestation instead of PR body text

The `Require no-mistakes` check grepped `github.event.pull_request.body`
for a fixed literal marker. That field is mutable and author-controlled,
so the check verified no evidence and bound to no commit: the marker
could be typed once, pasted into any pull request, and survived every
force-push and rebase. It was a label with a CI job attached, not an
attestation.

Two consequences were live. Every fork landing pull request carried a
genuine FAILURE on this check and was merged past it. And a change the
pipeline had genuinely validated could not be mirrored onto a fork
landing branch without hand-writing the marker, which forges an
attestation, so that class of work stalled.

MEASURED FIRST, DESIGNED AFTER. The pipeline emits nothing head-bound
to the forge today. Inspected on real pipeline-raised heads: commits are
unsigned and carry no trailers, the head has no commit statuses, the
only check runs are the repository's own Actions, and neither repository
advertises any notes or attestation ref. The binding does exist, but
only in the pipeline's local run record. The pull request body carries
no run identity and no head sha.

The check now verifies a git note on refs/notes/no-mistakes keyed by the
exact head commit. A note is keyed by a sha, so it names the commit it
covers without changing that commit's sha; it never rewrites the branch,
so producing one cannot disturb the pipeline's custody of it; and it
reaches the forge as an ordinary ref readable with contents: read.
bin/fm-attest.sh owns both sides: `write` transcribes the pipeline's own
run record and refuses unless that run covers this exact HEAD on this
branch and completed review, test, lint and push; `verify` is what CI
runs. docs/no-mistakes-attestation.md states what this establishes and
what it does not.

Absence never reads as evidence. A missing ref, a missing note for this
head, an unavailable head commit, a malformed note, a note naming
another commit, and a skipped required step each refuse in their own
words, because they need different repairs. An unrecognized field is
refused rather than ignored, so a later format cannot be read as a
weaker v1 one. A head repository that cannot be read stops the job
instead of resolving as either absent or present.

Every negative control was watched failing before the code was trusted,
against real pull request heads as well as fixtures: the old marker
pasted verbatim is refused; a genuine attestation copied onto another
head is refused; a genuine attestation carried across an amend is
refused; and each refusal was re-run with its own guard removed to prove
it is causal rather than vacuous. Each is paired with a matched positive
control, because a verifier that refused everything would satisfy
red-only assertions and would be a worse defect than the check replaced.

WHAT THIS DOES NOT ESTABLISH, stated rather than implied. It does not
establish who ran the pipeline. no-mistakes runs on the contributor's
machine with the contributor's own credentials, so nothing it emits can
be unforgeable by its operator; closing that needs an issuer the author
does not control, such as a check run posted by an app the base
repository installs, which is a change to the pipeline rather than to
this check. What is removed is the far weaker property that any text
typed into a mutable field satisfied it.

KNOWN COST, ACCEPTED. Until an attestation is published, this check
fails pull requests that the body-string check passed, including
genuinely pipeline-raised ones. That is the honest consequence of
requiring evidence that is not yet emitted, and it is actionable: the
failure names the one command that fixes it. Required-check
configuration is a repository setting and is untouched here.

The unblock this enables: because the attestation binds to a commit
rather than to pull request prose, a fork landing branch can be
validated in its own right without proposing anything upstream, which
removes the mutual exclusivity between holding a signature and
duplicating a live contribution.

* no-mistakes(review): reconcile attestation notes against the push target

* no-mistakes(review): keep run-record refusals distinct across tool streams

* no-mistakes(review): attest the run tip the pipeline advanced past HEAD

* no-mistakes(review): withhold push errors and name record faults distinctly

* no-mistakes(review): bound attestation reads through the shared timeout owner

* no-mistakes(review): redact credentials through one userinfo boundary owner

* no-mistakes(review): emit URLs only on a positive credential-free match

* no-mistakes(review): model scp-style remotes as a safe emitted shape

* no-mistakes(review): withhold a whole line when any URL is withheld

* no-mistakes(document): document attestation timeout knob and correct stale script docs

* no-mistakes(review): separate verifier failure from refusal and scrub every emitted line

* no-mistakes(review): discard scratch-file subprocess stderr and drop source-byte assertions

* no-mistakes(document): consolidate attestation redaction rationale into its declared owner

* no-mistakes(review): withhold query/fragment URLs and name unreadable attestation refs

* fork-land: carry the shared timeout owner this attestation work depends on

bin/fm-nm-run-lib.sh now reads no-mistakes run records through
bin/fm-timeout-lib.sh, which the fork trunk did not yet carry. Add that
owner unchanged from upstream, list it in docs/scripts.md, and select the
same test lanes its only fork consumer selects.

* fork-land: copy the shared timeout owner into the synthetic old bin

bin/fm-nm-run-lib.sh now sources bin/fm-timeout-lib.sh, so the backend
conformance fixture must copy it alongside every other sibling those
entrypoints source, or sourcing aborts under set -eu.
* fix(tests): bound the blocked-worker waits by wall clock, not poll count

The remote secondmate lifecycle suite failed on the fork trunk itself, so every
open fork PR inherited a red required check. The config-push inheritance barrier
waited 250 poll iterations for its deliberately blocked write; on the CI runner
those iterations elapse in 5.3 seconds, while the transaction needs longer than
that to traverse its SSH-boundary jobs - the sibling spawn barrier in the same
job took about 16 seconds. Upstream raised exactly this bound to 1500 in kunchenguid#1727
on 2026-08-04; the fork reconciliation a day later landed 250 at that one site
while keeping 1500 at its sibling.

A poll count is not a duration. It shrinks precisely when the work it waits for
is slowest, so restoring a larger count would leave the same defect one loaded
runner away. tests/lib.sh now owns fm_test_wait_file, which bounds the wait by
wall clock the way bin/fm-remote-job-lib.sh already bounds its own polls, and
distinguishes a producer that died from a bound that expired. Every blocked
worker wait in the suite uses it: 90 seconds for a remote transaction, 30 for a
local marker, both hang tripwires with margin over a measured 26-second worst
case rather than expected durations - a healthy wait ends when its marker
appears and costs nothing extra.

Measured on the base: the transaction reaches its blocked write after 297-319
iterations (23-26 seconds) on a loaded runner, and every serialization assertion
after it passes, which is the disconfirming evidence against a code-side defect.

tests/fm-test-lib-wait.test.sh pins the helper's contract. Each of its three
guarantees was witnessed red under a matching defect: a fixed poll count fails
the wall-clock case, a wait that ignores a dead producer fails the exit case,
and a wait that refuses to poll fails the completion case.

* fix(bin): stop a retired secondmate home from being rebuilt by its own teardown

With the wait bound fixed, the same suite reached its final case and failed
there: a remote secondmate retirement completed, reported success, and left the
retired home behind as a stray tree containing data/.parent-route/wake-ledger.tsv.

A remote secondmate is retired by a host-local teardown whose DATA is a private
directory INSIDE the home being removed (bin/fm-remote-secondmate-control.sh).
The terminal wake-ledger line is written after that removal, and
bin/fm-wake-ledger.sh creates its ledger's directory, so the telemetry write
rebuilt the tree the retirement had just deleted. The line was unreachable
evidence there in any case: it lived only inside the deleted home, and the
parent home never reaches its own ledger write for a remote retirement.

The write now skips a destination inside a home this teardown just removed, by
path and by the vanished directory, so neither spelling of the path resurrects
it. Its position is unchanged, so a teardown that refuses after the removal
still records no terminal line.

Both directions are covered and were witnessed red: without the guard the
retirement rebuilds the home, and with the guard applied too broadly a
retirement whose ledger lives outside that home loses its terminal line.

* fix(bin): decide ledger containment before the home is removed

The first guard asked whether the ledger belonged to the removed home partly by
testing whether its directory had vanished. That over-reached: any secondmate
retirement whose ledger directory did not exist yet took the skip path, so its
terminal line was dropped - and silently, because the skip bypassed the
"wake ledger terminal line not recorded" warning as well. Only the ledger that
lives inside the removed home should ever be skipped.

The question is now answered before the removal, while both paths still resolve,
and the answer is carried as a verdict. Both paths resolve through their nearest
existing ancestor, so a destination that does not exist yet still compares
correctly and two spellings of the same home still compare equal. Every other
destination keeps its previous behavior, including creating a directory that is
not there yet and warning when the write fails.

The new case pins exactly the regression: a retirement whose ledger lives
outside the removed home in a directory that does not exist yet must still
create it and record the line. It was witnessed red against the previous guard
while the other two cases stayed green.

* fix(tests): let the handoff suite's worker die before removing its temp root

This suite's EXIT trap signalled the remote job worker and removed the temp root
in the same breath. Signalling is not stopping: the worker can still be writing
job state under that root while the removal walks it, and `rm -rf` then fails
with "Directory not empty". Because that removal is the trap's last command, its
status becomes the script's, so a run whose every assertion passed still reports
failure - which is exactly how it presented, printing ALL TESTS PASSED and then
exiting 1.

The race is pre-existing on the trunk rather than new: the CI run for base
ed376cf logged the identical "Directory not empty" failure twice for the sibling
remote fixture, where it happened to be harmless because that suite's trap does
not end on the removal. What changed is only exposure - this script moved
between serial shards, and so onto a different runner.

The trap now waits for the worker to actually die before removing, the same
bounded wait tests/fm-remote-secondmate-lifecycle-e2e.test.sh already uses.

Witnessed red through a control that models the real condition, a worker that
keeps writing and does not die the instant it is signalled: removing immediately
after the signal failed 5 of 5 times with the identical message, and waiting for
the worker to die first failed 0 of 5.
… (land of upstream kunchenguid#1855) (#54)

* fix(bin): answer "cannot resolve this run head" instead of "not mine"

`fm_nm_head_matches_worktree` resolved the run head THROUGH THE WORKTREE and
treated a failed lookup as "no match". During validation no-mistakes commits
its fix rounds in its own gate-repo clone and does not push until the push
step, so the live run's tip is routinely an object the crew's worktree has
never seen. Measured 2026-08-06 on a lane actively running its lint step:
worktree HEAD d4032f0, live run head 5152b3a, `git cat-file -t 5152b3a` in
the worktree "Not a valid object name". The helper's own header said the
descendant case MUST match because pipeline fix commits advance the run tip
past local HEAD - the implementation could not see those commits, so the
documented normal case was structurally unmatchable during every fix round.

The rejected run then fell through to the coarse runs-list scan, which applied
the same rule per row: the live running row was skipped as unresolvable and an
OLDER, genuinely failed run sitting at the worktree's own head matched and won.
The answer was not "unknown" but confidently wrong, in the direction that makes
a working lane look dead: `state: failed - source: run-step - run failed`.

The rule is now three-valued - match, no match, unresolvable - and stays the
single owner shared by both consumers:

- fm-crew-state.sh: an active run on this crew's own branch whose tip cannot
  be resolved is attributed and reads as working, since bare `axi status`
  answers for the queried branch whenever that branch has a run. A TERMINAL run
  whose head cannot be bound is not attributed at all, because binding its head
  was the only thing that could tie its verdict to this worktree. The coarse
  scan now binds the branch's newest row and stops, rather than walking past
  the live row to an older one whose sha happens to match; an unresolvable
  newest row answers only "a run is active on this branch", never a terminal
  status.
- fm-teardown.sh: the direction is unchanged and deliberate. Only a positive
  code-identity MATCH authorizes aborting a parked run; both non-zero verdicts
  DECLINE, so teardown never aborts a run it cannot positively attribute. The
  cost of declining is a run left parked for firstmate to see; the cost of
  guessing is killing another crew's live validation.

Upstream PR 1816 does not fix this class: it owns current-run selection and
applies the same visibility test, so it rejects the same live rows.

Tests: new tests/fm-nm-run-lib.test.sh pins all three verdicts, including a
descendant commit created in a SEPARATE clone so it is genuinely absent from
the worktree's object store - the condition that produced this defect and the
one no existing test covered. Four reader cases and one teardown case cover the
consumers. Every case was witnessed red against the pre-fix logic or a targeted
mutation before green; the two coarse/reader regressions reproduce the measured
`state: failed - source: run-step - run failed` verbatim.

The test fixture rules added to firstmate-coding-guidelines are not incidental:
an earlier draft of these fixtures resolved a repo path to empty and ran
`git -C "" reset --hard` against the checkout the tests live in.

* no-mistakes(review): guard unresolvable heads from terminal verdicts; harden fixtures
AGENTS.md section 1 forbids naming an agent as a commit co-author, and bars
the fleet's captain-address and nautical conventions from commits, PRs, and
anything other tools read. Neither rule reached a worker: bin/fm-brief.sh
stated neither, so a generated brief carried the co-author rule only on
firstmate-repo tasks, and then only because those briefs separately name the
firstmate-coding-guidelines skill, which carries it. Every worker on every
other project was silently missed.

Both are structurally the same gap. A crewmate does not read this repo's
AGENTS.md for another project, and its own harness instructions may actively
tell it to append a Co-Authored-By trailer, so the brief is the only place
either rule can arrive. Commit 53932fb on fm/platform-landing-battery-windows-reds
shipped a Co-Authored-By trailer for exactly this reason, and a separate
incident leaked captain address into a commit subject the same way.

Render a "# Commit conventions" section from one shared value into all three
scaffolds that can reach a commit - ship for all three delivery modes, scout,
and the secondmate charter - placed beside each one's delivery instructions
rather than in the preamble. In the ship scaffold it is the last thing before
"the task is complete only when committed on your branch". The scout copy adds
that scratch commits are held to the same bar because a scout can be promoted
in place. Rule numbering is untouched, so no cross-reference moves.

test_every_committing_variant_carries_commit_conventions generates all five
variants and asserts both rules plus the placement constraint. Witnessed red
against the unfixed scaffold first: all five generated variants contained zero
occurrences of either rule.
…of upstream kunchenguid#1611) (#50)

* feat(bin): surface the shared validation daemon's liveness at session start

Every shipping task depends on one shared no-mistakes validation daemon, and
nothing ever asked whether it was running. It died silently three times and
stayed down 2.5 days, 2.9 days and 8 hours before anything restarted it; nobody
noticed on any occasion, because a silent outage is indistinguishable from a
quiet fleet.

Bootstrap now reads the daemon's pid file and sends signal 0 once per run, and
reports the result alongside the existing diagnostics: a VALIDATION_DAEMON line
when the daemon is down or unmeasurable, silence when it is healthy, and its
uptime as a BOOTSTRAP_INFO fact only under FM_BOOTSTRAP_VERBOSE_FACTS=1. A down
report carries how long the daemon has been down, taken from the last moment it
demonstrably wrote anything, so a 2.9-day outage reads as a 2.9-day outage.

The check observes only. It never starts, stops, restarts or reconfigures the
daemon, and it never invokes the no-mistakes CLI, whose ordinary commands
auto-start a daemon as a side effect. Starting a down daemon stays firstmate's
decision.

Three outcomes stay distinct rather than collapsing into two. The pid file is
JSON, not a bare integer, so piping it into a signal check reports a live daemon
as down; an unreadable pid file is therefore reported as unknown, because
calling it down manufactures false alarms and calling it alive recreates the
silence this check exists to end. A recorded pid of 0 is unknown for the same
reason: kill -0 0 signals the caller's own process group.

Tests cover alive, down, missing pid file, and four malformed pid files, and
construct the dead pid positively rather than inferring down from a live daemon.
tests/lib.sh pins NM_HOME at a nonexistent root so the machine's real daemon
cannot leak into any suite that asserts exact bootstrap output.

(cherry picked from commit ad100b8)

* no-mistakes(review): Captain: parse validation daemon PID files as JSON

(cherry picked from commit 404838f)

* no-mistakes(document): Document validation daemon startup diagnostics

(cherry picked from commit ab0ad0c)
…olds (land of upstream kunchenguid#1819) (#51)

Ports upstream PR kunchenguid#1819 onto this fork's trunk. Nothing here is redesigned:
bin/fm-ruling-reconcile.sh, the closure-authority enforcement in
fm-decision-hold.sh's `resolve --from-ruling`, the session-start
RULING_RECONCILE line, the documentation, and the tests are upstream's as
written.

What it does: a decision hold may be closed only when the ruling names the
hold identifier verbatim AND carries an explicit structured verdict.
Everything else escalates. There is no bulk close.

Provenance:
  upstream PR   kunchenguid#1819 (open, untouched)
  head commit   5311209
  its base      2cf0283
  landed onto   ed376cf (sbracewell64/firstmate main)

The dispatch brief named 41d2e5e as the source. That is the contribution's
first commit; the delivered contribution head is 5311209, which adds the
pipeline-review hardening - including the forged-provenance guard and its
test. This carries the full PR head so that guard lands with the feature.

The trunks have diverged, so the diff did not apply cleanly. Three
resolutions, all fork-versus-upstream divergence rather than changes to what

AGENTS.md - upstream has renamed X-mode to Relay and has dropped the
research-index/ state entry; this fork has neither change. The trunk's
wording and its research-index/ line are kept, and only kunchenguid#1819's own three
additions are applied: the ruling-index/ entry, the RULING_RECONCILE line in
the fleet-state digest, and the extra decision-hold-lifecycle load trigger.

bin/fm-session-start.sh header - upstream numbers the digest steps one higher
than this fork does, and this fork's read-only paragraph says "five"
bootstrap mutating sweeps where upstream says "six". The trunk's numbering
and its sweep count are kept; only kunchenguid#1819's substantive additions are applied
(the RULING_RECONCILE description and the ruling-index rebuild in the
skipped-when-read-only list). The five-versus-six wording is a pre-existing
trunk inconsistency with the same file's own step 2 comment, and is
deliberately left as trunk work rather than swept in here.

bin/fm-session-start.sh fleet-state digest - this fork emits a fleet-admission
block at exactly the point kunchenguid#1819 inserts its RULING_RECONCILE block. Both are
kept, admission first, each with its own `fi`.
…ons (#53)

* feat(loopspec): bind verifiers executably and actuate lawful transitions

LoopSpec validated specs nobody could run. Measured at HEAD: one spec
registered, zero callers of bin/fm-loopspec.sh anywhere in bin/, zero
entries in state/loopspec/, and execution_path_implemented false on all
sixteen triggers, so assert_runnable could never pass.

Two defects kept it inert.

The verifier was a bare name. verification.verifier was a slug the
machinery never executed, and finish took the verdict from its caller.
A spec naming a verifier that did not exist validated, claimed, and
reached a success terminal on an asserted pass - the named-but-
unreachable case, which looks bound and verifies nothing. Now
verifier_command names a repository-owned executable, an enabled spec
must have one that resolves and runs, `verify` executes it, and a
success terminal requires a recorded run bound to that spec version,
event key and iteration. NO_VERIFIER_RAN is never success, and the
party doing the work no longer certifies the work.

Nothing actuated. bin/fm-loop-actuate.sh is the governed wake-to-action
table: when one lawful transition follows from the recorded state, code
executes it instead of spending a coordinator turn. It is not a runtime
- no loop, no poll, no daemon, no storage of its own. Arming appends an
ordinary check record to the existing durable queue, and every execution
leaves loop=<spec>@<version> with its canonical verifier, without which
adoption cannot be demonstrated at all.

Selection keeps its ruled shape: the deterministic filter narrows over
tiny typed applicability headers, never spec bodies, and hands a genuine
two-or-three tie back as a decision rather than inventing a tie-break.

Also generalizes required_terminal_states, which hard-coded the first
spec's own vocabulary and made a second spec unauthorable, to the
universal safety stops plus required kinds.

Adds fork-landing as the first enabled production spec. It terminates at
a carried pull request whose checks have run, never a merged one, and
forbids merging outright: a loop that merged would convert a delivery
mechanism into an authority expansion.

* fix(loopspec): name an unregistered check set as unavailable, not unparsable

Production run against a freshly opened fork pull request exposed a
second shape from the forge: a pull request whose checks have not been
registered reports "no CI checks configured" rather than a summary line.

The verdict was already correct - unavailable, never a resolved empty
set, because a pull request nothing has examined must not read as
verified - but the diagnostic blamed an unparsable summary for what is
actually a pull request too young to have checks. The distinction
matters when reading why a loop did not advance.
firstmate.bat launches `wsl.exe --exec /bin/bash` so the launch never depends
on the login shell, but that session's PATH carries only the system directories
plus Windows interop. The menu probes each harness with `command -v`, so every
harness installed under the account's private bin directories rendered as "not
installed" and the Enter default fell through to whatever happened to resolve.

bin/fm-wsl-entry.sh now prepends ~/.local/bin and ~/bin when they exist, before
handing off to the launcher. Those are exactly the two directories the stock
~/.profile prepends and the ones every supported harness installer targets, so
this restores interactive parity as a filesystem fact rather than by evaluating
a login profile in a non-interactive launch. Only existing directories are
added, only once, and nothing already on the inherited PATH is removed or
reordered. firstmate.bat's --exec choice is unchanged.

Verified through the real bridge command: `wsl.exe --cd <repo> --exec /bin/bash
./bin/fm-wsl-entry.sh --print-menu` now renders a menu byte-identical to the
interactive `bin/fm-launch.sh --print-menu`, for every harness the menu lists.
…ers (#57)

Resolving CFVC Lane A residual uncertainty 4: whether the four
uninstalled adapters (opencode, pi-signed, grok, kimi) still work.

Measured: no CI job installs a harness binary, so all nine members of
the live-harness-optin family execute on every CI run and gate-skip on
an opt-in environment variable, exiting 0 without testing anything
(FM_TEST_SUMMARY total=9 failed=0 skipped_gate=9, 2026-08-07). With the
switch on and the binary absent they fail loudly instead, so the exit-0
seen in CI is a real gate skip rather than a vacuous pass.

The record stated the CI-versus-opt-in split without stating its
consequence: every per-harness row rests on the dated operator runs
alone. It now says so, and carries bounded output from a Linux run
carrying three of the seven, showing how the guard accounts for the
adapters it could not check.
… chokepoint (land of upstream kunchenguid#1830) (#58)

* feat(bin): record why every agent dispatch was necessary at the spawn chokepoint

CFVC-08. bin/fm-spawn.sh is the last gate before an agent turn exists and it
already writes state/<id>.meta, so it is where the justification record belongs.
Every ship and scout dispatch now records four fields:

  reasoning_required  derived from the reason code
  reason_code         a closed nine-token enum; an unknown value is refused
  capability_floor    verbatim from config/crew-dispatch.json
  escalation_policy   derived from kind plus the delivery contract

bin/fm-reasoning-lib.sh is the single owner of the enum, the derivations and the
stable refusal tokens. It records; it does not enforce - no dispatch is blocked
for reasoning too little. The vocabulary is closed because a free-text reason
cannot be counted, and the two derived fields are never caller-supplied so they
cannot disagree with the record they summarize.

TOOLING_GAP is the one code that is NOT a reasoning code. It names a turn taken
only because a deterministic reader is broken or absent. It records
reasoning_required=no so it can never be counted as justified reasoning, and it
requires --tooling-gap-item naming a work item that is currently OPEN in this
home's data/backlog.md. Without that check the code would launder every unfixed
tool into a permanent "necessary agent turn" - the single failure mode that
would make this record worse than no record at all.

SCOPE, and a documented replacement of completion criterion (a). The spec says
every new spawn record carries all four fields. A --secondmate spawn is excluded
and refuses all three flags: it provisions a standing home rather than
dispatching a task - AGENTS.md section 10 keeps a secondmate out of the backlog
for the same reason - and Lane B derived the enum entirely from task
invocations, so demanding one of its codes for a provisioning action would
manufacture exactly the rubber-stamp answer the enum exists to prevent. Absent
fields read as unknown and never as justified, so a secondmate record cannot be
miscounted either way. The criterion is replaced by a stronger tested pair:
every TASK dispatch carries all four fields, AND a secondmate spawn that passes
one is refused with a stable token rather than silently defaulted.

bin/fm-promote.sh recomputes escalation_policy, because promotion changes the
delivery contract that field is derived from. Leaving it would keep a scout's
report-only posture on a task that can now reach a merge gate.

RETIREMENT. The record was ABSENT, so no mechanism is replaced in code. What
retires is the untracked category of agent turns taken because a reader is
broken: before this, such a turn was indistinguishable in the record from
justified reasoning, and TOOLING_GAP plus its refusing filing check is what ends
that. No transitional second path is left alive - --reason-code is the only way
to record a reason, and it is required rather than optional.

CERTIFICATION. tests/fm-reasoning-required.test.sh, eight cases. Every case was
witnessed RED against the pre-change bin/fm-spawn.sh and bin/fm-promote.sh
before being accepted green. Three further targeted negative controls were run
and witnessed red: loosening the open-item match (the already-closed and prefix
rows go red), making reasoning_required always yes (the TOOLING_GAP row goes
red), and degrading an unreadable dispatch config to "unconfigured" (the
unverifiable-floor row goes red). The published-codes case asserts the recorded
value positively rather than the absence of a refusal, because the
absence-only version of it was vacuously green against the reverted
implementation and so proved nothing. The lint gate was also shown able to
reject (exit 1 on a deliberate violation) rather than trusted for being quiet.

Existing spawn call sites in 18 test files carry the new required flag.

Three failures remain in the touched set and were each proven PRE-EXISTING on the
unmodified contribution base by a stashed baseline run, not asserted: two scout
teardown decision gates refused because this environment has tasks-axi 0.2.3
against the required 0.2.4 floor, and one Pi extension case fails on a Node ESM
loader error. All three reproduce identically with these changes reverted.

* no-mistakes(review): fix array-form floor read, literal gap-item match, meta injection

* no-mistakes(document): document capability-floor dispatch axis in architecture.md

* test: declare a reason code at the fork's own spawn call sites

The landed contribution makes --reason-code required for every ship and
scout dispatch. Five suites that exist only on the fork trunk, or that
grew fork-only spawn call sites since the contribution was cut upstream,
still spawned without it and were refused before reaching the behavior
they pin.

Each call site now declares NL_RULE_CLASSIFICATION, matching how the
contribution adapted the suites it could see upstream. The Herdr launcher
helper picks the flag conditionally: a --secondmate spawn provisions a
standing home and refuses --reason-code outright, so passing it to every
spawn the helper drives would break the secondmate case instead.

No product behavior changes here; this is the fork-side half of the same
mechanical adaptation.
…(land of upstream kunchenguid#1828) (#67)

Lands upstream PR kunchenguid#1828 (head `5152b3ab`) onto this fork's
trunk so the running fleet gets it. Nothing was redesigned and nothing was
re-reviewed; the change was already validated upstream, the upstream
contribution stays open and untouched, and only that contribution's own changes
are carried here.

At the one point an aged wedge marker is about to escalate, a crew that is
provably working has its marker refreshed instead of escalated, so a worker whose
progress lives off the pane stops re-alarming every threshold. Refreshing rather
than dropping keeps the silence conditional: the verdict must be re-earned every
threshold, so a crew that freezes still ages out and escalates. Detection after a
freeze takes at most two thresholds instead of one; the bound is preserved, only
the constant changes. The gate's crew-state reads are capped per housekeeping
pass by FM_STALE_WORKING_GATE_READS, and a marker past the budget stays aged for
the next pass rather than being escalated or dropped.

Reconciled against this trunk, which advanced past the upstream base:

- The trunk's settled-terminal absorb and this contribution's provably-working
  refresh both consult crew_absorb_class at the same escalation point, so they
  now share one bounded read: settled drops the marker, working refreshes it,
  anything else escalates. Deferring past the read budget leaves the marker aged,
  so an absorb is delayed by a pass and never becomes a false wedge.
- The trunk's `settled` classifier action, its captain-gated pause-recheck skip,
  and its FM_CHILD_CPU_* and FM_PR_DIRTY_RESURFACE_SECS documentation are kept as
  the trunk has them; this contribution's wording is reapplied only where it
  deliberately changed something.
- `tests/fm-daemon.test.sh` asserted the per-wake path never reads crew state.
  That was true at the upstream base but is not true here: this trunk's
  settled-terminal absorb deliberately reads once per stale wake, before the
  status-line tests. The case now pins what this contribution actually owns - the
  gate does not run on the wake path, and the reader is consulted exactly once
  per wake rather than once per gate. This is the one substantive reconciliation
  in this landing and is called out for review.
- The suite now defaults FM_CREW_STATE_BIN to a shared inert fake, and the two
  cases that unset it restore that default instead. The trunk's per-wake read
  reaches crew_absorb_class from cases written before this contribution's
  fixture guard existed, and an unset stripped the default for every later case.

Verified on this branch: `bin/fm-test-run.sh tests/fm-daemon.test.sh` passes, and
two negative controls confirm both merged behaviours are live - disabling the
provably-working refresh fails with "a provably-working crew escalated a possible
wedge", and disabling the settled absorb fails with "a settled terminal state
escalated a possible wedge".
… mapping (#60)

* feat(loopspecs): unify the terminal-state vocabulary behind one owned mapping

Two vocabularies named the same terminal facts twice. A LoopSpec finalising on
no_progress_stalled and the platform execution node finalising on
iteration-cap-failed are one fact under two names, and the LoopSpec side's
maps_to was a free-form string carrying a third, unvalidated set of names.

loopspecs/terminal-states.json now owns a single unified terminal-state
vocabulary of nine members and the total mapping onto it from both source
vocabularies: eight LoopSpec terminal states and the eight FINALIZE_MATRIX
outcomes of the platform's scripts/runtime_execution_node.py, read at platform
commit 5d86b7e. Sixteen source names resolve to nine unified names, every
unified name is reachable, and no platform file is changed by this record - the
platform-side rename is a follow-on task there.

The map is enforced rather than documented. bin/fm-loopspec.sh checks it before
any spec is read against it and refuses a map that is not total, that leaves a
unified state unreachable, that is not a reduction, or that collapses two source
names of differing consequence without declaring where the difference still
lives. schema.json makes maps_to required and resolves it against the map
through an external_enums pointer, so a spec terminal state that is unmapped,
invented, disagreeing with the map or contradicting its unified kind is refused
rather than defaulted onto whatever looks closest.

no_delta's certification survives as a machine-checked property: exactly one
unified state may be reached without spending a model turn, it must be no_delta,
and it must stay neutral so reaching it can never demand a verifier verdict. The
test proves that behaviourally as well as declaratively, with a success terminal
under the same conditions as the negative control.

New subcommand: fm-loopspec.sh terminal-map, with --unified, --source,
--resolve and --json. Resolving an unmapped state refuses with the new stable
token refuse_unmapped_terminal.

* no-mistakes(review): guard jq null keys and refuse unknown terminal-map source

* fix(loopspecs): carry the fork-landing spec onto the unified vocabulary

The fork trunk gained loopspecs/fork-landing.json after this branch was cut.
It still declared the free-form maps_to names this change retires, and two of
its terminal states had no row in the unified map at all, so the new
terminal_mapped invariant refused the whole registry.

Record already_carried and carried_and_checks_resolved as loopspec source rows
- no_delta for the one whose description already forbids a model turn, goal_met
for the carry success - and move the spec's own maps_to values onto the unified
names.

tests/fm-loop-actuate.test.sh builds its own registry directory, so it now
copies terminal-states.json alongside schema.json; without it every fixture
registry is missing a file the interpreter requires.
…kunchenguid#1978) (#62)

* feat(bin): let the terminal record say a task failed

The fleet's terminal outcome was a constant. Teardown set outcome=landed
and only --force changed it, so nothing anywhere produced failed: a record
reading "40 terminal (landed 40)" was not a success rate, because the
numerator could not move.

Definition first. bin/fm-wake-ledger.sh now owns what a terminal outcome
means and what evidence stands behind it. The enum stays three members
pinned to the v1 line schema; every terminal record gains outcome_source
naming where its outcome came from - declared, discarded, unreleased, or
assumed. A record written before the field existed reads as assumed, which
is exactly what those records were, so the append-only file needs no
rewrite.

Field second. Teardown derives the outcome from the task's own last
declaration instead of a constant, and a --force discard still outranks it.
A second producer covers the case teardown never sees: a task that fails and
is never released was silent in the ledger, and silence there is
indistinguishable from a task that never failed. `sweep` records those once,
receipt-guarded, and a locked session start runs it.

Diagnostic only, deliberately. The report breaks outcomes down by evidence
and refuses to print a rate: this ledger counts released tasks while the
no-mistakes pipeline counts validation runs, and until that divergence is
reconciled any ratio would describe neither. The report names that gap on
every run.

The attempt counter and the unified terminal vocabulary are separate
increments and are not absorbed here.

* no-mistakes(review): count terminal sweep records from durable appends, add coverage

* no-mistakes(review): remove terminal-recorded receipt in remote-secondmate teardown

* no-mistakes(document): point AGENTS.md mutating-sweep list at bootstrap header owner
…3) (#63)

* feat(bin): count task attempts against a durable retry budget

Nothing counted attempts before this: no state field, no metadata field, and
brief rule 5's "if you hit the same obstacle twice, stop" made the worker the
arbiter of its own retry budget, evaluated from a context that resets on every
relaunch. "Should this be retried?" is now arithmetic over a number on disk.

bin/fm-attempt.sh owns state/<id>.attempt (attempt=, attempt_budget=,
terminal=). Every ship or scout spawn checks the budget before it creates
anything and commits the increment when it publishes task metadata, which also
carries the count as attempt=/attempt_budget=. An absent field reads as attempt
1, so a task dispatched before this retries at 2 rather than restarting its
budget. Secondmates are exempt: their relaunch is unattended liveness recovery,
not a retry.

Exhaustion is a named stop, not a silent one. It records the terminal state and
declares the failure on the task's own status log. Teardown retires the count on
an ordinary release, which is only reachable once the work landed, and keeps it
under --force, so discarding between attempts cannot make the budget unbounded.

Compatibility with the two delivered dependencies, neither of which is on the
trunk yet, and neither of whose producers this duplicates:

- CFVC-11 (fm/cfvc-11-terminal-vocabulary, loopspecs/terminal-states.json):
  exhaustion terminates in that unified vocabulary's budget_exhausted rather
  than a third name. The regression asserts membership whenever that file is
  present and says plainly when it is not.
- CFVC-12 (fm/cfvc-12-task-outcome-failure): the status declaration uses the
  failed: verb that its terminal-outcome derivation reads, so exhaustion books
  outcome=failed through that owner instead of a second ledger producer. The
  three-member v1 outcome enum is untouched.

Brief rule 5's prose is deleted in the same change and the rule list renumbered,
with its numbered cross-reference updated.

* no-mistakes(review): validate default budget env, fix retire premise, document fm-attempt.sh

* no-mistakes(document): document tooling status-line producers in AGENTS.md state inventory

* test(herdr): normalize the per-run attempt count in the projection meta parity check

The projection parity case spawns ONE task id twice - opted out, then
projected - and byte-compares the two metadata files after normalizing the
fields that legitimately differ per run. The durable attempt counter adds
another such field: the second spawn of that id is attempt 2 by construction,
so the comparison failed on a real per-run fact rather than on a projection
difference.

Only attempt= is normalized. attempt_budget= stays compared, because the budget
is a property of the task rather than of the run, and projection must not
change it - normalizing both would have retired real coverage to silence one
line.

Caught by fork CI, not locally: this suite is real-herdr-gated and the task
brief carries no Herdr lab guard, so CI is its verification path here.

* fix(bin): spend an attempt on a recorded failure, not on a launch

CI proved the increment semantics wrong. The Herdr suite drives a same-identity
reclaim: a task whose session dies leaves an agent-free husk, and fm-spawn is
re-run with the same id to replace the dead endpoint while the worktree, branch
and work are preserved. Counting every launch charged that recovery against the
retry budget, so a task could not be brought back after two session restarts -
"fm-hibit-resume-r1 has spent its retry budget: 2 of 2 attempts used". That is
the same harm already avoided for secondmates and not applied to crewmates.

The contract now reads the spec literally: an attempt increments ONLY when the
prior attempt has a recorded FAILED terminal outcome. A spawn following no
recorded failure - dead runtime, husk, freeze, any recovery reclaim - is a
CONTINUATION: the count persists, does not move, and is never refused. The
outcome record draws the line, not the spawn event, which is exactly why this
increment depends on the terminal outcome that can say the prior attempt failed.
A wedged worker is no exception; relaunching one continues unless the wedge was
recorded as a failure first.

The failure signal is the `failed:` verb on the task's own status log - the same
declaration the ledger derives outcome=failed from, so this reads CFVC-12's
evidence rather than a private second signal. failures= records how many were
seen when the count last moved, so only a failure newer than the last counted
attempt spends the next one. This script's own budget-exhaustion declaration is
excluded from that tally, or one refusal would manufacture its own successor. A
refusal preserves the tally it found: it opens nothing, so it must not consume
the pending failure.

A forced teardown needs its own record. The discard ends the attempt AND deletes
the status log carrying the declaration, so teardown --force now calls
`fm-attempt.sh end`, which marks the attempt ended (ended=1). Without it a
re-dispatch after a discard would read as a continuation and discarding between
attempts would make the budget unbounded - the hole keeping the record closed.

Witnessed red first, as instructed: tests/fm-backend-herdr-presentation-e2e.sh
reproduces the exact CI refusal against the pre-correction tree and passes
after, 23 cases, with the default-session tripwire intact. The suite drives
Herdr only through the guarded lab helper, which refuses the default session by
construction.

* test(attempt): give the spawn cases the reason code trunk now requires

Trunk began requiring --reason-code on every ship and scout spawn after this
branch was cut, so the attempt suite's six ship spawns were refused before they
could publish a count. Each now passes NL_RULE_CLASSIFICATION, matching the
other spawn-driving suites.

The secondmate case is deliberately left alone: the reason code is refused on
--secondmate, which provisions a standing home rather than dispatching a task.
…ename promote to reflag (#65)

* docs: open the fleet's vocabulary-collision registry

The fleet resolved name collisions wherever they surfaced, so a ruling was
only ever findable by whoever remembered making it, and the platform's own
Register 3 had no counterpart on this side.

Open docs/vocabulary-collisions.md as the single owner of every word carrying
more than one meaning across the fleet, the platform, and the vendor tools both
depend on. It ships seeded with the ruled dispositions rather than empty: axi
and execution keep their names with the evidence recorded, skill and watch and
lifecycle take mandated qualified forms, promotion splits with the fleet verb
becoming reflag, and kind splits into three axes.

A rename or a split row also states the obsolete name's retirement condition,
so no superseded path is left with an open-ended life. This lands before any
rename it governs: the map exists first, then the moves it records.

* feat(bin): split task identity into role, deliverable, and stage axes

One kind= field carried three independent facts at once: who the worker is,
what the task produces, and where the task stands in its life. Every consumer
reconstructed the axis it cared about from a value that also encoded the two it
did not, and the scout-to-ship operation expressed a lifecycle transition by
rewriting a deliverable type - which is why a reflagged ship and a commissioned
one were indistinguishable, and why a requested agent role had nowhere to land
that would not have made a fourth conflated dimension.

bin/fm-task-axis-lib.sh becomes the single owner of role=, deliverable=, and
stage=, of their values, and of the total derivation from the retired field, so
no consumer spells that mapping itself. Migration follows the ordered contract:

- Dual-write first. Every writer emits the axes beside kind=, which keeps
  reading unchanged while records converge.
- Backfill by derivation, forward-only and idempotent, in a startup sweep that
  runs only under the fleet lock and leaves a converged home byte-identical.
  Stage is deliberately NOT derived: the old field could not distinguish a
  reflagged ship from a commissioned one, so backfill records the
  lower-information value rather than inventing a fact.
- Migrate readers one axis at a time - role across the secondmate-membership
  consumers, then deliverable across the pipeline and teardown-protection
  consumers - splitting the conditions that had mixed both.

kind= stays dual-written for now; the registry owns its retirement condition.
While it stays, a record whose alias contradicts its axes is REFUSED rather
than resolved, because either side could be the stale one and teardown choosing
between a protected ship worktree and a scratch scout worktree by luck is how
unlanded work gets discarded.

The scout-to-ship operation becomes bin/fm-reflag.sh in the same change, since
it is what makes the stage axis true. The old name stays only as a bounded shim
that forwards, warns, and records each use, so its own retirement is settled by
evidence rather than by memory.

Coverage is red-capable: each guard was witnessed failing with its behavior
removed before being trusted green - the derivation table, dual-write,
backfill idempotence, the shim's evidence, and the refusal in the library, in
reflagging, and in teardown.

* docs: move the fleet's instructions onto the new names and axes

The rename and the axis split are only real once the instructions that drive
them say so, so this carries the fleet's own vocabulary across: the scout
outcome section reflags rather than promotes, the metadata field list names the
three axes and the deprecated field they replace, and the captain-facing
do-not-expose list drops a word the fleet no longer uses internally.

Knowledge routing gains one line: a word with a second live meaning goes to the
collision registry, never settled locally in whichever file it surfaced in.
That is the rule that keeps the registry from going stale the first time
someone is in a hurry.

Also moves teardown's admission release reminder into the admission library,
which already owns that policy. Its test previously reconstructed the function
by parsing teardown's source and eval-ing the fragment, so it broke the moment
the function read a variable defined outside it - and a test that reads
implementation source is exactly what CONTRIBUTING forbids. The reminder now
takes its inputs as arguments and the test calls it directly.

* fix(bin): move the watcher and nested-home checks onto the role axis

Two consumers still read the deprecated field: the watcher classified every
supervised window by it, and teardown's nested-home check read it on a parent
record. Both only ever ask whether they are looking at a persistent direct
report, so both are the role axis and neither needed what the work produces.

The watcher's helper is renamed to say what it answers, which is what made the
one remaining stale reference visible - a variable read with no assignment left,
caught at runtime by the triage suite rather than by lint.

* test: carry the axis library into the old-bin conformance fixture

The old-versus-new teardown conformance case builds a mixed tree: the entry
points come from the baseline commit while their siblings are copied from the
working tree. Several of those copied siblings now source the axis library, so
the fixture needs it present or the baseline entry point dies looking for it.

* docs: teach recovery and provisioning the identity axes

The recovery and secondmate procedures still told an agent to look for the
retired single field, so the instructions that decide which playbook applies
would have gone on naming a field the fleet is removing. Each of these asks
only who the worker is, so each now says role.

* docs: state plainly that the stage axis has one value nothing writes yet

The ruled value set includes delivered, and the spawn and the reflag write the
other two. Nothing writes delivered: the honest place to stamp it is after a
confirmed landing, inside the merge path's own private metadata rewrite, whose
ordering, device, and single-link invariants that path owns - so the writer is
a deliberate follow-up rather than something bolted on beside this split.

Say so in both the library and the registry, because a declared value that
never appears is otherwise read as evidence that the task did not land.

* refactor(bin): tidy the teardown identity block

Reads in the order it acts: the refusal and why it exists, then the axes it
protects. Also drops a local the remote path no longer reads and closes the gap
the moved admission reminder left.

* refactor(bin): drop teardown's now-unread copy of the deprecated field

Every teardown branch reads an axis now, and the ledger record carries role and
deliverable, so nothing consulted the alias teardown still parsed on the way in.

With that gone no consumer anywhere branches on the deprecated field, which
changes what its retirement is waiting for: not a reader migration, but a full
task cycle on the axes across every home. The registry says so precisely,
because a retirement condition nobody can evaluate is how an alias becomes
permanent.

* feat(bin): render the identity axes in the fleet view

The view's type column was the last thing reading the deprecated field, and it
could only ever show one of the three facts a row carries. It now renders the
role and deliverable together and appends the stage when it is not the spawn
default, so a reflagged ship is visible as one at a glance instead of being
indistinguishable from a commissioned one - which is the whole reason the
lifecycle axis exists.

* test: carry the axis library into the gotmp fake roots

Both fake roots symlink the real teardown and each sibling it sources, so the
new axis library has to be linked in beside them or teardown dies looking for it
before it reaches the behavior these cases assert.

* fix(bin): keep a polled record's PR identity intact when writing an axis

A task's metadata doubles as PR identity, and its parser refuses any
unrecognized key that appears after the pr= line - the rule that stops a
tampered record from smuggling a second identity past an armed merge poll.
Backfill appended, so the first startup sweep over a home with a live merge
watch invalidated exactly the records that had one, and the watcher stopped
honoring those polls. Nothing reported it, because a poll that is refused looks
the same as a poll with nothing to say.

Axis writes now land before the pr= line, through one helper both the backfill
and the reflag use, and the regression pins the position rather than only the
parse so a future writer cannot reintroduce it by appending again.

Found by the PR-check security suite, which was green on the base and red here.

* no-mistakes(review): make reflag atomic, dual-carry kind on v1 wire surfaces

* no-mistakes(document): migrate leftover scout-promotion wording to reflag

* no-mistakes(document): reflag retired promote verb in scout brief output
…tructing operational truth (#68)

* feat: consume a deterministic decision surface instead of reconstructing operational truth

Firstmate reconstructed operational facts conversationally - capacity, live
work, decision status - and the drift from the records was silent. The incident
this addresses was a report that queued work would dispatch "as capacity frees"
while nothing in the fleet was capacity-bound. Every fact needed to refute that
sentence was already recorded; nothing made it get read, and nothing refused
the sentence.

Add bin/fm-decision-surface.sh: a read-only composer over the already-landed
deterministic owners, plus three `check` verdicts that refuse a claim structured
state contradicts (a capacity claim against an admitting fleet, a ruled decision
reported pending, a dispatch of an identity already in flight). It adds no fact
of its own; every field names the owner it was read from. An unreadable census,
an undecidable admission policy, or an absent decision record is `unevaluable`
- the fact may not be asserted at all - never a quiet pass.

Rewire the instruction surface onto those owners and delete what they replace:
the capacity reasoning at intake, which now defers to the surface and keeps only
the semantic serialization judgment, and the run-step mapping restatement in
Validate, which bin/fm-crew-state.sh already owns in full.

Where no owner has landed, mark rather than delete. `owners` prints the durable
compensation ledger: each row is either owned by a landed command or pending
with the capability that must land first, and the skill maps every pending row
to the instruction it deliberately keeps alive. Attempt and retry counting, a
shared verifier verdict vocabulary, the pipeline invocation that replaces the
keystroke handoff, and backlog time gates all remain firstmate's for now.

Declare the platform seam without depending on it. The deterministic platform
publishes a richer projection - why_not_now, allowed transitions, path health -
and `platform-seam --probe-platform` measures its wiring rather than assuming
it. Probed against platform f0da880 the launcher answers but resolves none of
this home's fleet task ids, so the seam stays not-wired; consuming a projection
of other identities as fleet truth would be the same silent contradiction the
surface exists to prevent. config/decision-surface-platform holds one launcher
path, never a command line, so a private config file cannot become a
shell-execution seam.

Twelve behavior cases run against canned fm-fleet-snapshot.v1 documents with no
live fleet, worker, or platform. Each guarantee was confirmed by breaking it and
watching the suite fail; docs/verification/decision-surface.md records those
seven mutations and the probe evidence.

* no-mistakes(review): fix seam token match, probe kill grace, render, parsing

* fix: reconcile the decision surface with the landed identity axes and retry budget

The trunk landed two changes this surface must consume rather than talk past.

The identity-axis split replaced the overloaded `kind=` with role, deliverable,
and stage, keeping `kind` only as a deprecated compatibility alias. The task
projection read that alias, so it would have kept reading a field the census
retains only for migration. It now projects the three axes, and a test pins them
plus the absence of `kind` so a revert to the alias fails rather than passing
quietly.

The durable attempt-and-retry budget landed as bin/fm-attempt.sh, which makes
"should this be retried?" arithmetic over a recorded count. The compensation
ledger still marked that row pending, and the skill still listed the instruction
it was keeping alive. Both are wrong the moment the owner exists: a stale pending
row is exactly the silent gap the ledger exists to prevent, and this file's own
contract requires the row and its instruction to change in the same edit. The row
is now owned by bin/fm-attempt.sh and the skill's pending table drops it.
…cy (land of upstream kunchenguid#1923) (#66)

* fix(bin): allocate an empty pool slot instead of blockading on an occupied one

`treehouse get` hands out the first available slot and takes no slot
argument, so the pre-allocation guard could only refuse. One parked slot
therefore blockaded every spawn even when later slots were genuinely
empty, and the only way through was authorizing the parked slots by hand.

The guard now chooses as well as refuses: it names a demonstrably empty
slot that is parked at a detached HEAD or the default branch, and
fm-spawn acquires that slot by name with `treehouse enter`, which does
not reset it. An occupied slot is skipped untouched. The refusal is
preserved exactly where it still matters - with no empty slot to steer
to, the allocation falls back to `treehouse get`, so every available slot
must still be empty or explicitly authorized, and the refusal still names
each slot, its evidence and its apparent owner.

Liveness attribution no longer calls a worker gone on a stale recorded
pid alone. That pid is one process sampled when the slot was accepted, so
it stops matching for reasons that say nothing about the task. Stronger
bindings are read first and any one of them carries the live verdict:
HERDR_PANE_ID in a live process's environment matching the task's
recorded herdr_pane_id, GOTMPDIR matching its recorded tasktmp, or a live
process whose cwd is inside the slot.

Between choosing a slot and the pane's shell arriving in it, fm-spawn
holds the slot with one short-lived process of its own, because treehouse
reports a slot in-use while any process's cwd is inside it; the abort
path releases it.

tests/fm-worktree-guard.test.sh cases (s1) through (s4) and the rewritten
(o5) pin the skip, the preserved all-occupied refusal, both liveness
bindings with a negative control each, and the path-scoped reclaim
authority. docs/verification/worktree-allocation.md records the treehouse
behavior measured against v2.1.0.

* no-mistakes(review): serialize slot selection cross-home, record enter evidence, fix label

* no-mistakes(document): document slot-selecting pool allocation in remaining owner docs

* test(pool): give the directed-spawn case the reason code trunk now requires

Trunk began requiring --reason-code on every ship and scout spawn after this
branch was cut. The pre-existing spawn helper in this suite was updated on
trunk, but the pool-lock helper this branch adds was not, so its directed spawn
was refused before it could enter a slot. It now passes NL_RULE_CLASSIFICATION,
matching the helper beside it.
…land of upstream kunchenguid#1827) (#55)

* feat(bin): make an unobserved result a third value that cannot pass (land of upstream kunchenguid#1827)

Ports upstream PR kunchenguid#1827 onto this fork's trunk. Nothing here is redesigned:
bin/fm-verify.sh, bin/fm-verify-lib.sh, the PASS / FAIL / NO_VERIFIER_RAN law,
the check-conclusion partition (STARTUP_FAILURE as could-not-observe,
CANCELLED/TIMED_OUT as not-observed rather than FAIL), the shared rollup rule
for skipped/stale/neutral checks, the bearings label, and the witnessed-red
test controls are upstream's as written.

Provenance:
  upstream PR   kunchenguid#1827
  head commit   0c3afca
  its base      2cf0283
  landed onto   ed376cf (sbracewell64/firstmate main)

The two trunks diverged at upstream kunchenguid#1495, so the diff did not apply cleanly.
Resolutions, all of them fork-versus-upstream divergence rather than changes to
what kunchenguid#1827 does:

bin/fm-bearings-snapshot.sh - upstream sources bin/fm-timeout-lib.sh here; this
fork has no such file and inlines its own bounded gh call instead. Only the
fm-verify-lib.sh sourcing this contribution adds is kept. The check-rollup
splice itself applied unchanged.

bin/fm-test-run.sh - upstream's hunk carried three family entries; two of them
(fm-sessionstart-run.sh, fm-timeout-lib.sh) are for files this fork does not
have. Only the bin/fm-verify-lib.sh entry, which is this contribution's own,
is landed.

bin/fm-brief.sh - upstream had no verification-discipline block at all, so the
contribution introduced one. This fork already had one, in the older two-bullet
form, shared by the ship, scout and secondmate scaffolds. Its single definition
is replaced in place with the contribution's three-valued text rather than
adding a second definition, so all three scaffolds move together and the
one-owner rule holds. The header comment describing that block is updated to
the text it now emits.

tests/fm-brief.test.sh - both suites are kept. This fork's
test_standing_worker_rules_by_variant asserted the old block's wording; those
three assertions are re-pointed at the replacement text (witnessed negative
control, the three-valued rule, and missing-artifact-is-could-not-observe),
which is the same intent against the sentences that now exist. Upstream's
test_verification_discipline_is_the_type_rule is added alongside it.

docs/scripts.md - the fm-timeout-lib.sh row upstream's hunk carried does not
belong on this fork; the two fm-verify rows are landed.

Verified on this fork: bin/fm-lint.sh clean (ShellCheck 0.11.0, exit 0),
bin/fm-doc-audience-check.sh ok (surfaces=72 local_links=213), and
tests/fm-verify.test.sh, tests/fm-brief.test.sh and
tests/fm-bearings-snapshot.test.sh all pass.

* ci: bump the pinned Bearings count for the test this branch adds

The snapshot-compatibility job asserts an exact Bearings test count, and this
branch adds one Bearings case without moving the pin, so the job failed on this
PR before the rebase as well. Pinned count moved from 41 to 42, which is what
the suite now reports.
The implementation-committed -> validate transition, the fleet's most-travelled,
stops being actuated by firstmate typing `/no-mistakes` into a worker's composer.
The generated no-mistakes definition of done now has the worker call
`no-mistakes axi run --intent ...` itself as one blocking call the moment its
implementation commit lands, replacing the append-`done:`-and-stop step.

That actuator had a measured false-positive class: on 2026-07-03 two crewmates
were sent the trigger, both left it fully typed but unsubmitted in the composer
for minutes, and the send exited 0 with no error. It also spent a firstmate turn
whose entire semantic content was a transition the worker had already earned.

Consuming CFVC-07's contract, the brief has the worker judge the call by the run
result it prints rather than by its exit status: a return with no readable run
result is could-not-observe, which is never a pass and never a reason to retry.

Because a worker can now start a run while the one shared daemon is serving
another lane, the brief also carries the branch-scoping rule. A bare
`no-mistakes axi status` answers with another branch's run when the worker's own
branch has none, so a run whose `branch:` is not the worker's is another lane's
work: never responded to, aborted, or adopted, and never a reason to restart the
daemon.

Retirement, per the increment's contract: the per-harness keystroke quirk table
is deleted from the harness-adapters skill rather than wrapped, along with the
per-harness `Skill invocation` rows and the now-dangling skill-invocation load
triggers in AGENTS.md, firstmate-orca, stuck-crewmate-recovery, and
docs/configuration.md. The facts live code still depends on are kept and
re-anchored to command-shaped sends generally: codex's `$` popup settle scoping
and grok's slash-popup argument-hint hazard with its herdr submit-verification
fix. The exit command remains the only routine command-shaped steer.

No scheduler, watcher, queue, or wrapper is introduced: this is one command
inside an existing definition of done, and the shared-daemon prohibition is
unchanged.

Tests (tests/fm-worker-initiated-validation.test.sh) cover the three properties
the increment names, each absence assertion paired with a negative control that
reconstructs the retired shape and watches the same predicate go red. All four
cases were additionally witnessed failing against the pre-change generator
before being trusted green.

Rollout note: the change is harness-independent rather than staged per harness.
The replacement is a shell command every verified adapter already runs, so the
harness-dependent surface is removed rather than migrated, and gating it per
harness would add exactly the machinery the increment's certification forbids.
Already-scaffolded briefs keep their previous contract, so in-flight lanes are
unaffected; AGENTS.md section 7 tells firstmate to steer such a worker into the
run rather than restore the actuator.
@sbracewell64
sbracewell64 force-pushed the fm/cfvc-15-worker-calls-pipeline branch from 79915bc to 10fde35 Compare August 9, 2026 22:41
@sbracewell64

Copy link
Copy Markdown
Author

Closing: wrong venue, and the head bundles this fork's landing queue.

This pull request was opened against kunchenguid/firstmate main, but the branch is cut from a fork trunk that is ahead of upstream. The pushed head was rebased onto origin/main, so the pull request carries 63 commits, 179 changed files and +39,369/-1,240 - the fork's unlanded landing queue replayed onto upstream - while the actual change under review is 9 files and +218/-44.

The change itself is sound and unaffected: review returned low risk, and the pipeline's fixes are included. It is being re-raised at the fork venue on a fresh branch cut from the current fork trunk, carrying only its own commits.

No force-push was performed, and this branch is left in place rather than deleted.

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