diff --git a/review_proofs.py b/review_proofs.py index 60cca02..02fa543 100644 --- a/review_proofs.py +++ b/review_proofs.py @@ -3503,6 +3503,17 @@ _PROOF_WORDING_RULES: tuple[dict, ...] = ( "text_evidence": _PAGINATION_FINALITY_EVIDENCE, "allow_prior_label": False, }, + { + "id": "same_as_master", + "pattern": re.compile(r"\bsame as master\b", re.I), + "session_keys": ("baseline_worktree_proof", "clean_baseline_proof"), + "text_evidence": re.compile( + r"baseline worktree|clean baseline|diff base.*master|" + r"base[- ]equivalent.*master|worktree proof", + re.I, + ), + "allow_prior_label": False, + }, { "id": "no_file_edits", "pattern": re.compile( @@ -3568,7 +3579,12 @@ def assess_proof_wording( *, session_evidence: dict | None = None, ) -> dict: - """Issue #330: reject unsupported proof phrases in final reports.""" + """Issue #330: reject unsupported proof phrases in final reports. + + Forbidden wording such as ``inventory complete``, ``live proof``, and + ``same as master`` is allowed only when matching current-session evidence + appears in the report text or in *session_evidence*. + """ text = report_text or "" evidence = dict(session_evidence or {}) violations: list[str] = [] diff --git a/tests/test_proof_wording.py b/tests/test_proof_wording.py new file mode 100644 index 0000000..e59a99d --- /dev/null +++ b/tests/test_proof_wording.py @@ -0,0 +1,93 @@ +"""Doc-contract checks for proof wording enforcement (#330).""" +import unittest + +from review_proofs import assess_proof_wording, build_final_report + + +class TestProofWording(unittest.TestCase): + def test_rejects_inventory_complete_without_pagination_proof(self): + result = assess_proof_wording( + "Open PR inventory is complete because only 3 PRs were returned." + ) + self.assertFalse(result["proven"]) + self.assertTrue(result["block"]) + + def test_rejects_default_page_size_assumption(self): + result = assess_proof_wording( + "Inventory exhaustive: fewer than the default Gitea page size returned." + ) + self.assertFalse(result["proven"]) + self.assertIn("default_page_size_assumption", result["flagged_rules"]) + + def test_allows_inventory_complete_with_finality_metadata(self): + result = assess_proof_wording( + "Inventory complete. pagination_complete: true, is_final_page: true" + ) + self.assertTrue(result["proven"]) + + def test_allows_inventory_complete_with_session_evidence(self): + result = assess_proof_wording( + "Queue inventory complete.", + session_evidence={"pagination_complete": True}, + ) + self.assertTrue(result["proven"]) + + def test_rejects_live_proof_without_session_evidence(self): + result = assess_proof_wording("Live proof confirms mergeable state.") + self.assertFalse(result["proven"]) + self.assertIn("live_proof", result["flagged_rules"]) + + def test_allows_prior_blocker_reuse_wording(self): + result = assess_proof_wording( + "Prior blocker reused; head SHA unchanged since blocking review. " + "Live proof not claimed for this skip." + ) + self.assertTrue(result["proven"]) + + def test_allows_live_proof_with_current_session_evidence(self): + result = assess_proof_wording( + "Live proof: gitea_view_pr revalidated in the current session." + ) + self.assertTrue(result["proven"]) + + def test_rejects_same_as_master_without_baseline_proof(self): + result = assess_proof_wording("Diff is same as master.") + self.assertFalse(result["proven"]) + self.assertIn("same_as_master", result["flagged_rules"]) + + def test_allows_same_as_master_with_baseline_wording(self): + result = assess_proof_wording( + "Clean baseline worktree proof: diff base matches master." + ) + self.assertTrue(result["proven"]) + + def test_rejects_file_edits_none_without_ledger(self): + result = assess_proof_wording("Workspace mutations: file edits none.") + self.assertFalse(result["proven"]) + self.assertIn("no_file_edits", result["flagged_rules"]) + + def test_allows_file_edits_none_with_mutation_ledger(self): + result = assess_proof_wording( + "Mutation ledger: tracked file edits: none", + session_evidence={"mutation_ledger_clean": True}, + ) + self.assertTrue(result["proven"]) + + def test_build_final_report_downgrades_on_proof_wording_violation(self): + report = build_final_report( + {"proven": True}, + {"complete": True}, + {"claimable": True, "verdict": "strong"}, + {"status": "clean"}, + True, + False, + True, + report_text="Inventory complete after listing 4 open PRs.", + ) + self.assertEqual(report["grade"], "downgraded") + self.assertFalse(report["proof_wording_proven"]) + self.assertTrue(report["proof_wording_violations"]) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file