diff --git a/bin/fm-crew-state.sh b/bin/fm-crew-state.sh index df627b487f2..e425ecccba8 100755 --- a/bin/fm-crew-state.sh +++ b/bin/fm-crew-state.sh @@ -496,7 +496,11 @@ if [ "$HAVE_RUN" = 1 ]; then if [ -n "$outcome" ]; then case "$outcome" in - passed) RUN_STATE="done"; RUN_DETAIL="run passed: PR merged/closed" ;; + # `passed` is a claim about the pipeline's own nine steps completing. + # It establishes nothing about the forge: the PR may still be open, + # already merged, or not exist. Only teardown's own pr_is_merged + # check establishes landing, so this detail stays neutral about it. + passed) RUN_STATE="done"; RUN_DETAIL="run passed: pipeline complete" ;; checks-passed) RUN_STATE="done"; RUN_DETAIL="checks green: PR ready for review" ;; failed) RUN_STATE=failed; RUN_DETAIL="run failed" ;; cancelled) RUN_STATE=failed; RUN_DETAIL="run cancelled" ;; diff --git a/tests/fm-crew-state.test.sh b/tests/fm-crew-state.test.sh index 602b3e5cfc3..e7df0c259ae 100755 --- a/tests/fm-crew-state.test.sh +++ b/tests/fm-crew-state.test.sh @@ -671,6 +671,30 @@ test_terminal_passed() { pass "terminal passed run is authoritative" } +test_terminal_passed_does_not_claim_landing() { + # Regression for issue #2421: a pipeline `passed` outcome is a claim about + # the run's own steps, not about the forge. The PR may be open, already + # merged, or absent; rendering "PR merged/closed" (the old wording) or any + # other forge state invents landing the helper never established. + reset_fakes + local d; d=$(new_case passed-open-pr) + make_repo_on_branch "$d/wt" fm/feat-landing + make_fakebin "$d" >/dev/null + fm_write_meta "$d/state/feat-landing.meta" "window=fm:fm-feat-landing" "worktree=$d/wt" "kind=ship" + FM_FAKE_AXI_STATUS="$(run_passed fm/feat-landing)" + local out; out=$(run_crew_state "$d" feat-landing) + assert_contains "$out" "run passed" "passed run keeps its run claim" + assert_contains "$out" "pipeline complete" "detail stays neutral about the forge" + case "$out" in + *merged/closed*|*awaiting*review*) + fail "passed run must not render any forge claim about the PR" + ;; + *) + pass "no forge claim for an outcome=passed run" + ;; + esac +} + test_terminal_failed() { reset_fakes local d; d=$(new_case failed) @@ -1427,6 +1451,7 @@ test_ci_fixing_after_green_stays_working test_top_level_fixing_ci_running_after_green_stays_working test_top_level_fixing_done_log_stays_working test_terminal_passed +test_terminal_passed_does_not_claim_landing test_terminal_failed test_cross_branch_attribution_via_runs_list test_cross_branch_attribution_picks_most_recent_row