mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
refactor(gateway): rename submit_for_qa to open_pr; pin atomic preconditions
Pre-fix, submit_for_qa opened a PR (side effect) and returned OK with next='call i_am_done' — agents read the verb name, assumed they were done with QA handoff, never called i_am_done, and PRs ended up orphaned (PR #12 in the 2026-05-08 trace). Two changes: 1. Rename submit_for_qa -> open_pr so the verb name matches the semantic. The PR opens here; the actual QA handoff happens at i_am_done. Renamed across: - choreographer/_impl.py (method) - mcp/flow_server.py (tool registration + _TOOLS dict) - api/routes/v2/flow_dev.py (route + handler) - api/schemas/v2/flow.py (OpenPrRequest) - services/gateway/verb_gates.py (_STATE_VERBS) - services/gateway/role_config.py (developer flow manifest) - services/gateway/content_actions.py (commit-success next= hint) - agent_sdk/server.py (post-tool guidance map) - runtime/orchestrator.py (developer prompt) - agents/prompts/{base,roles/developer,_generated/*}.md - tests/unit/gateway/test_submit_for_qa.py -> test_open_pr.py - tests/unit/api/routes/v2/test_flow_dev.py - tests/unit/gateway/test_verb_gates.py - tests/unit/api/test_correlation_id.py - tests/unit/mcp_servers/test_flow_server.py - tests/integration/test_full_lifecycle_real_db.py 2. New regression test (test_open_pr_does_not_create_pr_if_no_commits) pins the atomic invariant: preconditions (assignee, commits, no-prior-PR) must be checked BEFORE git.create_pr/push_branch run. Any future re-ordering breaks the test. Tests: 3128 passing (3127 + 1 new), 100% coverage, ruff clean. Note: TaskService.submit_for_qa() (the v1-layer service method) is INTENTIONALLY not renamed — it's a different layer used by the v1 routes. The rename here is only the gateway verb surface.
This commit is contained in:
@@ -1049,3 +1049,38 @@ async def test_i_will_work_on_envelope_carries_introspection_on_rejection() -> N
|
||||
assert isinstance(body["valid_next_verbs"], list)
|
||||
# Lifecycle verbs are NOT in the list for a completed task.
|
||||
assert "i_will_work_on" not in body["valid_next_verbs"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_open_pr_does_not_create_pr_if_no_commits() -> None:
|
||||
"""Atomic invariant: if commits[] is empty, open_pr must NOT call
|
||||
git.create_pr. Pre-fix this was already true at the verb level, but
|
||||
this test pins it as a regression: any future refactor that
|
||||
re-orders precondition vs side effect breaks the test."""
|
||||
dev_id = uuid4()
|
||||
task_id = uuid4()
|
||||
task = MagicMock(
|
||||
status="in_progress",
|
||||
assigned_to=dev_id,
|
||||
commits=[],
|
||||
pr_number=None,
|
||||
branch_name="feature/backend/abc",
|
||||
id=task_id,
|
||||
title="t",
|
||||
team="backend",
|
||||
task_type="code",
|
||||
)
|
||||
task_svc = AsyncMock()
|
||||
task_svc.get.return_value = task
|
||||
task_svc.agent_for.return_value = MagicMock(role="developer", team="backend")
|
||||
git_svc = AsyncMock()
|
||||
git_svc.create_pr = AsyncMock()
|
||||
git_svc.push_branch = AsyncMock()
|
||||
deps = _make_deps(task=task_svc, git=git_svc)
|
||||
c = Choreographer(deps)
|
||||
env = await c.open_pr(dev_id, task_id)
|
||||
body = env.as_dict()
|
||||
assert body["error"] == "invalid_state"
|
||||
assert "no commits" in body["message"]
|
||||
git_svc.create_pr.assert_not_called()
|
||||
git_svc.push_branch.assert_not_called()
|
||||
|
||||
Reference in New Issue
Block a user