feat: unify coordinator semantics (Muse role alignment phase 1)
Platform coordinator.py is the single source of truth; the skill coordinator template (templates/roles/00-coordinator.md + SKILL.md appendix 00 + body) now derives from it instead of describing a second, model-played coordinator (U1-U13). - server: _create_task accepts parent_task_id (404/400 checks, coordinator-under-coordinator rejected); follow-ups to a running coordinator return 409 at entry (_create_followup / _deliver_to_thread) with documented alternatives (U14/U15). - tests: 8 new coordinator tests (depth limit, lineage, replay, follow-up 409); appendix sync/provenance/transplant suites green. - check_appendix.py: 12 chapters match templates/roles/.
This commit is contained in:
1 parent
834eced686
commit
f6df76ee8f
5 files changed
+308
-28
No files matched your search
@@ -238,6 +238,25 @@ class WorkerCoordinatorTestCase(unittest.TestCase):
|
||||
self.assertEqual(backend["effort"], "high")
|
||||
self.assertEqual(backend["status"], "queued")
|
||||
|
||||
def test_sync_children_never_carry_coordinator_parts(self):
|
||||
# 纵深防御(Muse 角色对齐 P1):_coord_sync_children 建出
|
||||
# 的子任务行 coordinator_parts 恒为 NULL,子任务永为普通任务。
|
||||
_insert_task(self.db_path, "coord", coordinator_parts=self._parts())
|
||||
worker = self._worker()
|
||||
task = worker.get_task("coord")
|
||||
worker._coord_sync_children(task, task["coordinator_parts"])
|
||||
conn = sqlite3.connect(self.db_path)
|
||||
try:
|
||||
rows = conn.execute(
|
||||
"SELECT id, coordinator_parts FROM tasks"
|
||||
" WHERE parent_task_id = ?",
|
||||
("coord",)).fetchall()
|
||||
finally:
|
||||
conn.close()
|
||||
self.assertEqual(len(rows), 2)
|
||||
for task_id, parts in rows:
|
||||
self.assertIsNone(parts, task_id)
|
||||
|
||||
def test_sync_requeues_failed_keeps_completed(self):
|
||||
_insert_task(self.db_path, "coord", coordinator_parts=self._parts())
|
||||
_insert_task(self.db_path, "c-old", status="failed",
|
||||
@@ -469,6 +488,103 @@ class ServerCoordinatorTestCase(unittest.TestCase):
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(len(data["tasks"]), 3)
|
||||
|
||||
def _create_task(self, extra=None):
|
||||
body = {"project_id": self.project["id"], "prompt": "做点事"}
|
||||
if extra:
|
||||
body.update(extra)
|
||||
return self._req("POST", "/tasks", body, self.owner_key)
|
||||
|
||||
def test_create_child_coordinator_under_plain_parent_ok(self):
|
||||
status, parent = self._create_task()
|
||||
self.assertEqual(status, 201)
|
||||
status, child = self._create_coord(
|
||||
extra={"parent_task_id": parent["id"]})
|
||||
self.assertEqual(status, 201)
|
||||
self.assertEqual(child["parent_task_id"], parent["id"])
|
||||
self.assertEqual(len(child["coordinator_parts"]), 2)
|
||||
|
||||
def test_create_coordinator_under_coordinator_rejected(self):
|
||||
status, coord = self._create_coord()
|
||||
self.assertEqual(status, 201)
|
||||
status, body = self._create_coord(
|
||||
extra={"parent_task_id": coord["id"]})
|
||||
self.assertEqual(status, 400)
|
||||
self.assertIn("coordinator task cannot be created under a"
|
||||
" coordinator task", body["error"])
|
||||
status, data = self._req(
|
||||
"GET", "/tasks?project_id=" + self.project["id"],
|
||||
key=self.owner_key)
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(len(data["tasks"]), 1)
|
||||
|
||||
def test_create_parent_task_checks(self):
|
||||
status, _body = self._create_coord(
|
||||
extra={"parent_task_id": "no-such-task"})
|
||||
self.assertEqual(status, 404)
|
||||
status, other = self._req(
|
||||
"POST", "/projects", {"name": "other"}, self.owner_key)
|
||||
self.assertEqual(status, 201)
|
||||
status, parent = self._create_task(
|
||||
extra={"project_id": other["id"]})
|
||||
self.assertEqual(status, 201)
|
||||
status, body = self._create_coord(
|
||||
extra={"parent_task_id": parent["id"]})
|
||||
self.assertEqual(status, 400)
|
||||
status, body = self._create_coord(
|
||||
extra={"parent_task_id": 123})
|
||||
self.assertEqual(status, 400)
|
||||
|
||||
def test_create_lineage_without_coordinator_ok(self):
|
||||
status, coord = self._create_coord()
|
||||
self.assertEqual(status, 201)
|
||||
status, child = self._create_task(
|
||||
extra={"parent_task_id": coord["id"]})
|
||||
self.assertEqual(status, 201)
|
||||
self.assertEqual(child["parent_task_id"], coord["id"])
|
||||
|
||||
def test_create_idempotent_replay_keeps_single_task(self):
|
||||
status, parent = self._create_task()
|
||||
self.assertEqual(status, 201)
|
||||
extra = {"idempotency_key": "idem-parent",
|
||||
"parent_task_id": parent["id"]}
|
||||
status, first = self._create_task(extra=extra)
|
||||
self.assertEqual(status, 201)
|
||||
status, second = self._create_task(extra=extra)
|
||||
self.assertEqual(status, 200)
|
||||
self.assertEqual(second["id"], first["id"])
|
||||
status, data = self._req(
|
||||
"GET", "/tasks?project_id=" + self.project["id"],
|
||||
key=self.owner_key)
|
||||
self.assertEqual(len(data["tasks"]), 2)
|
||||
|
||||
def test_followup_to_coordinator_rejected(self):
|
||||
status, coord = self._create_coord()
|
||||
self.assertEqual(status, 201)
|
||||
status, body = self._req(
|
||||
"POST", "/tasks/" + coord["id"] + "/followups",
|
||||
{"body": "改一下分工", "mode": "queued"}, self.owner_key)
|
||||
self.assertEqual(status, 409)
|
||||
self.assertIn("coordinator tasks do not accept follow-up",
|
||||
body["error"])
|
||||
status, body = self._req(
|
||||
"POST", "/tasks/" + coord["id"] + "/thread/message",
|
||||
{"body": "改一下分工", "mode": "queued"}, self.owner_key)
|
||||
self.assertEqual(status, 409)
|
||||
self.assertIn("coordinator tasks do not accept follow-up",
|
||||
body["error"])
|
||||
|
||||
def test_followup_to_plain_task_ok(self):
|
||||
status, task = self._create_task()
|
||||
self.assertEqual(status, 201)
|
||||
status, _body = self._req(
|
||||
"POST", "/tasks/" + task["id"] + "/followups",
|
||||
{"body": "继续", "mode": "queued"}, self.owner_key)
|
||||
self.assertEqual(status, 201)
|
||||
status, body = self._req(
|
||||
"POST", "/tasks/" + task["id"] + "/thread/message",
|
||||
{"body": "继续", "mode": "queued"}, self.owner_key)
|
||||
self.assertEqual(status, 201)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user