diff --git a/.gitea/scripts/gitea-merge-queue.py b/.gitea/scripts/gitea-merge-queue.py index 29b561a60..5fafebe22 100644 --- a/.gitea/scripts/gitea-merge-queue.py +++ b/.gitea/scripts/gitea-merge-queue.py @@ -300,17 +300,18 @@ def _is_tier_low_pending_ok( ) -> bool: """Return True if tier:low PR can tolerate sop-checklist pending state. - Per sop-checklist-config.yaml tier_failure_mode, tier:low uses soft-fail: - sop-checklist posts state=pending when acks are satisfied (missing - manager/ceo acks are informational only). The queue should accept - pending instead of waiting for success. + GENERIC PENDING-AS-GREEN REMOVED (Researcher + CR2 RC on #2368): + The prior soft-fail accepted ANY pending sop-checklist for tier:low, + which allowed required checks to pass without genuine verification. + Pending required sop-checklist must now always HOLD and appear in + missing_or_bad. This function is retained as a policy hook but + currently always returns False so pending never counts green. + + If a positively identifiable genuine soft-fail state is defined in + future (e.g., a specific check-run conclusion), implement it here + with strict positive identification — never default to pass. """ - if "tier:low" not in pr_labels: - return False - if "sop-checklist" not in context: - return False - status = latest_statuses.get(context) or {} - return status_state(status) == "pending" + return False def required_contexts_green( diff --git a/.gitea/scripts/tests/test_gitea_merge_queue.py b/.gitea/scripts/tests/test_gitea_merge_queue.py index 83740b6a5..127d94abd 100644 --- a/.gitea/scripts/tests/test_gitea_merge_queue.py +++ b/.gitea/scripts/tests/test_gitea_merge_queue.py @@ -44,6 +44,35 @@ def test_required_contexts_green_rejects_missing_and_pending(): ] +def test_required_contexts_green_rejects_volume_skipped_even_for_tier_low(): + """volume-skipped pending is a partial view, not a genuine soft-fail. + + Per sop-checklist.py:1179-1187, volume_skipped posts pending with a + '[volume-skipped]' prefix. The merge queue must NOT treat this as an + acceptable soft-fail for tier:low — the gate did not finish evaluating. + """ + latest = mq.latest_statuses_by_context([ + {"context": "CI / all-required (pull_request)", "status": "success"}, + { + "context": "sop-checklist / all-items-acked (pull_request)", + "status": "pending", + "description": "[volume-skipped] comment-cap=1000 hit; please file ...", + }, + ]) + + ok, missing_or_bad = mq.required_contexts_green( + latest, + [ + "CI / all-required (pull_request)", + "sop-checklist / all-items-acked (pull_request)", + ], + pr_labels={"tier:low"}, + ) + + assert ok is False + assert "sop-checklist / all-items-acked (pull_request)=pending" in missing_or_bad + + def test_choose_next_pr_sorts_by_queue_label_timestamp_then_number(): issues = [ {