Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
164 commits
Select commit Hold shift + click to select a range
4e4e4f3
feat: replay inactive terminal crew outcomes
JTInventory Aug 13, 2026
c7ce8e1
no-mistakes(review): Fixed inactive routing, rollback, and timeout po…
Aug 13, 2026
b9bf10a
no-mistakes(review): Propagated scan failures and rejected unsafe sta…
Aug 13, 2026
65ae712
no-mistakes(review): Fixed inactive replay isolation, routing, receip…
Aug 13, 2026
8794471
no-mistakes(review): Harden inactive replay claims and secondmate his…
Aug 13, 2026
b65deda
no-mistakes(review): Hardened inactive replay and secondmate route re…
Aug 14, 2026
10b8a8f
no-mistakes(review): Hardened inactive replay and history route recovery
Aug 14, 2026
52de8ce
no-mistakes(review): Hardened presentation recovery and secondmate pa…
Aug 14, 2026
009849c
no-mistakes(review): Hardened inactive replay and secondmate route re…
Aug 14, 2026
9cf6b9b
no-mistakes(review): Hardened replay recovery, scan retry, and route …
Aug 14, 2026
0ca206d
no-mistakes(review): Fixed undelivered cleanup and history-route replay
Aug 14, 2026
6d62061
no-mistakes(review): Fix secondmate acknowledgement route binding
Aug 14, 2026
1df3edf
no-mistakes(review): Fix wake drain, receipt retry, history binding
Aug 14, 2026
d27be5a
no-mistakes(review): Validate active routes and clamp oversized cadence
Aug 14, 2026
afef758
no-mistakes(review): Make cleanup correlation-aware and history repla…
Aug 14, 2026
237ab3b
no-mistakes(review): Fixed delivery-unknown fallback and deferred wak…
Aug 14, 2026
c09be49
no-mistakes(review): Suppress finalized rows and recover orphaned pen…
Aug 14, 2026
c6e50d3
no-mistakes(review): Make inactive outcome presentation crash-safe
Aug 14, 2026
2c345f9
no-mistakes(review): Harden inactive receipt recovery and route cleanup
Aug 14, 2026
99c1789
no-mistakes(review): Harden inactive presentation and secondmate rout…
Aug 14, 2026
4ca554c
no-mistakes(review): Harden post-output claims and durable route cleanup
Aug 14, 2026
2ed5676
no-mistakes(review): Harden deferred receipts and secondmate cleanup
Aug 14, 2026
22a7600
no-mistakes(review): Harden caller-confirmed receipts and transaction…
Aug 14, 2026
ca0c69c
no-mistakes(review): Harden inactive replay ownership and cleanup rec…
Aug 14, 2026
6a9685c
no-mistakes(review): Harden replay claims and transactional cleanup
Aug 14, 2026
29585d6
no-mistakes(review): Fix direct finalization and generation-bound wat…
Aug 14, 2026
5928ff6
no-mistakes(review): Retry deferred output completion before acknowle…
Aug 14, 2026
8724ea7
no-mistakes(review): Hardened replay retry and bounded receipt mainte…
Aug 14, 2026
804776b
no-mistakes(review): Applied fail-closed replay fixes; focused verifi…
Aug 14, 2026
09aaf3f
no-mistakes(review): Hardened generation-bound replay handoff
Aug 14, 2026
37f5d3e
no-mistakes(review): Hardened emission handoff and scan budget
Aug 14, 2026
a9e37e6
no-mistakes(review): Hardened uncertain output and surface retry hand…
Aug 14, 2026
d85c3c7
no-mistakes(review): Serialized replay and bounded maintenance scans
Aug 14, 2026
dc8cc91
no-mistakes(review): Hardened confirmation and replay retry safety
Aug 14, 2026
e4ec813
no-mistakes(review): Hardened deferred handoff and one-wake suppression
Aug 14, 2026
4ae5245
no-mistakes(review): Hardened deferred reports and uncertain wake pub…
Aug 15, 2026
b4db259
feat: replay inactive terminal crew outcomes
JTInventory Aug 13, 2026
5b789a2
no-mistakes(review): Fixed inactive routing, rollback, and timeout po…
Aug 13, 2026
b7d37b1
no-mistakes(review): Propagated scan failures and rejected unsafe sta…
Aug 13, 2026
709014b
no-mistakes(review): Fixed inactive replay isolation, routing, receip…
Aug 13, 2026
3dd778c
no-mistakes(review): Harden inactive replay claims and secondmate his…
Aug 13, 2026
2ad9ef0
no-mistakes(review): Hardened inactive replay and secondmate route re…
Aug 14, 2026
366b7e7
no-mistakes(review): Hardened inactive replay and history route recovery
Aug 14, 2026
649ee9c
no-mistakes(review): Hardened presentation recovery and secondmate pa…
Aug 14, 2026
0dae0d4
no-mistakes(review): Hardened inactive replay and secondmate route re…
Aug 14, 2026
b258281
no-mistakes(review): Hardened replay recovery, scan retry, and route …
Aug 14, 2026
80b2a4f
no-mistakes(review): Fixed undelivered cleanup and history-route replay
Aug 14, 2026
be22395
no-mistakes(review): Fix secondmate acknowledgement route binding
Aug 14, 2026
55d23a9
no-mistakes(review): Fix wake drain, receipt retry, history binding
Aug 14, 2026
6ce45ce
no-mistakes(review): Validate active routes and clamp oversized cadence
Aug 14, 2026
7a42d76
no-mistakes(review): Make cleanup correlation-aware and history repla…
Aug 14, 2026
22d348e
no-mistakes(review): Fixed delivery-unknown fallback and deferred wak…
Aug 14, 2026
8df0401
no-mistakes(review): Suppress finalized rows and recover orphaned pen…
Aug 14, 2026
6a4cb26
no-mistakes(review): Make inactive outcome presentation crash-safe
Aug 14, 2026
7f5a851
no-mistakes(review): Harden inactive receipt recovery and route cleanup
Aug 14, 2026
c46c2cb
no-mistakes(review): Harden inactive presentation and secondmate rout…
Aug 14, 2026
3598a07
no-mistakes(review): Harden post-output claims and durable route cleanup
Aug 14, 2026
1e367f6
no-mistakes(review): Harden deferred receipts and secondmate cleanup
Aug 14, 2026
f51adee
no-mistakes(review): Harden caller-confirmed receipts and transaction…
Aug 14, 2026
d41358d
no-mistakes(review): Harden inactive replay ownership and cleanup rec…
Aug 14, 2026
802049d
no-mistakes(review): Harden replay claims and transactional cleanup
Aug 14, 2026
1c5bb7d
no-mistakes(review): Fix direct finalization and generation-bound wat…
Aug 14, 2026
e6df9c2
no-mistakes(review): Retry deferred output completion before acknowle…
Aug 14, 2026
e580fb4
no-mistakes(review): Hardened replay retry and bounded receipt mainte…
Aug 14, 2026
90a0ec8
no-mistakes(review): Applied fail-closed replay fixes; focused verifi…
Aug 14, 2026
fb5e67b
no-mistakes(review): Hardened generation-bound replay handoff
Aug 14, 2026
40547b6
no-mistakes(review): Hardened emission handoff and scan budget
Aug 14, 2026
2129b23
no-mistakes(review): Hardened uncertain output and surface retry hand…
Aug 14, 2026
5101362
no-mistakes(review): Serialized replay and bounded maintenance scans
Aug 14, 2026
87d1981
no-mistakes(review): Hardened confirmation and replay retry safety
Aug 14, 2026
4046a19
no-mistakes(review): Hardened deferred handoff and one-wake suppression
Aug 14, 2026
79685dc
no-mistakes(review): Hardened deferred reports and uncertain wake pub…
Aug 15, 2026
ac1b890
no-mistakes(review): Captain: incarnation-bound pane-idle proof gates…
Aug 15, 2026
97ca57c
no-mistakes(review): Captain: replay now revalidates current pane idl…
Aug 15, 2026
7b396ed
no-mistakes(review): Revalidated pane idleness and honored effective …
Aug 15, 2026
067af8a
no-mistakes(review): Bounded watcher replay and effective-state recon…
Aug 15, 2026
e097022
no-mistakes(review): Harden inactive replay and generation-bound ackn…
Aug 15, 2026
eea5ab9
no-mistakes(review): Hardened inactive replay retries and generation-…
Aug 15, 2026
d2c1d59
no-mistakes(review): Hardened inactive replay retries and pane-proof …
Aug 15, 2026
d1c9ade
no-mistakes(review): Harden inactive replay retries and effective rou…
Aug 15, 2026
a9cc2e2
no-mistakes(review): Bound inactive replay retries and publication proof
Aug 15, 2026
63093a3
no-mistakes(review): Harden inactive replay locks and bounded retries
Aug 15, 2026
2cc0c70
no-mistakes(review): Bound cleanup and maintenance replay operations
Aug 15, 2026
7fe4ab4
no-mistakes(review): Bound secondmate maintenance replay proof
Aug 15, 2026
a1479d7
no-mistakes(review): Preserve legacy ship metadata replay
Aug 15, 2026
0fc13ca
no-mistakes(review): Bound replay persistence and cleared completed r…
Aug 15, 2026
e9ce44c
no-mistakes(review): Hardened retry merging, bounded pane indexing, a…
Aug 15, 2026
971250a
no-mistakes(review): Handled route races and bound parent-home valida…
Aug 15, 2026
48215d9
Merge commit 'refs/no-mistakes/recover/01M01F6YR9W919G8B6XVWRXSFV' in…
JTInventory Aug 15, 2026
4d52c2f
no-mistakes(review): Fix retry duplication and bound pane-index scans
Aug 15, 2026
a0c2c3d
no-mistakes(review): Durable retry recovery and bounded pane-index re…
Aug 15, 2026
3818e46
no-mistakes(review): Make pane indexing portable and replay state res…
Aug 15, 2026
1d79ef2
no-mistakes(review): Harden replay correlation and bounded scan publi…
Aug 15, 2026
4fa4f0a
no-mistakes(review): Bound replay scans with durable progress
Aug 15, 2026
1c14732
no-mistakes(review): Bound replay progress and preserve durable retry…
Aug 15, 2026
dbd93e3
no-mistakes(review): Refresh pane indexes and retain partial replay s…
Aug 15, 2026
ce5206b
no-mistakes(review): Strict cursor validation and resumable scan regr…
Aug 15, 2026
5f563c7
no-mistakes(review): Make pane-index publication retries idempotent
Aug 15, 2026
5d9d65f
no-mistakes(review): Bound watcher pane lookups and stale scans
Aug 15, 2026
205b464
no-mistakes(review): Retry metadata indexing across stamp races
Aug 15, 2026
4161933
no-mistakes(review): Bound snapshots, stale scans, and wake draining
Aug 15, 2026
ff83a30
no-mistakes(review): Harden bounded replay indexing and wake recovery
Aug 15, 2026
b89cf44
no-mistakes(review): Harden bounded replay and durable wake recovery
Aug 15, 2026
f6bef01
no-mistakes(review): Preserve wake progress across retries and timeouts
Aug 16, 2026
1023cb9
no-mistakes(review): Harden snapshot, receipt, queue, and backend val…
Aug 16, 2026
75dcd8c
no-mistakes(review): Harden durable wake recovery and tmux compatibility
Aug 16, 2026
a6a8e3e
no-mistakes(review): Preserve durable inactive replay across interrup…
Aug 16, 2026
af88b1a
no-mistakes(review): Make inactive scans resumable and supervision bo…
Aug 16, 2026
3ad5f5c
no-mistakes(review): Harden resumable replay cursors and bounded reco…
Aug 16, 2026
6354329
no-mistakes(review): Harden bounded resumable inactive replay
Aug 16, 2026
f909f74
no-mistakes(review): Fix metadata freshness and event cursor retention
Aug 16, 2026
10341b3
no-mistakes(review): Recovery-safe freshness ordering fixed
Aug 16, 2026
3980df3
no-mistakes(review): Hardened inactive replay and wake-drain recovery
Aug 16, 2026
638343b
no-mistakes(review): Reject ambiguous indexed pane metadata hints
Aug 16, 2026
0c09172
no-mistakes(review): Transactional wake rollback and secondmate recov…
Aug 16, 2026
0015d1b
no-mistakes(review): Remove finalized wakes and validate receipt iden…
Aug 16, 2026
768858b
no-mistakes(review): Bind secondmate replay suppression to parent cor…
Aug 16, 2026
bc67772
no-mistakes(review): Make wake transaction recovery cleanup idempotent
Aug 16, 2026
d6da8a8
no-mistakes(review): Reject legacy secondmate receipt fingerprints
Aug 16, 2026
298d966
no-mistakes(review): Bind inactive replay to canonical snapshots and …
Aug 16, 2026
d1d3cc6
no-mistakes(review): Harden run-step replay, route correlation, and w…
Aug 16, 2026
96e619e
no-mistakes(review): Require bound run evidence and retire restore so…
Aug 16, 2026
48087a6
no-mistakes(review): Harden run bindings and watcher lock ordering
Aug 16, 2026
91dad14
no-mistakes(review): Reject run rebinding within an incarnation
Aug 16, 2026
4817e33
no-mistakes(review): Enforce exact routes, lock context, and global p…
Aug 16, 2026
b043f5e
no-mistakes(review): Bind terminal evidence and retire owned drain so…
Aug 16, 2026
40f2e0a
no-mistakes(review): Require pre-bound terminal evidence and watcher-…
Aug 16, 2026
2816c66
no-mistakes(review): Advance resumable cursors and portable watcher t…
Aug 16, 2026
fc0e584
no-mistakes(review): Require explicit lifecycle run bindings
Aug 16, 2026
3362804
no-mistakes(review): Enforced lifecycle bindings and bounded metadata…
Aug 16, 2026
584363d
no-mistakes(review): Generate spawn run bindings and validate exact t…
Aug 16, 2026
d51e5d9
no-mistakes(review): Require real run IDs and preserve once-only wake…
Aug 16, 2026
c5ce90d
no-mistakes(review): Add real run-binding bridge and ordered deferred…
Aug 16, 2026
d54243f
no-mistakes(review): Make run binding publication generation-checked …
Aug 16, 2026
2fcb23a
no-mistakes(review): Rollback run evidence and advance reclaim cursors
Aug 16, 2026
15b6fd2
no-mistakes(review): Stage run evidence before metadata activation
Aug 16, 2026
c95e7b7
no-mistakes(review): Make run-binding publication failures recoverable
Aug 16, 2026
9c04f01
no-mistakes(review): Reject staged run binding rebinding
Aug 16, 2026
a7981cd
no-mistakes(review): Reject evidence rebinding and preserve read-only…
Aug 16, 2026
9ae6a84
no-mistakes(review): Harden inactive replay binding and attribution c…
Aug 16, 2026
f431969
no-mistakes(review): Preserve legacy ship fallback while rejecting du…
Aug 16, 2026
cbf2637
no-mistakes(review): Harden Herdr event source writes against symlink…
Aug 16, 2026
be58821
no-mistakes(review): Hardened replay parsing, bounded writes, metadat…
Aug 17, 2026
b144065
no-mistakes(review): Validated complete run output and hardened no-fo…
Aug 17, 2026
0f4d3fa
no-mistakes(review): Harden inactive replay provenance and recovery w…
Aug 17, 2026
5e54267
no-mistakes(review): Fail closed pending bindings, task paths, and br…
Aug 17, 2026
fbf5a54
no-mistakes(review): Hardened replay writes, wakes, and process cleanup
Aug 17, 2026
750f9b3
no-mistakes(review): Hardened replay bindings, route correlation, and…
Aug 17, 2026
e598295
no-mistakes(review): Hardened route reuse and bridge descendant cleanup
Aug 17, 2026
dda13f0
no-mistakes(review): Harden wake schemas, wrapper writes, and drain o…
Aug 17, 2026
ca71b8f
no-mistakes(review): Fix bridge waits, pipelines, chmod, and wake val…
Aug 17, 2026
344d170
no-mistakes(review): Harden route, queue, drain, and pane persistence
Aug 17, 2026
4f9673c
no-mistakes(review): Hardened staged recovery, queue durability, and …
Aug 17, 2026
7beab6f
no-mistakes(review): Hardened durable publication boundaries against …
Aug 17, 2026
bddd4f6
no-mistakes(review): Hardened read-only state and durable replay publ…
Aug 17, 2026
742af3a
no-mistakes(review): Hardened bridge cleanup against PID reuse
Aug 17, 2026
2318cec
no-mistakes(test): Preserved worker declaration before run-binding PA…
Aug 17, 2026
8451a57
no-mistakes(test): Fixed optional worktree parsing; focused teardown …
Aug 17, 2026
e42c2e8
no-mistakes(document): Documented inactive terminal replay contracts
Aug 17, 2026
edc7cc2
no-mistakes(lint): Captain: fix inactive reconciliation ShellCheck wa…
Aug 17, 2026
eb34a02
no-mistakes(lint): Fix ShellCheck warnings across scripts and fixtures
Aug 17, 2026
ddb20e7
no-mistakes: apply CI fixes
Aug 17, 2026
142d54e
no-mistakes: apply CI fixes
Aug 17, 2026
5352241
no-mistakes: apply CI fixes
Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ jobs:
run: |
set -eu
bash tests/fm-worker-isolation.test.sh
bash tests/fm-inactive-outcome.test.sh
bash tests/fm-watch-session.test.sh
bash tests/fm-slot-occupant-proof.test.sh
FM_TEARDOWN_TEST_FOCUS=s1 bash tests/fm-teardown.test.sh
Expand Down
8 changes: 5 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,13 @@ data/ personal fleet records; LOCAL, gitignored as a whole
<id>/brief.md per-task crewmate brief, or per-secondmate charter brief when kind=secondmate
<id>/report.md scout task deliverable, written by the crewmate; survives teardown
projects/ cloned repos; gitignored; READ-ONLY for you
state/ volatile runtime signals; gitignored
state/ gitignored operational state: volatile runtime signals plus durable local inactive-outcome receipts
<id>.status appended by crewmates: "<state>: <note>" wake-event lines, not current-state truth
<id>.turn-ended touched by turn-end hooks
<id>.meta written by fm-spawn: window=, worktree=, project=, harness=, kind=, mode=, yolo=; kind=secondmate also records home= and projects= (fm-pr-check appends pr= and verified pr_head= when available)
<id>.meta written by fm-spawn: window=, worktree=, project=, harness=, kind=, mode=, yolo=, and spawn_incarnation=; no-mistakes ship tasks also carry the run-binding handoff/state and, after binding, run_id=; kind=secondmate also records home= and projects= (fm-pr-check appends pr= and verified pr_head= when available)
<id>.check.sh optional slow poll you write per task (e.g. merged-PR check)
terminal-outcomes/ fingerprinted local inactive terminal-outcome receipts; the locked wake drain is the only path that presents and acknowledges them
.inactive-outcome-* bounded scan, claim, cursor, and retry state; never touch
x-watch.check.sh generated X-mode relay poll shim; present only when opted in (section 14)
x-inbox/ generated X-mode pending mention payloads; fmx-respond drains it (section 14)
x-outbox/ generated X-mode dry-run reply previews; inspect it when FMX_DRY_RUN is set (section 14)
Expand Down Expand Up @@ -393,7 +395,7 @@ If one pair fails, the rest still run and the batch exits non-zero.
The script resolves the harness (`fm-harness.sh crew`), owns the verified launch templates, resolves the project's delivery mode (`fm-project-mode.sh`) for ship/scout tasks, and records `harness=`, `kind=`, `mode=`, and `yolo=` in the task's meta; a non-flag third argument containing whitespace is treated as a raw launch command (only for verifying new adapters).
For `kind=secondmate`, the same script launches in the registered or explicit firstmate home instead of running `treehouse get` for a project, records `home=` and `projects=`, and uses the charter brief as the launch prompt.

For ship and scout tasks, the script creates the window (in your current tmux session, or a dedicated `firstmate` session when you are outside tmux), runs `treehouse get`, waits for the worktree subshell, asserts the resolved worktree is a genuine isolated worktree distinct from the primary checkout (aborting the spawn otherwise, to prevent the worktree tangle of section 8), installs the turn-end hook, records `state/<id>.meta`, and launches the agent with the brief.
For ship and scout tasks, the script creates the window (in your current tmux session, or a dedicated `firstmate` session when you are outside tmux), runs `treehouse get`, waits for the worktree subshell, asserts the resolved worktree is a genuine isolated worktree distinct from the primary checkout (aborting the spawn otherwise, to prevent the worktree tangle of section 8), installs the turn-end hook, records `state/<id>.meta`, and launches the agent with the brief. No-mistakes ship spawns install a task-local command bridge in the child PATH; when the child invokes `no-mistakes axi run`, the bridge captures the actual returned run ID and publishes its binding under the existing task lock before the command can continue. Ship spawns never fabricate or infer a run ID, and refuse to launch if this handoff cannot be installed.
For `kind=secondmate`, the script creates the same kind of window but starts directly in the persistent home.
Before launching a secondmate, the script fast-forwards its home worktree to firstmate's own current default-branch commit, so a freshly spawned or recovery-respawned secondmate always starts on firstmate's current version.
This is a purely local fast-forward of tracked files - never a fetch from origin, and never touching the gitignored operational dirs - so the secondmate's backlog, projects, and any prior in-flight work are untouched; a dirty, diverged, or in-flight home is left as-is and launches unchanged.
Expand Down
1 change: 1 addition & 0 deletions bin/backends/herdr.sh
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ fm_backend_herdr_server_ensure() { # <session>
local session=$1 running out i
running=$(fm_backend_herdr_cli "$session" status --json 2>/dev/null | jq -r '.server.running // false' 2>/dev/null)
[ "$running" = "true" ] && return 0
[ "${FM_BACKEND_HERDR_NO_SERVER_START:-0}" = 1 ] && return 1
( fm_backend_herdr_cli "$session" server >/dev/null 2>&1 & ) || return 1
for i in $(seq 1 20); do
running=$(fm_backend_herdr_cli "$session" status --json 2>/dev/null | jq -r '.server.running // false' 2>/dev/null)
Expand Down
153 changes: 134 additions & 19 deletions bin/fm-crew-state.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ STATE="${FM_STATE_OVERRIDE:-$FM_HOME/state}"
. "$SCRIPT_DIR/fm-backend.sh"
# shellcheck source=bin/fm-numeric-lib.sh
. "$SCRIPT_DIR/fm-numeric-lib.sh"
# shellcheck source=bin/fm-wake-lib.sh
FM_SESSION_LOCK_BOOTSTRAP=1
FM_WAKE_LIB_NO_INIT=1
. "$SCRIPT_DIR/fm-wake-lib.sh"
# shellcheck source=bin/fm-run-step-lib.sh
FM_RUN_STEP_READ_ONLY=1
. "$SCRIPT_DIR/fm-run-step-lib.sh"
trap 'fm_run_step_binding_release_held >/dev/null 2>&1 || true' EXIT

ID=${1:-}
[ -n "$ID" ] || { echo "usage: fm-crew-state.sh <id>" >&2; exit 2; }
Expand Down Expand Up @@ -368,6 +376,62 @@ nm_run_ids_for_branch() { # <branch> <list-output>
# CREW_BRANCH is empty at detached HEAD (a just-spawned crew, or a scout's
# scratch worktree); with no branch there is no run to attribute to this crew.
CREW_BRANCH=$(git -C "$WT" symbolic-ref --quiet --short HEAD 2>/dev/null || true)
META_INCARNATION=$(awk -F= '$1 == "spawn_incarnation" { print substr($0, index($0, "=") + 1); n++ } END { exit(n == 1 ? 0 : 1) }' "$META" 2>/dev/null || true)
META_SPAWN_STATE=absent
META_SPAWN_STATE_COUNT=0
META_BINDING_STATE=absent
META_BINDING_STATE_COUNT=0
while IFS= read -r META_BINDING_LINE; do
case "$META_BINDING_LINE" in
spawn_state=*)
META_SPAWN_STATE_COUNT=$((META_SPAWN_STATE_COUNT + 1))
META_SPAWN_STATE=${META_BINDING_LINE#*=}
;;
run_binding_state=*)
META_BINDING_STATE_COUNT=$((META_BINDING_STATE_COUNT + 1))
META_BINDING_STATE=${META_BINDING_LINE#*=}
;;
esac
done < "$META"
if [ "$META_SPAWN_STATE_COUNT" -ne 0 ]; then
[ "$META_SPAWN_STATE_COUNT" = 1 ] || META_SPAWN_STATE=invalid
case "$META_SPAWN_STATE" in
starting|aborted) ;;
*) META_SPAWN_STATE=invalid ;;
esac
fi
if [ "$META_BINDING_STATE_COUNT" -ne 0 ]; then
[ "$META_BINDING_STATE_COUNT" = 1 ] || META_BINDING_STATE=invalid
case "$META_BINDING_STATE" in
pending|staged|bound) ;;
*) META_BINDING_STATE=invalid ;;
esac
fi
BOUND_RUN_ID=
BOUND_RUN_STATUS=75
BOUND_RUN_REQUIRED=0
BOUND_RUN_EVIDENCE=$(fm_run_step_binding_path "$ID") || exit 1
if [ -e "$BOUND_RUN_EVIDENCE" ] || [ -L "$BOUND_RUN_EVIDENCE" ]; then
BOUND_RUN_REQUIRED=1
fi
case "$META_BINDING_STATE" in
pending|bound|staged|invalid) BOUND_RUN_REQUIRED=1 ;;
esac
case "$META_SPAWN_STATE" in
starting|aborted|invalid) BOUND_RUN_REQUIRED=1 ;;
esac
if [ "$KIND" = ship ] && [ -n "$CREW_BRANCH" ] && [ -n "$META_INCARNATION" ]; then
if fm_run_step_binding_read_held "$ID" "$META_INCARNATION"; then
BOUND_RUN_ID=$FM_RUN_STEP_HELD_VALUE
BOUND_RUN_STATUS=0
else
BOUND_RUN_STATUS=$?
fi
[ "$BOUND_RUN_STATUS" = 1 ] && {
printf '%s\n' 'state: unknown · source: run-step · incarnation binding could not be read' >&2
exit 1
}
fi

# A branch match is not enough when branch names are reused. Accept a run only
# when its recorded head is the local worktree HEAD, or a descendant of that
Expand All @@ -387,30 +451,49 @@ HAVE_RUN=0
# Scouts and secondmates never drive a no-mistakes validation of their own
# worktree, so skip the lookup for them and read state from pane/log directly.
if [ "$KIND" = ship ] && [ -n "$CREW_BRANCH" ] && command -v no-mistakes >/dev/null 2>&1; then
RUN_OUT=$(nm_run axi status)
if [ -n "$RUN_OUT" ]; then
run_branch=$(strip_quotes "$(nm_field branch)")
if [ -n "$run_branch" ] && [ "$run_branch" = "$CREW_BRANCH" ] && nm_run_head_matches_worktree; then
HAVE_RUN=1
if [ "$BOUND_RUN_STATUS" = 0 ] || [ "$BOUND_RUN_REQUIRED" = 1 ]; then
BOUND_RUN_REQUIRED=1
if [ "$BOUND_RUN_STATUS" = 0 ]; then
RUN_OUT=$(nm_run axi status --run "$BOUND_RUN_ID")
run_id=$(strip_quotes "$(nm_field id)")
run_branch=$(strip_quotes "$(nm_field branch)")
if [ "$run_id" = "$BOUND_RUN_ID" ] && [ "$run_branch" = "$CREW_BRANCH" ] \
&& nm_run_head_matches_worktree; then
HAVE_RUN=1
else
RUN_OUT=
fi
else
# The active-or-most-recent run is for another branch, or its branch name
# matches but its code identity does not. Inspect bounded recent runs for
# this branch until one is compatible with the worktree.
list_out=$(nm_run axi)
candidate_ids=$(nm_run_ids_for_branch "$CREW_BRANCH" "$list_out")
while IFS= read -r rid; do
[ -n "$rid" ] || continue
RUN_OUT=$(nm_run axi status --run "$rid")
run_branch=$(strip_quotes "$(nm_field branch)")
if [ "$run_branch" = "$CREW_BRANCH" ] && nm_run_head_matches_worktree; then
HAVE_RUN=1
break
fi
done <<< "$candidate_ids"
RUN_OUT=
fi
else
RUN_OUT=$(nm_run axi status)
if [ -n "$RUN_OUT" ]; then
run_branch=$(strip_quotes "$(nm_field branch)")
if [ -n "$run_branch" ] && [ "$run_branch" = "$CREW_BRANCH" ] && nm_run_head_matches_worktree; then
HAVE_RUN=1
else
# The active-or-most-recent run is for another branch, or its branch name
# matches but its code identity does not. Inspect bounded recent runs for
# this branch until one is compatible with the worktree.
list_out=$(nm_run axi)
candidate_ids=$(nm_run_ids_for_branch "$CREW_BRANCH" "$list_out")
while IFS= read -r rid; do
[ -n "$rid" ] || continue
RUN_OUT=$(nm_run axi status --run "$rid")
run_branch=$(strip_quotes "$(nm_field branch)")
if [ "$run_branch" = "$CREW_BRANCH" ] && nm_run_head_matches_worktree; then
HAVE_RUN=1
break
fi
done <<< "$candidate_ids"
fi
fi
fi
fi

[ "$HAVE_RUN" = 1 ] || [ "$BOUND_RUN_REQUIRED" = 0 ] || emit unknown run-step "bound run unavailable"

# --- run-step authoritative path -------------------------------------------

if [ "$HAVE_RUN" = 1 ]; then
Expand Down Expand Up @@ -530,6 +613,38 @@ if [ "$HAVE_RUN" = 1 ]; then
RUN_DETAIL="$RUN_DETAIL${SEP}convergence-round=$convergence_round${SEP}convergence-fingerprint=unavailable"
fi

run_id=$(strip_quotes "$(nm_field id)")
run_step_id_count=$(grep -c '^run_step_id=' "$META" 2>/dev/null || true)
run_step_id=
if [ "$run_step_id_count" = 1 ]; then
run_step_id=$(grep '^run_step_id=' "$META" 2>/dev/null | cut -d= -f2- || true)
fi
incarnation=$(awk -F= '$1 == "spawn_incarnation" { print substr($0, index($0, "=") + 1); n++ } END { exit(n == 1 ? 0 : 1) }' "$META" 2>/dev/null || true)
case "$RUN_STATE" in
done|failed)
case "$run_step_id_count" in
0) run_step_id_matches_meta=1 ;;
1) [ "$run_step_id" = "$run_id" ] && run_step_id_matches_meta=1 || run_step_id_matches_meta=0 ;;
*) run_step_id_matches_meta=0 ;;
esac
if [ "$run_step_id_matches_meta" != 1 ] || [ -z "$run_id" ] || [ -z "$incarnation" ]; then
printf '%s\n' 'state: unknown · source: run-step · incarnation binding unavailable' >&2
exit 75
fi
fm_run_step_binding_validate "$ID" "$run_id" "$incarnation" || {
status=$?
printf '%s\n' 'state: unknown · source: run-step · incarnation binding unavailable' >&2
[ "$status" = 75 ] && exit 75
exit 1
}
;;
esac
case "$RUN_STATE" in
working|parked|paused|done|failed)
[ -n "$run_id" ] && RUN_DETAIL="$RUN_DETAIL${SEP}run-id=$run_id"
;;
esac

emit "$RUN_STATE" run-step "$RUN_DETAIL"
fi

Expand Down
Loading