fix: post-finale completeness sweep — routing surface, provider config, budgets, compose env, interactive exemption (#661)

This commit is contained in:
Renzo F
2026-07-23 09:41:27 +02:00
committed by GitHub
parent 21d6730400
commit d4b7e1e7b8
45 changed files with 2064 additions and 256 deletions
+76
View File
@@ -22,6 +22,7 @@ from roboco.api.routes.tasks import (
from roboco.api.routes.tasks import (
router as tasks_router,
)
from roboco.config import settings
from roboco.db.tables import AgentTable, ProjectTable, TaskTable, WorkSessionTable
from roboco.exceptions import GitError, TaskLifecycleError
from roboco.foundation.policy.lifecycle import STATUS_GRAPH
@@ -278,6 +279,35 @@ async def test_get_task_by_id(task_client: dict) -> None:
assert response.status_code == HTTPStatus.OK
@pytest.mark.asyncio
async def test_get_task_by_id_includes_spend_when_budgets_enabled(
task_client: dict, monkeypatch: pytest.MonkeyPatch
) -> None:
"""spend_usd is populated (0.0 with no spawn sessions yet) once
ROBOCO_TASK_BUDGETS_ENABLED is on — the extra DB read only runs then."""
monkeypatch.setattr(settings, "task_budgets_enabled", True)
client = task_client["client"]
task = _seed_task(task_client)
await task_client["db"].flush()
response = await client.get(f"/api/tasks/{task.id}", headers=_HDR)
assert response.status_code == HTTPStatus.OK
assert response.json()["spend_usd"] == 0.0
@pytest.mark.asyncio
async def test_get_task_by_id_omits_spend_when_budgets_disabled(
task_client: dict, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Flag off => spend_usd stays null, the same as before this field existed."""
monkeypatch.setattr(settings, "task_budgets_enabled", False)
client = task_client["client"]
task = _seed_task(task_client)
await task_client["db"].flush()
response = await client.get(f"/api/tasks/{task.id}", headers=_HDR)
assert response.status_code == HTTPStatus.OK
assert response.json()["spend_usd"] is None
@pytest.mark.asyncio
async def test_update_task(task_client: dict) -> None:
client = task_client["client"]
@@ -291,6 +321,52 @@ async def test_update_task(task_client: dict) -> None:
assert response.status_code in (HTTPStatus.OK, HTTPStatus.UNPROCESSABLE_ENTITY)
@pytest.mark.asyncio
async def test_update_task_rejects_zero_budget_usd(task_client: dict) -> None:
"""#654: a 0 cap would block every claim immediately — rejected at the
request boundary, never stored."""
client = task_client["client"]
task = _seed_task(task_client)
await task_client["db"].flush()
response = await client.patch(
f"/api/tasks/{task.id}",
json={"budget_usd": 0},
headers=_HDR,
)
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
@pytest.mark.asyncio
async def test_update_task_rejects_negative_budget_usd(task_client: dict) -> None:
client = task_client["client"]
task = _seed_task(task_client)
await task_client["db"].flush()
response = await client.patch(
f"/api/tasks/{task.id}",
json={"budget_usd": -5},
headers=_HDR,
)
assert response.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
@pytest.mark.asyncio
async def test_update_task_accepts_positive_budget_usd(task_client: dict) -> None:
# budget_usd is a _PRIVILEGED_UPDATE_FIELDS / non-"PM lighter" field —
# a plain main_pm PATCH would 403 here, so exercise the CEO's full scope.
_as_ceo(task_client)
client = task_client["client"]
task = _seed_task(task_client)
await task_client["db"].flush()
budget = 12.5
response = await client.patch(
f"/api/tasks/{task.id}",
json={"budget_usd": budget},
headers=_HDR,
)
assert response.status_code == HTTPStatus.OK
assert response.json()["budget_usd"] == budget
@pytest.mark.asyncio
async def test_update_task_status_override_recovers_blocked(task_client: dict) -> None:
"""A privileged PATCH with ``status`` + ``force`` is applied as an audited