From c1d2bad901da59fa19ae8abe0b5a7231240e11dd Mon Sep 17 00:00:00 2001 From: jcwalker3 Date: Wed, 22 Jul 2026 04:08:43 -0500 Subject: [PATCH 1/2] feat(issue-628): Add unit and integration tests for autonomous canonical handoffs and task orchestration --- tests/test_issue_628_orchestration.py | 189 ++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 tests/test_issue_628_orchestration.py diff --git a/tests/test_issue_628_orchestration.py b/tests/test_issue_628_orchestration.py new file mode 100644 index 0000000..2cb0c54 --- /dev/null +++ b/tests/test_issue_628_orchestration.py @@ -0,0 +1,189 @@ +"""Integration tests for autonomous canonical handoffs and dependency-aware task orchestration (#628). + +Verifies the 21 acceptance criteria specified in umbrella Issue #628: +- Non-terminal stage handoff generation and retrieval +- Multi-worker concurrency and exclusive task assignment isolation +- Structured dependency graph integration with the work allocator +- Head SHA invalidation and stale review decision protection +""" + +import unittest +from unittest.mock import MagicMock, patch +import os +import json +import tempfile + +from canonical_thread_handoff import ( + format_cth_body, + parse_cth_comment, + assess_cth_comment, +) +import dependency_graph +from control_plane_db import ControlPlaneDB +from allocator_service import ( + WorkCandidate, + classify_skip, + ROLE_AUTHOR, + ROLE_REVIEWER, + ROLE_MERGER, + ROLE_RECONCILER, + OWNERSHIP_OWN, + OWNERSHIP_FOREIGN, +) + + +class TestIssue628Orchestration(unittest.TestCase): + def setUp(self): + self._tmp = tempfile.TemporaryDirectory() + self.db_path = os.path.join(self._tmp.name, "cp.sqlite3") + self.db = ControlPlaneDB(self.db_path) + + def tearDown(self): + self._tmp.cleanup() + + def test_canonical_handoff_serialization_and_retrieval(self): + """AC1 & AC2: Every non-terminal stage stores and retrieves a valid canonical handoff.""" + handoff = format_cth_body( + cth_type="Author Handoff", + status="completed", + next_owner="reviewer", + current_blocker="none", + decision="Implementation complete, tests passing", + proof="pytest tests/test_issue_628_orchestration.py passed", + next_action="Review PR and run reviewer pre-flight", + ready_to_paste_prompt="Review PR for issue #628", + ) + self.assertIn("CTH: Author Handoff", handoff) + + parsed = parse_cth_comment(handoff) + self.assertIsNotNone(parsed) + self.assertEqual(parsed["cth_type"], "Author Handoff") + + assessment = assess_cth_comment(handoff) + self.assertFalse(assessment["block"]) + + def test_exclusive_task_unit_single_owner(self): + """AC5 & AC6: Concurrency isolation ensures an exclusive task unit has only one active owner.""" + candidate = WorkCandidate( + kind="issue", + number=628, + title="Umbrella #628 test candidate", + state="open", + labels=["status:in-progress"], + blocked=False, + dependency_unmet=False, + ) + # Foreign ownership MUST be skipped + skip_foreign = classify_skip( + c=candidate, + role=ROLE_AUTHOR, + terminal_pr=None, + claim_ownership=OWNERSHIP_FOREIGN, + ) + self.assertIsNotNone(skip_foreign) + self.assertIn("active lease", skip_foreign) + + # Own/Self claim remains selectable for session resumption + skip_self = classify_skip( + c=candidate, + role=ROLE_AUTHOR, + terminal_pr=None, + claim_ownership=OWNERSHIP_OWN, + ) + self.assertIsNone(skip_self) + + def test_durable_dependency_graph_blocking(self): + """AC8, AC9, AC10: Durable dependency edges exclude blocked tasks from assignment.""" + # Upsert a blocking dependency edge between issue 628 and blocker 601 + self.db.upsert_dependency_edge( + remote="prgs", + org="Scaled-Tech-Consulting", + repo="Gitea-Tools", + source_kind="issue", + source_number=628, + target_kind="issue", + target_number=601, + edge_type=dependency_graph.EDGE_ISSUE_BLOCKED_BY_ISSUE, + state=dependency_graph.STATE_UNMET, + blocking_condition="Target issue #601 is not closed", + completion_condition="Target issue #601 is closed", + evidence={"source": "unit_test"}, + ) + + edges = self.db.list_dependency_edges( + remote="prgs", + org="Scaled-Tech-Consulting", + repo="Gitea-Tools", + source_kind="issue", + source_number=628, + ) + self.assertEqual(len(edges), 1) + self.assertEqual(edges[0]["state"], "unmet") + self.assertEqual(edges[0]["target_number"], 601) + + # When dependency is unmet, candidate is blocked from selection + candidate = WorkCandidate( + kind="issue", + number=628, + title="Blocked candidate", + state="open", + labels=[], + blocked=False, + dependency_unmet=True, + dependency_reason="issue#628 is blocked by unmet dependency issue#601", + ) + skip_reason = classify_skip( + c=candidate, + role=ROLE_AUTHOR, + terminal_pr=None, + claim_ownership=OWNERSHIP_OWN, + ) + self.assertIsNotNone(skip_reason) + self.assertIn("issue#601", skip_reason) + + def test_dependency_completion_reevaluation(self): + """AC11: Dependency completion updates edge state to MET.""" + self.db.upsert_dependency_edge( + remote="prgs", + org="Scaled-Tech-Consulting", + repo="Gitea-Tools", + source_kind="issue", + source_number=628, + target_kind="issue", + target_number=601, + edge_type=dependency_graph.EDGE_ISSUE_BLOCKED_BY_ISSUE, + state=dependency_graph.STATE_UNMET, + blocking_condition="Target issue #601 is open", + completion_condition="Target issue #601 is closed", + evidence={"source": "unit_test"}, + ) + + # Mark edge as met upon target issue closure + self.db.upsert_dependency_edge( + remote="prgs", + org="Scaled-Tech-Consulting", + repo="Gitea-Tools", + source_kind="issue", + source_number=628, + target_kind="issue", + target_number=601, + edge_type=dependency_graph.EDGE_ISSUE_BLOCKED_BY_ISSUE, + state=dependency_graph.STATE_MET, + blocking_condition="Target issue #601 is open", + completion_condition="Target issue #601 is closed", + evidence={"source": "target_closed_event"}, + ) + + edges = self.db.list_dependency_edges( + remote="prgs", + org="Scaled-Tech-Consulting", + repo="Gitea-Tools", + source_kind="issue", + source_number=628, + ) + self.assertEqual(len(edges), 1) + self.assertEqual(edges[0]["state"], "met") + + +if __name__ == "__main__": + unittest.main() From 3acfb039a96ae3978ed7eef95d5b680bed476fa8 Mon Sep 17 00:00:00 2001 From: Jason Walker <913443@dadeschools.net> Date: Fri, 24 Jul 2026 07:35:26 -0400 Subject: [PATCH 2/2] fix(pr-795): honest scope for #628 building-block regression tests Address REQUEST_CHANGES on PR #795: stop claiming all 21 umbrella ACs from a single unit-test module. Scope docs and cases to child issue #878 (CTH/format, ownership classify_skip, dependency edges). Does not close umbrella #628. --- tests/test_issue_628_orchestration.py | 52 ++++++++++++++++----------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/tests/test_issue_628_orchestration.py b/tests/test_issue_628_orchestration.py index 2cb0c54..91e9437 100644 --- a/tests/test_issue_628_orchestration.py +++ b/tests/test_issue_628_orchestration.py @@ -1,10 +1,20 @@ -"""Integration tests for autonomous canonical handoffs and dependency-aware task orchestration (#628). +"""Regression tests for #628 building blocks (child scope only). -Verifies the 21 acceptance criteria specified in umbrella Issue #628: -- Non-terminal stage handoff generation and retrieval -- Multi-worker concurrency and exclusive task assignment isolation -- Structured dependency graph integration with the work allocator -- Head SHA invalidation and stale review decision protection +Honest scope: unit coverage of pre-existing APIs used by umbrella #628. +This module does **not** implement or verify all 21 umbrella acceptance +criteria, automatic handoff store/retrieve, multi-worker product wiring, +or end-to-end orchestration. + +Covered building blocks: +- CTH format / parse / assess (`format_cth_body`, `parse_cth_comment`, + `assess_cth_comment`) +- Exclusive-ownership skip classification (`classify_skip` with + OWNERSHIP_FOREIGN vs OWNERSHIP_OWN) +- Durable dependency edges (`upsert_dependency_edge` / list) and skip + when dependency_unmet +- Edge state transition UNMET -> MET + +Parent umbrella remains #628; this slice is a scoped child issue only. """ import unittest @@ -42,7 +52,7 @@ class TestIssue628Orchestration(unittest.TestCase): self._tmp.cleanup() def test_canonical_handoff_serialization_and_retrieval(self): - """AC1 & AC2: Every non-terminal stage stores and retrieves a valid canonical handoff.""" + """Building block: format/parse/assess a CTH body (not full AC1/AC2 product path).""" handoff = format_cth_body( cth_type="Author Handoff", status="completed", @@ -51,7 +61,7 @@ class TestIssue628Orchestration(unittest.TestCase): decision="Implementation complete, tests passing", proof="pytest tests/test_issue_628_orchestration.py passed", next_action="Review PR and run reviewer pre-flight", - ready_to_paste_prompt="Review PR for issue #628", + ready_to_paste_prompt="Review PR for child issue #878 (parent #628)", ) self.assertIn("CTH: Author Handoff", handoff) @@ -63,11 +73,11 @@ class TestIssue628Orchestration(unittest.TestCase): self.assertFalse(assessment["block"]) def test_exclusive_task_unit_single_owner(self): - """AC5 & AC6: Concurrency isolation ensures an exclusive task unit has only one active owner.""" + """Building block: classify_skip foreign vs own ownership.""" candidate = WorkCandidate( kind="issue", - number=628, - title="Umbrella #628 test candidate", + number=878, + title="Child #878 ownership classify candidate", state="open", labels=["status:in-progress"], blocked=False, @@ -93,14 +103,14 @@ class TestIssue628Orchestration(unittest.TestCase): self.assertIsNone(skip_self) def test_durable_dependency_graph_blocking(self): - """AC8, AC9, AC10: Durable dependency edges exclude blocked tasks from assignment.""" - # Upsert a blocking dependency edge between issue 628 and blocker 601 + """Building block: unmet dependency edge + classify_skip on dependency_unmet.""" + # Upsert a blocking dependency edge between issue 878 and blocker 601 self.db.upsert_dependency_edge( remote="prgs", org="Scaled-Tech-Consulting", repo="Gitea-Tools", source_kind="issue", - source_number=628, + source_number=878, target_kind="issue", target_number=601, edge_type=dependency_graph.EDGE_ISSUE_BLOCKED_BY_ISSUE, @@ -115,7 +125,7 @@ class TestIssue628Orchestration(unittest.TestCase): org="Scaled-Tech-Consulting", repo="Gitea-Tools", source_kind="issue", - source_number=628, + source_number=878, ) self.assertEqual(len(edges), 1) self.assertEqual(edges[0]["state"], "unmet") @@ -124,13 +134,13 @@ class TestIssue628Orchestration(unittest.TestCase): # When dependency is unmet, candidate is blocked from selection candidate = WorkCandidate( kind="issue", - number=628, + number=878, title="Blocked candidate", state="open", labels=[], blocked=False, dependency_unmet=True, - dependency_reason="issue#628 is blocked by unmet dependency issue#601", + dependency_reason="issue#878 is blocked by unmet dependency issue#601", ) skip_reason = classify_skip( c=candidate, @@ -142,13 +152,13 @@ class TestIssue628Orchestration(unittest.TestCase): self.assertIn("issue#601", skip_reason) def test_dependency_completion_reevaluation(self): - """AC11: Dependency completion updates edge state to MET.""" + """Building block: dependency edge state can transition UNMET -> MET.""" self.db.upsert_dependency_edge( remote="prgs", org="Scaled-Tech-Consulting", repo="Gitea-Tools", source_kind="issue", - source_number=628, + source_number=878, target_kind="issue", target_number=601, edge_type=dependency_graph.EDGE_ISSUE_BLOCKED_BY_ISSUE, @@ -164,7 +174,7 @@ class TestIssue628Orchestration(unittest.TestCase): org="Scaled-Tech-Consulting", repo="Gitea-Tools", source_kind="issue", - source_number=628, + source_number=878, target_kind="issue", target_number=601, edge_type=dependency_graph.EDGE_ISSUE_BLOCKED_BY_ISSUE, @@ -179,7 +189,7 @@ class TestIssue628Orchestration(unittest.TestCase): org="Scaled-Tech-Consulting", repo="Gitea-Tools", source_kind="issue", - source_number=628, + source_number=878, ) self.assertEqual(len(edges), 1) self.assertEqual(edges[0]["state"], "met")