mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
[feature] delegate carries dev-task collision surface (sequencing S1)
The cell/main PM's delegate verb now carries the dev-task collision surface (intends_to_touch / adds_migration / touches_shared) and an explicit depends_on override through DelegateRequest -> DelegateInputs -> _create_subtask_from_inputs -> create_subtask, and create_subtask forwards sequence / dependency_ids / batch_id / surfaces into the prepared TaskCreateRequest instead of dropping them (the base create already persists them at task.py:878-884). This is the plumbing for the multi-level sequencing model edge kind 3 (dev-task collision DAG). Previously a dev task delegated with a collision surface or an explicit dependency lost it before persistence — dependency_ids was always [], so the only dev-task ordering was the weak assignee-keyed spawn barrier (the live 2026-06-27 out-of-order break: 40842957 started before 9b3682b8's PR merged). Phase S2 runs SequencingService over the surfaced siblings and wires the DAG via add_dependency.
This commit is contained in:
@@ -7,7 +7,7 @@ No DB required — Choreographer is mocked.
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from uuid import uuid4
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
@@ -296,6 +296,50 @@ async def test_delegate_dispatches_inputs_bundle() -> None:
|
||||
assert inputs.task_type == "code"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delegate_forwards_collision_surfaces_to_inputs() -> None:
|
||||
"""The dev-task collision surface (intends_to_touch / adds_migration /
|
||||
touches_shared) and an explicit depends_on override must round-trip from
|
||||
the HTTP body through DelegateInputs so the cell PM can express the
|
||||
dev-task collision DAG (the missing edge kind 3 — see the multi-level
|
||||
sequencing design). Today these fields do not exist on DelegateRequest /
|
||||
DelegateInputs, so the test would 422 / AttributeError — the red signal.
|
||||
"""
|
||||
mock_chore = MagicMock()
|
||||
mock_chore.delegate = AsyncMock(
|
||||
return_value=_make_envelope(status="created", task_id=_TASK_ID)
|
||||
)
|
||||
client = TestClient(_build_app(mock_chore))
|
||||
dep_id = str(uuid4())
|
||||
|
||||
resp = client.post(
|
||||
"/api/v1/flow/cell_pm/delegate",
|
||||
json={
|
||||
"parent_task_id": _TASK_ID,
|
||||
"title": "Implement /v1/foo",
|
||||
"description": "Add the foo endpoint with passing tests.",
|
||||
"assigned_to": "be-dev-1",
|
||||
"team": "backend",
|
||||
"task_type": "code",
|
||||
"nature": "technical",
|
||||
"estimated_complexity": "medium",
|
||||
"acceptance_criteria": ["GET /v1/foo returns 200 with body"],
|
||||
"intends_to_touch": ["roboco/api/routes/v1/foo.py"],
|
||||
"adds_migration": True,
|
||||
"touches_shared": False,
|
||||
"depends_on": [dep_id],
|
||||
},
|
||||
headers=_HEADERS,
|
||||
)
|
||||
|
||||
assert resp.status_code == _HTTP_200, resp.text
|
||||
inputs = mock_chore.delegate.await_args.args[2]
|
||||
assert inputs.intends_to_touch == ["roboco/api/routes/v1/foo.py"]
|
||||
assert inputs.adds_migration is True
|
||||
assert inputs.touches_shared is False
|
||||
assert inputs.depends_on == [UUID(dep_id)]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_submit_up_dispatches_notes() -> None:
|
||||
"""POST /api/v1/flow/cell_pm/submit_up forwards task_id and notes."""
|
||||
|
||||
@@ -7,7 +7,7 @@ No DB required — Choreographer is mocked.
|
||||
from __future__ import annotations
|
||||
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from uuid import uuid4
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
from fastapi import FastAPI
|
||||
@@ -273,6 +273,49 @@ async def test_delegate_to_cell_pm_dispatches_inputs_bundle() -> None:
|
||||
assert inputs.task_type == "planning"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_delegate_forwards_collision_surfaces_to_inputs() -> None:
|
||||
"""Main-PM delegate forwards the dev-task collision surface + depends_on
|
||||
override through DelegateInputs (parity with the cell-PM route — the
|
||||
main PM delegates cell-tasks/dev-tasks the same way)."""
|
||||
mock_chore = MagicMock()
|
||||
mock_chore.delegate = AsyncMock(
|
||||
return_value=_make_envelope(status="created", task_id=_TASK_ID)
|
||||
)
|
||||
client = TestClient(_build_app(mock_chore))
|
||||
dep_id = str(uuid4())
|
||||
|
||||
resp = client.post(
|
||||
"/api/v1/flow/main_pm/delegate",
|
||||
json={
|
||||
"parent_task_id": _TASK_ID,
|
||||
"title": "Backend slice",
|
||||
"description": "Plan + drive backend work for feature X end to end.",
|
||||
"assigned_to": "be-pm",
|
||||
"team": "backend",
|
||||
"task_type": "planning",
|
||||
"nature": "technical",
|
||||
"estimated_complexity": "high",
|
||||
"acceptance_criteria": [
|
||||
"all subtasks created with acceptance criteria",
|
||||
"branch + PR opened against the slice",
|
||||
],
|
||||
"intends_to_touch": ["roboco/services/foo.py"],
|
||||
"adds_migration": True,
|
||||
"touches_shared": True,
|
||||
"depends_on": [dep_id],
|
||||
},
|
||||
headers=_HEADERS,
|
||||
)
|
||||
|
||||
assert resp.status_code == _HTTP_200, resp.text
|
||||
inputs = mock_chore.delegate.await_args.args[2]
|
||||
assert inputs.intends_to_touch == ["roboco/services/foo.py"]
|
||||
assert inputs.adds_migration is True
|
||||
assert inputs.touches_shared is True
|
||||
assert inputs.depends_on == [UUID(dep_id)]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_escalate_to_ceo_dispatches() -> None:
|
||||
mock_chore = MagicMock()
|
||||
|
||||
Reference in New Issue
Block a user