Introduce thread_state_ledger_validator for the mandatory two-comment workflow: tagged [CONTROLLER HANDOFF] paired with [THREAD STATE LEDGER]. Wire validation into final_report_validator and gitea_create_issue_comment, add runbook docs, worked examples, and tests. Closes #507 Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
187 lines
6.6 KiB
Python
187 lines
6.6 KiB
Python
"""Tests for two-comment workflow validation (#507)."""
|
|
import sys
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from thread_state_ledger_validator import ( # noqa: E402
|
|
BLOCKER_CLASSIFICATIONS,
|
|
assess_controller_handoff_comment,
|
|
assess_thread_state_ledger_comment,
|
|
assess_two_comment_workflow,
|
|
assess_two_comment_pair,
|
|
)
|
|
|
|
|
|
def _minimal_handoff(**overrides):
|
|
lines = [
|
|
"[CONTROLLER HANDOFF] PR #203 / Issue #182 — review complete",
|
|
"",
|
|
"Identity/profile:",
|
|
"- Active profile: prgs-reviewer",
|
|
"- Authenticated identity: sysadmin",
|
|
"",
|
|
"Server-side mutation ledger:",
|
|
"- gitea_submit_pr_review → APPROVED review posted to Gitea",
|
|
"",
|
|
"Blockers:",
|
|
"- none",
|
|
]
|
|
text = "\n".join(lines)
|
|
for key, value in overrides.items():
|
|
if f"{key}:" in text:
|
|
text = text.replace(f"{key}:", f"{key}: {value}", 1)
|
|
return text
|
|
|
|
|
|
def _minimal_ledger(**overrides):
|
|
lines = [
|
|
"[THREAD STATE LEDGER] PR #203 — APPROVED review posted to Gitea",
|
|
"",
|
|
"What is true now:",
|
|
"- PR state: open",
|
|
"- Current head SHA: 0fdc8f582026b72a229d59a172c0a63ac4aaeaf9",
|
|
"- Server-side decision state: APPROVED review posted to Gitea",
|
|
"- Local verdict/state: APPROVE verdict prepared locally",
|
|
"- Latest known validation: tests passed locally",
|
|
"",
|
|
"What changed:",
|
|
"- APPROVED review posted to Gitea at pinned head",
|
|
"",
|
|
"What is blocked:",
|
|
"- Blocker classification: no blocker",
|
|
"",
|
|
"Who/what acts next:",
|
|
"- Next actor: merger",
|
|
"- Required action: merge on explicit operator command",
|
|
"- Do not do: re-post APPROVE",
|
|
"- Resume from: PR #203 review feedback",
|
|
]
|
|
text = "\n".join(lines)
|
|
for key, value in overrides.items():
|
|
text = text.replace(f"- {key}:", f"- {key}: {value}")
|
|
return text
|
|
|
|
|
|
class TestTwoCommentWorkflow(unittest.TestCase):
|
|
def test_valid_combined_report_passes(self):
|
|
report = _minimal_handoff() + "\n\n" + _minimal_ledger()
|
|
result = assess_two_comment_workflow(report)
|
|
self.assertTrue(result["proven"])
|
|
self.assertFalse(result["block"])
|
|
|
|
def test_missing_ledger_after_tagged_handoff_blocks(self):
|
|
result = assess_two_comment_workflow(_minimal_handoff())
|
|
self.assertTrue(result["block"])
|
|
self.assertTrue(
|
|
any("THREAD STATE LEDGER" in r for r in result["reasons"])
|
|
)
|
|
|
|
def test_legacy_controller_handoff_section_not_required_ledger(self):
|
|
legacy = "## Controller Handoff\n\n- Task: work issue\n- Next: continue\n"
|
|
result = assess_two_comment_workflow(legacy)
|
|
self.assertFalse(result["block"])
|
|
|
|
def test_ambiguous_approved_blocks(self):
|
|
handoff = _minimal_handoff().replace(
|
|
"APPROVED review posted to Gitea",
|
|
"approved",
|
|
)
|
|
report = handoff + "\n\n" + _minimal_ledger()
|
|
result = assess_two_comment_workflow(report)
|
|
self.assertTrue(result["block"])
|
|
self.assertTrue(any("approved" in r.lower() for r in result["reasons"]))
|
|
|
|
def test_ambiguous_merged_blocks(self):
|
|
handoff = _minimal_handoff() + "\n\n- Narrative: merged successfully"
|
|
ledger = _minimal_ledger()
|
|
report = handoff + "\n\n" + ledger
|
|
result = assess_two_comment_workflow(report)
|
|
self.assertTrue(result["block"])
|
|
self.assertTrue(any("merge" in r.lower() for r in result["reasons"]))
|
|
|
|
def test_blocked_without_gate_blocks(self):
|
|
handoff = _minimal_handoff().replace("- none", "- blocked")
|
|
ledger = _minimal_ledger().replace(
|
|
"no blocker", "blocked but no gate named"
|
|
)
|
|
report = handoff + "\n\n" + ledger
|
|
result = assess_two_comment_workflow(report)
|
|
self.assertTrue(result["block"])
|
|
|
|
def test_mutation_ledger_contradiction_blocks(self):
|
|
handoff = _minimal_handoff().replace(
|
|
"APPROVED review posted to Gitea",
|
|
"none — no server-side state changed",
|
|
)
|
|
handoff += "\n- Narrative: APPROVED review posted to Gitea"
|
|
report = handoff + "\n\n" + _minimal_ledger()
|
|
result = assess_two_comment_workflow(report)
|
|
self.assertTrue(result["block"])
|
|
self.assertTrue(any("contradict" in r.lower() for r in result["reasons"]))
|
|
|
|
def test_local_verdict_as_server_side_blocks(self):
|
|
ledger = _minimal_ledger(
|
|
**{
|
|
"Server-side decision state": "APPROVE verdict prepared locally",
|
|
"Local verdict/state": "none",
|
|
}
|
|
)
|
|
report = _minimal_handoff() + "\n\n" + ledger
|
|
result = assess_two_comment_workflow(report)
|
|
self.assertTrue(result["block"])
|
|
|
|
|
|
class TestHandoffComment(unittest.TestCase):
|
|
def test_handoff_comment_valid(self):
|
|
result = assess_controller_handoff_comment(_minimal_handoff())
|
|
self.assertTrue(result["proven"])
|
|
|
|
def test_handoff_without_marker_skips(self):
|
|
result = assess_controller_handoff_comment("casual comment")
|
|
self.assertTrue(result["proven"])
|
|
self.assertFalse(result["performed"])
|
|
|
|
|
|
class TestLedgerComment(unittest.TestCase):
|
|
def test_ledger_requires_blocker_classification(self):
|
|
ledger = _minimal_ledger().replace(
|
|
"Blocker classification: no blocker",
|
|
"something unclear",
|
|
)
|
|
result = assess_thread_state_ledger_comment(ledger)
|
|
self.assertTrue(result["block"])
|
|
|
|
def test_all_blocker_classifications_recognized(self):
|
|
self.assertIn("no blocker", BLOCKER_CLASSIFICATIONS)
|
|
self.assertIn("environment/tooling blocker", BLOCKER_CLASSIFICATIONS)
|
|
|
|
|
|
class TestPairedComments(unittest.TestCase):
|
|
def test_pair_passes(self):
|
|
result = assess_two_comment_pair(_minimal_handoff(), _minimal_ledger())
|
|
self.assertTrue(result["proven"])
|
|
|
|
def test_pair_missing_ledger_blocks(self):
|
|
result = assess_two_comment_pair(_minimal_handoff(), "")
|
|
self.assertTrue(result["block"])
|
|
|
|
|
|
class TestExamples(unittest.TestCase):
|
|
"""Smoke-test bundled examples from docs."""
|
|
|
|
def test_examples_module_loads(self):
|
|
from thread_state_ledger_examples import EXAMPLES # noqa: E402
|
|
|
|
self.assertGreaterEqual(len(EXAMPLES), 8)
|
|
for name, handoff, ledger in EXAMPLES:
|
|
result = assess_two_comment_pair(handoff, ledger)
|
|
self.assertTrue(
|
|
result["proven"],
|
|
f"example '{name}' failed: {result['reasons']}",
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main() |