[R115] originate ci_watch/dep_update fix tasks as PLANNING coordination roots

The Main-PM-code-impossibility guard (commit e202ce39, Thread 4 of this
audit) made team=MAIN_PM + task_type=CODE impossible — a Main PM coordinates,
it does not write code. But the ci_watch and dep_update engines still
originated their fix tasks as task_type=TaskType.CODE assigned to main-pm,
so task_svc.create raised MAIN_PM_NO_CODE and NO fix task was ever opened
— a regression introduced by the earlier audit fix (confirmed: the engine
tests pass at e202ce39~1 and fail at HEAD).

Mirror the hardened self_heal_engine precedent (self_heal_engine.py:197)
which already uses task_type=TaskType.PLANNING for its Main-PM coordination
root with an explicit 'decompose the fix and delegate the code work to a
cell dev — the Main PM does not write the fix itself' description. Both
engines now originate PLANNING coordination roots with matching delegation
guidance in the description + acceptance criteria. confirmed_by_human
stays True for both (they ride the normal delivery flow without the CEO
gate, unlike self-heal — intentional per the architecture).

The dedupe/open-cap queries (list_open_ci_watch_tasks /
list_open_dep_update_tasks) key on source + non-terminal status + git_url,
NOT task_type, so the type change does not break dedup (still one open fix
task per repo).

The two source-test fixtures (test_ci_watch_source / test_dep_update_source)
created CODE+MAIN_PM tasks directly to exercise the listing queries — same
guard violation; switched to PLANNING (the queries assert on source/status,
not task_type, so the fixture type matches the engines' corrected type).
This commit is contained in:
Renn F
2026-06-28 22:59:07 +02:00
parent af2a3056db
commit 47bb5403b1
4 changed files with 22 additions and 16 deletions
+11 -9
View File
@@ -155,21 +155,23 @@ class CiWatchEngine(BaseService):
f"This project's CI is red on its default branch.\n\n" f"This project's CI is red on its default branch.\n\n"
f"{sample.detail}\n\n" f"{sample.detail}\n\n"
f"Evidence: {sample.raw_ref}\n\n" f"Evidence: {sample.raw_ref}\n\n"
"Investigate and fix the regression at its root so CI returns " "This is a Main-PM coordination root: decompose the fix and "
"to green. This task was opened automatically by the CI-watch " "delegate the code work to a cell dev — the Main PM does not "
"loop and is READY TO START NOW — no approval needed; pick it " "write the fix itself. This task was opened automatically by "
"up and coordinate the fix. It still ships through the normal " "the CI-watch loop and is READY TO START NOW — no approval "
"gates (QA, PR review, and the CEO's merge)." "needed; plan the fix and delegate it. It still ships through "
"the normal gates (QA, PR review, and the CEO's merge)."
), ),
acceptance_criteria=[ acceptance_criteria=[
f"CI on {slug}'s default branch is green again", "The CI regression is decomposed into a code-fix subtask "
"The cause of the failing run is fixed at its root, not " "delegated to a cell developer",
"masked or skipped", f"CI on {slug}'s default branch is green again and the fix "
"merged through the normal gates",
], ],
team=Team.MAIN_PM, team=Team.MAIN_PM,
assigned_to=_foundation.AGENTS["main-pm"].uuid, assigned_to=_foundation.AGENTS["main-pm"].uuid,
created_by=_foundation.AGENTS["system"].uuid, created_by=_foundation.AGENTS["system"].uuid,
task_type=TaskType.CODE, task_type=TaskType.PLANNING,
nature=TaskNature.TECHNICAL, nature=TaskNature.TECHNICAL,
estimated_complexity=Complexity.MEDIUM, estimated_complexity=Complexity.MEDIUM,
project_id=cast("UUID", project.id), project_id=cast("UUID", project.id),
+9 -5
View File
@@ -100,7 +100,9 @@ class DepUpdateEngine(BaseService):
title=f"Update dependencies on {slug}", title=f"Update dependencies on {slug}",
description=( description=(
"Dependency updates are available for this project.\n\n" "Dependency updates are available for this project.\n\n"
"Upgrade the dependencies to their latest compatible versions, " "This is a Main-PM coordination root: decompose the upgrade "
"and delegate the code work to a cell dev — the Main PM does "
"not run the upgrade itself. Plan the dependency upgrade, "
"refresh the lockfile(s), and make sure the full gate passes " "refresh the lockfile(s), and make sure the full gate passes "
"with no behavioural breakage. This task was opened " "with no behavioural breakage. This task was opened "
"automatically by the dependency-update bot and is READY TO " "automatically by the dependency-update bot and is READY TO "
@@ -108,14 +110,16 @@ class DepUpdateEngine(BaseService):
"normal gates (QA, PR review, and the CEO's merge)." "normal gates (QA, PR review, and the CEO's merge)."
), ),
acceptance_criteria=[ acceptance_criteria=[
"Dependencies are upgraded to latest compatible and the " "The dependency upgrade is decomposed into a code-fix "
"lockfile(s) are refreshed", "subtask delegated to a cell developer",
"The full quality gate passes with no behavioural regression", "Dependencies are upgraded to latest compatible, the "
"lockfile(s) are refreshed, and the full quality gate passes "
"with no behavioural regression",
], ],
team=Team.MAIN_PM, team=Team.MAIN_PM,
assigned_to=_foundation.AGENTS["main-pm"].uuid, assigned_to=_foundation.AGENTS["main-pm"].uuid,
created_by=_foundation.AGENTS["system"].uuid, created_by=_foundation.AGENTS["system"].uuid,
task_type=TaskType.CODE, task_type=TaskType.PLANNING,
nature=TaskNature.TECHNICAL, nature=TaskNature.TECHNICAL,
estimated_complexity=Complexity.MEDIUM, estimated_complexity=Complexity.MEDIUM,
project_id=cast("UUID", project.id), project_id=cast("UUID", project.id),
@@ -86,7 +86,7 @@ async def _make_ci_watch_task(
team=Team.MAIN_PM, team=Team.MAIN_PM,
assigned_to=MAIN_PM_UUID, assigned_to=MAIN_PM_UUID,
created_by=SYSTEM_UUID, created_by=SYSTEM_UUID,
task_type=TaskType.CODE, task_type=TaskType.PLANNING,
nature=TaskNature.TECHNICAL, nature=TaskNature.TECHNICAL,
estimated_complexity=Complexity.MEDIUM, estimated_complexity=Complexity.MEDIUM,
project_id=cast("UUID", project.id), project_id=cast("UUID", project.id),
@@ -84,7 +84,7 @@ async def _make_task(
team=Team.MAIN_PM, team=Team.MAIN_PM,
assigned_to=MAIN_PM_UUID, assigned_to=MAIN_PM_UUID,
created_by=SYSTEM_UUID, created_by=SYSTEM_UUID,
task_type=TaskType.CODE, task_type=TaskType.PLANNING,
nature=TaskNature.TECHNICAL, nature=TaskNature.TECHNICAL,
estimated_complexity=Complexity.MEDIUM, estimated_complexity=Complexity.MEDIUM,
project_id=cast("UUID", project.id), project_id=cast("UUID", project.id),