fix(gateway): cell PM completes its own cell task; drop main-PM handoff

submit_up bubbled the cell task to Main PM (_handoff_to_main_pm), but
main_pm_complete rejects any task with a parent_task_id ("only operates
on root tasks"), so the cell->root PR had no one to merge it and the
cell task wedged at awaiting_pm_review. _maybe_advance_parent_to_pm_review
already intends the CELL PM to complete it.

Cell PM now owns cell completion:
- submit_up no longer hands off to Main PM; the cell task stays assigned
  to the cell PM, which is respawned to complete() it. Removed the
  now-unused _handoff_to_main_pm.
- cell_pm_complete resolves the merge target from the parent task's real
  branch_name (shared merge_chain.resolve_parent_branch, also used by the
  PR side-effects) so the cell->root PR merges into feature/main_pm/...,
  not the team-mis-derived feature/<cellteam>/... (same root cause as the
  prior PR-base fix).
- submit_up description + next_hint updated; lifecycle artifacts regen.

Main PM still only completes the ROOT (root->master + escalate-to-CEO).
First run to reach cell-PM bubble-up exposed this.
This commit is contained in:
Renn F
2026-05-23 05:16:56 +02:00
parent 32b6b31dd6
commit e3def6b3a2
12 changed files with 128 additions and 81 deletions
@@ -882,10 +882,13 @@ async def test_delegate_invalid_team_enum_rejected() -> None:
@pytest.mark.asyncio
async def test_submit_up_opens_pr_and_reassigns_to_main_pm() -> None:
async def test_submit_up_opens_pr_and_keeps_cell_pm_assignment() -> None:
"""#182: submit_up opens the cell→root PR and moves the cell task to
awaiting_pm_review, but does NOT hand it off to Main PM. The cell PM
owns cell completion (it is respawned to `complete` the task), so the
assignment stays put — no reassign."""
pm_id = uuid4()
task_id = uuid4()
main_pm_id = uuid4()
t = MagicMock(
id=task_id,
status="in_progress",
@@ -907,7 +910,6 @@ async def test_submit_up_opens_pr_and_reassigns_to_main_pm() -> None:
task_svc.agent_for.return_value = MagicMock(role="cell_pm", team="backend")
task_svc.all_subtasks_terminal.return_value = True
task_svc.submit_pm_review.return_value = after
task_svc.main_pm_agent.return_value = MagicMock(id=main_pm_id)
git_svc = AsyncMock()
git_svc.create_pr.return_value = {"pr_number": 12, "pr_url": "x"}
journal_svc = AsyncMock()
@@ -922,7 +924,7 @@ async def test_submit_up_opens_pr_and_reassigns_to_main_pm() -> None:
assert env.error is None
assert env.status == "awaiting_pm_review"
git_svc.create_pr.assert_awaited_once()
task_svc.reassign.assert_awaited_once()
task_svc.reassign.assert_not_called()
@pytest.mark.asyncio