Merge pull request 'Add role-boundary proofs for reviewer queue workflows (Issue #175)' (#192) from feat/issue-175-role-pivot-wall into master
This commit was merged in pull request #192.
This commit is contained in:
@@ -23,6 +23,7 @@ sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.par
|
||||
from review_proofs import ( # noqa: E402
|
||||
assess_controller_handoff,
|
||||
assess_inventory_completeness,
|
||||
assess_role_boundary,
|
||||
assess_self_review_contamination,
|
||||
assess_validation_report,
|
||||
build_final_report,
|
||||
@@ -99,6 +100,21 @@ def _good_contamination():
|
||||
)
|
||||
|
||||
|
||||
def _good_role_boundary():
|
||||
return assess_role_boundary(
|
||||
{
|
||||
"task_role": "reviewer",
|
||||
"task_kind": "blind_pr_queue_review",
|
||||
"reviewer_namespace_used": True,
|
||||
"author_namespace_used": False,
|
||||
"author_mutations": [],
|
||||
"review_mutations": [],
|
||||
"operator_authorized_author_work": False,
|
||||
"scratch_evidence_claimed": False,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class TestCheckoutProof(unittest.TestCase):
|
||||
"""Required behavior 1 + 2: prove HEAD == pinned PR head or stop."""
|
||||
|
||||
@@ -368,6 +384,74 @@ class TestSelfReviewContamination(unittest.TestCase):
|
||||
self.assertEqual(result["status"], "unknown")
|
||||
|
||||
|
||||
class TestRoleBoundary(unittest.TestCase):
|
||||
"""Issue #175: reviewer queue tasks must not pivot into author work."""
|
||||
|
||||
def test_reviewer_queue_without_author_mutations_is_clean(self):
|
||||
result = _good_role_boundary()
|
||||
self.assertEqual(result["status"], "clean")
|
||||
self.assertEqual(result["violations"], [])
|
||||
|
||||
def test_reviewer_queue_author_mutation_without_authorization_violates(self):
|
||||
result = assess_role_boundary(
|
||||
{
|
||||
"task_role": "reviewer",
|
||||
"task_kind": "blind_pr_queue_review",
|
||||
"reviewer_namespace_used": True,
|
||||
"author_namespace_used": True,
|
||||
"author_mutations": ["claim issue #171", "push branch"],
|
||||
"operator_authorized_author_work": False,
|
||||
"mixed_namespace_justification": (
|
||||
"author namespace was used for implementation"
|
||||
),
|
||||
}
|
||||
)
|
||||
self.assertEqual(result["status"], "violation")
|
||||
self.assertTrue(any("pivot" in r for r in result["violations"]))
|
||||
|
||||
def test_mixed_namespace_use_without_justification_is_warning(self):
|
||||
result = assess_role_boundary(
|
||||
{
|
||||
"task_role": "reviewer",
|
||||
"task_kind": "blind_pr_queue_review",
|
||||
"reviewer_namespace_used": True,
|
||||
"author_namespace_used": True,
|
||||
"author_mutations": [],
|
||||
}
|
||||
)
|
||||
self.assertEqual(result["status"], "warning")
|
||||
self.assertTrue(
|
||||
any("mixed" in r.lower() for r in result["reasons"])
|
||||
)
|
||||
|
||||
def test_author_task_cannot_perform_review_mutations(self):
|
||||
result = assess_role_boundary(
|
||||
{
|
||||
"task_role": "author",
|
||||
"reviewer_namespace_used": False,
|
||||
"author_namespace_used": True,
|
||||
"review_mutations": ["approve PR"],
|
||||
}
|
||||
)
|
||||
self.assertEqual(result["status"], "violation")
|
||||
self.assertTrue(
|
||||
any("reviewer-only" in r for r in result["violations"])
|
||||
)
|
||||
|
||||
def test_scratch_only_notes_are_not_durable_evidence(self):
|
||||
result = assess_role_boundary(
|
||||
{
|
||||
"task_role": "reviewer",
|
||||
"task_kind": "blind_pr_queue_review",
|
||||
"reviewer_namespace_used": True,
|
||||
"scratch_evidence_claimed": True,
|
||||
"scratch_evidence_durable": False,
|
||||
}
|
||||
)
|
||||
self.assertEqual(result["status"], "warning")
|
||||
self.assertTrue(any("scratch-only" in r for r in result["reasons"]))
|
||||
|
||||
|
||||
class TestFinalReport(unittest.TestCase):
|
||||
"""Required behavior 6 + acceptance criteria: the report must
|
||||
distinguish each proof, and only a fully proven run earns an "A"."""
|
||||
@@ -381,6 +465,7 @@ class TestFinalReport(unittest.TestCase):
|
||||
"identity_eligible": True,
|
||||
"merge_performed": False,
|
||||
"issue_status_verified": True,
|
||||
"role_boundary": _good_role_boundary(),
|
||||
}
|
||||
kwargs.update(overrides)
|
||||
return build_final_report(**kwargs)
|
||||
@@ -393,6 +478,7 @@ class TestFinalReport(unittest.TestCase):
|
||||
self.assertTrue(report["identity_eligible"])
|
||||
self.assertTrue(report["pr_author_distinct_from_reviewer"])
|
||||
self.assertEqual(report["session_contamination"], "clean")
|
||||
self.assertEqual(report["role_boundary"], "clean")
|
||||
self.assertTrue(report["validated_on_pinned_head"])
|
||||
self.assertFalse(report["merge_performed"])
|
||||
self.assertTrue(report["issue_status_verified"])
|
||||
@@ -465,6 +551,37 @@ class TestFinalReport(unittest.TestCase):
|
||||
self.assertNotEqual(report["grade"], "A")
|
||||
self.assertFalse(report["merge_allowed"])
|
||||
|
||||
def test_missing_role_boundary_downgrades_and_blocks_merge(self):
|
||||
kwargs = {
|
||||
"checkout_proof": _good_checkout(),
|
||||
"inventory": _good_inventory(),
|
||||
"validation": _good_validation(),
|
||||
"contamination": _good_contamination(),
|
||||
"identity_eligible": True,
|
||||
"merge_performed": False,
|
||||
"issue_status_verified": True,
|
||||
}
|
||||
report = build_final_report(**kwargs)
|
||||
self.assertNotEqual(report["grade"], "A")
|
||||
self.assertFalse(report["merge_allowed"])
|
||||
self.assertEqual(report["role_boundary"], "warning")
|
||||
|
||||
def test_role_boundary_violation_blocks_report(self):
|
||||
boundary = assess_role_boundary(
|
||||
{
|
||||
"task_role": "reviewer",
|
||||
"task_kind": "blind_pr_queue_review",
|
||||
"reviewer_namespace_used": True,
|
||||
"author_namespace_used": True,
|
||||
"author_mutations": ["create PR"],
|
||||
"operator_authorized_author_work": False,
|
||||
"mixed_namespace_justification": "implementation pivot",
|
||||
}
|
||||
)
|
||||
report = self._report(role_boundary=boundary)
|
||||
self.assertEqual(report["grade"], "blocked")
|
||||
self.assertFalse(report["merge_allowed"])
|
||||
|
||||
|
||||
class TestStdoutIsolation(unittest.TestCase):
|
||||
"""Regression test for #178: tests must not close or corrupt stdout/stderr
|
||||
|
||||
Reference in New Issue
Block a user