GitNexus/eval/tests/test_pr_evaluation.py
Gergő Magyar 5d1bc3dcea
feat(eval): request PR cost comparisons by label (#3622)
* feat(eval): measure PR cost before merge

Reuse authenticated historical MCP measurements for one pinned PR preview. Keep the protected main evaluator and publish separate evidence that cannot authorize a release.

* fix(eval): gate PR cost runs on a label

Require a repository writer label event and retain the opt-in label
and exact PR head before dispatch and paid sessions. Keep the
request handler on trusted base code and execute evaluation on main.

* ci: document trusted cost label trigger

* fix(eval): restrict previews to maintainer labels

Remove manual PR preview inputs and authorize Maintain/Admin roles before calling the protected reusable evaluator. Compare workflow measurement settings with historical evidence instead of exempting the entire workflow.

---------

Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
2026-10-18 11:00:08 +01:00

601 lines
22 KiB
Python

"""PR cost measurements retain the historical task pins and quality floor."""
import json
from copy import deepcopy
from datetime import UTC, datetime
import pytest
import yaml
from workflow_bench import pr_evaluation
from workflow_bench.release_report import build_report, validate_report
NOW = datetime.now(UTC)
OLD = "a" * 40
NEW = "b" * 40
HARNESS = "c" * 40
REPO = "owner/repo"
@pytest.fixture
def evidence():
metadata = {
"runtime_sha": OLD,
"harness_sha": OLD,
"task_set_digest": "d" * 64,
"model": "gpt-5.6-sol",
"effort": "xhigh",
"runs": 3,
"tasks": {name: {"sha": "e" * 40, "oracle_digest": "f" * 64} for name in ("one", "two")},
}
rows = [
{
"task": task,
"run": run,
"arm": arm,
"model": metadata["model"],
"effort": "xhigh",
"task_base_sha": "e" * 40,
"oracle_digest": "f" * 64,
"sandbox_backend": "bwrap",
"sandbox_dependency_content_digest": "9" * 64,
"ok": True,
"resolved": True,
"authored_tests_passed": True,
"oracle_passed": True,
"error_kind": None,
"transcript_missing": False,
"cost_usd": 1.0,
"duration_s": 60.0,
}
for task in metadata["tasks"]
for run in range(3)
for arm in ("baseline_nomcp", "baseline")
]
previous = build_report(rows, metadata, now=NOW)
candidate = [
{**row, "cost_usd": 0.8, "internal_path": "/private/transcript"} for row in rows if row["arm"] == "baseline"
]
return previous, candidate, {**metadata, "runtime_sha": NEW, "harness_sha": HARNESS}
def compare(evidence):
previous, rows, metadata = evidence
return pr_evaluation.compare_candidate(
rows, metadata, previous, candidate_sha=NEW, pr=3621, baseline_run=39039452570, now=NOW
)
def test_comparison_measures_only_new_mcp_cells_and_preserves_historical_cost(evidence):
before = deepcopy(evidence)
report = compare(evidence)
assert report["complete"] and report["quality_preserved"] and report["improved"]
assert report["previous"]["total_cost_usd"] == 6
assert report["candidate"]["total_cost_usd"] == pytest.approx(4.8)
assert report["cost_reduction_pct"] == pytest.approx(20)
assert report["candidate"]["solved"] == report["previous"]["solved"] == 6
assert len(report["per_run"]) == 6
assert all(row["arm"] == "baseline" and "internal_path" not in row for row in report["per_run"])
assert evidence == before
assert "not release approval" in pr_evaluation.render_markdown(report)
with pytest.raises(ValueError, match="unsupported"):
validate_report(report, runtime_sha=NEW, task_set_digest=evidence[0]["task_set_digest"], now=NOW)
@pytest.mark.parametrize(
"change",
[
lambda rows: rows.pop(),
lambda rows: rows.append(dict(rows[0])),
lambda rows: rows[0].update(cost_usd=None),
lambda rows: rows[0].update(cost_usd=float("nan")),
lambda rows: rows[0].update(cost_usd=10**1000),
lambda rows: rows[0].update(duration_s=-1),
lambda rows: rows[0].update(model="other-model"),
lambda rows: rows[0].update(task_base_sha="0" * 40),
lambda rows: rows[0].update(oracle_digest="0" * 64),
lambda rows: rows[0].update(sandbox_dependency_content_digest="0" * 64),
lambda rows: rows[0].update(sandbox_backend="host-unsafe"),
lambda rows: rows[0].update(error_kind="session-error", ok=False),
lambda rows: rows[0].update(transcript_missing=True),
lambda rows: rows[0].update(arm="baseline_nomcp"),
lambda rows: rows[0].update(resolved="true"),
lambda rows: rows[0].update(oracle_passed=False),
lambda rows: rows.append(None),
],
)
def test_incomplete_or_mismatched_cells_cannot_claim_savings(evidence, change):
change(evidence[1])
report = compare(evidence)
assert not report["complete"] and not report["improved"]
assert report["problems"]
assert report["cost_reduction_pct"] is None
json.dumps(report, allow_nan=False)
def test_lower_cost_with_a_per_task_solve_regression_is_not_an_improvement(evidence):
evidence[1][0].update(resolved=False, oracle_passed=False, error_kind="oracle-failed")
report = compare(evidence)
assert report["complete"] and not report["quality_preserved"] and not report["improved"]
assert report["tasks"]["one"]["solve_delta"] == -1
@pytest.mark.parametrize(
"field,value",
[
("runtime_sha", OLD),
("model", "other"),
("effort", "high"),
("runs", 1),
("task_set_digest", "0" * 64),
("tasks", {}),
],
)
def test_candidate_metadata_must_match_the_requested_revision_and_previous_protocol(evidence, field, value):
evidence[2][field] = value
assert not compare(evidence)["complete"]
def test_cost_regression_remains_a_valid_measurement(evidence):
for row in evidence[1]:
row["cost_usd"] = 1.1
report = compare(evidence)
assert report["complete"] and report["quality_preserved"] and not report["improved"]
assert report["cost_reduction_pct"] == pytest.approx(-10)
def pr_payload():
return {
"state": "open",
"head": {"sha": NEW},
"base": {"ref": "main", "repo": {"full_name": REPO}},
"labels": [{"name": pr_evaluation.COST_LABEL}],
}
def test_pr_resolution_is_bound_to_the_explicit_reviewed_sha(monkeypatch):
monkeypatch.setattr(pr_evaluation, "_gh_json", lambda endpoint: pr_payload())
assert pr_evaluation.resolve_pr(REPO, 3621, NEW) == NEW
@pytest.mark.parametrize(
"mutation",
[
lambda pr: pr.update(state="closed"),
lambda pr: pr["head"].update(sha=OLD),
lambda pr: pr["base"].update(ref="feature"),
lambda pr: pr["base"]["repo"].update(full_name="other/repo"),
lambda pr: pr.update(labels=[]),
lambda pr: pr.pop("labels"),
],
)
def test_moved_or_wrong_target_pr_is_rejected_before_paid_work(monkeypatch, mutation):
payload = pr_payload()
mutation(payload)
monkeypatch.setattr(pr_evaluation, "_gh_json", lambda endpoint: payload)
with pytest.raises(ValueError):
pr_evaluation.resolve_pr(REPO, 3621, NEW)
@pytest.fixture
def label_request(monkeypatch):
event = {
"repository": {"full_name": REPO},
"action": "labeled",
"label": {"name": pr_evaluation.COST_LABEL},
"sender": {"login": "maintainer"},
"number": 3621,
"pull_request": pr_payload(),
}
run = {
"id": 90,
"conclusion": "success",
"head_branch": "main",
"event": "workflow_dispatch",
"head_repository": {"full_name": REPO},
"head_sha": OLD,
}
payloads = {
f"repos/{REPO}/collaborators/maintainer/permission": {"permission": "write", "role_name": "maintain"},
f"repos/{REPO}/pulls/3621": pr_payload(),
f"repos/{REPO}/actions/workflows/release-evaluation.yml/runs?branch=main&status=success&per_page=100": {
"workflow_runs": [run],
},
f"repos/{REPO}/actions/runs/90/artifacts?per_page=100": {
"total_count": 1,
"artifacts": [{"name": f"release-agent-evaluation-{OLD}", "expired": False}],
},
}
calls = []
monkeypatch.setattr(pr_evaluation, "_gh_json", payloads.__getitem__)
monkeypatch.setattr(pr_evaluation, "_gh", lambda args: calls.append(args))
return event, payloads, calls
def test_label_dispatch_pins_head_main_baseline_and_model(label_request):
event, _, calls = label_request
assert pr_evaluation.dispatch_labeled_pr(REPO, event) == 90
assert not calls # Requests emit outputs for a reusable job, never manual dispatch.
@pytest.mark.parametrize("permission", ["write", "read", "triage", "none", "custom-role", ""])
def test_label_requires_repository_write_permission(label_request, permission):
event, payloads, calls = label_request
payloads[f"repos/{REPO}/collaborators/maintainer/permission"]["role_name"] = permission
with pytest.raises(ValueError, match="repository maintainers or administrators"):
pr_evaluation.dispatch_labeled_pr(REPO, event)
assert not calls
@pytest.mark.parametrize("role,permission", [("maintain", "write"), ("admin", "admin")])
def test_maintain_and_admin_roles_may_request_comparison(label_request, role, permission):
event, payloads, calls = label_request
payloads[f"repos/{REPO}/collaborators/maintainer/permission"].update(role_name=role, permission=permission)
assert pr_evaluation.dispatch_labeled_pr(REPO, event) == 90
assert not calls
def test_missing_role_does_not_grant_maintainer_authority(label_request):
event, payloads, calls = label_request
payloads[f"repos/{REPO}/collaborators/maintainer/permission"].pop("role_name")
with pytest.raises(ValueError, match="maintainers"):
pr_evaluation.dispatch_labeled_pr(REPO, event)
assert not calls
def test_label_cli_emits_validated_reusable_workflow_outputs(label_request, monkeypatch, tmp_path, capsys):
event, _, calls = label_request
source = tmp_path / "event.json"
source.write_text(json.dumps(event))
monkeypatch.setattr("sys.argv", ["pr_evaluation", "dispatch-label", "--repo", REPO, "--event", str(source)])
pr_evaluation.main()
assert capsys.readouterr().out == f"pr=3621\nsha={NEW}\ncomparison_run_id=90\n"
assert not calls
@pytest.mark.parametrize(
"mutation",
[
lambda event: event.update(action="synchronize"),
lambda event: event["label"].update(name="run-ci"),
lambda event: event["repository"].update(full_name="other/repo"),
lambda event: event["sender"].update(login="name/permission"),
],
)
def test_unrelated_events_never_dispatch(label_request, mutation):
event, _, calls = label_request
mutation(event)
with pytest.raises(ValueError):
pr_evaluation.dispatch_labeled_pr(REPO, event)
assert not calls
@pytest.mark.parametrize(
"mutation",
[
lambda pr: pr.update(labels=[]),
lambda pr: pr["head"].update(sha=OLD),
],
)
def test_label_or_head_changed_during_discovery_never_dispatches(label_request, monkeypatch, mutation):
event, payloads, calls = label_request
reads = 0
def api(endpoint):
nonlocal reads
if endpoint == f"repos/{REPO}/pulls/3621":
reads += 1
if reads == 2:
mutation(payloads[endpoint])
return payloads[endpoint]
monkeypatch.setattr(pr_evaluation, "_gh_json", api)
with pytest.raises(ValueError):
pr_evaluation.dispatch_labeled_pr(REPO, event)
assert reads == 2 and not calls
@pytest.mark.parametrize(
"artifact",
[
{"name": f"pr-cost-comparison-{NEW}", "expired": False},
{"name": f"release-agent-evaluation-{OLD}", "expired": True},
None,
],
)
def test_label_skips_preview_expired_and_probe_runs(label_request, artifact):
event, payloads, calls = label_request
runs = next(value["workflow_runs"] for value in payloads.values() if "workflow_runs" in value)
runs.insert(0, {**runs[0], "id": 91})
payloads[f"repos/{REPO}/actions/runs/91/artifacts?per_page=100"] = {
"total_count": int(artifact is not None),
"artifacts": [] if artifact is None else [artifact],
}
assert pr_evaluation.dispatch_labeled_pr(REPO, event) == 90
assert not calls
def test_label_without_a_full_baseline_does_not_dispatch(label_request):
event, payloads, calls = label_request
payloads[f"repos/{REPO}/actions/runs/90/artifacts?per_page=100"]["artifacts"] = []
with pytest.raises(ValueError, match="no full successful"):
pr_evaluation.dispatch_labeled_pr(REPO, event)
assert not calls
def test_previous_measurement_accepts_only_preview_orchestration_changes(monkeypatch):
monkeypatch.setattr(
pr_evaluation,
"_gh_json",
lambda endpoint: {"status": "ahead", "files": [{"filename": "eval/workflow_bench/pr_evaluation.py"}]},
)
pr_evaluation.require_comparable_harness(REPO, OLD, HARNESS)
@pytest.fixture
def workflow_protocol_pair():
old = {
"jobs": {
"evaluate": {
"runs-on": "self-hosted",
"environment": "gitnexus-evolution",
"timeout-minutes": 1260,
"env": {"MODEL": "gpt-5.6-sol", "EFFORT": "xhigh"},
"steps": [
{"uses": "setup-node@abc", "with": {"node-version": "22"}},
{"name": "Prepare", "run": "uv run prepare --runs 3\n"},
{
"name": "Run",
"timeout-minutes": 1140,
"run": (
"set -euo pipefail\nfor runtime in stable candidate; do\n"
" uv run --locked --extra dev python -m workflow_bench.runner "
"--arms baseline_nomcp baseline --runs 3 --include-expensive "
"--workers 3 --timeout 3600\ndone\n"
),
},
],
}
},
}
new = deepcopy(old)
job = new["jobs"]["evaluate"]
job["env"].update(INPUT_PR="3621", INPUT_PR_SHA=NEW, COMPARISON_RUN_ID="42")
job["steps"][-1]["run"] = (
"set -euo pipefail\narms=(baseline_nomcp baseline)\n"
'if [[ -n "$INPUT_PR" ]]; then arms=(baseline); fi\n'
"for runtime in stable candidate; do\n"
' if [[ -n "$INPUT_PR" && "$runtime" == stable ]]; then continue; fi\n'
" uv run --locked --extra dev python -m workflow_bench.runner "
'--arms "${arms[@]}" --runs 3 --include-expensive '
"--workers 3 --timeout 3600\ndone\n"
)
return old, new
def test_changed_workflow_requires_matching_measurement_protocol(monkeypatch, workflow_protocol_pair):
old, new = workflow_protocol_pair
monkeypatch.setattr(
pr_evaluation,
"_gh_json",
lambda endpoint: {
"status": "ahead",
"files": [{"filename": pr_evaluation.EVALUATION_WORKFLOW}],
},
)
calls = []
def api(args):
calls.append(args)
return yaml.safe_dump(old if f"ref={OLD}" in args[1] else new)
monkeypatch.setattr(pr_evaluation, "_gh", api)
pr_evaluation.require_comparable_harness(REPO, OLD, HARNESS)
assert len(calls) == 2 and all("Accept: application/vnd.github.raw+json" in call for call in calls)
new["jobs"]["evaluate"]["steps"][-1]["run"] += "echo changed measurement\n"
with pytest.raises(ValueError, match="measurement protocol changed"):
pr_evaluation.require_comparable_harness(REPO, OLD, HARNESS)
@pytest.mark.parametrize(
"before,after",
[
("--workers 3", "--workers 1"),
("--timeout 3600", "--timeout 60"),
("--runs 3", "--runs 2"),
(" --include-expensive", ""),
("arms=(baseline);", "arms=(baseline_nomcp);"),
],
)
def test_workflow_protocol_rejects_changed_paid_settings(workflow_protocol_pair, before, after):
old, new = workflow_protocol_pair
step = new["jobs"]["evaluate"]["steps"][-1]
step["run"] = step["run"].replace(before, after)
assert pr_evaluation.measurement_protocol(yaml.safe_dump(old)) != pr_evaluation.measurement_protocol(
yaml.safe_dump(new)
)
@pytest.mark.parametrize(
"mutation",
[
lambda job: job["steps"][0]["with"].update({"node-version": "24"}),
lambda job: job["steps"][-1].update({"timeout-minutes": 1200}),
lambda job: job["env"].update(MODEL="another-model"),
lambda job: job["env"].update(ANTHROPIC_BASE_URL="another-provider"),
lambda job: job["steps"][1].update(run="uv run prepare --runs 2\n"),
],
)
def test_workflow_protocol_rejects_changed_setup_or_environment(workflow_protocol_pair, mutation):
old, new = workflow_protocol_pair
mutation(new["jobs"]["evaluate"])
assert pr_evaluation.measurement_protocol(yaml.safe_dump(old)) != pr_evaluation.measurement_protocol(
yaml.safe_dump(new)
)
def test_workflow_protocol_rejects_extra_paid_invocations(workflow_protocol_pair):
_, new = workflow_protocol_pair
new["jobs"]["evaluate"]["steps"].append({"run": "python -m workflow_bench.runner"})
with pytest.raises(ValueError, match="one paid"):
pr_evaluation.measurement_protocol(yaml.safe_dump(new))
@pytest.mark.parametrize(
"comparison",
[
{"status": "diverged", "files": []},
{"status": "ahead"},
{"status": "ahead", "files": [{"filename": "eval/workflow_bench/runner.py"}]},
{"status": "ahead", "files": [{"filename": ".github/claude-canary-runtime/package-lock.json"}]},
{"status": "ahead", "files": [{"filename": "other.py", "previous_filename": "eval/workflow_bench/runner.py"}]},
{"status": "ahead", "files": [{"filename": "other.py"}] * 300},
],
)
def test_changed_measurement_inputs_or_unknown_history_reject_previous_evidence(monkeypatch, comparison):
monkeypatch.setattr(pr_evaluation, "_gh_json", lambda endpoint: comparison)
with pytest.raises(ValueError):
pr_evaluation.require_comparable_harness(REPO, OLD, HARNESS)
@pytest.fixture
def github(evidence, monkeypatch):
from workflow_bench import release_report
previous = evidence[0]
payload = {
"run": {
"id": 42,
"path": ".github/workflows/release-evaluation.yml",
"conclusion": "success",
"head_branch": "main",
"head_sha": OLD,
"event": "workflow_dispatch",
"head_repository": {"full_name": REPO},
},
"artifacts": {"total_count": 1, "artifacts": [{"name": f"release-agent-evaluation-{OLD}", "expired": False}]},
"report": previous,
}
def api(endpoint):
if "/artifacts?" in endpoint:
return payload["artifacts"]
if "/compare/" in endpoint:
return {"status": "ahead", "files": []}
return payload["run"]
def download(arguments):
from pathlib import Path
assert arguments[:4] == ["run", "download", "42", "--repo"]
assert arguments[4] == REPO
(Path(arguments[-1]) / "agent-evaluation.json").write_text(json.dumps(payload["report"]))
return ""
monkeypatch.setattr(pr_evaluation, "_gh_json", api)
monkeypatch.setattr(pr_evaluation, "_gh", download)
monkeypatch.setattr(release_report, "suite_binding", lambda: ([], previous["task_set_digest"], previous["tasks"]))
return payload
def test_baseline_download_authenticates_run_artifact_and_recomputes_report(github):
assert pr_evaluation.fetch_baseline(REPO, 42, HARNESS, "gpt-5.6-sol", "xhigh") == github["report"]
@pytest.mark.parametrize(
"mutation",
[
lambda g: g["run"].update(conclusion="failure"),
lambda g: g["run"].update(head_branch="feature"),
lambda g: g["run"].update(path=".github/workflows/other.yml"),
lambda g: g["run"].update(id=99),
lambda g: g["run"]["head_repository"].update(full_name="fork/repo"),
lambda g: g["artifacts"].update(total_count=101),
lambda g: g["artifacts"]["artifacts"][0].update(expired=True),
lambda g: g["artifacts"]["artifacts"][0].update(name=f"pr-cost-comparison-{OLD}"),
lambda g: g["report"].update(runtime_sha=NEW),
lambda g: g["report"].update(harness_sha=HARNESS),
lambda g: g["report"].update(complete=False),
lambda g: g["report"]["arms"]["baseline"].update(mean_cost_usd=0),
],
)
def test_untrusted_or_tampered_historical_evidence_is_rejected_before_paid_work(github, mutation):
mutation(github)
with pytest.raises(ValueError):
pr_evaluation.fetch_baseline(REPO, 42, HARNESS, "gpt-5.6-sol", "xhigh")
def test_cli_writes_sanitized_candidate_report_and_marks_missing_measurements_incomplete(
evidence, tmp_path, monkeypatch
):
previous, rows, metadata = evidence
baseline, results, meta, out = (tmp_path / name for name in ("previous.json", "rows.jsonl", "metadata.json", "out"))
baseline.write_text(json.dumps(previous))
meta.write_text(json.dumps(metadata))
results.write_text("\n".join(json.dumps(row) for row in rows))
monkeypatch.setattr(
"sys.argv",
[
"pr-evaluation",
"summarize",
"--results",
str(results),
"--metadata",
str(meta),
"--baseline",
str(baseline),
"--out",
str(out),
"--sha",
NEW,
"--pr",
"3621",
"--run-id",
"42",
],
)
pr_evaluation.main()
text = (out / "pr-cost-comparison.json").read_text()
assert json.loads(text)["improved"]
assert "/private/transcript" not in text
results.unlink()
with pytest.raises(SystemExit) as exc:
pr_evaluation.main()
assert exc.value.code == 1
assert not json.loads((out / "pr-cost-comparison.json").read_text())["complete"]
assert "Incomplete evidence" in (out / "pr-cost-comparison.md").read_text()
def bindings(previous):
return [
{
"id": task,
"resolved_sha": pin["sha"],
"oracle_digest": pin["oracle_digest"],
"sandbox_dependency_content_digest": "9" * 64,
}
for task, pin in previous["tasks"].items()
]
def test_dependency_preflight_accepts_identical_prepared_task_bytes(evidence):
previous = evidence[0]
pr_evaluation.check_dependency_bindings(bindings(previous), previous)
@pytest.mark.parametrize(
"mutation",
[
lambda bs: bs.pop(),
lambda bs: bs.append(dict(bs[0])),
lambda bs: bs[0].update(resolved_sha=NEW),
lambda bs: bs[0].update(oracle_digest="0" * 64),
lambda bs: bs[0].update(sandbox_dependency_content_digest="0" * 64),
],
)
def test_dependency_preflight_rejects_incompatible_bytes_before_paid_sessions(evidence, mutation):
previous = evidence[0]
prepared = bindings(previous)
mutation(prepared)
with pytest.raises(ValueError):
pr_evaluation.check_dependency_bindings(prepared, previous)