Skip to content

Merge upstream round 4 through kunchenguid/firstmate@0866a770 - #267

Merged
HelloWorldSungin merged 10 commits into
mainfrom
fm/fm-upstream-sync-2026-09-02-round-4
Sep 4, 2026
Merged

HelloWorldSungin merged 10 commits into
mainfrom
fm/fm-upstream-sync-2026-09-02-round-4

Conversation

@HelloWorldSungin

@HelloWorldSungin HelloWorldSungin commented Sep 4, 2026

Copy link
Copy Markdown
Owner

Round 4 of the TRACK sync plan. One upstream merge of the contiguous first-parent range from measured merge base 9e3df47b to endpoint kunchenguid/firstmate@0866a770234502364c268a768cb7c66cc321c629, seven changes, on fork base 0f364f17dada691c2bde5d2a7e244e21fbba64e0.

No cherry-pick, no rebase, no squash, no force. The merge commit's parents are exactly 0f364f17 and 0866a770. Upstream had advanced past the plan's ceiling at intake and that did not widen this round; re-planning the tail is a separate decision.

Applicability of the seven changes

# Upstream change Verdict
21 kunchenguid/firstmate#3247 feat(bin): add trusted process-event extension bindings (1fbc7bb1) Taken, with one adapted fixture. The extension package, binding, handshake, and evidence boundary are new files with no fork counterpart. .agents/skills/process-event-sources/SKILL.md and docs/captain-hold-lifecycle.md merged as unions with this fork's Lavish artifact-missing verdict kept beside upstream's new generic classify routing and read command. Upstream's remote extension fixture mirrors a fixed list of bin/ files onto a fake remote root; this fork's bin/fm-remote-job-lib.sh sources bin/fm-timeout-lib.sh, which that list did not carry, so the remote entrypoint refused before it could publish a binding. The owner was added to the list. This change also carries an inherited red described under "Attributed failures" below.
22 kunchenguid/firstmate#3269 fix(bin): deliver safety rules to promoted workers (c7fdef92) Conflicted with fork work, resolved by extending upstream's owner. See "C2" below.
23 kunchenguid/firstmate#3321 fix(bin): present Lavish feedback as structured output (debe4bfa) Taken, textual resolution only. bin/fm-procevent-lavish.sh gains upstream's read command beside this fork's canonical_artifact_path and gone-path retire. Both sides edited the header and usage(); the header is the union and the fork's growth-proof usage() (which prints every leading comment line) is kept over upstream's hardcoded sed -n '2,107p' line range, because upstream's range no longer covers the merged header.
24 kunchenguid/firstmate#3322 fix: keep task records and backlog transitions atomic (1260adce) Taken as a contract change, with two ordering adaptations. See "Change 24" below.
25 kunchenguid/firstmate#3342 fix(bin): contain promote and Relay metadata publishing (d71f4b9c) Taken cleanly. No fork hunk in the contained region.
26 kunchenguid/firstmate#2877 fix(bin): absorb turn-end wakes during bounded pane churn (a56a78ac) Taken cleanly in code, prose merged as a union. As the round plan predicted, the churn absorber does not overlap this fork's trap-hygiene or wedge-hold watcher changes; bin/fm-watch.sh had no code conflict. The two conflicts were both header prose, resolved as unions so config/turnend-churn-absorb is described beside this fork's first-sighting, keyed-open-decision, and declared-wait absorb rules. docs/architecture.md, docs/configuration.md, and AGENTS.md carry the same union.
27 kunchenguid/firstmate#3369 fix(bin): safely unregister custom checks (0866a770) Taken cleanly. bin/fm-check-unregister.sh is new; AGENTS.md section 7 and docs/scripts.md gained its contract row beside this fork's existing fm-check-register.sh sentence.

C2 - who owns the definition of done (change 22)

Upstream moved the definition of done into a new bin/fm-dod-lib.sh rendered by both bin/fm-brief.sh and bin/fm-promote.sh, so a promoted scout receives the same delivery contract a briefed worker does. That library hardcodes fm/<task-id>, knows no design kind, and hands a no-mistakes worker off with done: {summary}.

This fork renders four things it cannot: the --continue-branch task branch (HelloWorldSungin/firstmate#149), the ADR design kind (HelloWorldSungin/firstmate#126), the configured declared-wait verb, and the canonical blocked: implemented and committed, ready to validate handoff that bin/fm-trigger-validation.sh's canonical_handoff resolves. That string exists nowhere upstream.

Resolution: upstream's library is adopted as the owner and extended, rather than the fork keeping an inline copy. It now exposes fm_dod_fragments, fm_dod_fragments_continue_branch, fm_dod_render, and upstream's fm_dod_block. bin/fm-brief.sh sets the fragments its task shape needs and renders; bin/fm-promote.sh still calls fm_dod_block, which is that same rendering for a default ship task. The declared-wait verb is resolved from bin/fm-classify-lib.sh's own constant rather than restated. Upstream's stronger --yes/-y prohibition is taken in full, in both the ship and ADR renderings and in CONTRIBUTING.md.

Extending the library rather than keeping the fork's inline rendering was chosen because it is the only option that gives a promoted scout the fork's handoff too. Under the alternative, a promoted no-mistakes worker would still have reported done: and never reached this fork's validation trigger.

Behavioural proof, not file presence. A new regression, test_generated_brief_handoff_is_the_line_the_trigger_closes in tests/fm-trigger-validation.test.sh, generates a real ship brief, takes the handoff status line out of it with no restatement, appends that line, runs bin/fm-trigger-validation.sh, and asserts the open-decision fold is empty afterwards. It was proved to fail on drift: rewording the handoff in bin/fm-dod-lib.sh to "ready for validation" turns it red with the trigger did not close the handoff the generated brief tells a worker to append: blocked: implemented and committed, ready for validation. That is the exact silent break this collision risked, and nothing else in the suite caught it. Upstream's own byte-identity check between the promote and brief paths stays green, so the two renderings still cannot drift.

The generated brief's only other change against fork main is upstream's --yes wording; the golden fixture and the exact design-DOD expectation were refreshed to match, and every other byte of all six brief variants is unchanged.

Change 24 - atomic backlog transitions

Upstream makes bin/fm-spawn.sh and bin/fm-teardown.sh run the tasks-axi start/done transitions themselves through bin/fm-backlog-transition-lib.sh, records a pending close in state/<id>.backlog-close before destructive cleanup, and replaces teardown's rm -f of the task record with a close.

Taken, with the ordering this fork requires:

  • The completion manifest still precedes the close. publish_outcome_manifest, the GBrain capture it wraps, and the model-watermark append all still run before the close that removes the record, and $STATE/$ID.meta was removed from the later receipt rm -f lists on both the main and remote-secondmate paths so the validated transition owns it.
  • A preserving refusal now withdraws the close it recorded. The pending-close record exists so a crash mid-cleanup can still be finished, which upstream's own test_interrupted_destructive_cleanup_leaves_a_recoverable_close pins by SIGTERM-ing teardown during the worktree return. This fork's cleanup also carries refusals upstream has no equivalent of that deliberately keep a task: an unpublishable completion manifest, a model-routing verdict that gained a turn, and a Herdr close that could not be confirmed gone. Left as-is, the next session start would replay the pending close and remove the very record those refusals exist to preserve. withdraw_pending_backlog_close is called at exactly those refusal sites, and deliberately not from the exit trap, which a signal would also reach. A new colocated regression, test_a_preserving_refusal_withdraws_the_close_it_recorded in tests/fm-backlog-atomicity.test.sh, drives a real teardown whose manifest cannot be published and then runs a real bootstrap, asserting the item stays In flight and the record survives the restart. Both halves now pin each other.
  • A watermark-capture failure keeps its published record. Upstream removes a published record when the dispatch fails after publication. This fork's claude dispatch captures a model-evidence watermark after that record is published and after the endpoint and worktree already exist, and refuses when the capture fails; unwinding there would strand a live pane and local copy with nothing naming them. bin/fm-spawn.sh clears its rollback intent on exactly those two refusals. The backlog item stays Queued, which upstream's own worker-record reconciliation converges to In flight at the next locked session start, so the state this leaves is one upstream already knows how to correct. Every other post-publication failure keeps upstream's rollback unchanged, and test_claude_watermark_failure_preserves_recoverable_metadata pins the retained record.

Two other adaptations this change forced:

  • tests/fm-teardown.test.sh's two tasks-axi done prompt cases are superseded by upstream's two backlog-close cases and were dropped with the functions upstream deleted; upstream's seed_backlog_in_flight and backlog_row_state helpers are carried, and the fixture record now writes spawn_gen= beside this fork's branch=.
  • tests/fm-endpoint-binding-migrate.test.sh's fixture home gained a data/ directory, because cleanup now resolves the home's backlog directory before it can reach the binding refusal that test is about.

Where the trees disagreed with the round plan

The plan (data/fm-upstream-issue-reconcile/report.md, dated 2026-09-02) lists 40 paths changed on both sides for this round. The measured set is 41: tests/fm-secondmate-reconcile.test.sh is also in it, and it conflicted. Both sides had written the same four-hour cooldown boundary assertion in different shapes; upstream's, which pins the reconcile's clock through a fake date, replaced this fork's minute-wide bracket, because pinning the clock is the stronger form of the same assertion rather than a workaround for host load.

Everything else in the plan's sections 3.3, 3.4, and 4 for this round matched what the trees actually contained, including its prediction that change 26 would not overlap the fork's watcher changes.

Test and lint evidence

All three runs used the same command shape on this host: every portable CI lane (portable-parallel-1, portable-parallel-2, and every portable-serial-<k>of<n> shard) run concurrently, plus bin/fm-lint.sh. Parent baselines were measured in isolated temporary clones so this branch and its merge structure were never touched.

scripts run failing lint
Fork parent 0f364f17 179 1 pass
Upstream parent 0866a770 161 14 pass
Merge result 182 8 pass

bin/fm-doc-audience-check.sh (126 surfaces, 717 local links) and bin/fm-pointer-check.sh (32 checked, 0 broken) both pass on the merge result.

Attributed failures

Every one of the merge result's eight failures reproduces on the upstream parent with the identical assertion text, and none of them fails on the fork parent:

Script fork 0f364f17 upstream 0866a770 merge Failing assertion
fm-procevent.test.sh pass fail fail reconcile never claimed the registered source
fm-procevent-when.test.sh pass fail fail the winning concurrent arm did not produce an outcome
fm-remote-reply.test.sh pass fail fail process-event runner never claimed the remote reply source
fm-watch-triage.test.sh pass fail fail the fixture captured no process-event result
fm-remote-secondmate-lifecycle-e2e.test.sh pass fail fail remote reply source did not capture the correlated answer
fm-captain-hold-lifecycle.test.sh pass fail fail dies silently at bin/fm-procevent.sh start fixture-src
fm-bearings-board.test.sh pass fail fail the order-proof board build failed
fm-extension-binding.test.sh pass fail fail local bind returned no binding retirement identity, an isolated extension conformance section failed

The first six share one root cause and it was bisected: running tests/fm-procevent.test.sh in an isolated clone of the upstream line passes at 9e3df47b (this round's merge base) and fails at 1fbc7bb1 (change 21). The minimal reproduction is two commands in a scratch home:

bin/fm-procevent.sh register lavish demo-src -- cat /etc/hostname   # registered: demo-src (lavish)
bin/fm-procevent.sh start demo-src                                  # error: cannot claim source: demo-src

The claim runs inside the re-executed _start child, so the failure is inside upstream's new claim path rather than anything this fork touched, and it does not depend on FM_PROCEVENT_CLAIM_ROOT (an isolated claim root fails the same way). fm-extension-binding's two remaining reds are error[mode-unsafe]: ... mode is unsafe: 775, which is this host's umask 002 rather than a code fault.

Settled by this PR's own CI: all eight were host-specific. Every behaviour lane is green on head ece09b03d220fd0e00eed9c025bf82c34e48d79b - both portable parallel shards, all eight portable serial shards, the required real-Herdr lane, and the stock macOS Bash compatibility lane - so none of these eight failures reproduce in a clean environment. They are this development host's umask 002, its live process-event claim root, and its installed herdr, not defects this round carries. The bisection to change 21 still stands as a local-environment sensitivity worth knowing about; it is not a reason to hold this round.

Nothing was widened, skipped, or quieted to make a red pass. The merge result has fewer failures than the upstream parent because six of upstream's fourteen (fm-bootstrap, fm-watcher-lock, fm-on, fm-remote-doctor, fm-session-start, fm-test-run) are shapes this fork already diverges on and they pass here.

The fork parent's single failure, fm-backend-herdr.test.sh (container_ensure should echo ..., got empty), passes on the merge result. It is a flake in that suite against a real herdr on this host, not something this round repaired.

Divergence survival

Every active entry in docs/fork-divergence.md names the test that pins it, and all of those pinning tests are inside the portable lanes this sweep ran. Their verdicts on the merge result:

Ledger entry Pinning test Verdict
Agy crew adapter fm-agy-adapter.test.sh pass
Pinned ShellCheck download retry budget fm-lint.test.sh pass
LLM quota sidecar fm-quota-sidecar.test.sh pass
Scout completion gate reopened by a firstmate steer fm-send-strict.test.sh pass
Pre-move crash fixture fm-backlog-handoff.test.sh pass
Watcher restart hand-over; watcher stop-signal disposition fm-watcher-lock.test.sh pass
Run-progress wedge hold; watcher live declared-wait routing and cadence fm-watch-triage.test.sh, fm-daemon.test.sh fm-daemon pass; fm-watch-triage red only on the inherited process-event claim assertion above, with every wedge-hold, pause-streak, and live-declaration case passing
Herdr pre-Enter footer read fm-backend-herdr.test.sh pass
Remote job worker descendant reaping; bounded remote job stdin capture fm-remote-job-orphan-reap.test.sh, fm-remote-job.test.sh pass
Default per-script bound on every test sweep fm-test-run.test.sh, fm-on.test.sh pass
No-mistakes run attribution fm-crew-state.test.sh pass
Fleet snapshot per-task timeout and abandoned child work fm-home-summary-refresh.test.sh pass
Pi away-mode supervision standby fm-pi-watch-extension.test.sh pass
Fork-local compliance-gate event scope; live PR body refresh fm-no-mistakes-required-gate.test.sh pass
Merge-proof contract fm-pr-merge.test.sh pass
Upstream-read-only posture in shared tracked docs fm-agents-hard-rules.test.sh pass
GBrain per-home knowledge memory fm-bootstrap.test.sh, fm-brief.test.sh, fm-gbrain-capture.test.sh, fm-recall.test.sh pass
Fleet dashboard and agent-event instrumentation fm-dashboard-events.test.sh, fm-dashboard-gbrain.test.sh pass

Beyond those, the fork's ready-to-validate handoff is now proved end to end against the trigger by the new regression described under C2, rather than by observing that the string still exists in a file.

Ledger changes

docs/fork-divergence.md gains three entries, all created by this round:

  • Definition-of-done owner carries this fork's ready-to-validate handoff - upstream's library adopted as the owner and extended.
  • A preserving refusal withdraws the pending backlog close - and why a crash deliberately does not.
  • A watermark-capture failure keeps the published task record - and why every other post-publication failure keeps upstream's rollback.

No entry was retired and no entry's intent changed.

Parked branches

docs/fork-divergence.md lists five deliberately unlanded branches: fm/fm-afk-injection-wedge, fm/fm-crew-state-blind-during-fix-round, fm/fm-parked-decision-stale-noise, fm/fm-subagent-model-routing-guard, and fm/fm-vault-drift-check. None entered this round. This branch descends only from 0f364f17 and 0866a770, which the merge commit's two parents show.

Contract decisions (firstmate, not the captain)

Both AGENTS.md contract questions raised by changes 22 and 24 were put to firstmate and decided by firstmate. The captain was not consulted on either.

1. Who moves the backlog item - adopt upstream. Sections 7 and 10 agree, with "ship, design, or scout" restored where upstream says "ship or scout", because upstream has no design kind while this fork's gate does cover it. Firstmate's reasoning: change 24 was taken as code, so the spawn genuinely does move the item now, and keeping the old section 7 sentence would leave the always-loaded contract describing behaviour the code no longer has - a false statement there is worse than either option. Beyond that, "update the backlog on every dispatch" was a documented promise rather than a mechanism, and upstream converts it into something that refuses; an enforced mechanism is not reverted back to an instruction someone has to remember.

Secondmate exemption, verified rather than assumed. A persistent secondmate is deliberately never a backlog work item in this fork, so firstmate required proof that upstream's refuse-without-an-item path does not catch one before this lands.

  • Code path: fm_backlog_transition_applies returns a skip on kind = secondmate as its first check, before any data resolution. In bin/fm-spawn.sh the entire refuse-without-a-row block sits inside that gate's then branch, and the else branch refuses only on the unresolvable-data status a secondmate can never reach. bin/fm-teardown.sh resolves the same gate from the record's own kind.
  • Dispatch, behavioural: upstream's own test_a_persistent_secondmate_is_never_a_backlog_item spawns a secondmate into a home with no backlog row for it and asserts the spawn succeeds and publishes its record. It passes on the merge result.
  • Retirement, behavioural: this is the half the round actually moved, since cleanup now routes the record removal through the validated transition, and nothing pinned it. A new colocated regression, test_retiring_a_persistent_secondmate_needs_no_backlog_item, retires a real kind=secondmate record in a home that has a real backlog with no row for it, and asserts the retirement succeeds, records no pending close, and adds no backlog row.
  • That regression was proved non-vacuous: deleting the kind exemption from fm_backlog_transition_applies makes the same retirement exit non-zero with its backlog item could not be closed atomically (Task "..." not found in this backlog), after the record is already gone, leaving a pending close a session start would keep retrying for an agent that was never a work item.

No fork adaptation to the exemption itself was needed; it was already correct on both paths, and only the retirement half lacked coverage.

2. Restating the definition of done - no change. Section 7 is left exactly as it was. The sentence about the canonical ready-to-validate blocked: entry is still true of the merged code, and firstmate's reasoning is that section 7 owns the contract, bin/fm-dod-lib.sh now owns the rendering, and the new regression owns the enforcement: three owners, one fact. Naming the library in an always-loaded file at its size ceiling would create a second owner for a mechanism detail and buy nothing executable, which is the documented-promise defect wearing the other face.

Expected red check

This round ships direct-PR, deliberately. The no-mistakes pipeline rebases onto origin/main, which would replay and linearize a merge-only branch and destroy exactly the structure this PR exists to create. The fork's compliance workflow still requires a no-mistakes signature, so PR must be raised via no-mistakes is red by design. Every other required check must pass.

Head

Pushed head: ece09b03d220fd0e00eed9c025bf82c34e48d79b
Merge commit: 601db8e7817d43c624c881e01897096b0a66feac, parents 0f364f17dada691c2bde5d2a7e244e21fbba64e0 and 0866a770234502364c268a768cb7c66cc321c629

Mergeability checked against main at that head: mergeable: true. mergeable_state is unstable, which is the by-design red compliance check below rather than a conflict.

CI verdict on that head: 16 of 17 checks pass, and the single failure is PR must be raised via no-mistakes, which this direct-PR round carries deliberately for the reason stated below.

M00NLIG7 and others added 10 commits August 29, 2026 15:53
…3247)

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* feat(extensions): bind trusted external process-event adapters

* no-mistakes(review): Enforce owner and remote-home conformance

* no-mistakes(review): Enforce serialized remote extension package lifecycle

* no-mistakes(review): Enforce identity-conditional extension retirement

* no-mistakes(review): Serialize extension retirement and recover crash cuts

* no-mistakes(review): Unify retirement worker and lifecycle lock ownership

* no-mistakes(review): Harden extension lifecycle retirement serialization

* no-mistakes(review): Unify extension registration and overridden-state lifecycle boundaries

* no-mistakes(document): Clarify built-in-only captain answer routing

* no-mistakes(lint): Captain: fix extension binding ShellCheck findings

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes: apply CI fixes

* no-mistakes(review): Use isolated UID mapping for owner conformance

* no-mistakes(review): Captain: remove forbidden CI ownership wrapper

* no-mistakes(review): Serialize extension binding publication

* no-mistakes(review): Document ordinary CI owner-fixture exclusion

* no-mistakes(review): Quarantine orphaned handshake descendants

* no-mistakes(test): Fix orphan attribution

* no-mistakes(test): Harden process tracker baseline

* no-mistakes(test): Harden detached descendant attribution

* no-mistakes(test): Use exact invocation-group cleanup

* no-mistakes(test): Bound remote conformance transport crossings

* no-mistakes(test): Parallelize isolated extension conformance tests

* no-mistakes(test): Lifecycle suite still exceeds deadline

* no-mistakes(review): Split extension conformance and forward remote transfer input

* no-mistakes(review): Forward malformed remote payloads through fm-on

* no-mistakes(review): Bound extension coordinator failure cleanup

* no-mistakes(test): Skip repeated orphan sweep in coordinator children

* no-mistakes(test): Queue isolated extension sections through bounded workers

* no-mistakes(test): Bound extension coordinator lane cleanup

* no-mistakes(test): Split remote lifecycle coordinator sections

* no-mistakes(test): Coordinator probes pass; aggregate deadline remains

* no-mistakes(test): Launch extension sections concurrently

* no-mistakes(test): Fix coordinator marker publication

* no-mistakes(test): Stabilize extension binding coordinator timing

* no-mistakes(lint): Fix extension binding ShellCheck warnings

* fix(extensions): prove invocation cleanup before retirement

* no-mistakes(review): Harden process-event inbox confinement

* no-mistakes(review): Preserve legacy capture parity

* no-mistakes(review): Protect external registry staging

* no-mistakes(test): Stabilize bounded extension conformance aggregate

* no-mistakes(document): Document external evidence confinement

* no-mistakes(ci): CI phase fixed. The failure was a flaky fixture in `tests/fm-remote-transport-lanes.test.sh`: its “fresh/in-use” staging directory had no live owner identity, so the real worker correctly reaped it once the 1-second age boundary elapsed on slower CI. The fixture now records the active test shell’s exact PID/start identity and cleans those records before removal. Verified: `bash tests/fm-remote-transport-lanes.test.sh` exits 0 with all checks passing; `git diff --check` passes. Provider check retrieval was also retried successfully, resolving the selected manual CI finding. Changed file: `tests/fm-remote-transport-lanes.test.sh`

* no-mistakes(review): Harden extension staging and lifecycle reservation

* no-mistakes(review): Harden external staging and lifecycle reservations

* no-mistakes(review): Wire capture helper into remote conformance

* no-mistakes(review): Pin external capture handoff and signal failures

* no-mistakes(review): Bind pinned capture authority to inherited descriptor

* no-mistakes(review): Harden descriptor-bound capture authority

* no-mistakes(review): Harden core capture reservation authority

* no-mistakes(review): Harden capture reservation boundaries

* no-mistakes(review): Harden capture reservations and cleanup

* no-mistakes(review): Harden capture handoff and reservation cleanup

* no-mistakes(review): Bind capture handoff to claim descriptors

* no-mistakes(review): Release lifecycle locks after host crashes

* no-mistakes(review): Pin reservation recovery to recorded state roots

* no-mistakes(review): Reject control bytes in claim state roots

* no-mistakes(test): Stabilize extension capture descriptor handoff

* no-mistakes(document): Document extension capture authority boundary

* no-mistakes(lint): Fix ShellCheck extension binding warnings

* no-mistakes(ci): CI phase result: fixed `bin/fm-procevent.sh` by initializing the shared `capture_state` sentinel for built-in adapters under `set -u`. This prevents normal built-in captures from aborting before publication. Verified: `bash -n bin/fm-procevent.sh` and `git diff --check` pass. The focused process-event suite was run locally but stopped earlier at a local detached-runner claim failure (`reconcile never claimed the registered source`), before the CI-reported post-capture path; CI evidence confirms the fixed unset-variable failure affected the failing remote, board, watcher, and process-event checks

* no-mistakes(document): Correct extension namespace creation timing

* no-mistakes(lint): Initialize capture locals for ShellCheck
* fix(bin): deliver the real definition of done to a promoted scout, and ban --yes

A promoted scout used to receive a free-form placeholder instead of the
mode-specific Definition of done a briefed ship worker gets, so it never
saw the ask-user escalation rule or the --yes prohibition. That gap is the
concrete reason one incident's worker drove validation with --yes and
answered its own ask-user findings.

- Add bin/fm-dod-lib.sh as the single owner of a ship task's mode-specific
  Definition of done, rendered by both bin/fm-brief.sh and bin/fm-promote.sh
  so the two contracts cannot drift.
- bin/fm-promote.sh now writes data/<id>/ship-instructions.md carrying the
  scratch inventory, clean base, ship branch, and that Definition of done,
  and prints the fm-send.sh command that delivers it.
- State the --yes ban as a prohibition rather than a preference, without
  claiming an enforcement the tool does not provide.
- Cover both through the real promotion and brief paths in
  tests/fm-task-delivery.test.sh and tests/fm-brief.test.sh.

* no-mistakes(review): Publish promotion instructions before committing task state

* no-mistakes(review): Supersede conflicting scout delivery rules after promotion

* no-mistakes(review): Reject invalid promotion instruction destinations

* no-mistakes(document): Align documentation with promotion delivery contracts

* no-mistakes(ci): Fixed both CI findings. Promoted workers now receive an explicit worktree-isolation check before branch creation, with instructions to stop and escalate if they are in the primary checkout. Updated behavioral coverage to verify the delivered promotion payload, and aligned the ask-user authority test with the new fleet-wide --yes prohibition. Verified with bin/fm-lint.sh, tests/fm-brief.test.sh, tests/fm-ask-user-authority.test.sh, tests/fm-task-delivery.test.sh, and git diff --check

* no-mistakes(ci): Made tests/fm-ask-user-authority.test.sh executable so the modified colocated behavioral test runs directly like the surrounding test suite. Verified bin/fm-lint.sh, fm-brief, ask-user-authority, and task-delivery tests; all pass. git diff --check is clean

* no-mistakes(ci): Strengthened tests/fm-task-delivery.test.sh to behaviorally verify that real promotion and brief generation deliver byte-identical Definition-of-done blocks for all three modes. Verified tests/fm-task-delivery.test.sh, tests/fm-brief.test.sh, bin/fm-lint.sh, and git diff --check. The outer pipeline can now commit and attest the updated head

* no-mistakes(ci): Fixed promotion isolation instructions so any checkout other than the launched disposable worktree requires escalation, including another non-primary worktree. Updated behavioral coverage against the delivered promotion payload. Verified fm-task-delivery, fm-brief, fm-ask-user-authority, full fm-lint/ShellCheck, workflow lint, and git diff checks
)

* fix(bin): present complete Lavish board feedback as structured output

Give the Lavish adapter a read-only presentation so a handler sees every
annotation and the session-ending tag=message as its own field, instead of
grepping a truncated raw capture.

* no-mistakes(review): Preserve unquoted messages and prioritize captain prose

* no-mistakes(document): Document structured Lavish result reads

* no-mistakes(ci): Fixed Lavish `read` completeness: rows missing declared fields are excluded from presented items, counted as malformed, and force `complete: no`. Added behavioral regression coverage through the adapter interface. `bin/fm-lint.sh`, syntax checks, and focused valid/malformed read checks passed. The portable-serial failure was an unrelated secondmate cooldown timing flake
* fix(records): pair backlog transitions with the record that moves

Dispatch and completion each moved a task's physical record and its
backlog row as two independently timed steps, so a crash or a forgotten
follow-up could leave the two disagreeing: a record with no in-flight
row, an in-flight row with no owner, or a finished task still shown in
flight.

Fold each backlog transition into the script that performs the physical
change, under the per-task lock it already holds and before it reports
success. Dispatch moves the item to In flight after publishing the task
record and fails loudly, removing its provisional record, when that
transition cannot land. Completion records an authoritative close and
performs it before removing the record, so an interrupted cleanup can be
finished later, and its closing message now confirms what already
happened rather than instructing a future step.

Add a same-home reconciliation sweep to session start so a home that was
interrupted mid-transition settles its own books on restart, replaying a
recorded close and restoring an in-flight row it already owns a worker
for. It never reads or writes another home; the fleet snapshot and the
cross-home nudge stay as backstops.

Close records are validated before they are trusted: the file is read as
raw bytes and rejected outright when it carries a NUL or other control
byte, every field must be well formed and non-duplicated, the id must
match the record it was found under, the data location must resolve
inside this home, and each close argument must carry a permitted,
well-formed value. Writer and reader share one validator so a record
this home publishes always remains replayable, independent of locale.

Homes configured for a manual backlog, and homes with no backlog at all,
stay exempt and are unaffected.

* no-mistakes(review): Remove stale bootstrap migration helper invocation

* no-mistakes(review): Preserve pending closes and narrow signal deferral

* no-mistakes(review): Record close before destructive teardown

* no-mistakes(review): Refuse pending closes before creating resources

* no-mistakes(review): Guard relaunches and preserve cleanup warnings

* no-mistakes(review): Reject symlinked records and clarify cleanup guidance

* no-mistakes(review): Align dispatch eligibility and protect close replay

* no-mistakes(review): Unify exact task incarnation parsing

* no-mistakes(review): Render resolved configured backlog path

* no-mistakes(review): Harden transition path boundaries against symlinks

* no-mistakes(review): Validate lifecycle state before resource actions

* no-mistakes(review): Enforce transition tooling and continuous state locks

* no-mistakes(review): Consolidate same-home lifecycle file boundaries

* no-mistakes(review): Enforce canonical lifecycle containment and tooling contracts

* no-mistakes(review): Reject final-component lifecycle record symlinks

* no-mistakes(document): Document lifecycle record path boundaries

* no-mistakes(lint): Quote literal done tokens in atomicity tests

* no-mistakes(ci): Fixed all PR-caused CI failures: bootstrap now treats an absent state directory as an empty fresh home while retaining unsafe-state checks; nested remote secondmate retirement accepts records already removed with the retired home; teardown fixtures now provide valid data/manual-backend configuration; and the manual reminder assertion checks the configured absolute backlog path. Verified the reported tests, remote lifecycle E2E, backlog atomicity suite, Bash syntax, diff checks, and ShellCheck. The documented pre-existing captain-hold failure was intentionally untouched

* no-mistakes(ci): Fixed Behavior portable serial 3 by adding `od` to the teardown test’s lsof-free PATH fixture. The new close-record validator legitimately requires `od`; its omission caused teardown to fail before process-group cleanup and stall the shard. Verified the full `tests/fm-teardown.test.sh` suite passes, plus Bash syntax, ShellCheck, and `git diff --check`

* no-mistakes(ci): Fixed close replay to durably retain incomplete-cleanup evidence before removing task metadata. Subsequent retries now emit the reconciliation warning even after a backlog probe or close failure. Updated the behavioral regression and verified the full atomicity suite under stock macOS Bash 3.2, plus shellcheck and diff checks

* fix(records): validate record bytes without an uncurated tool

The byte validation added for close records and directory paths shelled
out to od. The spawn and teardown lifecycle runs under a curated command
set that deliberately excludes it, so on any restricted PATH the check
could not run, the data directory read as unresolvable, and dispatch and
cleanup refused - wedging the lifecycle rather than protecting it.

An earlier attempt made the failing test pass by adding od to that
curated set. That fixed the test to agree with the defect and quietly
widened the contract the fixture exists to pin, so it is reverted here.

Inspect the bytes with perl instead, which is already in the curated set
and already used in this repo for the same portability reason. The
emitted values are identical to od's, so the rejection semantics are
unchanged: NUL and other control bytes are still refused, legitimate
paths containing spaces or non-ASCII characters still round-trip, and
the check stays independent of the process locale.

The restricted-PATH teardown case now passes because the validator no
longer needs od, not because the fixture was loosened.

* no-mistakes(review): Enforce dispatch eligibility and atomic remote record publication

* no-mistakes(document): Document dispatch eligibility and cleanup alerts
…3342)

* fix: publish promote and Relay meta rewrites through contained replace

Bare mv still rewrote live task records in place, so a symlink meta could
be followed to a target outside state/. Route those field rewrites through
the shared publisher and drop the unused library aliases.

Co-authored-by: Cursor <cursoragent@cursor.com>

* no-mistakes(review): Refuse dangling symlinks during X metadata clear

* no-mistakes(review): Refuse unsafe metadata before follow-up and promotion side effects

* no-mistakes(review): Exercise dangling symlink refusal through clear helper

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…d#2877)

* fix(watch): absorb a turn-end whose pane churned since the previous poll

The watcher's "absorb a benign turn-end when the crew is provably working"
triage was structurally unreachable for any harness whose semantic busy state
has no verified source. crew_absorb_class only reports working for an actively
running no-mistakes step or an exact busy verdict, and bin/fm-crew-state.sh can
only answer unknown for such an adapter, so codex crewmates surfaced a signal
wake at every turn boundary with nothing to act on - a full supervisor drain,
inspect and acknowledge turn per worker turn, scaling with the number of workers
in flight and drowning the wakes that matter in identical noise.

Widen the proof rather than bound the wake rate. A wake carrying only bare
turn-ended markers is now also benign when the task's pane content changed since
the previous poll, compared against the same state/.hash-* marker the staleness
backbone already records and already trusts as liveness. That evidence claims no
harness semantics, so it fabricates no busy verdict an adapter has not earned,
and it needs no adapter cooperation.

Absorb stays evidence-driven in both directions. A wake naming any status file
keeps the strict proof, every captain-relevant verb still surfaces immediately,
and an unresolvable task, a missing prior hash, a failed or empty capture, or an
unchanged pane all surface exactly as before. The absorb defers rather than
swallows: a crew that has stopped renders nothing further, so its now-static pane
surfaces through the staleness backbone within a poll or two. Bounding the
surfacing rate instead would have suppressed genuinely stopped workers.

The derivation lives with the .hash-* marker format in bin/fm-watch.sh, which
owns it, and costs one bounded capture reached only for a no-verb turn-end whose
crew is not already provably working.

* no-mistakes(review): Captain, guard pane-churn absorption from collisions and secondmates

* no-mistakes(review): Captain, make watcher marker identities injective

* no-mistakes(review): Captain, isolate ambiguous legacy markers and restore Herdr sourcing

* no-mistakes(review): Captain, localize pane-churn collision guard

* no-mistakes(review): Captain, reject malformed pane-churn hashes

* no-mistakes(document): Document pane-churn turn-end evidence

* no-mistakes: apply CI fixes

* fix(watch): gate and bound the pane-churn turn-end absorb

Make the pane-churn form of positive work evidence opt-in per home and
bound how long it may defer one endpoint's bare turn-ends.

Absorbing a bare turn-end on pane churn is now reached only when the home
creates config/turnend-churn-absorb. The other two proofs read a verdict
the harness itself vouches for, while this one infers execution from
rendered bytes, so widening the absorb is a home's choice rather than a
default every fleet inherits. With the flag absent the predicate returns
on its first line and triage is unchanged.

Churn and pane staleness read the same pane, so neither can be the
other's only backstop. A pane that renders continuously never presents
the two consecutive identical hashes the staleness backbone needs, so an
unbounded churn absorb left a worker that had genuinely stopped behind
such a renderer with no path to surface at all. One endpoint's turn-ends
may now ride churn evidence for at most FM_TURNEND_CHURN_ABSORB_SECS,
tracked in state/.churn-since-*, after which the wake surfaces and the
window restarts. The bound is evaluated before any .stale- state is
touched, so a wake that surfaces there leaves the staleness backbone's
own classification alone.

Covers both with behavioral tests: the same churning fixture that absorbs
with the flag surfaces and queues without it, and a spent deferral window
surfaces and restarts. The four existing safety guards now run with the
flag enabled so they keep proving their specific guard.

* no-mistakes(review): Fail closed on invalid churn deferral state

* no-mistakes(review): Validate persisted churn deadlines before arithmetic

* no-mistakes(review): Make churn deadlines transactional and bounds safe

* no-mistakes(review): Compose turn-end evidence per task from one snapshot

* no-mistakes(review): Restore strict turn-end fallback guards

* no-mistakes(document): Clarify pane-churn supervision documentation

* no-mistakes(lint): Fix watcher arithmetic lint issues

* no-mistakes: apply CI fixes

* no-mistakes(document): Clarify pane-churn fail-closed documentation

* fix(bin): prioritize active pipeline-owned crew runs (kunchenguid#3194)

* fix(bin): bind the live pipeline-owned run instead of a superseded failed row

fm-crew-state.sh bound a superseded FAILED no-mistakes run to a task instead
of the LIVE replacement run: the live run's pipeline-owned lane head is not a
git object in the task worktree, so head-equality attribution rejected it and
the coarse runs-list fallback silently continued past the RUNNING row onto an
older failed row whose head equalled the stale worktree HEAD. The home summary
then flipped invalid and Bearings hid the home's live work (F10).

Attribution precedence now follows the daemon's own identity:
- An ACTIVE run for the task's branch binds without head equality while
  branch_sync.state is pipeline_owned (fm_nm_run_is_pipeline_owned_active);
  the pipeline owning the branch is itself the attribution.
- A genuinely failed run with no later run on the branch still reports failed
  through the unchanged head-equality path - real failures are not hidden.
- In the coarse runs scan, an unresolvable head is unknown attribution and
  stops the scan (fm_nm_head_resolvable) instead of falling through to an
  older row; a resolvable-but-mismatched head keeps the historical
  reused-branch skip.

The exemption never applies to a terminal run and requires pipeline_owned
specifically, both pinned by negative-control tests. Fixture shape verified
against the live incident run's real axi status output.

* no-mistakes(document): Updated run-attribution documentation ownership

* no-mistakes(review): Captain, make watcher marker identities injective

* no-mistakes(review): Captain, localize pane-churn collision guard

* no-mistakes(review): Compose turn-end evidence per task from one snapshot

* no-mistakes(review): Restore strict turn-end fallback guards

* no-mistakes(document): Align pane-churn watcher documentation

* no-mistakes(ci): Captain, fixed the flaky cooldown boundary test by freezing its executable clock. The failure reproduced before the fix and passed five consecutive full-suite runs afterward. Extended ShellCheck passed; full lint stopped because actionlint 1.7.12 is not installed

---------

Co-authored-by: Kun Chen <3233006+kunchenguid@users.noreply.github.com>
* fix(bin): add a safe owner for custom-check retirement

Agents were improvising rm of check files with unset STATE/ID, which wedges
headless panes. Unregister validates the id and state directory first.

Co-authored-by: Cursor <cursoragent@cursor.com>

* no-mistakes(review): Refuse explicitly empty custom-check state overrides

* no-mistakes(document): Document custom-check retirement safety contract

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Round 4 of the TRACK sync plan: the contiguous upstream range from merge base
9e3df47 to 0866a77, seven first-parent changes, taken as one merge with no
cherry-picking, rebasing, or squashing.

Three collisions were resolved on meaning rather than by side:

- The definition of done. Upstream's new bin/fm-dod-lib.sh is adopted as its one
  owner and extended to carry this fork's fragments, so a promoted scout and a
  briefed worker still receive the same contract and the fork's canonical
  ready-to-validate handoff survives. Upstream's stronger --yes ban is taken.
  A new regression drives the trigger with the handoff line taken out of a real
  generated brief, so the two files cannot drift apart silently again.
- The atomic backlog transitions. Cleanup still publishes its completion
  manifest, captures task knowledge, and appends its model watermark before the
  close that removes the record, and a refusal that deliberately preserves a
  task now withdraws the pending close it recorded on the way in.
- Dispatch rollback. A claude watermark-capture failure keeps its published
  record rather than stranding a live endpoint and local copy with nothing
  naming them; every other post-publication failure keeps upstream's rollback.

The Lavish adapter, the process-event skill, and the bootstrap and watcher
prose are unions of both intents. docs/fork-divergence.md records the three
divergences this round creates.
…ixture its bound owner

The three divergences the merge creates - the extended definition-of-done owner,
the pending-close withdrawal on a preserving refusal, and the retained record on
a watermark-capture failure - are recorded in the fork divergence ledger.

Upstream's remote extension fixture mirrors a fixed list of bin/ files onto its
fake remote root. This fork's bin/fm-remote-job-lib.sh sources
bin/fm-timeout-lib.sh, which that list does not carry, so the remote entrypoint
refused before it could publish the binding. Adding the owner to the list makes
the remote transport case exercise what it was written to exercise.
Upstream's atomic transitions already carry a regression for the dispatch half
of the secondmate exemption: a mate spawns with no backlog row and must succeed.
Retirement is the other half, and it is the half this round moved, because
cleanup now routes the record removal through the validated transition.

Removing the kind exemption from fm_backlog_transition_applies makes a real
secondmate retirement exit non-zero with "its backlog item could not be closed
atomically", after the record is already gone, and leaves a pending close a
session start would keep retrying for an agent that was never a work item. This
case asserts the successful retirement, the absent pending-close record, and the
absent backlog row, so that path cannot regress silently.
@HelloWorldSungin
HelloWorldSungin merged commit ab9f96f into main Sep 4, 2026
16 of 17 checks passed
@HelloWorldSungin
HelloWorldSungin deleted the fm/fm-upstream-sync-2026-09-02-round-4 branch September 4, 2026 23:26
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.

4 participants