mirror of
https://github.com/rennf93/roboco.git
synced 2026-08-03 07:23:24 +02:00
Post-audit sweep over the 135 audit-fix commits since19a474d3: 1. Stripped every # Fxxx: audit-ID token from comments AND every Fxxx token from docstring openings across 211 blocks / ~626 lines. The CEO flagged these twice: audit-issue IDs in code confuse future devs/agents. The descriptive text is preserved; only the Fxxx token is removed (and bloated narrative blocks trimmed to 1-3 lines keeping the one non-obvious invariant). 2. Trimmed bloated comments/docstrings to the concise standard (1-3 lines). 3. Added missing behavior-change docs for the audit-fix batch: prompts/roles (documenter, pr_reviewer, qa), user-facing docs (api auth, websockets, agent-gateway, megatask, merge-model, task-lifecycle, grok, resilience, conventions, panel, security, troubleshooting), and the RAG corpus (cell-pm, main-pm, pr-reviewer, qa roles; conventions; messaging-tools; escalation; megatask; task-claiming workflows). Comment/docstring/prose ONLY — zero code-line edits (verified: the diff contains no def/class/return/if/for/await/assignment/call lines). Gates green: ruff format + ruff check clean, mypy clean on roboco/. The only pytest failures are the pre-existing sync_branch tracing-decision gap (B1,250be5c2) — not sweep-caused and tracked separately.
127 lines
5.0 KiB
Python
127 lines
5.0 KiB
Python
"""The loud-fail guard: a delivery gate refuses when the suite can't run.
|
|
|
|
When toolchain matching is on and the acting agent's workspace recorded a
|
|
``broken`` toolchain status (the project's suite cannot be collected under the
|
|
provisioned interpreter), the dev/QA/PR gates must block — never let a role
|
|
"pass" on a source read. Off, or any non-broken / unknown status, never blocks.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from roboco.config import settings
|
|
from roboco.services.gateway.choreographer import Choreographer, ChoreographerDeps
|
|
from structlog.testing import capture_logs
|
|
|
|
|
|
def _make_choreographer(*, status: str | None) -> Choreographer:
|
|
base: dict[str, Any] = {
|
|
"task": AsyncMock(),
|
|
"work_session": AsyncMock(),
|
|
"git": AsyncMock(),
|
|
"a2a": AsyncMock(),
|
|
"journal": AsyncMock(),
|
|
"audit": AsyncMock(),
|
|
"evidence_repo": AsyncMock(),
|
|
}
|
|
base["git"].toolchain_status_for_task.return_value = status
|
|
return Choreographer(ChoreographerDeps(**base))
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_guard_blocks_when_broken_and_flag_on(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "toolchain_match_enabled", True)
|
|
c = _make_choreographer(status="broken")
|
|
env = await c._toolchain_broken_guard(uuid4(), MagicMock())
|
|
assert env is not None
|
|
body = env.as_dict()
|
|
assert body["error"] == "invalid_state"
|
|
assert "i_am_blocked" in body["remediate"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_guard_passes_when_ok(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(settings, "toolchain_match_enabled", True)
|
|
c = _make_choreographer(status="ok")
|
|
assert await c._toolchain_broken_guard(uuid4(), MagicMock()) is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_guard_passes_when_status_unknown_or_missing(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
monkeypatch.setattr(settings, "toolchain_match_enabled", True)
|
|
for status in ("unknown", None):
|
|
c = _make_choreographer(status=status)
|
|
assert await c._toolchain_broken_guard(uuid4(), MagicMock()) is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_guard_inert_when_flag_off(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
monkeypatch.setattr(settings, "toolchain_match_enabled", False)
|
|
c = _make_choreographer(status="broken")
|
|
assert await c._toolchain_broken_guard(uuid4(), MagicMock()) is None
|
|
# Flag off => the workspace is never consulted at all.
|
|
c.git.toolchain_status_for_task.assert_not_awaited()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_guard_warns_loudly_on_unknown(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
# 'unknown' still fails open (never strands a task), but it must not be
|
|
# silent — a warning is emitted so the hollow pass is visible to operators.
|
|
monkeypatch.setattr(settings, "toolchain_match_enabled", True)
|
|
c = _make_choreographer(status="unknown")
|
|
with capture_logs() as logs:
|
|
env = await c._toolchain_broken_guard(uuid4(), MagicMock())
|
|
assert env is None
|
|
assert any(e.get("event") == "toolchain.unverified_gate_pass" for e in logs)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_guard_silent_when_no_marker(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
# No marker (None) is benign — flag on but not yet provisioned / not a test
|
|
# project — and must stay silent so the warning means something.
|
|
monkeypatch.setattr(settings, "toolchain_match_enabled", True)
|
|
c = _make_choreographer(status=None)
|
|
with capture_logs() as logs:
|
|
env = await c._toolchain_broken_guard(uuid4(), MagicMock())
|
|
assert env is None
|
|
assert not any(e.get("event") == "toolchain.unverified_gate_pass" for e in logs)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_guard_reviewer_remediation_uses_pr_fail_not_i_am_blocked(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
# pr_pass runs this guard on the REVIEWER's workspace; the remediation must
|
|
# use pr_fail (not i_am_blocked — a reviewer has no i_am_blocked verb) so the
|
|
# PR returns to needs_revision for the dev to fix the environment.
|
|
monkeypatch.setattr(settings, "toolchain_match_enabled", True)
|
|
c = _make_choreographer(status="broken")
|
|
env = await c._toolchain_broken_guard(uuid4(), MagicMock(), reviewer=True)
|
|
assert env is not None
|
|
body = env.as_dict()
|
|
assert body["error"] == "invalid_state"
|
|
assert "i_am_blocked" not in body["remediate"]
|
|
assert "pr_fail" in body["remediate"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_guard_dev_remediation_still_uses_i_am_blocked(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
# the dev (i_am_done) path keeps i_am_blocked — a dev has that verb, so the
|
|
# reviewer flag must not change the dev-path wording.
|
|
monkeypatch.setattr(settings, "toolchain_match_enabled", True)
|
|
c = _make_choreographer(status="broken")
|
|
env = await c._toolchain_broken_guard(uuid4(), MagicMock())
|
|
assert env is not None
|
|
body = env.as_dict()
|
|
assert "i_am_blocked" in body["remediate"]
|