fix(bin): enforce crew-state and merge outcome truth - #103
Merged
Conversation
…truth Port kunchenguid/firstmate kunchenguid#3194, kunchenguid#3104, and kunchenguid#3064 into this fork. Crew-state run attribution (kunchenguid#3194): bin/fm-nm-run-lib.sh gains fm_nm_head_resolvable and fm_nm_run_is_pipeline_owned_active, and bin/fm-crew-state.sh binds an active pipeline-owned run by branch without requiring its lane head to resolve in the task worktree, stops the coarse runs scan on an unresolvable newer head instead of falling through to a superseded terminal row, and still surfaces a genuine unsuperseded failure. Merge-outcome truth (kunchenguid#3064): bin/fm-pr-merge.sh records pr= and arms the merge poll once before the forge call, then reads the live GitHub state back and accepts only merged or in-merge-queue as success. Anything else refuses non-zero naming the observed state, quotes the forge's own output apart from the verdict, names exact merge-queue retry flags without choosing a method, and falls back to the gh-axi view only when gh's read fails. Durable merge outcome (kunchenguid#3104): new bin/fm-merge-outcome-lib.sh publishes one role-routed outcome for a merge this home performed or its poll detected, deduplicated through the merge-notified marker now owned by bin/fm-pr-lib.sh; the watcher routes merged polls through it before retiring, teardown removes the marker, and the mate charter names landed work as a report trigger. Fork adaptations: fm-pr-merge stays GitHub-only (GitLab merge parity has not landed here), fm-inactive-reconcile does not exist here so its diagnostic hardening is not ported, the merge-outcome library never re-sources fm-wake-lib inside a process that already loaded it because this fork's watcher carries recovery-marker state across marker transitions, the OMP supervision-branch merge prohibition in fm-pr-merge is retained, and the OMP crew pane fallback is pinned by a new crew-state case. Claude-Session: https://claude.ai/code/session_013LDmHMU6VLbFa9jW1J4FQV
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Port upstream kunchenguid/firstmate pull requests kunchenguid#3194, kunchenguid#3104, and kunchenguid#3064 (crew-state run attribution + merge-outcome truth) into this fork, faithfully but adapted to the fork, preserving the fork's OMP crew/backend support so OMP crew-state attribution and merge/close reporting do not regress.
Requirements, in their current accepted form:
Crew-state run attribution (fix(bin): prioritize active pipeline-owned crew runs kunchenguid/firstmate#3194): bin/fm-crew-state.sh must attribute the current run/step to the live crew incarnation. Precedence: (a) a daemon-attributed ACTIVE no-mistakes run for the task's branch from axi status is authoritative while branch_sync.state is pipeline_owned, without requiring head equality, reporting working with run-step detail; (b) otherwise the newest terminal run for the branch that no later run supersedes surfaces its terminal state, and a genuinely failed run with no later run must still report failed; (c) an unresolvable run head classifies as unknown attribution and must never cause a silent fallback onto an older row; in the coarse
no-mistakes runsscan an ACTIVE row for this branch whose head cannot be resolved must not be skipped in favor of an older terminal row. The attribution helpers (fm_nm_head_resolvable, fm_nm_run_is_pipeline_owned_active) live in bin/fm-nm-run-lib.sh as the single owner of the run-attribution rule; the exemption requires an ACTIVE run and branch_sync.state=pipeline_owned specifically; fm-teardown.sh keeps strict head equality. Tests exercise the real bin/fm-crew-state.sh entrypoint with a PATH-stubbed no-mistakes.Merge-outcome truth (fix(bin): verify the real GitHub merge outcome instead of reporting an unproved merge kunchenguid/firstmate#3064): bin/fm-pr-merge.sh must verify the real GitHub merge outcome instead of reporting an unproved merge. After the gh-axi merge call, read the pull request's live state back and accept exactly two outcomes as success: merged, or in the merge queue. Anything else fails non-zero naming the concrete observed state (state, merged, isInMergeQueue). When the base branch requires a merge queue, the refusal names the queue's configured merge method and the exact
-- --auto --<method>retry flags; never silently choose a merge method for the caller. No-rule, unreadable, conflicting, and unrecognised queue rule readings are reported apart. A caller who already passed --auto with the queue's method is told to re-check the queue state rather than handed back the flags. Refusals quote the merge command's own output separately from the script's verdict; a failed merge command keeps its original error first and never claims auto-merge was armed. PR metadata recording (pr=, pr_head=) and merge-poll arming happen once, unconditionally, before the forge call and survive any later failure. gh-axi always performs the merge; gh's GraphQL read is an optional queue-aware enrichment, and when gh is absent or its read fails the gh-axi view still proves a landed merge; a refusal built on the gh-axi view says the merge queue could not be observed. A caller-requested --auto that leaves the PR neither merged nor queued is refused.Durable merge outcome (fix(bin): durably report merged pull requests kunchenguid/firstmate#3104): a self-performed merge that succeeds emits exactly one durable outcome; a refused, failed, queued, or unconfirmed merge records nothing. In a secondmate home (.fm-secondmate-home present) append-once to the parent reply channel in the shape
done [key=merged-<task-id>]: merged <task-id> <pr-url>(remote route: state/parent-replies.status; local route: the parent home's state/.status). In a main home append a durable check wake naming the merged PR. The existing merge poll'smergedconfirmation in bin/fm-watch.sh feeds the same emitter so self and poll origins produce one consistent durably-recorded outcome, with no second watch path. At-most-once is achieved by reusing the canonical merge-notification marker (fm_pr_poll_merge_already_notified / fm_pr_poll_merge_mark_notified) in bin/fm-pr-lib.sh; the outcome is published before the marker is committed so a failed commit stays eligible for at-least-once retry. The new bin/fm-merge-outcome-lib.sh is a separate small library. A merge that landed but whose record could not be written prints an actionable line to stderr and still exits 0. The mate charter in bin/fm-brief.sh names "work you landed" (a merge performed under standing merge authority and one the captain merged on the forge) as a reporting trigger. Teardown removes the merge-notified marker.Fork adaptations, all deliberate: bin/fm-pr-merge.sh stays GitHub-only and continues to refuse a GitLab merge request URL before recording state because GitLab merge parity has not landed in this fork, so upstream's GitLab merge tests are not ported; bin/fm-inactive-reconcile.sh does not exist in this fork so its diagnostic hardening is not ported; bin/fm-merge-outcome-lib.sh must not re-source bin/fm-wake-lib.sh inside a process that already loaded it because this fork's watcher carries recovery-marker state across marker transitions; the OMP supervision-branch merge prohibition (fm_lease_forbid_branch) in bin/fm-pr-merge.sh is retained; the main-home merge-outcome row is an ordinary check-kind wake, which the OMP branch dispatcher keeps main-owned; an OMP crew with unknown attribution falls to its ordinary pane fallback rather than a superseded failed row, pinned by a crew-state test.
Tests: port the upstream tests, adapted for the fork, into tests/fm-crew-state.test.sh, tests/fm-pr-merge.test.sh, tests/fm-pr-check-security.test.sh, and tests/fm-brief.test.sh; the ported tests plus the relevant existing crew-state and merge tests and bin/fm-lint.sh (shellcheck) must pass. Tests exercise behavior through executable interfaces with PATH-stubbed forge CLIs and never assert implementation source bytes.
This is firstmate's own shared tracked material, so .agents/skills/firstmate-coding-guidelines/SKILL.md is binding: one sentence per line in tracked Markdown, plain dash never an em dash, no agent co-author, shellcheck-clean bin scripts through bin/fm-lint.sh, colocated tests named .test.sh extending existing runners, one-owner rule for contracts, script headers own mechanics. Documentation updated: AGENTS.md state list and merge/validation sentences, docs/architecture.md, docs/scripts.md, docs/configuration.md.
Firstmate-Validation-Generation: 141a0f50463896b871e5015ed4476fdb
What Changed
Risk Assessment
Testing
Ran the four relevant executable-interface test scripts with PATH-stubbed dependencies; all passed and logs were captured as evidence artifacts. No UI surface was involved, and linting was not run because this assigned test phase explicitly prohibits linters.
Evidence: Crew-state targeted tests
Evidence: PR merge targeted tests
Evidence: PR check security targeted tests
Evidence: Brief targeted tests
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
bin/fm-pr-merge.sh:259- Whenghis present but its GraphQL read fails,github_read_outcomeonly accepts thegh-axifallback if it reportsmerged=true; ifgh-axi pr viewreports an open/unqueued PR, the script exits via the generic “could not read” path and never runsgithub_report_unmerged_outcome. Thus a concrete fallback observation such asstate=openis not reported withisInMergeQueue=unknownor the required “merge queue could not be observed” refusal. This contradicts the intent that “when gh is absent or its read fails the gh-axi view still proves a landed merge; a refusal built on the gh-axi view says the merge queue could not be observed.”✅ **Test** - passed
✅ No issues found.
bash tests/fm-crew-state.test.shbash tests/fm-pr-merge.test.shbash tests/fm-pr-check-security.test.shbash tests/fm-brief.test.shVerifiedgit status --shortis clean after testing✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.