131 lines
6 KiB
Python
131 lines
6 KiB
Python
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
spec = importlib.util.spec_from_file_location("monitor", ROOT / "scripts/monitor_dependency_triage.py")
|
|
monitor = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(monitor)
|
|
|
|
|
|
class DependencyTriageTests(unittest.TestCase):
|
|
def test_yaml_task_lists_and_quoted_hash_are_preserved(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
scratch = Path(directory)
|
|
repo = scratch / "repo"
|
|
(repo / "workplans").mkdir(parents=True)
|
|
(scratch / "repos.json").write_text(json.dumps([{"slug": "example", "local_path": str(repo)}]))
|
|
(repo / "workplans/plan.md").write_text('''---
|
|
id: EX-WP-0001
|
|
type: workplan
|
|
status: blocked
|
|
flavor: implementation
|
|
---
|
|
```task
|
|
id: EX-WP-0001-T01
|
|
status: wait
|
|
depends_on:
|
|
- EX-WP-0002-T01
|
|
needs_human: true
|
|
blocking_reason: "Review packet #1"
|
|
wait_until: 2026-10-04
|
|
```
|
|
''')
|
|
subprocess.run([sys.executable, str(monitor.SCANNER)], check=True, capture_output=True,
|
|
env=dict(os.environ, TRIAGE_SCRATCH=directory))
|
|
task = json.loads((scratch / "files.json").read_text())["T"]["EX-WP-0001-T01"]
|
|
self.assertEqual(task["depends_on"], ["EX-WP-0002-T01"])
|
|
self.assertEqual(task["reason"], "Review packet #1")
|
|
self.assertTrue(task["needs_human"])
|
|
self.assertEqual(task["wait_until"], "2026-10-04")
|
|
|
|
def records(self):
|
|
task = {"wp": "plan", "status": "wait", "hub_id": "uuid", "needs_human": True,
|
|
"reason": "Review", "decision_id": "decision", "blocked_on": None,
|
|
"wait_until": None, "depends_on": []}
|
|
return {"WP": {"plan": {"status": "blocked", "flavor": "implementation"}}, "T": {"task": task}}
|
|
|
|
def test_missing_projection_is_visible(self):
|
|
result = monitor.audit(self.records(), [])
|
|
self.assertEqual(result["qualifier_drift"][0]["field"], "projection")
|
|
|
|
def test_decision_sync_gap_is_detected(self):
|
|
result = monitor.audit(self.records(), [{"id": "uuid", "status": "wait",
|
|
"needs_human": True, "blocking_reason": "Review"}])
|
|
self.assertEqual(result["qualifier_drift"], [{"task": "task", "field": "decision_id",
|
|
"file": "decision", "hub": None}])
|
|
self.assertEqual(result["unrouted_human_gates"], [])
|
|
|
|
def test_residuals_are_excluded_from_actionable_findings(self):
|
|
records = self.records()
|
|
records["WP"]["plan"]["flavor"] = "residual"
|
|
result = monitor.audit(records, [])
|
|
self.assertEqual(result["wait_tasks"], 0)
|
|
self.assertEqual(result["qualifier_drift"], [])
|
|
|
|
def test_terminal_predecessor_keeps_separate_human_gate(self):
|
|
records = self.records()
|
|
records["T"]["task"]["depends_on"] = ["predecessor"]
|
|
records["T"]["predecessor"] = {"wp": "plan", "status": "done"}
|
|
result = monitor.audit(records, [])
|
|
self.assertTrue(result["terminal_dependency_waits"][0]["needs_human"])
|
|
self.assertEqual(result["human_gates"], 1)
|
|
|
|
def test_tasks_follow_offset_pagination(self):
|
|
response = MagicMock()
|
|
with patch.object(monitor, "urlopen", return_value=response) as fetch, patch.object(
|
|
monitor.json, "load", side_effect=[[{"id": str(i)} for i in range(200)], [{"id": "last"}]]):
|
|
rows = monitor.get_rows("http://hub", "tasks")
|
|
self.assertEqual(len(rows), 201)
|
|
self.assertIn("offset=200", fetch.call_args_list[1].args[0])
|
|
|
|
def test_messages_do_not_use_unsupported_offset(self):
|
|
with patch.object(monitor, "urlopen", return_value=MagicMock()) as fetch, patch.object(
|
|
monitor.json, "load", return_value=[{"id": str(i)} for i in range(200)]):
|
|
rows = monitor.get_rows("http://hub", "messages", from_agent="owner")
|
|
self.assertEqual(len(rows), 200)
|
|
self.assertEqual(fetch.call_count, 1)
|
|
self.assertNotIn("offset", fetch.call_args.args[0])
|
|
|
|
def handoff_data(self):
|
|
original = {"id": "proposal", "created_at": "2026-10-03T01:00:00Z"}
|
|
reply = {"id": "reply", "created_at": "2026-10-03T02:00:00Z", "from_agent": "owner",
|
|
"to_agent": "the-custodian", "thread_id": "proposal", "subject": "Accepted"}
|
|
records = {"WP": {"owner-plan": {"repo": "owner", "flavor": "implementation"}},
|
|
"T": {"owner-task": {"wp": "owner-plan", "status": "todo"}}}
|
|
receipt = {"reply_message_id": "reply", "disposition": "accepted", "reason": "Owned locally",
|
|
"owner_workplan_id": "owner-plan", "owner_task_ids": ["owner-task"]}
|
|
return original, reply, records, receipt
|
|
|
|
def test_unreviewed_reply_does_not_satisfy_handoff(self):
|
|
original, reply, records, _ = self.handoff_data()
|
|
self.assertEqual(monitor.handoff_case("owner", original, [reply], records)["state"], "waiting")
|
|
|
|
def test_reviewed_owner_handoff_preserves_implementation_status(self):
|
|
original, reply, records, receipt = self.handoff_data()
|
|
case = monitor.handoff_case("owner", original, [reply], records, receipt)
|
|
self.assertEqual(case["state"], "satisfied")
|
|
self.assertEqual(case["obligor_workplan_id"], "owner-plan")
|
|
self.assertEqual(records["T"]["owner-task"]["status"], "todo")
|
|
|
|
def test_unrelated_thread_cannot_satisfy_handoff(self):
|
|
original, reply, records, receipt = self.handoff_data()
|
|
reply["thread_id"] = "different-proposal"
|
|
with self.assertRaises(ValueError):
|
|
monitor.handoff_case("owner", original, [reply], records, receipt)
|
|
|
|
def test_residual_owner_plan_cannot_accept_handoff(self):
|
|
original, reply, records, receipt = self.handoff_data()
|
|
records["WP"]["owner-plan"]["flavor"] = "residual"
|
|
with self.assertRaises(ValueError):
|
|
monitor.handoff_case("owner", original, [reply], records, receipt)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|