"""Tests for the two-turn worker→review handoff. After a worker DONE the runtime no longer treats the last execute-turn prose as the canonical completion_report. Instead it spawns a hidden `report::::v1` work item that resumes the same worker session under a dedicated report-generation prompt; only after that report turn finishes does the review card get created. dispatch / delivery work items skip the report step (they don't need one). """ from __future__ import annotations import tempfile import unittest from pathlib import Path from unittest.mock import AsyncMock, MagicMock from opc.core.config import OPCConfig, RoleConfig from opc.core.events import EventBus from opc.core.models import ( DelegationWorkItem, Phase, Task, TaskResult, TaskStatus, ) from opc.database.store import OPCStore from opc.layer2_organization.communication import CommunicationManager from opc.layer2_organization.company_mode import ( CompanyWorkItemExecutor, report_work_item_id_for_attempt, review_work_item_id_for_attempt, ) from opc.layer2_organization.org_engine import OrgEngine from opc.layer2_organization.work_item_links import set_linked_work_item_id def _build_executor(store: OPCStore, org_engine: OrgEngine) -> CompanyWorkItemExecutor: communication = CommunicationManager(store, EventBus(), org_engine=org_engine) return CompanyWorkItemExecutor( org_engine=org_engine, communication=communication, approval_engine=MagicMock(), memory=None, execute_task=AsyncMock(), save_task=store.save_task, store=store, ) def _make_org_engine(root: Path) -> OrgEngine: config = OPCConfig() config.org.company_profile = "custom" config.org.roles = [ RoleConfig(id="ceo", name="CEO", responsibility="Set direction.", reports_to="owner"), RoleConfig(id="cto", name="CTO", responsibility="Lead engineering.", reports_to="ceo"), RoleConfig(id="engineer", name="Engineer", responsibility="Build features.", reports_to="cto"), ] return OrgEngine(config, root) def _build_child_work_item() -> DelegationWorkItem: return DelegationWorkItem( work_item_id="wi-child", run_id="run-1", cell_id="team::cto", team_id="team::cto", role_id="engineer", seat_id="seat::team::cto::engineer", manager_role_id="cto", manager_seat_id="seat::team::cto::cto", title="Build feature", summary="Ship the feature.", kind="execute", projection_id="wi-child", phase=Phase.RUNNING, metadata={ "team_id": "team::cto", "seat_id": "seat::team::cto::engineer", "manager_role_id": "cto", "manager_seat_id": "seat::team::cto::cto", "runtime_model": "multi_team_org", "activation_state": "active", "work_kind": "execute", }, ) def _build_worker_task() -> Task: task = Task( id="task-engineer", title="Build feature", project_id="proj1", session_id="session-root", parent_session_id="session-root", assigned_to="engineer", status=TaskStatus.DONE, metadata={ "execution_mode": "company_mode", "runtime_model": "multi_team_org", "work_item_runtime": True, "delegation_run_id": "run-1", "delegation_team_id": "team::cto", "delegation_seat_id": "seat::team::cto::engineer", "work_item_role_id": "engineer", "manager_role_id": "cto", "manager_seat_id": "seat::team::cto::cto", "work_item_projection_id": "wi-child", "work_kind": "execute", }, ) set_linked_work_item_id(task, "wi-child") return task class WorkerExecuteDoneSpawnsReportTests(unittest.IsolatedAsyncioTestCase): """Phase 1 of the handoff: worker execute turn finishes. Expectation: a hidden report work item is created in the worker seat's queue. The review work item is NOT created yet — that happens only after the report turn finishes. """ async def test_worker_done_spawns_report_card_and_no_review_card(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) store = OPCStore(root / "tasks.db") await store.initialize() try: org_engine = _make_org_engine(root) executor = _build_executor(store, org_engine) await store.save_delegation_work_item(_build_child_work_item()) worker_task = _build_worker_task() await store.save_task(worker_task) await executor._apply_done_transition( worker_task, result=TaskResult( status=TaskStatus.DONE, content="All shipped — handoff prose from execute turn.", ), ) report_id = report_work_item_id_for_attempt("wi-child", 1) report_card = await store.get_delegation_work_item(report_id) self.assertIsNotNone( report_card, "report card must be spawned when worker execute turn finishes", ) self.assertEqual(report_card.kind, "report") self.assertEqual(report_card.phase, Phase.READY) self.assertTrue(report_card.metadata.get("report_execution_work_item")) self.assertTrue(report_card.metadata.get("hidden_from_company_kanban")) self.assertEqual( report_card.metadata.get("current_turn_mode"), "report_required", ) self.assertEqual( report_card.metadata.get("report_target_work_item_id"), "wi-child", ) # The same worker seat owns the report card — it's the # worker's own session being resumed for the handoff. self.assertEqual(report_card.role_id, "engineer") self.assertEqual(report_card.seat_id, "seat::team::cto::engineer") # The review card must NOT exist yet. review_id = review_work_item_id_for_attempt("wi-child", 1) self.assertIsNone( await store.get_delegation_work_item(review_id), "review card must wait for the report turn to finish", ) finally: await store.close() async def test_dispatch_kind_skips_report_step(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) store = OPCStore(root / "tasks.db") await store.initialize() try: org_engine = _make_org_engine(root) executor = _build_executor(store, org_engine) # CEO dispatch card. work_kind=dispatch routes directly # to APPROVED — no review, and no report turn. child = _build_child_work_item() child.metadata = dict(child.metadata or {}) child.metadata["work_kind"] = "dispatch" await store.save_delegation_work_item(child) worker_task = _build_worker_task() worker_task.metadata = dict(worker_task.metadata or {}) worker_task.metadata["work_kind"] = "dispatch" await store.save_task(worker_task) await executor._apply_done_transition( worker_task, result=TaskResult(status=TaskStatus.DONE, content="dispatched"), ) self.assertIsNone( await store.get_delegation_work_item( report_work_item_id_for_attempt("wi-child", 1) ), "dispatch DONE must not spawn a report turn", ) self.assertIsNone( await store.get_delegation_work_item( review_work_item_id_for_attempt("wi-child", 1) ), "dispatch DONE must not spawn a review turn", ) finally: await store.close() class ReportTurnDoneSpawnsReviewTests(unittest.IsolatedAsyncioTestCase): """Phase 2 of the handoff: the report turn finishes. Expectation: the review work item is now spawned, and the completion_report it carries is the report turn's output (not the original execute prose). The hidden report card itself transitions to APPROVED. """ async def _setup_after_execute_done( self, store: OPCStore, executor: CompanyWorkItemExecutor ) -> tuple[Task, str]: await store.save_delegation_work_item(_build_child_work_item()) worker_task = _build_worker_task() await store.save_task(worker_task) await executor._apply_done_transition( worker_task, result=TaskResult( status=TaskStatus.DONE, content="execute turn prose — should NOT end up as completion_report", ), ) report_id = report_work_item_id_for_attempt("wi-child", 1) report_card = await store.get_delegation_work_item(report_id) self.assertIsNotNone(report_card) # In production the dispatcher claims the report card # (READY → RUNNING) before the worker actually runs the report # turn. We bypass the dispatcher here, so flip it manually so # _apply_report_done_transition can later close it RUNNING → # APPROVED via the canonical transition table. await store.update_delegation_work_item(report_id, phase=Phase.RUNNING) return worker_task, report_id def _report_turn_task( self, *, report_card_id: str, target_work_item_id: str ) -> Task: # The materialized Task that the dispatcher would build for the # report card. We construct it directly here. task = Task( id="task-report-1", title="Write handoff report", project_id="proj1", session_id="session-root", parent_session_id="session-root", assigned_to="engineer", status=TaskStatus.DONE, metadata={ "execution_mode": "company_mode", "runtime_model": "multi_team_org", "work_item_runtime": True, "delegation_run_id": "run-1", "delegation_team_id": "team::cto", "delegation_seat_id": "seat::team::cto::engineer", "work_item_role_id": "engineer", "manager_role_id": "cto", "manager_seat_id": "seat::team::cto::cto", "report_execution_work_item": True, "report_target_work_item_id": target_work_item_id, "work_kind": "report", "work_item_turn_type": "report", "current_turn_mode": "report_required", }, ) set_linked_work_item_id(task, report_card_id) set_linked_work_item_id(task, report_card_id) return task async def test_report_turn_done_spawns_review_with_report_as_completion(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) store = OPCStore(root / "tasks.db") await store.initialize() try: org_engine = _make_org_engine(root) executor = _build_executor(store, org_engine) _worker_task, report_id = await self._setup_after_execute_done( store, executor ) # Need to also set the parent's review_owner_role_id / # review_owner_seat_id, which the canonical execute-DONE # path stamps on the parent metadata. (Done by the # earlier _apply_done_transition call.) Sanity check: parent = await store.get_delegation_work_item("wi-child") self.assertIsNotNone(parent) self.assertEqual(parent.metadata.get("review_owner_role_id"), "cto") report_task = self._report_turn_task( report_card_id=report_id, target_work_item_id="wi-child" ) report_payload = ( "Handoff report:\n\n" '{"summary":"Built the feature with tests.",\n' ' "deliverables":[{"name":"feature.py","path":"/tmp/feature.py","status":"complete"}],\n' ' "acceptance_status":[{"criterion":"feature works","met":true,"evidence":"tests pass"}],\n' ' "risks":["minor flakiness on Windows"],\n' ' "next_actions":["reviewer to verify integration test"]}' ) await executor._apply_done_transition( report_task, result=TaskResult(status=TaskStatus.DONE, content=report_payload), ) review_id = review_work_item_id_for_attempt("wi-child", 1) review_card = await store.get_delegation_work_item(review_id) self.assertIsNotNone( review_card, "review card must be spawned after report turn finishes", ) self.assertEqual(review_card.kind, "review") self.assertEqual( review_card.metadata.get("review_completion_report"), report_payload, "review_completion_report must come from the report turn, not the execute turn", ) evidence = review_card.metadata.get("review_evidence", {}) or {} worker_report = evidence.get("worker_report") or {} self.assertIn( "Built the feature", str(worker_report.get("summary", "")), "parsed worker report should be merged into review_evidence", ) # The hidden report card itself is now APPROVED. report_card_after = await store.get_delegation_work_item(report_id) self.assertEqual(report_card_after.phase, Phase.APPROVED) finally: await store.close() async def test_report_json_parsing_failure_falls_back_to_full_prose(self) -> None: with tempfile.TemporaryDirectory() as tmpdir: root = Path(tmpdir) store = OPCStore(root / "tasks.db") await store.initialize() try: org_engine = _make_org_engine(root) executor = _build_executor(store, org_engine) _worker_task, report_id = await self._setup_after_execute_done( store, executor ) # Pure prose handoff — no JSON. Per design we DO NOT # re-prompt the worker; we hand the prose to the # reviewer as-is. pure_prose = "I built the thing. It works. Tests pass. No JSON." report_task = self._report_turn_task( report_card_id=report_id, target_work_item_id="wi-child" ) await executor._apply_done_transition( report_task, result=TaskResult(status=TaskStatus.DONE, content=pure_prose), ) review_id = review_work_item_id_for_attempt("wi-child", 1) review_card = await store.get_delegation_work_item(review_id) self.assertIsNotNone(review_card) self.assertEqual( review_card.metadata.get("review_completion_report"), pure_prose, "prose handoff must reach the reviewer verbatim", ) # No worker_report field when parsing failed (or it's empty). evidence = review_card.metadata.get("review_evidence", {}) or {} self.assertFalse( evidence.get("worker_report"), "no worker_report block expected when parsing failed", ) finally: await store.close() class ReportCardRunnableFilterTests(unittest.TestCase): """Pin the dispatcher-runnability filters for report cards. Two parallel filters in company_mode.py independently decide whether a hidden card is runnable: ``_work_item_is_runnable`` (the engine enqueue gate) and the materialization filter inside ``_materialize_work_item_tasks``. Both must let report cards through, or the worker session is never re-engaged for the handoff turn and the parent stays at AWAITING_MANAGER_REVIEW forever — the new22/app-4 stuck-at-review pathology. """ def _make_parent_and_report(self) -> tuple[DelegationWorkItem, DelegationWorkItem]: parent = DelegationWorkItem( work_item_id="wi-parent", run_id="run-1", cell_id="team::cto", team_id="team::cto", role_id="engineer", seat_id="seat::team::cto::engineer", manager_role_id="cto", manager_seat_id="seat::team::cto::cto", title="Build feature", summary="Ship the feature.", kind="execute", projection_id="wi-parent", phase=Phase.AWAITING_MANAGER_REVIEW, metadata={ "runtime_model": "multi_team_org", "team_id": "team::cto", }, ) report = DelegationWorkItem( work_item_id="report::wi-parent::v1", run_id="run-1", cell_id="team::cto", team_id="team::cto", role_id="engineer", seat_id="seat::team::cto::engineer", parent_work_item_id="wi-parent", kind="report", projection_id="report::wi-parent::v1", phase=Phase.READY, metadata={ "runtime_model": "multi_team_org", "report_execution_work_item": True, "hidden_from_company_kanban": True, "report_target_work_item_id": "wi-parent", "team_id": "team::cto", }, ) return parent, report def test_runnable_filter_passes_report_card(self) -> None: parent, report = self._make_parent_and_report() wi_map = {parent.work_item_id: parent, report.work_item_id: report} self.assertTrue( CompanyWorkItemExecutor._work_item_is_runnable(report, wi_map), "report card must be considered runnable so the engine enqueues " "it; the new22/app-4 pathology was that the hidden+not-review " "filter excluded it and the parent stayed at AWAITING_MANAGER_REVIEW.", ) def test_runnable_filter_skips_report_when_parent_left_review(self) -> None: parent, report = self._make_parent_and_report() # Parent has somehow advanced past review (e.g. CANCELLED). The # report card is now obsolete and must NOT be claimed. parent.phase = Phase.CANCELLED wi_map = {parent.work_item_id: parent, report.work_item_id: report} self.assertFalse( CompanyWorkItemExecutor._work_item_is_runnable(report, wi_map), "report card must not run when its parent is no longer in review", ) if __name__ == "__main__": unittest.main()