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:
@@ -200,6 +200,10 @@ async def test_propose_postmortem_completes_the_task_and_stamps_marker() -> None
|
||||
assert payload is not None
|
||||
assert payload["failed_stage"] == "awaiting_qa"
|
||||
assert payload["process_change"]["kind"] == "prompt_fix"
|
||||
# A non-playbook process change stays "proposed" — the CEO's per-item
|
||||
# approve/dismiss decision (CoronerService) is still open.
|
||||
assert payload["process_change"]["status"] == "proposed"
|
||||
assert payload["process_change"]["materialized_task_id"] is None
|
||||
assert payload["playbook_id"] is None
|
||||
assert task.status == TaskStatus.COMPLETED
|
||||
|
||||
@@ -244,6 +248,9 @@ async def test_propose_postmortem_drafts_playbook_when_kind_is_playbook() -> Non
|
||||
playbook_svc.draft.assert_awaited_once()
|
||||
payload = engine.complete_with_postmortem.await_args.args[1]
|
||||
assert payload["playbook_id"] == str(drafted.id)
|
||||
# A "playbook" kind already routed into the curation queue above —
|
||||
# CoronerService refuses to act on it (see its own test module).
|
||||
assert payload["process_change"]["status"] == "not_applicable"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -424,6 +424,11 @@ async def test_propose_market_brief_persists_and_completes_the_exploration_task(
|
||||
assert payload["headline"] == "A rival tool shipped agentic PR review this week"
|
||||
assert len(payload["findings"]) == len(findings)
|
||||
assert payload["findings"][0]["id"] == "finding-0"
|
||||
# Each finding still carries its own per-item CEO decision (Periscope
|
||||
# Service.approve_finding/reject_finding) even though the exploration
|
||||
# task completes here.
|
||||
assert payload["findings"][0]["status"] == "proposed"
|
||||
assert payload["findings"][0]["materialized_task_id"] is None
|
||||
assert payload["threats"] == ["Feature parity gap"]
|
||||
assert payload["opportunities"] == ["Lean into structured findings"]
|
||||
assert payload["positioning_note"] == "Emphasize the findings ledger in messaging"
|
||||
|
||||
@@ -393,6 +393,11 @@ async def test_propose_quality_report_persists_and_completes_the_exploration_tas
|
||||
assert len(payload["items"]) == len(items)
|
||||
assert payload["items"][0]["id"] == "item-0"
|
||||
assert payload["items"][0]["area"] == "waivers"
|
||||
# Each item still carries its own per-item CEO decision (SentinelService.
|
||||
# approve_item/reject_item) even though the exploration task completes
|
||||
# here.
|
||||
assert payload["items"][0]["status"] == "proposed"
|
||||
assert payload["items"][0]["materialized_task_id"] is None
|
||||
assert (
|
||||
payload["overall_assessment"]
|
||||
== "Drift is concentrated in one hotspot, not systemic"
|
||||
|
||||
Reference in New Issue
Block a user