mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(tasks): AC identity + child->parent AC linkage (guardrails spec 1/4)
Foundation for the decomposition-coverage and roll-up AC-verification gates. Acceptance criteria were a flat list[str] with no per-criterion identity, so nothing could relate a child task's criteria to the parent's — letting a PM drop half a parent's ACs unnoticed (PR #175). - migration 036: additive acceptance_criteria_ids + parent_ac_refs array columns; backfills stable md5(task_id:index) ids for existing rows. - Task model + TaskCreateRequest + db table: the two fields. - TaskService.create generates one stable id per criterion (1:1) when absent. - DelegateInputs.covers_parent_criteria -> child.parent_ac_refs (the linkage), propagated through create_subtask. - regression-safe (53 task tests green) + 1 new test. Coverage gate (spec 2), roll-up AC gate (spec 4), per-dev sequenced queues (spec 3) build on this. Design: docs/SPEC_AC_GUARDRAILS_2026-06-16.md.
This commit is contained in:
@@ -18,9 +18,13 @@ from roboco.models.base import (
|
||||
AgentRole,
|
||||
AgentStatus,
|
||||
BlockerResolverType,
|
||||
Complexity,
|
||||
TaskNature,
|
||||
TaskStatus,
|
||||
TaskType,
|
||||
Team,
|
||||
)
|
||||
from roboco.models.task import TaskCreateRequest
|
||||
from roboco.services.task import GatewayAgentView, TaskService
|
||||
|
||||
|
||||
@@ -590,6 +594,33 @@ async def test_admin_set_status_non_blocked_is_bare_status_set() -> None:
|
||||
assert task.assigned_to == owner
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_generates_ac_ids_and_carries_parent_ac_refs() -> None:
|
||||
# Every task gets one stable id per acceptance criterion (1:1), and a
|
||||
# decomposition child carries the parent AC ids it covers — the linkage the
|
||||
# coverage + roll-up gates rely on.
|
||||
svc = TaskService(
|
||||
MagicMock(add=MagicMock(), flush=AsyncMock(), execute=AsyncMock())
|
||||
)
|
||||
req = TaskCreateRequest(
|
||||
title="t",
|
||||
description="d",
|
||||
acceptance_criteria=["crit a", "crit b", "crit c"],
|
||||
team=Team.BACKEND,
|
||||
created_by=uuid4(),
|
||||
task_type=TaskType.CODE,
|
||||
nature=TaskNature.TECHNICAL,
|
||||
estimated_complexity=Complexity.MEDIUM,
|
||||
project_id=uuid4(),
|
||||
parent_ac_refs=["parent-ac-1", "parent-ac-2"],
|
||||
)
|
||||
task = await svc.create(req)
|
||||
n = len(req.acceptance_criteria)
|
||||
assert len(task.acceptance_criteria_ids) == n
|
||||
assert len(set(task.acceptance_criteria_ids)) == n
|
||||
assert list(task.parent_ac_refs) == ["parent-ac-1", "parent-ac-2"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unblock_with_branch_resumes_in_progress() -> None:
|
||||
# A task claimed (has a branch) before it blocked resumes in_progress.
|
||||
|
||||
Reference in New Issue
Block a user