From 089980790f8f1382f5a4aa02cb7f5ad37f8f20d9 Mon Sep 17 00:00:00 2001 From: hongming-codex-laptop Date: Tue, 12 May 2026 22:15:26 -0700 Subject: [PATCH 1/2] ci: hard-fail unfilled SOP checklist body --- .gitea/scripts/sop-checklist-gate.py | 7 +++-- .../scripts/tests/test_sop_checklist_gate.py | 26 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.gitea/scripts/sop-checklist-gate.py b/.gitea/scripts/sop-checklist-gate.py index 1fb24693..25a99b59 100755 --- a/.gitea/scripts/sop-checklist-gate.py +++ b/.gitea/scripts/sop-checklist-gate.py @@ -640,8 +640,11 @@ def render_status( shown += f", +{len(missing) - 3}" desc_parts.append(f"missing: {shown}") if missing_body: - desc_parts.append(f"body-unfilled: {len(missing_body)}") - state = "success" if not missing else "failure" + shown = ", ".join(missing_body[:3]) + if len(missing_body) > 3: + shown += f", +{len(missing_body) - 3}" + desc_parts.append(f"body-unfilled: {shown}") + state = "success" if not missing and not missing_body else "failure" return state, " — ".join(desc_parts) diff --git a/.gitea/scripts/tests/test_sop_checklist_gate.py b/.gitea/scripts/tests/test_sop_checklist_gate.py index d951f974..7622c79a 100644 --- a/.gitea/scripts/tests/test_sop_checklist_gate.py +++ b/.gitea/scripts/tests/test_sop_checklist_gate.py @@ -410,6 +410,7 @@ class TestRenderStatus(unittest.TestCase): self._state_with(all_slugs), {it["slug"]: False for it in self.items}, ) + self.assertEqual(state, "failure") self.assertIn("body-unfilled", desc) @@ -519,6 +520,31 @@ class TestEndToEndAckFlow(unittest.TestCase): self.assertEqual(result_state, "success") self.assertIn("7/7", desc) + def test_all_acks_still_fail_when_body_section_unfilled(self): + items = _items_by_slug() + aliases = _numeric_aliases() + comments = [ + _comment("qa-bot", "/sop-ack comprehensive-testing"), + _comment("eng-bot", "/sop-ack local-postgres-e2e"), + _comment("eng-bot", "/sop-ack staging-smoke"), + _comment("mgr-bot", "/sop-ack root-cause"), + _comment("eng-bot", "/sop-ack five-axis-review"), + _comment("mgr-bot", "/sop-ack no-backwards-compat"), + _comment("eng-bot", "/sop-ack memory-consulted"), + ] + + def probe(slug, users): + return list(users) + + state = sop.compute_ack_state(comments, "alice-author", items, aliases, probe) + body = {it["slug"]: True for it in items.values()} + body["root-cause"] = False + items_list = list(items.values()) + result_state, desc = sop.render_status(items_list, state, body) + self.assertEqual(result_state, "failure") + self.assertIn("7/7", desc) + self.assertIn("body-unfilled: root-cause", desc) + if __name__ == "__main__": unittest.main(verbosity=2) -- 2.45.2 From f9261212bd9b59373b3c44d72e2d314feb426e71 Mon Sep 17 00:00:00 2001 From: hongming-codex-laptop Date: Tue, 12 May 2026 22:42:46 -0700 Subject: [PATCH 2/2] fix(sop-checklist): post success (not pending) for tier:low PRs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tier:low PRs are low-risk changes that do not require peer acks. Posting 'pending' instead of 'success' caused a deadlock when sop-checklist/all-items-acked is a BP required context — pending does not satisfy the merge gate. Change: mode=soft → state always "success", description prefix changes from "[soft-fail]" to "[info tier:low]" for clarity. Fixes internal#376 (all molecule-core/main merges blocked). --- .gitea/scripts/sop-checklist-gate.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitea/scripts/sop-checklist-gate.py b/.gitea/scripts/sop-checklist-gate.py index 25a99b59..995fbc7b 100755 --- a/.gitea/scripts/sop-checklist-gate.py +++ b/.gitea/scripts/sop-checklist-gate.py @@ -620,8 +620,8 @@ def render_status( state is "success" if every item has at least one valid ack (body section presence is informational only — peer-ack is the - real gate). "pending" is reserved for the soft-fail path - (tier:low) and is set by the caller. + real gate). tier:low PRs receive state="success" (soft-fail — no + acks required); the description carries "[info tier:low]" prefix. """ n = len(items) fully_acked = [ @@ -776,9 +776,12 @@ def main(argv: list[str] | None = None) -> int: state, description = render_status(items, ack_state, body_state) mode = get_tier_mode(pr, cfg) - if state == "failure" and mode == "soft": - state = "pending" - description = f"[soft-fail tier:low] {description}" + if mode == "soft": + # tier:low: acks are informational only — post success so BP gate passes. + # Description carries "[info tier:low]" prefix so reviewers know acks + # were not required (vs a tier:medium+ PR that truly passed all acks). + state = "success" + description = f"[info tier:low] {description}" # Diagnostics to job log. print(f"::notice::PR #{args.pr} author={author} head={head_sha[:7]} mode={mode}") -- 2.45.2