[F044] pr_pass gate remediation points the reviewer at pr_fail, not i_am_blocked

The pr_pass gate runs the toolchain + conventions guards on the REVIEWER's
workspace, but their remediation text said 'call i_am_blocked' — a verb the
PR reviewer does not have. The reviewer would chase a verb they cannot call
instead of rejecting the PR.

Make the guards reviewer-aware: a reviewer=True flag (passed by _pr_pass_blocked)
switches the remediation to pr_fail(issues=[...]) — the reviewer's reject
lever, sending the PR back to needs_revision for the dev to fix the
environment / validator. The dev (i_am_done) path keeps i_am_blocked, which a
dev does have. _conventions_guard (the pr_pass path) now passes reviewer=True
through to _conventions_rejection.
This commit is contained in:
Renn F
2026-06-28 11:30:55 +02:00
parent 2f32228673
commit 05a5852bb5
5 changed files with 99 additions and 18 deletions
@@ -78,6 +78,22 @@ async def test_pr_pass_guard_blocks_when_validator_cannot_run(
assert await c._conventions_guard(uuid4(), MagicMock(), {}) is not None
@pytest.mark.asyncio
async def test_pr_pass_guard_could_not_run_remediation_uses_pr_fail(
monkeypatch: pytest.MonkeyPatch,
) -> None:
# F044: _conventions_guard is the pr_pass (reviewer) path. A reviewer has no
# i_am_blocked verb, so the could_not_run remediation must point at pr_fail
# (the reviewer's reject lever) — not tell them to call a verb they lack.
monkeypatch.setattr(settings, "conventions_enabled", True)
c = _make_choreographer(check_result={"findings": [], "could_not_run": True})
env = await c._conventions_guard(uuid4(), MagicMock(), {})
assert env is not None
body = env.as_dict()
assert "i_am_blocked" not in body["remediate"]
assert "pr_fail" in body["remediate"]
@pytest.mark.asyncio
async def test_pr_pass_guard_inert_when_flag_off(
monkeypatch: pytest.MonkeyPatch,
@@ -93,3 +93,37 @@ async def test_guard_silent_when_no_marker(monkeypatch: pytest.MonkeyPatch) -> N
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:
# F044: the pr_pass gate runs this guard on the REVIEWER's workspace. A PR
# reviewer has no i_am_blocked verb, so the dev-path remediation ("call
# i_am_blocked(reason='toolchain')") sends them to a verb they cannot call.
# The reviewer's reject lever is pr_fail — the remediation must point there
# so the PR goes back 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:
# F044: the dev (i_am_done) path keeps i_am_blocked — a dev DOES have that
# verb, so the original remediation is correct there. 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"]