mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
fix(gateway): unblock PM planning + drop magic delegate task_type
Two coupled fixes from the 2026-05-08 smoke-test trace: 1. pm_cannot_execute_code is now scoped to i_will_work_on (the EXECUTION verb) only. Pre-fix it also fired on i_will_plan, which deadlocked any code-typed parent: cell_pm couldn't plan, so couldn't transition parent to in_progress, so couldn't delegate. PMs PLAN code-typed parents and DELEGATE the work — that's exactly the verb we were blocking. 2. delegate.task_type is now REQUIRED at both the HTTP boundary (DelegateRequest) and the choreographer dataclass (DelegateInputs). The pre-fix default of 'code' silently changed semantics whenever a caller forgot the field — main-pm's call in the smoke trace omitted it, schema defaulted to 'code', and the cell PM downstream was wedged. Also drops the choreographer's task.task_type fallback (the DB column is NOT NULL anyway). Plus middleware coverage tests for the parallel ServiceError → 4xx handler hierarchy added in the prior session, restoring 100% coverage across the touched files. Tests: 3101 passing, 100% coverage, ruff clean.
This commit is contained in:
@@ -336,7 +336,15 @@ async def test_main_pm_cannot_claim_code_task_via_i_will_work_on() -> None:
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_cell_pm_cannot_claim_code_task_via_i_will_plan() -> None:
|
||||
async def test_cell_pm_can_plan_code_typed_parent_via_i_will_plan() -> None:
|
||||
"""Rule change (2026-05-08): the PM-cannot-execute-code guard belongs
|
||||
on `i_will_work_on` (the EXECUTION verb), not on `i_will_plan` (the
|
||||
PLANNING verb). PMs decompose code-typed parent tasks into
|
||||
developer-claimable subtasks all the time; that's planning, not
|
||||
executing. Pre-fix this rejection deadlocked every code-typed parent
|
||||
in the smoke test (see PRE_GATEWAY_LIFECYCLE.md and the 2026-05-08
|
||||
audit-log analysis).
|
||||
"""
|
||||
pm_id = uuid4()
|
||||
task_id = uuid4()
|
||||
target = MagicMock(
|
||||
@@ -353,10 +361,12 @@ async def test_cell_pm_cannot_claim_code_task_via_i_will_plan() -> None:
|
||||
deps = _make_deps(task=task_svc)
|
||||
c = Choreographer(deps)
|
||||
|
||||
env = await c.i_will_plan(pm_id, task_id, plan="x")
|
||||
env = await c.i_will_plan(pm_id, task_id, plan="Decompose into 2 dev subtasks.")
|
||||
body = env.as_dict()
|
||||
assert body["error"] == "not_authorized"
|
||||
assert "code" in body["message"].lower() or "execute" in body["message"].lower()
|
||||
# The PM-cannot-execute-code rejection must NOT fire on i_will_plan.
|
||||
assert body.get("error") != "not_authorized", (
|
||||
f"i_will_plan was rejected for a code-typed parent; envelope: {body}"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user