The PR-review gate's pr_fail sends a cell->root / root->master PR back to
needs_revision, still owned by the cell/main PM. The dispatch side
(_dispatch_revision_coordination_roots) re-spawns the owning PM for any
PM-owned needs_revision task, but the readiness gate
(_readiness_check_role_for_status) only waived the dev/doc role restriction for
coordination tasks — and a gate-failed assembled PR has a project + branch, so
it is NOT coordination. Result: dispatch routed the cell PM, readiness refused
it ("state=needs_revision requires role in {developer,documenter} but agent
be-pm is cell_pm"), and the task deadlocked. Pass owner_is_pm (derived from the
task's assignee, like the dispatch side) so the readiness waiver also fires for
a PM-owned revision, not just coordination roots. QA stays excluded.
A coordination root (team=main_pm, product-linked, no repo) the CEO sends back
lands in needs_revision, but the dev dispatcher skips it (not a cell team) and
the closure path only handles paused parents — so it sat in needs_revision
forever. (NOT a foundation-spec gap: the spec already allows needs_revision ->
claimed for any role.)
- _dispatch_revision_coordination_roots: re-spawn the owning PM for a
needs_revision coordination root so it re-coordinates the revision (registered
in the dispatch loop after PM closure)
- _readiness_check_role_for_status: widen the dev-owned states (needs_revision,
verifying) to also accept cell_pm/main_pm for coordination roots — a pure
widening; normal code tasks stay dev/doc-only
- 16 unit tests (dispatcher decision + readiness widening)
Smoke-8 surfaced a tight respawn loop: QA failed a PR cleanly, container
exited 0, then _check_health bumped error_count and respawned QA with
the same task_id. But by then the task was in needs_revision (dev's
state), so QA's claim_review was rejected — and the cycle repeated on
the next health tick. Token-burning loop.
Two layers:
1. _check_health now reads docker's exit code. exit_code == 0 →
graceful (intentional handoff via i_am_idle / clean shutdown) →
reset error_count, do NOT auto-restart. Non-zero → keep the
existing crash-retry behavior. Refactored into
_inspect_container_state + _handle_stopped_container to keep
xenon's complexity check happy.
2. _readiness_check_role_for_status now includes the dev-owned
states (needs_revision, verifying) so a misrouted spawn for QA /
PM / board on these statuses fails the readiness gate before the
gateway has to reject it. Defense in depth — the right path is
#1 (don't respawn on clean exit at all), but if some other code
path tries to spawn QA on needs_revision the gate now catches it.
Tests: 12 new (5 for _check_health graceful/crash matrix + 7 for the
expanded role-status table). Pre-gateway names (none of which were
needed here) untouched.