refactor(content): move orchestration markers off quick_context to typed jsonb

This commit is contained in:
Renn F
2026-06-21 02:48:07 +02:00
parent 7e74112fcc
commit 0fba4eed01
21 changed files with 270 additions and 316 deletions
+35 -16
View File
@@ -1,36 +1,53 @@
"""required_cells decomposition gate — marker parse + uncovered-cell coverage.
The Main PM must create a subtask for each cell the brief explicitly names
(recorded as a ``required_cells:`` marker on the parent's quick_context). The
gate is inert when no marker is present, so legacy decompositions never block.
(recorded as a ``required_cells`` orchestration marker on the parent). The gate
is inert when no marker is present, so legacy decompositions never block.
"""
from __future__ import annotations
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4
import pytest
from roboco.services.task import TaskService, extract_required_cells
def _task(required_cells: list[str] | None = None) -> SimpleNamespace:
om = {"required_cells": required_cells} if required_cells is not None else None
return SimpleNamespace(orchestration_markers=om)
# ---------------------------------------------------------------------------
# extract_required_cells (marker parser)
# extract_required_cells (marker reader)
# ---------------------------------------------------------------------------
def test_extract_required_cells_absent_is_empty() -> None:
assert extract_required_cells(None) == []
assert extract_required_cells("original_developer: abc\ndoc_notes: y") == []
assert extract_required_cells(_task()) == []
assert (
extract_required_cells(
SimpleNamespace(orchestration_markers={"original_developer": "abc"})
)
== []
)
def test_extract_required_cells_parses_and_normalizes() -> None:
qc = "original_developer: abc\nrequired_cells: Backend, Frontend , UX/UI"
assert extract_required_cells(qc) == ["backend", "frontend", "ux_ui"]
def test_extract_required_cells_normalizes() -> None:
assert extract_required_cells(_task(["Backend", "Frontend ", "UX/UI"])) == [
"backend",
"frontend",
"ux_ui",
]
def test_extract_required_cells_dedups_in_order() -> None:
out = extract_required_cells("required_cells: backend, backend, frontend")
assert out == ["backend", "frontend"]
assert extract_required_cells(_task(["backend", "backend", "frontend"])) == [
"backend",
"frontend",
]
# ---------------------------------------------------------------------------
@@ -38,10 +55,12 @@ def test_extract_required_cells_dedups_in_order() -> None:
# ---------------------------------------------------------------------------
def _service(parent_qc: str | None, child_teams: list[str | None]) -> TaskService:
def _service(
required_cells: list[str] | None, child_teams: list[str | None]
) -> TaskService:
"""A TaskService whose get()/get_subtasks() return a parent + these children."""
svc = TaskService(MagicMock())
parent = MagicMock(quick_context=parent_qc)
parent = _task(required_cells)
children = [MagicMock(team=t) for t in child_teams]
object.__setattr__(svc, "get", AsyncMock(return_value=parent))
object.__setattr__(svc, "get_subtasks", AsyncMock(return_value=children))
@@ -50,25 +69,25 @@ def _service(parent_qc: str | None, child_teams: list[str | None]) -> TaskServic
@pytest.mark.asyncio
async def test_uncovered_inert_without_marker() -> None:
svc = _service("doc_notes: x", ["backend"])
svc = _service(None, ["backend"])
assert await svc.uncovered_required_cells(uuid4()) == []
@pytest.mark.asyncio
async def test_uncovered_flags_the_dropped_cell() -> None:
# Brief named backend+frontend+ux_ui; only backend+frontend got subtasks.
svc = _service("required_cells: backend, frontend, ux_ui", ["backend", "frontend"])
svc = _service(["backend", "frontend", "ux_ui"], ["backend", "frontend"])
assert await svc.uncovered_required_cells(uuid4()) == ["ux_ui"]
@pytest.mark.asyncio
async def test_uncovered_empty_when_all_named_cells_covered() -> None:
svc = _service("required_cells: backend, frontend", ["frontend", "backend"])
svc = _service(["backend", "frontend"], ["frontend", "backend"])
assert await svc.uncovered_required_cells(uuid4()) == []
@pytest.mark.asyncio
async def test_uncovered_normalizes_child_team_form() -> None:
# Marker uses underscore, child team uses the slash form — they match.
svc = _service("required_cells: ux_ui", ["UX/UI"])
svc = _service(["ux_ui"], ["UX/UI"])
assert await svc.uncovered_required_cells(uuid4()) == []