Merge pull request 'feat: comment-then-stop policy for partial PR reconciliation (Closes #302)' (#375) from feat/issue-302-partial-reconciliation-policy into master

This commit was merged in pull request #375.
This commit is contained in:
2026-07-07 08:51:12 -05:00
4 changed files with 322 additions and 0 deletions
+6
View File
@@ -78,6 +78,12 @@ def test_reconcile_landed_workflow_contract():
assert "canonical: true" in text
assert "Do not review or merge normal PRs" in text
assert "Already-landed proof" in text
# Issue #302: partial reconciliation policy must stay documented.
assert "## 12A. Partial reconciliation policy (#302)" in text
assert "comment-then-stop" in text
assert "PARTIAL_RECONCILE_COMMENT_THEN_STOP" in text
assert "RECOVERY_HANDOFF_ONLY" in text
assert "resolve_partial_reconciliation_plan" in text
def test_create_issue_workflow_contract():
+129
View File
@@ -26,6 +26,8 @@ from review_proofs import ( # noqa: E402
assess_already_landed_report_state,
assess_already_landed_review_gate,
assess_author_pr_report,
assess_partial_reconciliation_report,
resolve_partial_reconciliation_plan,
assess_validation_environment_proof,
assess_full_suite_failure_approval_gate,
assess_queue_ordering_proof,
@@ -2641,6 +2643,133 @@ class TestReviewerBaselineValidationProof(unittest.TestCase):
self.assertFalse(report["baseline_validation_proven"])
class TestPartialReconciliationPlan(unittest.TestCase):
"""Issue #302: policy when PR-close capability is missing (Option B)."""
def _plan(self, **overrides):
kwargs = {
"ancestor_proven": True,
"capabilities": {
"close_pr": False,
"comment_pr": True,
"close_issue": True,
"comment_issue": True,
},
}
kwargs.update(overrides)
return resolve_partial_reconciliation_plan(**kwargs)
def test_close_capability_present_allows_full_reconcile(self):
plan = self._plan(capabilities={
"close_pr": True, "comment_pr": True,
"close_issue": True, "comment_issue": True,
})
self.assertEqual(plan["outcome"], "FULL_RECONCILE_CLOSE_ALLOWED")
self.assertIn("close_pr", plan["allowed_mutations"])
def test_close_missing_with_comment_posts_comment_then_stops(self):
plan = self._plan()
self.assertEqual(
plan["outcome"], "PARTIAL_RECONCILE_COMMENT_THEN_STOP")
self.assertEqual(plan["allowed_mutations"], ("comment_pr",))
for field in (
"PR head SHA",
"target branch SHA",
"ancestor proof",
"linked issue status",
"required missing capability",
):
self.assertIn(field, plan["required_comment_fields"])
def test_comment_capability_missing_produces_handoff_only(self):
plan = self._plan(capabilities={
"close_pr": False, "comment_pr": False,
"close_issue": True, "comment_issue": True,
})
self.assertEqual(plan["outcome"], "RECOVERY_HANDOFF_ONLY")
self.assertEqual(plan["allowed_mutations"], ())
def test_all_capabilities_missing_produces_handoff_only(self):
plan = self._plan(capabilities={
"close_pr": False, "comment_pr": False,
"close_issue": False, "comment_issue": False,
})
self.assertEqual(plan["outcome"], "RECOVERY_HANDOFF_ONLY")
self.assertEqual(plan["allowed_mutations"], ())
def test_unproven_ancestry_blocks_all_mutations(self):
plan = self._plan(ancestor_proven=False, capabilities={
"close_pr": True, "comment_pr": True,
"close_issue": True, "comment_issue": True,
})
self.assertEqual(plan["outcome"], "GATE_NOT_PROVEN")
self.assertEqual(plan["allowed_mutations"], ())
def test_missing_capability_dict_fails_closed(self):
plan = self._plan(capabilities=None)
self.assertEqual(plan["outcome"], "RECOVERY_HANDOFF_ONLY")
self.assertEqual(plan["allowed_mutations"], ())
class TestPartialReconciliationReport(unittest.TestCase):
"""Issue #302: reports must explain why comments were or were not posted."""
def _plan(self, outcome):
capabilities = {
"FULL_RECONCILE_CLOSE_ALLOWED": {
"close_pr": True, "comment_pr": True,
"close_issue": True, "comment_issue": True,
},
"PARTIAL_RECONCILE_COMMENT_THEN_STOP": {
"close_pr": False, "comment_pr": True,
"close_issue": True, "comment_issue": True,
},
"RECOVERY_HANDOFF_ONLY": {
"close_pr": False, "comment_pr": False,
"close_issue": False, "comment_issue": False,
},
}[outcome]
return resolve_partial_reconciliation_plan(
ancestor_proven=True, capabilities=capabilities)
def test_partial_report_requires_comment_and_missing_capability(self):
plan = self._plan("PARTIAL_RECONCILE_COMMENT_THEN_STOP")
good = (
"Reconciliation comment posted on PR #278 with ancestor proof. "
"PR close skipped: close capability gitea.pr.close missing; "
"stopped for authorized close."
)
result = assess_partial_reconciliation_report(good, plan=plan)
self.assertTrue(result["complete"])
bad = "Stopped. Nothing done."
result = assess_partial_reconciliation_report(bad, plan=plan)
self.assertTrue(result["block"])
def test_handoff_only_report_requires_no_comment_explanation(self):
plan = self._plan("RECOVERY_HANDOFF_ONLY")
good = (
"No reconciliation comment posted: comment capability missing. "
"No Gitea mutation performed; recovery handoff produced."
)
result = assess_partial_reconciliation_report(good, plan=plan)
self.assertTrue(result["complete"])
bad = "Recovery handoff produced."
result = assess_partial_reconciliation_report(bad, plan=plan)
self.assertTrue(result["block"])
def test_full_reconcile_report_requires_close_result(self):
plan = self._plan("FULL_RECONCILE_CLOSE_ALLOWED")
good = "PR close result: closed PR #278 after ancestor proof."
result = assess_partial_reconciliation_report(good, plan=plan)
self.assertTrue(result["complete"])
bad = "Reconciliation done."
result = assess_partial_reconciliation_report(bad, plan=plan)
self.assertTrue(result["block"])
class TestAlreadyLandedReviewGate(unittest.TestCase):
"""Issue #292: PR head already on target blocks approval and merge."""