Viewing files at commit e9b767a51662Commit detailsView latest

Browse files
tests/test_workflow_oracles.py 422 lines · 25.2 KB · python
1
"""Negative task-oracle tests: fixture validators are not trusted evidence."""
2
from __future__ import annotations
3
4
import json
5
import subprocess
6
from pathlib import Path
7
import shutil
8
import sys
9
import tempfile
10
import unittest
11
from unittest import mock
12
13
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
14
import workflow_ab
15
import agent_workflow
16
from oakbench import workflow_oracles
17
18
19
class ExistingOracleFalsePassTest(unittest.TestCase):
20
    def test_oracle_version_is_pinned(self):
21
        self.assertEqual(workflow_oracles.VERSION, "2026-09-11.2")
22
23
    def test_deleting_rewritten_configs_fails(self):
24
        with tempfile.TemporaryDirectory() as tmp:
25
            repo = Path(tmp)
26
            workflow_ab.make_wide_config_fixture(repo)
27
            for path in (repo / "services").glob("*/config.toml"):
28
                path.write_text(path.read_text().replace("timeout_ms = 2500", "timeout_ms = 3000"))
29
            (repo / "services/service-0003/config.toml").unlink()
30
            self.assertIsNotNone(workflow_ab.workflow_worktree_payload_failure(
31
                workflow_ab.WORKFLOWS["wide_config_refactor"], repo
32
            ))
33
34
    def test_empty_manifest_fails(self):
35
        with tempfile.TemporaryDirectory() as tmp:
36
            repo = Path(tmp)
37
            (repo / "manifest.json").write_text('{"assets": []}')
38
            self.assertIsNotNone(workflow_ab.workflow_worktree_payload_failure(
39
                workflow_ab.WORKFLOWS["large_asset_manifest"], repo
40
            ))
41
42
43
44
def finish_configs(repo):
45
    for path in (repo / "services").glob("*/config.toml"):
46
        path.write_text(path.read_text().replace("timeout_ms = 2500", "timeout_ms = 3000"))
47
48
49
def finish_assets(repo):
50
    path = repo / "manifest.json"
51
    data = json.loads(path.read_text())
52
    for entry in data["assets"]:
53
        entry["reviewed"] = True
54
    path.write_text(json.dumps(data))
55
56
57
class TrustedWorkflowOracleTest(unittest.TestCase):
58
    def assert_both_lanes_reject(self, name, repo, reason):
59
        workflow = workflow_ab.WORKFLOWS[name]
60
        failure = workflow_ab.workflow_worktree_payload_failure(workflow, repo)
61
        self.assertIsNotNone(failure)
62
        self.assertIn(reason, failure)
63
        # This is the exact external command run by real-agent run_oracle.
64
        proc = agent_workflow.run_command(agent_workflow.test_command_for(workflow), repo)
65
        self.assertEqual(proc.returncode, 1, proc.stderr)
66
        self.assertIn(reason, proc.stdout)
67
68
    def test_exact_configs_reject_loss_extras_wrong_values_and_types(self):
69
        with tempfile.TemporaryDirectory() as tmp:
70
            repo = Path(tmp)
71
            workflow_ab.make_wide_config_fixture(repo)
72
            finish_configs(repo)
73
            self.assertIsNone(workflow_oracles.validate("wide_config_refactor", repo))
74
            path = repo / "services/service-0003/config.toml"
75
            original = path.read_bytes()
76
            for wrong in (b"", original.replace(b"3000", b"1800"), original + b"retries = 99\n"):
77
                with self.subTest(wrong=wrong):
78
                    path.write_bytes(wrong)
79
                    self.assert_both_lanes_reject("wide_config_refactor", repo, "config_payload_mismatch")
80
            path.unlink()
81
            self.assert_both_lanes_reject("wide_config_refactor", repo, "config_path_set_mismatch")
82
            path.symlink_to("../../service-0000/config.toml")
83
            self.assert_both_lanes_reject("wide_config_refactor", repo, "payload_path_type")
84
            path.unlink()
85
            path.write_bytes(original)
86
            extra = repo / "services/extra"
87
            extra.write_bytes(b"unexpected")
88
            self.assert_both_lanes_reject("wide_config_refactor", repo, "config_path_set_mismatch")
89
            extra.unlink()
90
            untouched = repo / "services/service-0001/config.toml"
91
            untouched.write_bytes(untouched.read_bytes().replace(b"1800", b"3000"))
92
            self.assert_both_lanes_reject("wide_config_refactor", repo, "config_payload_mismatch")
93
94
    def test_assets_reject_manifest_lies_and_content_corruption(self):
95
        with tempfile.TemporaryDirectory() as tmp:
96
            repo = Path(tmp)
97
            workflow_ab.make_large_asset_fixture(repo)
98
            finish_assets(repo)
99
            self.assertIsNone(workflow_oracles.validate("large_asset_manifest", repo))
100
            path = repo / "manifest.json"
101
            valid = json.loads(path.read_text())
102
            variants = [
103
                ({"assets": []}, "manifest_asset_count"),
104
                ({"assets": valid["assets"][:3]}, "manifest_asset_count"),
105
                ({"assets": valid["assets"] + [valid["assets"][0]]}, "manifest_asset_count"),
106
                ({"assets": [valid["assets"][0]] * 4}, "manifest_duplicate_asset"),
107
                ({"assets": [{**item, "bytes": True} for item in valid["assets"]]}, "manifest_asset_fields"),
108
                ({"assets": [{**item, "reviewed": 1} for item in valid["assets"]]}, "manifest_asset_fields"),
109
                ({"assets": [{**item, "path": "../outside"} for item in valid["assets"]]}, "manifest_asset_identity"),
110
                ({"assets": None}, "manifest_asset_count"),
111
                ({}, "manifest_object_shape"),
112
            ]
113
            for data, reason in variants:
114
                with self.subTest(data=data):
115
                    path.write_text(json.dumps(data))
116
                    self.assert_both_lanes_reject("large_asset_manifest", repo, reason)
117
            path.write_text('{"assets": [], "assets": []}')
118
            self.assert_both_lanes_reject("large_asset_manifest", repo, "duplicate_json_key")
119
            path.write_text(json.dumps(valid))
120
            extra = repo / "assets/asset-04.bin"
121
            extra.write_bytes(b"extra")
122
            self.assert_both_lanes_reject("large_asset_manifest", repo, "asset_path_set_mismatch")
123
            extra.unlink()
124
            asset = repo / "assets/asset-03.bin"
125
            with asset.open("r+b") as stream:
126
                stream.write(b"X")
127
            self.assert_both_lanes_reject("large_asset_manifest", repo, "asset_content_mismatch")
128
            asset.unlink()
129
            self.assert_both_lanes_reject("large_asset_manifest", repo, "asset_path_set_mismatch")
130
131
    def test_deleted_or_noop_fixture_validator_cannot_bypass_oracle(self):
132
        for name, filename, maker, finish in (
133
            ("wide_config_refactor", "validate_configs.py", workflow_ab.make_wide_config_fixture, finish_configs),
134
            ("large_asset_manifest", "validate_manifest.py", workflow_ab.make_large_asset_fixture, finish_assets),
135
        ):
136
            with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp:
137
                repo = Path(tmp) / "repo"
138
                repo.mkdir()
139
                artifacts = Path(tmp) / "artifacts"
140
                artifacts.mkdir()
141
                maker(repo)
142
                before = agent_workflow.workspace_manifest(repo)
143
                finish(repo)
144
                validator = repo / filename
145
                for replacement in (b"raise SystemExit(0)\n", None):
146
                    if replacement is None:
147
                        validator.unlink()
148
                    else:
149
                        validator.write_bytes(replacement)
150
                    self.assert_both_lanes_reject(name, repo, "fixture_validator_modified_or_missing")
151
                    subject = workflow_ab.Subject(name="git", kind="git", label="git", bin=Path("/git"))
152
                    with mock.patch.object(agent_workflow, "vcs_status", return_value=("", 0, 0.0)), \
153
                         mock.patch.object(agent_workflow, "commit_count", return_value=2):
154
                        result = agent_workflow.run_oracle(
155
                            subject, workflow_ab.WORKFLOWS[name], repo, before, 1, artifacts,
156
                        )
157
                    self.assertFalse(result.success)
158
                    self.assertFalse(result.checks["required_checks_passed"])
159
                    self.assertIn("fixture_validator_modified_or_missing", (artifacts / "oracle.test.log").read_text())
160
161
    def test_invalid_trusted_oracle_envelope_is_unmeasured(self):
162
        with tempfile.TemporaryDirectory() as tmp:
163
            repo = Path(tmp)
164
            workflow_ab.make_wide_config_fixture(repo)
165
            before = agent_workflow.workspace_manifest(repo)
166
            finish_configs(repo)
167
            artifacts = repo / "logs"
168
            artifacts.mkdir()
169
            subject = workflow_ab.Subject("git", "git", "Git", Path("/git"))
170
            for output, code in (("", 1), ("{}", 0), ("not json", 0),
171
                                 (json.dumps({"oracle_version": workflow_oracles.VERSION,
172
                                              "workflow_integrity_source": "trusted", "workflow_integrity_passed": None}), 77)):
173
                with mock.patch.object(agent_workflow, "run_command", return_value=subprocess.CompletedProcess([], code, output, "")), \
174
                     mock.patch.object(agent_workflow, "vcs_status", return_value=("", 0, 0)), \
175
                     mock.patch.object(agent_workflow, "commit_count", return_value=2):
176
                    result = agent_workflow.run_oracle(subject, workflow_ab.WORKFLOWS["wide_config_refactor"], repo, before, 1, artifacts)
177
                self.assertIsNone(result.evidence["workflow_integrity_passed"])
178
                self.assertEqual(result.evidence["workflow_integrity_unmeasured_reason"], "trusted_oracle_evidence_invalid_or_missing")
179
180
    def test_unavailable_required_check_does_not_hide_measured_failure(self):
181
        with tempfile.TemporaryDirectory() as tmp:
182
            repo = Path(tmp)
183
            workflow_ab.make_wide_config_fixture(repo)
184
            before = agent_workflow.workspace_manifest(repo)
185
            finish_configs(repo)
186
            evidence = {"oracle_version": workflow_oracles.VERSION, "workflow_integrity_source": "trusted",
187
                "workflow_integrity_passed": None, "workflow_integrity_unmeasured_reason": "missing"}
188
            with mock.patch.object(agent_workflow, "run_command", return_value=subprocess.CompletedProcess([], 77, json.dumps(evidence), "")), \
189
                 mock.patch.object(agent_workflow, "vcs_status", return_value=("dirty", 1, 0)), \
190
                 mock.patch.object(agent_workflow, "expected_change_ok", return_value=True), \
191
                 mock.patch.object(agent_workflow, "commit_count", return_value=2):
192
                result = agent_workflow.run_oracle(workflow_ab.Subject("git", "git", "Git", Path("/git")),
193
                    workflow_ab.WORKFLOWS["wide_config_refactor"], repo, before, 1, repo)
194
            self.assertIsNone(result.checks["required_checks_passed"])
195
            self.assertEqual(result.failure_reason, "final_vcs_clean")
196
197
    def test_nontrusted_check_return77_is_unmeasured(self):
198
        with tempfile.TemporaryDirectory() as tmp:
199
            repo = Path(tmp)
200
            with mock.patch.object(agent_workflow, "run_command", return_value=subprocess.CompletedProcess([], 77, "", "missing tool")), \
201
                 mock.patch.object(agent_workflow, "vcs_status", return_value=("", 0, 0)), \
202
                 mock.patch.object(agent_workflow, "expected_change_ok", return_value=True), \
203
                 mock.patch.object(agent_workflow, "commit_count", return_value=2):
204
                result = agent_workflow.run_oracle(workflow_ab.Subject("git", "git", "Git", Path("/git")),
205
                    workflow_ab.WORKFLOWS["bugfix_test_loop"], repo, {}, 1, repo)
206
            self.assertIsNone(result.checks["required_checks_passed"])
207
            self.assertIsNone(result.failure_reason)
208
            self.assertIsNone(result.evidence["workflow_integrity_passed"])
209
            self.assertEqual(result.evidence["workflow_integrity_unmeasured_reason"], "required_check_instrument_unavailable")
210
            self.assertEqual(result.evidence["workflow_integrity_evidence_class"], "subject_status_only")
211
212
    def test_fixture_module_shadowing_is_not_executed(self):
213
        with tempfile.TemporaryDirectory() as tmp:
214
            repo = Path(tmp)
215
            workflow_ab.make_wide_config_fixture(repo)
216
            finish_configs(repo)
217
            (repo / "json.py").write_text("raise RuntimeError('untrusted import executed')")
218
            proc = agent_workflow.run_command(
219
                agent_workflow.test_command_for(workflow_ab.WORKFLOWS["wide_config_refactor"]), repo,
220
            )
221
            self.assertEqual(proc.returncode, 0, proc.stderr)
222
223
    def test_scripted_unavailable_instrument_row_persists(self):
224
        from oakbench.rows import validate_rows
225
        from oakbench.results import ResultsStore
226
        with tempfile.TemporaryDirectory() as tmp:
227
            repo = Path(tmp)
228
            workflow_ab.make_wide_config_fixture(repo)
229
            finish_configs(repo)
230
            subject = workflow_ab.Subject("git", "git", "Git", Path("/subject/not-reader"))
231
            with mock.patch.object(workflow_oracles, "resolve_git_instrument", return_value=(None, {})):
232
                evidence = workflow_ab.workflow_integrity_fields(subject, workflow_ab.WORKFLOWS["wide_config_refactor"], repo, [])
233
            row = workflow_ab.sum_rows([], {"bench_id": "test", "profile": "test"}, subject,
234
                workflow_ab.WORKFLOWS["wide_config_refactor"], 0, "workflow", "workflow.total", evidence)
235
            self.assertEqual(row["returncode"], 77)
236
            self.assertEqual(validate_rows([row], "workflow"), [])
237
            ResultsStore(repo / "results", "workflow").write("test", [row])
238
239
    def test_workflow_summary_fails_when_trusted_payload_fails(self):
240
        with tempfile.TemporaryDirectory() as tmp:
241
            repo = Path(tmp)
242
            workflow_ab.make_wide_config_fixture(repo)
243
            finish_configs(repo)
244
            (repo / "services/service-0003/config.toml").unlink()
245
            subject = workflow_ab.Subject(name="oak", kind="oak", label="oak", bin=Path("/oak"))
246
            fields = workflow_ab.workflow_integrity_fields(
247
                subject, workflow_ab.WORKFLOWS["wide_config_refactor"], repo, [],
248
            )
249
            self.assertFalse(fields["correctness_passed"])
250
            self.assertIn("config_path_set_mismatch", fields["correctness_failure_reason"])
251
            self.assertEqual(fields["oracle_version"], workflow_oracles.VERSION)
252
253
254
@unittest.skipIf(shutil.which("git") is None, "git not installed")
255
class CommittedWorkflowOracleTest(unittest.TestCase):
256
    def test_subject_binary_never_becomes_committed_tree_reader(self):
257
        with tempfile.TemporaryDirectory() as tmp:
258
            repo = Path(tmp)
259
            workflow_ab.make_wide_config_fixture(repo)
260
            finish_configs(repo)
261
            subject = workflow_ab.Subject("git", "git", "Git candidate", Path("/candidate/git"))
262
            with mock.patch.object(workflow_oracles, "resolve_git_instrument", return_value=(None, {})) as resolve:
263
                workflow_ab.workflow_integrity_fields(subject, workflow_ab.WORKFLOWS["wide_config_refactor"], repo, [])
264
                resolve.assert_called_once_with(None)
265
            command = agent_workflow.test_command_for(workflow_ab.WORKFLOWS["wide_config_refactor"], subject)
266
            self.assertEqual(command[command.index("--binary") + 1], "/candidate/git")
267
            self.assertNotIn("--git-instrument", command)
268
269
    def test_nontrusted_git_records_actual_scoped_head_evidence(self):
270
        with tempfile.TemporaryDirectory() as tmp:
271
            repo = Path(tmp)
272
            workflow_ab.make_bugfix_fixture(repo)
273
            path = repo / "app/pricing.py"
274
            path.write_text(path.read_text().replace("return cents - percent", "return cents - ((cents * percent) // 100)"))
275
            self.git(repo, "init"); self.git(repo, "add", "."); self.git(repo, "commit", "-m", "fixed")
276
            fields = workflow_ab.workflow_integrity_fields(workflow_ab.Subject("git", "git", "Git", Path(shutil.which("git"))),
277
                workflow_ab.WORKFLOWS["bugfix_test_loop"], repo, [])
278
            self.assertTrue(fields["workflow_integrity_passed"])
279
            self.assertEqual(fields["workflow_integrity_evidence_class"], "subject_binary_committed_payload")
280
            self.assertEqual(fields["workflow_subject_binary_sha256"], fields["git_head_instrument_sha256"])
281
282
    def test_task_evidence_resolves_instrument_once(self):
283
        with tempfile.TemporaryDirectory() as tmp:
284
            repo = Path(tmp)
285
            workflow_ab.make_wide_config_fixture(repo)
286
            finish_configs(repo)
287
            actual = workflow_oracles.resolve_git_instrument
288
            with mock.patch.object(workflow_oracles, "resolve_git_instrument", wraps=actual) as resolver:
289
                evidence = workflow_oracles.task_evidence("wide_config_refactor", repo, "git", shutil.which("git"))
290
            self.assertEqual(resolver.call_count, 1)
291
            self.assertFalse(evidence["workflow_integrity_passed"])
292
293
    def test_bound_subject_hashes_are_reused_without_binary_reads(self):
294
        for kind, metadata, expected in (
295
            ("oak", {"oak_export_instrument_sha256": "a" * 64}, "subject_produced_committed_tree"),
296
            ("git", {"workflow_subject_binary_sha256": "a" * 64, "git_head_instrument_sha256": "a" * 64}, "subject_binary_committed_tree"),
297
        ):
298
            with mock.patch.object(Path, "read_bytes", side_effect=AssertionError("redundant binary read")), \
299
                 mock.patch.object(workflow_oracles.shutil, "which", side_effect=AssertionError("redundant subject lookup")):
300
                result = workflow_oracles.reader_evidence(kind, "/subject", metadata)
301
            self.assertEqual(result["workflow_integrity_evidence_class"], expected)
302
303
    def test_killed_git_instrument_is_unmeasured(self):
304
        with mock.patch.object(workflow_oracles, "resolve_git_instrument", return_value=("/git", {})), \
305
             mock.patch.object(workflow_oracles.subprocess, "run", return_value=subprocess.CompletedProcess([], -9, b"", b"")):
306
            self.assertEqual(workflow_oracles.validate_git_head("wide_config_refactor", Path("/tmp")), "git_head_oracle_unavailable")
307
            self.assertEqual(workflow_oracles.git_head_text(Path("/tmp"), "x"), (None, "git_head_oracle_unavailable"))
308
309
    def test_path_instrument_and_missing_instrument_are_not_subject_failures(self):
310
        real_git = shutil.which("git")
311
        with tempfile.TemporaryDirectory() as tmp:
312
            repo = Path(tmp)
313
            workflow_ab.make_wide_config_fixture(repo)
314
            finish_configs(repo)
315
            with mock.patch.object(workflow_oracles.shutil, "which", return_value=None) as lookup:
316
                evidence = workflow_oracles.task_evidence("wide_config_refactor", repo, "git")
317
                lookup.assert_called_with("git")
318
                self.assertIsNone(evidence["workflow_integrity_passed"])
319
                self.assertEqual(evidence["workflow_integrity_unmeasured_reason"], "git_head_oracle_unavailable")
320
                self.assertIsNone(evidence["git_head_instrument_sha256"])
321
                self.assertEqual(evidence["workflow_integrity_evidence_class"], "worktree_payload")
322
            binary, metadata = workflow_oracles.resolve_git_instrument(real_git)
323
            self.assertTrue(Path(binary).is_absolute())
324
            self.assertEqual(len(metadata["git_head_instrument_sha256"]), 64)
325
            self.assertTrue(metadata["git_head_instrument_version"])
326
327
    @unittest.skipUnless(shutil.which("oak"), "Oak installed binary required")
328
    def test_correct_oak_worktree_cannot_hide_partial_checkpoint(self):
329
        binary = shutil.which("oak")
330
        with tempfile.TemporaryDirectory() as tmp:
331
            repo = Path(tmp)
332
            workflow_ab.make_wide_config_fixture(repo)
333
            def oak(*args):
334
                process = subprocess.run([binary, *args], cwd=repo, env=workflow_ab.base_env(), capture_output=True, timeout=60)
335
                self.assertEqual(process.returncode, 0, process.stderr)
336
            oak("init"); oak("commit", "--no-verify")
337
            finish_configs(repo)
338
            oak("commit", "--no-verify", "services/service-0000/config.toml")
339
            self.assertIsNone(workflow_oracles.validate("wide_config_refactor", repo))
340
            evidence = workflow_oracles.task_evidence("wide_config_refactor", repo, "oak", binary)
341
            self.assertIs(evidence["workflow_integrity_passed"], False, evidence)
342
            self.assertEqual(evidence["workflow_integrity_evidence_class"], "subject_produced_committed_tree_attempt")
343
            self.assertIn("git_head_config_payload_mismatch", evidence["workflow_integrity_failure_reason"])
344
            oak("commit", "--no-verify")
345
            verified = workflow_oracles.task_evidence("wide_config_refactor", repo, "oak", binary)
346
            self.assertIs(verified["workflow_integrity_passed"], True, verified)
347
            self.assertNotEqual(evidence["oak_export_head"], verified["oak_export_head"])
348
349
    def git(self, repo, *args):
350
        result = subprocess.run(
351
            [shutil.which("git"), *args], cwd=repo, env=workflow_ab.base_env(),
352
            stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True,
353
        )
354
        self.assertEqual(result.returncode, 0, result.stderr)
355
356
    def test_correct_worktree_cannot_hide_partial_commit(self):
357
        for name, maker, finish, staged in (
358
            ("wide_config_refactor", workflow_ab.make_wide_config_fixture, finish_configs,
359
             "services/service-0000/config.toml"),
360
            ("large_asset_manifest", workflow_ab.make_large_asset_fixture, finish_assets, "manifest.json"),
361
        ):
362
            with self.subTest(name=name), tempfile.TemporaryDirectory() as tmp:
363
                repo = Path(tmp)
364
                maker(repo)
365
                self.git(repo, "init")
366
                self.git(repo, "add", ".")
367
                if name == "large_asset_manifest":
368
                    self.git(repo, "rm", "--cached", "assets/asset-03.bin")
369
                self.git(repo, "commit", "-m", "initial")
370
                finish(repo)
371
                self.git(repo, "add", staged)
372
                self.git(repo, "commit", "-m", "partial task")
373
                self.assertIsNone(workflow_oracles.validate(name, repo))
374
                self.assertIsNotNone(workflow_oracles.validate_git_head(name, repo))
375
                subject = workflow_ab.Subject(name="git", kind="git", label="Git", bin=Path(shutil.which("git")))
376
                fields = workflow_ab.workflow_integrity_fields(subject, workflow_ab.WORKFLOWS[name], repo, [])
377
                self.assertFalse(fields["correctness_passed"])
378
                proc = agent_workflow.run_command(agent_workflow.test_command_for(workflow_ab.WORKFLOWS[name], subject), repo)
379
                self.assertEqual(proc.returncode, 1, proc.stderr)
380
                self.assertIn("git_head_", proc.stdout)
381
                self.git(repo, "add", ".")
382
                self.git(repo, "commit", "-m", "complete task")
383
                self.assertIsNone(workflow_oracles.validate_git_head(name, repo))
384
                proc = agent_workflow.run_command(agent_workflow.test_command_for(workflow_ab.WORKFLOWS[name], subject), repo)
385
                self.assertEqual(proc.returncode, 0, proc.stderr)
386
                observed = json.loads(proc.stdout)
387
                self.assertEqual(observed["workflow_integrity_evidence_class"], "subject_binary_committed_tree")
388
                self.assertEqual(observed["workflow_subject_binary_sha256"], observed["git_head_instrument_sha256"])
389
390
391
class RealSubjectWorkflowOracleTest(unittest.TestCase):
392
    def test_mock_agent_config_and_asset_tasks_on_real_vcs(self):
393
        for kind in ("git", "oak"):
394
            binary = shutil.which(kind)
395
            if binary is None:
396
                with self.subTest(kind=kind):
397
                    self.skipTest(f"{kind} not installed")
398
                continue
399
            for name in ("wide_config_refactor", "large_asset_manifest"):
400
                with self.subTest(kind=kind, name=name), tempfile.TemporaryDirectory() as tmp:
401
                    root = Path(tmp)
402
                    subject = workflow_ab.Subject(name=kind, kind=kind, label=kind, bin=Path(binary))
403
                    workflow = workflow_ab.WORKFLOWS[name]
404
                    fixture = workflow_ab.prepare_fixture(root, workflow)
405
                    row = agent_workflow.run_one(
406
                        agent=agent_workflow.AgentConfig(name="mock", adapter="deterministic_mock", provider="local",
407
                                                        bin=None, model=None, version="test"),
408
                        subject=subject, workflow=workflow, fixture=fixture, run_index=0,
409
                        run_root=root / "runs", artifacts_root=root / "artifacts", timeout_seconds=300,
410
                        track="agent-default", agent_environment="minimal", instruction_level="full-docs",
411
                        bench_id="oracle-integration", timestamp_utc="2026-09-10T00:00:00Z", source={}, host_meta={},
412
                    )
413
                    self.assertTrue(row["success"], row.get("failure_reason"))
414
                    self.assertEqual(agent_workflow.validate_row(row), [])
415
                    self.assertIn(workflow_oracles.VERSION, row["scenario_version"])
416
                    self.assertEqual(row["oracle_version"], workflow_oracles.VERSION)
417
                    self.assertTrue(row["workflow_integrity_passed"])
418
                    self.assertIn("git_head_instrument_sha256", row)
419
                    from oakbench.populations import comparison_identity
420
                    identity = comparison_identity(row)
421
                    self.assertEqual(identity["oracle_version"], workflow_oracles.VERSION)
422
                    self.assertEqual(identity["env_isolation_version"], agent_workflow.ENV_ISOLATION_VERSION)