[F067] flow_main_pm: add missing /triage route

main_pm's manifest advertises triage (lifecycle.intents_for_role(MAIN_PM)
includes it via _PM_ROLES, alongside triage_all) but flow_main_pm.py had no
POST /triage route, so a main_pm agent calling triage hit a raw 404 that
bypassed the per-verb circuit breaker. Added the route mirroring flow_cell_pm's
/triage — wires to the existing team-scoped choreographer.triage (uses pm.team,
works for any PM role; Main PM gets its own team's blocked/awaiting tasks).
Fix direction: add-route, NOT remove-from-manifest — the manifest is spec-correct
(intents_for_role by construction); removing triage would contradict the spec
and leave main_pm with only cross-team triage_all. TDD: test_triage_route_exists_and_dispatches.
This commit is contained in:
Renn F
2026-06-28 17:16:21 +02:00
parent bb94e6baf8
commit 2e0792d00d
2 changed files with 43 additions and 0 deletions
+20
View File
@@ -105,6 +105,26 @@ async def triage_all(
return envelope_to_response(env, request)
@router.post("/triage")
async def triage(
request: Request,
_body: TriageRequest,
x_agent_id: _AgentIdHeader,
choreographer: _ChoreographerDep,
) -> dict:
"""Team-scoped triage for the Main PM (own-team blocked / awaiting_pm_review).
F067: the main_pm manifest advertises ``triage`` (lifecycle.intents_for_role
includes it for MAIN_PM via _PM_ROLES) alongside ``triage_all``; without a
route a main_pm agent calling `triage` hit a raw 404 that bypassed the
per-verb circuit breaker. ``choreographer.triage`` is team-scoped (uses
``pm.team``) and works for any PM role — Main PM gets its own team's
blocked/awaiting tasks; ``triage_all`` is the cross-team sweep.
"""
env = await choreographer.triage(x_agent_id)
return envelope_to_response(env, request)
@router.post("/submit_root")
async def submit_root(
request: Request,
@@ -362,3 +362,26 @@ async def test_resume_dispatches() -> None:
)
assert resp.status_code == _HTTP_200
mock_chore.resume.assert_awaited_once()
@pytest.mark.asyncio
async def test_triage_route_exists_and_dispatches() -> None:
"""F067: POST /api/v1/flow/main_pm/triage must exist and wire to
choreographer.triage. The main_pm manifest (from lifecycle.intents_for_role)
advertises `triage` alongside `triage_all`, so a main_pm agent calling
`triage` must hit a real route — not a raw 404 that bypasses the circuit
breaker. Mirrors flow_cell_pm's /triage route (the choreographer.triage
impl is team-scoped and works for any PM role).
"""
mock_chore = MagicMock()
mock_chore.triage = AsyncMock(return_value=_make_envelope(status="idle"))
client = TestClient(_build_app(mock_chore))
resp = client.post(
"/api/v1/flow/main_pm/triage",
json={},
headers=_HEADERS,
)
assert resp.status_code == _HTTP_200, resp.text
body = resp.json()
assert body["status"] == "idle"
mock_chore.triage.assert_awaited_once()