mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
feat(board): materialize program items as Main-PM roots, make reports actionable (#711)
Two coupled gaps in the Board Program output path. Approved items were created unowned and in BACKLOG. Nothing dispatches BACKLOG, and once activated a cell PM claimed the parentless task as a root, where _cell_pm_complete resolves its merge target through resolve_parent_branch — which for a parentless task falls through to the project head rung. The result was a cell branch merging straight into the trunk, bypassing the Main-PM root, the root->master PR and the CEO gate (live: PRs #703 and #704 both targeted slave directly). All eight materializers now create a PENDING, main-pm-assigned root with team=Team.MAIN_PM, matching what approve_and_start does for an intake draft. The team is load-bearing, not cosmetic: _next_hint_pr_fail, _deliver_pr_fail_to_owner, delegate's wave-chain dispatch and the PR layer label all key on it, and a cell-teamed root drops the 'do NOT re-submit the root' steer that exists because of PR #138's infinite pr_fail loop. The item's own cell survives as a delegation hint in the description, which is what the Main PM's briefing renders. Periscope, Sentinel and Coroner produced artifacts with no way to act on them — three panel surfaces carried explicit 'no approve/reject UI' comments while each item already held a machine-readable suggested action. They now have per-item approve and dismiss, modelled on the roadmap queue: idempotent per item, CEO-gated, deep-copy-before-mutate so SQLAlchemy's dirty check still fires, and every decision recorded through record_decision so it reaches the next cycle's prompt. Approving materializes through the same corrected Main-PM-owned path. Target project resolves to each engine's own existing anchor — RoboCo's project for Periscope and Sentinel, the incident's project for Coroner — and fails with a clean invalid_state naming what is unresolvable rather than guessing at a repo. Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
@@ -2,13 +2,14 @@ from __future__ import annotations
|
||||
|
||||
from types import SimpleNamespace
|
||||
from typing import TYPE_CHECKING, cast
|
||||
from uuid import uuid4
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from roboco.db.tables import AgentTable, ProjectTable, TaskTable
|
||||
from roboco.models import AgentRole, AgentStatus, Team
|
||||
from roboco.models.base import TaskNature, TaskStatus, TaskType
|
||||
from roboco.seeds.initial_data import AGENT_UUIDS
|
||||
from roboco.services.base import NotFoundError
|
||||
from roboco.services.prompter import (
|
||||
PrompterService,
|
||||
@@ -138,9 +139,28 @@ async def redraft_setup(db_session: AsyncSession) -> AsyncIterator[dict]:
|
||||
metrics={},
|
||||
)
|
||||
|
||||
main_pm = _agent("main-pm", AgentRole.MAIN_PM)
|
||||
# merge() with the fixed AGENT_UUIDS id: idempotent whether or not another
|
||||
# test already committed this exact "main-pm"-slugged row on the shared
|
||||
# session-scoped test DB (mirrors test_prompter.py's identical upsert) —
|
||||
# an unconditional insert with a fresh random id here would collide with
|
||||
# any other test's row on the slug's unique index.
|
||||
main_pm = await db_session.merge(
|
||||
AgentTable(
|
||||
id=UUID(AGENT_UUIDS["main-pm"]),
|
||||
name="main-pm",
|
||||
slug="main-pm",
|
||||
role=AgentRole.MAIN_PM,
|
||||
team=None,
|
||||
status=AgentStatus.ACTIVE,
|
||||
model_config={},
|
||||
system_prompt="x",
|
||||
capabilities=[],
|
||||
permissions={},
|
||||
metrics={},
|
||||
)
|
||||
)
|
||||
po = _agent(f"product-owner-{uuid4().hex[:4]}", AgentRole.PRODUCT_OWNER)
|
||||
db_session.add_all([main_pm, po])
|
||||
db_session.add(po)
|
||||
await db_session.flush()
|
||||
project = ProjectTable(
|
||||
id=uuid4(),
|
||||
|
||||
Reference in New Issue
Block a user