Viewing files at commit 2735d7f5558eCommit detailsView latest

Browse files
tests/test_claim_eligibility.py 242 lines · 13.3 KB · python
1
"""Adversarial saved-baseline comparisons and claim eligibility controls."""
2
from dataclasses import replace
3
import contextlib
4
import io
5
import json
6
from pathlib import Path
7
import subprocess
8
import sys
9
import tempfile
10
import unittest
11
from unittest.mock import patch
12
13
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
14
import benchmark_stats
15
from oakbench.baseline_book import Target, book_json, cells_from_rows, load_baseline_book
16
from oakbench.populations import comparison_identity
17
from oakbench.scorecard import evaluate_targets
18
19
20
def rows(subject="git", **overrides):
21
    result = []
22
    for index in range(30):
23
        row = dict(subject=subject, subject_kind="git" if subject == "git" else "oak",
24
                   bench_id="campaign", run=index, lane="core", profile="standard",
25
                   scenario="tiny", operation="status.clean", returncode=0,
26
                   elapsed_ms=10.0 if subject == "git" else 1.0,
27
                   benchmark_track="core-equivalent", command_semantics_version=1,
28
                   fixture_version=1, scenario_version=1, runner_class="mac-arm",
29
                   cache_state="warm", env_isolation_version="1", load_tier="idle",
30
                   binary_sha256="git-sha" if subject == "git" else "oak-sha")
31
        row.update(overrides)
32
        result.append(row)
33
    return result
34
35
36
TARGET = Target("t", "core", "standard", "tiny", "status.clean", "oak_local", "git",
37
                "elapsed_ms", "median", "ratio_at_most", .8, {})
38
39
40
def baseline(source=None):
41
    return cells_from_rows(rows() if source is None else source, lane="core", metrics=("elapsed_ms",),
42
                           statistics_names=("median",), noise_floor_pct=1.0, bootstrap_samples=20)[0]
43
44
45
def evaluate(candidate=None, cell=None, mode="claim"):
46
    cell = baseline() if cell is None else cell
47
    return evaluate_targets(rows("oak_local") if candidate is None else candidate,
48
                            [TARGET], {cell.key: cell}, mode=mode, bootstrap_samples=20)[0]
49
50
51
class ClaimEligibilityTest(unittest.TestCase):
52
    def test_comparable_independent_trials_allow_different_vcs_binary_hashes(self):
53
        result = evaluate()
54
        self.assertEqual(result["verdict"], "GREEN")
55
        self.assertTrue(result["claim_eligible"])
56
57
    def test_saved_baseline_mismatch_is_invalid_even_in_diagnostic_mode(self):
58
        for field, value in [("runner_class", "linux"), ("fixture_version", 2),
59
                             ("benchmark_track", "agent-default"), ("cache_state", "cold"),
60
                             ("remote_transport", "network"), ("load_tier", "cpu50"),
61
                             ("output_oracle_version", "different")]:
62
            with self.subTest(field=field):
63
                for mode in ("claim", "diagnostic"):
64
                    result = evaluate(rows("oak_local", **{field: value}), mode=mode)
65
                    self.assertEqual(result["verdict"], "INVALID")
66
                    self.assertFalse(result["claim_eligible"])
67
68
    def test_missing_and_duplicate_trials_cannot_manufacture_precision(self):
69
        candidates = [rows("oak_local")[:1], rows("oak_local", run=0),
70
                      rows("oak_local") + [rows("oak_local")[0]] * 1000]
71
        missing = rows("oak_local")
72
        del missing[0]["bench_id"]
73
        candidates.append(missing)
74
        for candidate in candidates:
75
            with self.subTest(n=len(candidate)):
76
                result = evaluate(candidate)
77
                self.assertEqual(result["verdict"], "AMBER")
78
                self.assertFalse(result["claim_eligible"])
79
80
    def test_unknown_baseline_evidence_cannot_be_public_green(self):
81
        for changes in ({"noise_floor_pct": None}, {"noise_floor_pct": float("nan")},
82
                        {"noise_floor_pct": -1}, {"comparison_identity": None},
83
                        {"comparison_identity": {}}, {"independent_n": 1},
84
                        {"attempted_n": None}, {"failure_n": 1}, {"ci_low": None}):
85
            with self.subTest(changes=changes):
86
                result = evaluate(cell=replace(baseline(), **changes))
87
                self.assertNotEqual(result["verdict"], "GREEN")
88
                self.assertFalse(result["claim_eligible"])
89
90
    def test_low_or_unknown_confidence_cannot_qualify_as_claim_evidence(self):
91
        for confidence in (None, 0.5, float("nan"), 1.0):
92
            with self.subTest(baseline_confidence=confidence):
93
                self.assertFalse(evaluate(cell=replace(baseline(), confidence=confidence))["claim_eligible"])
94
        cell = cells_from_rows(rows(), lane="core", metrics=("elapsed_ms",), statistics_names=("median",),
95
                               noise_floor_pct=1.0, bootstrap_samples=20, confidence=.5)[0]
96
        self.assertFalse(evaluate(cell=cell)["claim_eligible"])
97
        cell = baseline()
98
        result = evaluate_targets(rows("oak_local"), [TARGET], {cell.key: cell}, mode="claim",
99
                                  confidence=.5, bootstrap_samples=20)[0]
100
        self.assertEqual(result["verdict"], "AMBER")
101
        self.assertEqual(result["confidence"], .5)
102
        self.assertEqual(result["bootstrap_samples"], 20)
103
104
    def test_invalid_interval_arguments_rejected_before_scoring_or_building(self):
105
        for kwargs in ({"bootstrap_samples": 0}, {"bootstrap_samples": -1}, {"bootstrap_samples": True},
106
                       {"bootstrap_samples": 1.5}, {"confidence": 0}, {"confidence": 1},
107
                       {"confidence": float("nan")}, {"confidence": float("inf")}):
108
            with self.subTest(kwargs=kwargs):
109
                with self.assertRaises(ValueError):
110
                    evaluate_targets([], [], {}, **kwargs)
111
                with self.assertRaises(ValueError):
112
                    cells_from_rows([], lane="core", **kwargs)
113
114
    def test_uniformly_unknown_cache_cannot_be_claim_evidence(self):
115
        result = evaluate(rows("oak_local", cache_state="unknown"), baseline(rows(cache_state="unknown")))
116
        self.assertEqual(result["verdict"], "AMBER")
117
        self.assertIn("cache_state", " ".join(result["messages"]))
118
119
    def test_legacy_diagnostic_is_explicitly_not_claim_eligible(self):
120
        cell = replace(baseline(), comparison_identity=None, noise_floor_pct=None)
121
        result = evaluate(rows("oak_local")[:1], cell, mode="diagnostic")
122
        self.assertEqual(result["verdict"], "GREEN")
123
        self.assertFalse(result["claim_eligible"])
124
        self.assertIn("diagnostic only", " ".join(result["messages"]))
125
126
    def test_baseline_builder_preserves_failures_and_unmeasured_denominators(self):
127
        source = rows() + [dict(rows()[0], returncode=1), dict(rows()[0], elapsed_ms=None)]
128
        cell = baseline(source)
129
        self.assertEqual(cell.attempted_n, 32)
130
        self.assertEqual(cell.failure_n, 1)
131
        self.assertFalse(evaluate(cell=cell)["claim_eligible"])
132
133
    def test_baseline_builder_rejects_mixed_populations(self):
134
        with self.assertRaisesRegex(ValueError, "incompatible baseline population"):
135
            baseline(rows() + rows(runner_class="linux"))
136
137
    def test_book_v2_roundtrip_and_duplicate_keys_rejected(self):
138
        book = book_json([baseline()])
139
        self.assertEqual(book["schema_version"], 2)
140
        with tempfile.TemporaryDirectory() as tmp:
141
            path = Path(tmp) / "book.json"
142
            path.write_text(json.dumps(book))
143
            cell = next(iter(load_baseline_book(path).values()))
144
            self.assertTrue(evaluate(cell=cell)["claim_eligible"])
145
            book["cells"].append(book["cells"][0])
146
            path.write_text(json.dumps(book))
147
            with self.assertRaisesRegex(ValueError, "duplicate baseline cell"):
148
                load_baseline_book(path)
149
150
    def test_unknown_identity_version_rejected(self):
151
        book = book_json([baseline()])
152
        book["cells"][0]["comparison_identity"]["schema_version"] = 99
153
        with tempfile.TemporaryDirectory() as tmp:
154
            path = Path(tmp) / "book.json"
155
            path.write_text(json.dumps(book))
156
            with self.assertRaisesRegex(ValueError, "unsupported baseline comparison_identity"):
157
                load_baseline_book(path)
158
159
    def test_versioned_patch_guard_requires_actual_success(self):
160
        target = replace(TARGET, guards={"patch_apply_ok": True})
161
        cell = baseline(rows(patch_oracle_version="apply-tree-v1"))
162
        for evidence in (None, False, True):
163
            candidate = rows("oak_local", patch_oracle_version="apply-tree-v1", patch_apply_ok=evidence)
164
            result = evaluate_targets(candidate, [target], {cell.key: cell}, mode="claim", bootstrap_samples=20)[0]
165
            self.assertEqual(result["verdict"], "GREEN" if evidence is True else "INVALID")
166
167
    def test_failure_only_recall_guard_fails_without_crashing(self):
168
        target = replace(TARGET, guards={"information_recall_at_least": 1.0})
169
        cell = baseline()
170
        result = evaluate_targets(rows("oak_local", returncode=1), [target], {cell.key: cell})[0]
171
        self.assertEqual(result["verdict"], "INVALID")
172
173
    def test_nested_fixture_version_normalizes_but_subject_prompt_does_not_compare(self):
174
        row = rows("oak_local")[0]
175
        del row["fixture_version"]
176
        row["fixture"] = {"fixture_version": 1}
177
        row["task_prompt_id"] = "oak-specific-label"
178
        self.assertEqual(comparison_identity(row), comparison_identity(rows()[0]))
179
180
    def test_cli_defaults_to_claim_and_refuses_legacy_evidence(self):
181
        target = {"schema_version": 1, "targets": [dict(id="t", lane="core", profile="standard",
182
                  scenario="tiny", operation="status.clean", subject="oak_local", comparator="git",
183
                  metric="elapsed_ms", goal=dict(type="ratio_at_most", value=.8))]}
184
        with tempfile.TemporaryDirectory() as tmp:
185
            root = Path(tmp)
186
            (root / "rows.jsonl").write_text("\n".join(json.dumps(row) for row in rows("oak_local")))
187
            (root / "book.json").write_text(json.dumps(book_json([replace(baseline(), comparison_identity=None)])))
188
            (root / "targets.json").write_text(json.dumps(target))
189
            command = [sys.executable, "scripts/scorecard.py", str(root / "rows.jsonl"),
190
                       "--baseline-book", str(root / "book.json"), "--targets", str(root / "targets.json"), "--json"]
191
            proc = subprocess.run(command, capture_output=True, text=True)
192
            self.assertEqual(proc.returncode, 1)
193
            self.assertEqual(json.loads(proc.stdout)[0]["mode"], "claim")
194
195
196
class OutcomeReportingTest(unittest.TestCase):
197
    def test_failure_only_cells_and_unknown_outcomes_stay_visible(self):
198
        source = [dict(rows()[0], returncode=1, elapsed_ms=999),
199
                  dict(rows()[0], returncode=None), dict(rows()[0], returncode=77)]
200
        with tempfile.TemporaryDirectory() as tmp:
201
            path = Path(tmp) / "rows.jsonl"
202
            path.write_text("\n".join(json.dumps(row) for row in source))
203
            output = io.StringIO()
204
            with patch.object(sys, "argv", ["benchmark_stats.py", str(path)]), contextlib.redirect_stdout(output):
205
                self.assertEqual(benchmark_stats.main(), 0)
206
            self.assertIn("| `git` | `tiny/status.clean` | 2 | 0 | 1 | 1 | 1 | 0 |", output.getvalue())
207
            self.assertIn("| 0 | unmeasured |", output.getvalue())
208
            self.assertNotIn("999", output.getvalue())
209
210
211
212
class AgentProducerIdentityTest(unittest.TestCase):
213
    def test_producer_records_runner_and_explicit_remaining_claim_blockers(self):
214
        from types import SimpleNamespace
215
        import agent_workflow as aw
216
        import workflow_ab
217
        from oakbench.populations import missing_claim_identity
218
        with tempfile.TemporaryDirectory() as tmp:
219
            root = Path(tmp)
220
            stdout, stderr, prompt = [root / name for name in ("stdout", "stderr", "prompt")]
221
            for path in (stdout, stderr, prompt): path.write_text("")
222
            execution = aw.AgentExecution(0, False, 1, stdout, stderr, prompt)
223
            oracle = SimpleNamespace(elapsed_ms=1, vcs_ms=1, test_ms=1, success=True, checks={}, failure_reason=None,
224
                changed_files=[], test_returncode=0, test_output_bytes=0, dirty_files_at_end=0, commit_delta=1,
225
                bytes_written=0, artifact_paths=[])
226
            with patch.object(aw, "runner_fields", return_value={"runner_class": "dedicated-test", "runner_id": "observed", "runner_profile": {}}):
227
                row = aw.make_row("synthetic", "2026-09-11T00:00:00Z", "test",
228
                    aw.AgentConfig("test", "codex_exec_json", "provider", Path(sys.executable), "version", "model"),
229
                    workflow_ab.Subject("git", "git", "Git", Path("/git")), workflow_ab.WORKFLOWS["bugfix_test_loop"],
230
                    "agent-default", 0, root, root, 0, 0, execution, oracle, "task", {}, {}, {}, "minimal", "full-docs")
231
            self.assertEqual(aw.validate_row(row), [])
232
            identity = comparison_identity(row)
233
            self.assertEqual(identity["runner_class"], "dedicated-test")
234
            self.assertEqual(identity["oracle_version"], aw.ORACLE_VERSION)
235
            self.assertEqual(row["cache_state_reason"], "cache_state_unset")
236
            self.assertIsNone(row["calibration_version"])
237
            self.assertTrue(row["calibration_unmeasured_reason"])
238
            self.assertEqual(missing_claim_identity(identity, "agent", "elapsed_ms", "agent.workflow"), ["cache_state"])
239
            self.assertEqual(missing_claim_identity(identity, "agent", "tokens", "agent.workflow"), ["calibration_version"])
240
241
if __name__ == "__main__":
242
    unittest.main()