pluginbench 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. pluginbench/__init__.py +3 -0
  2. pluginbench/__main__.py +3 -0
  3. pluginbench/assessment-charts.html +790 -0
  4. pluginbench/assessment.py +316 -0
  5. pluginbench/assessment_charts.py +1225 -0
  6. pluginbench/assessment_metrics.py +1956 -0
  7. pluginbench/assessment_models.py +127 -0
  8. pluginbench/baselines.py +792 -0
  9. pluginbench/claude.py +374 -0
  10. pluginbench/cli.py +672 -0
  11. pluginbench/code_diff.py +207 -0
  12. pluginbench/codex.py +451 -0
  13. pluginbench/codex_events.py +585 -0
  14. pluginbench/collectors.py +591 -0
  15. pluginbench/compare.py +927 -0
  16. pluginbench/components.py +129 -0
  17. pluginbench/coverage_report.py +610 -0
  18. pluginbench/datasets.py +207 -0
  19. pluginbench/evaluation.example.yaml +45 -0
  20. pluginbench/evaluation.py +1651 -0
  21. pluginbench/evaluation_report.py +1004 -0
  22. pluginbench/fallow_parse.mjs +101 -0
  23. pluginbench/harbor_backend.py +2946 -0
  24. pluginbench/isolation.py +314 -0
  25. pluginbench/judge.py +683 -0
  26. pluginbench/language_analysis.py +915 -0
  27. pluginbench/ledger.py +217 -0
  28. pluginbench/measurement_profiles.py +616 -0
  29. pluginbench/native_treatment_runtime.js +221 -0
  30. pluginbench/native_treatments.py +155 -0
  31. pluginbench/observer_data/go-analysis-v1/go.mod +10 -0
  32. pluginbench/observer_data/go-analysis-v1/go.sum +10 -0
  33. pluginbench/observer_data/go-analysis-v1/main.go +64 -0
  34. pluginbench/observer_data/oxc-parser-0.126.0/package-lock.json +449 -0
  35. pluginbench/observer_data/oxc-parser-0.126.0/package.json +5 -0
  36. pluginbench/parallel.py +101 -0
  37. pluginbench/processes.py +100 -0
  38. pluginbench/remeasure.py +992 -0
  39. pluginbench/run_lock.py +227 -0
  40. pluginbench/run_status.py +518 -0
  41. pluginbench/study.py +1858 -0
  42. pluginbench/study_review.py +924 -0
  43. pluginbench/submissions.py +486 -0
  44. pluginbench/task_sources.py +403 -0
  45. pluginbench/treatments.py +388 -0
  46. pluginbench/trial_containers.py +94 -0
  47. pluginbench/vendor/ECHARTS-LICENSE +222 -0
  48. pluginbench/vendor/ECHARTS-NOTICE +5 -0
  49. pluginbench/vendor/ZRENDER-LICENSE +29 -0
  50. pluginbench/vendor/echarts.min.js +48 -0
  51. pluginbench-0.5.0.dist-info/METADATA +294 -0
  52. pluginbench-0.5.0.dist-info/RECORD +58 -0
  53. pluginbench-0.5.0.dist-info/WHEEL +4 -0
  54. pluginbench-0.5.0.dist-info/entry_points.txt +2 -0
  55. pluginbench-0.5.0.dist-info/licenses/LICENSE +201 -0
  56. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-LICENSE +222 -0
  57. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ECHARTS-NOTICE +5 -0
  58. pluginbench-0.5.0.dist-info/licenses/src/pluginbench/vendor/ZRENDER-LICENSE +29 -0
@@ -0,0 +1,792 @@
1
+ """Reuse complete isolated baseline cohorts, never their observer results."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import copy
6
+ import fcntl
7
+ import hashlib
8
+ import json
9
+ import re
10
+ import shutil
11
+ import tarfile
12
+ import tempfile
13
+ import tomllib
14
+ from contextlib import ExitStack
15
+ from pathlib import Path
16
+ from typing import Any
17
+
18
+ from pluginbench import components, harbor_backend, submissions
19
+ from pluginbench.codex_events import failed_before_first_turn
20
+ from pluginbench.native_treatments import DELIVERY_POLICY, NativeCapabilities
21
+ from pluginbench.assessment_metrics import inspect_snapshot
22
+ from pluginbench.assessment_models import AttemptInput
23
+ from pluginbench.datasets import TaskSpec, native_identity, portable_native_identity, task_digest
24
+
25
+
26
+ def _hash(value: object) -> str:
27
+ return hashlib.sha256(
28
+ json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
29
+ ).hexdigest()
30
+
31
+
32
+ def _inventory(root: Path) -> dict[str, dict[str, object]]:
33
+ files: dict[str, dict[str, object]] = {}
34
+ for path in sorted(root.rglob("*")):
35
+ if path.is_symlink() or not (path.is_file() or path.is_dir()):
36
+ raise ValueError(f"baseline cache contains an unsupported artifact: {path}")
37
+ if path == root / "manifest.json":
38
+ continue
39
+ files[path.relative_to(root).as_posix()] = {
40
+ "mode": path.stat().st_mode & 0o777,
41
+ "sha256": hashlib.sha256(path.read_bytes()).hexdigest() if path.is_file() else None,
42
+ }
43
+ return files
44
+
45
+
46
+ def _copy_attempt(source: Path, target: Path) -> None:
47
+ def ignored(directory: str, names: list[str]) -> list[str]:
48
+ return [
49
+ name
50
+ for name in names
51
+ if name == "unsupported-submission"
52
+ and Path(directory).parent == source
53
+ and (Path(directory) / "unsupported-submission.json").is_file()
54
+ ]
55
+
56
+ shutil.copytree(source, target, ignore=ignored, symlinks=True)
57
+ for stage in source.iterdir():
58
+ raw = stage / "unsupported-submission"
59
+ if raw.is_dir():
60
+ # Keep unsupported link evidence inert; never extract or follow its targets.
61
+ with tarfile.open(target / stage.name / "unsupported-submission.tar", "w") as archive:
62
+ archive.add(raw, arcname="unsupported-submission", recursive=True)
63
+
64
+
65
+ def _docker_executable(value: object) -> dict[str, object] | None:
66
+ if not isinstance(value, dict):
67
+ return None
68
+ if not all(
69
+ isinstance(value.get(key), str) and Path(value[key]).is_absolute()
70
+ for key in ("path", "resolved_path")
71
+ ):
72
+ return None
73
+ digest, version = value.get("sha256"), value.get("version")
74
+ if (
75
+ not isinstance(digest, str)
76
+ or not re.fullmatch(r"[a-f0-9]{64}", digest)
77
+ or not isinstance(version, str)
78
+ or not version
79
+ ):
80
+ return None
81
+ return {key: field for key, field in value.items() if key != "path"}
82
+
83
+
84
+ def _matches_docker_client(artifacts: Path, expected: dict[str, object]) -> bool:
85
+ try:
86
+ actual = json.loads((artifacts / "docker-client.json").read_text())
87
+ except (OSError, ValueError):
88
+ return False
89
+ if not isinstance(actual, dict):
90
+ return False
91
+ cli = _docker_executable(expected.get("docker_cli"))
92
+ if cli is None or _docker_executable(actual.get("docker_cli")) != cli:
93
+ return False
94
+ plugins, observed = expected.get("docker_plugins"), actual.get("docker_plugins")
95
+ if (
96
+ not isinstance(plugins, dict)
97
+ or not isinstance(observed, dict)
98
+ or set(plugins) != {"compose", "buildx"}
99
+ or set(observed) != set(plugins)
100
+ ):
101
+ return False
102
+ for name, identity in plugins.items():
103
+ binary = _docker_executable(identity)
104
+ if binary is None or _docker_executable(observed[name]) != binary:
105
+ return False
106
+ return True
107
+
108
+
109
+ def _eligible(*, attempt: Path, record: dict[str, Any], coding: object, task: TaskSpec) -> bool:
110
+ """Admission binds actual native source, private runtime and cleanup receipts."""
111
+ if not isinstance(coding, dict):
112
+ return False
113
+ expected = coding.get("isolation")
114
+ identity = coding.get("native_task_identity")
115
+ settings = coding.get("study")
116
+ if (
117
+ not isinstance(expected, dict)
118
+ or not isinstance(identity, dict)
119
+ or not isinstance(settings, dict)
120
+ ):
121
+ return False
122
+ image = identity.get("image_identity")
123
+ toolchain = identity.get("toolchain_identity")
124
+ if (
125
+ not isinstance(image, dict)
126
+ or not isinstance(toolchain, dict)
127
+ or toolchain.get("image_id") != image.get("image_id")
128
+ or toolchain.get("harness") != settings.get("harness")
129
+ or toolchain.get("harness_version") != coding.get("harness_version")
130
+ or not isinstance(identity.get("isolation"), dict)
131
+ or harbor_backend.runtime_contract(identity["isolation"])
132
+ != harbor_backend.runtime_contract(expected)
133
+ ):
134
+ return False
135
+ runtime = record.get("runtime") or {}
136
+ for actual, requested in (
137
+ (record.get("model"), settings.get("model")),
138
+ (runtime.get("harness"), str(settings.get("harness")) + "-cli"),
139
+ (runtime.get("reasoning_effort"), settings.get("reasoning")),
140
+ (runtime.get("harness_version"), coding.get("harness_version")),
141
+ ):
142
+ if actual is not None and actual != requested:
143
+ return False
144
+ try:
145
+ return all(
146
+ _matches_native_stage(
147
+ target=attempt / stage["id"], stage=stage, coding=coding, task=task
148
+ )
149
+ for stage in record["stages"]
150
+ )
151
+ except (OSError, ValueError, KeyError, TypeError):
152
+ return False
153
+
154
+
155
+ def _matches_treatment_delivery(
156
+ *, artifacts: Path, invocation: dict[str, Any], execution: dict[str, Any],
157
+ coding: dict[str, Any],
158
+ ) -> bool:
159
+ """Bind a native vanilla cache entry to its installed policy and empty treatment."""
160
+ if "delivery" not in coding:
161
+ return True # Historical cache identities cannot equal a native identity.
162
+ if coding["delivery"] != DELIVERY_POLICY:
163
+ return False
164
+ inventory: dict[str, list[object]] = {"guidance": [], "skills": [], "plugins": []}
165
+ if (invocation.get("delivery") != DELIVERY_POLICY
166
+ or invocation.get("capabilities") != coding.get("capabilities")
167
+ or invocation.get("treatment_inventory") != inventory
168
+ or (artifacts / "prompt.txt").read_bytes() != b""):
169
+ return False
170
+ receipt = json.loads((artifacts / "native-treatment.json").read_text())
171
+ if (not isinstance(receipt, dict) or receipt.get("status") != "verified"
172
+ or receipt.get("delivery") != DELIVERY_POLICY
173
+ or receipt.get("inventory") != inventory
174
+ or execution.get("native_treatment") != receipt):
175
+ return False
176
+ configuration = receipt.get("effective_config")
177
+ if not isinstance(configuration, str):
178
+ return False
179
+ actual = tomllib.loads(configuration)
180
+ policy = NativeCapabilities.model_validate(coding.get("capabilities"))
181
+ expected = policy.codex_config(
182
+ model=coding["study"]["model"], effort=coding["study"]["reasoning"],
183
+ )
184
+ for key, value in expected.items():
185
+ if isinstance(value, dict):
186
+ observed = actual.get(key)
187
+ if not isinstance(observed, dict) or any(
188
+ observed.get(setting) != required for setting, required in value.items()
189
+ ):
190
+ return False
191
+ elif actual.get(key) != value:
192
+ return False
193
+ return True
194
+
195
+
196
+ def _matches_native_stage(
197
+ *, target: Path, stage: dict[str, Any], coding: dict[str, Any], task: TaskSpec
198
+ ) -> bool:
199
+ identity, expected, settings = (
200
+ coding["native_task_identity"],
201
+ coding["isolation"],
202
+ coding["study"],
203
+ )
204
+ artifacts = target / "artifacts"
205
+ invocation = json.loads((artifacts / "invocation.json").read_text())
206
+ # Attempts record the runtime they ran with; entries from before that ran with the cohort's.
207
+ ran_with = invocation.get("runtime", expected)
208
+ if harbor_backend.runtime_contract(ran_with) != harbor_backend.runtime_contract(expected):
209
+ return False
210
+ if not _matches_docker_client(artifacts, ran_with):
211
+ return False
212
+ observed_identity = invocation.get("native_task_identity")
213
+ if (
214
+ not isinstance(observed_identity, dict)
215
+ or portable_native_identity(observed_identity) != identity
216
+ ):
217
+ return False
218
+ if any(
219
+ invocation.get(key) != value
220
+ for key, value in {
221
+ "backend": harbor_backend.PROTOCOL,
222
+ "harness": str(settings.get("harness")) + "-cli",
223
+ "harness_version": coding.get("harness_version"),
224
+ "requested_model": settings.get("model"),
225
+ "requested_effort": settings.get("reasoning"),
226
+ "timeout_seconds": settings.get("timeout_seconds", coding.get("timeout_seconds")),
227
+ "task_assets_sha256": identity.get("task_assets_sha256"),
228
+ }.items()
229
+ ):
230
+ return False
231
+ execution = json.loads((target / "execution.json").read_text())
232
+ if not _matches_treatment_delivery(
233
+ artifacts=artifacts, invocation=invocation, execution=execution, coding=coding,
234
+ ):
235
+ return False
236
+ if execution.get("private_runtime_removed") is not True:
237
+ return False
238
+ for key, requested in (
239
+ ("actual_primary_models", settings.get("model")),
240
+ ("runtime_effort_evidence", settings.get("reasoning")),
241
+ ):
242
+ values = execution.get(key, [])
243
+ if not isinstance(values, list) or any(value != requested for value in values):
244
+ return False
245
+ receipt = json.loads((artifacts / "runtime-isolation.json").read_text())
246
+ before = json.loads((artifacts / "before-source.json").read_text())
247
+ exported = submissions.validate_export(
248
+ diagnostic=target / "diagnostic-source", artifacts=artifacts, execution=execution
249
+ )
250
+ if (
251
+ receipt.get("export") != exported
252
+ or not _native_export_matches(before, identity, identity.get("source_digest"))
253
+ or not _native_export_matches(exported, identity, exported["snapshot_digest"])
254
+ ):
255
+ return False
256
+ if before.get("container_id") != exported.get("container_id") or before.get(
257
+ "project"
258
+ ) != exported.get("project"):
259
+ return False
260
+ if execution.get("native_verifier_result") is not None and not _native_grader_matches(
261
+ receipt.get("verifier"), identity
262
+ ):
263
+ return False
264
+ if stage.get("snapshot"):
265
+ source = submissions.validate(
266
+ before=task.fixture,
267
+ task_dir=task.native_task,
268
+ identity=native_identity(task),
269
+ artifacts=artifacts,
270
+ execution=execution,
271
+ snapshot=target / "snapshot",
272
+ )
273
+ if source["snapshot_digest"] != stage["snapshot"]["sha256"]:
274
+ return False
275
+ else:
276
+ failure = json.loads((target / "unsupported-submission.json").read_text())
277
+ if failure.get("source_policy") != submissions.SOURCE_POLICY:
278
+ return False
279
+ with tempfile.TemporaryDirectory(prefix="pluginbench-cache-source-") as temporary:
280
+ try:
281
+ submissions.reconstruct(
282
+ before=task.fixture,
283
+ task_dir=task.native_task,
284
+ identity=native_identity(task),
285
+ artifacts=artifacts,
286
+ execution=execution,
287
+ destination=Path(temporary).resolve() / "source",
288
+ )
289
+ except ValueError:
290
+ pass
291
+ else:
292
+ return False
293
+ return _native_cleanup_matches(receipt.get("cleanup"))
294
+
295
+
296
+ def _native_grader_matches(verifier: object, identity: dict[str, Any]) -> bool:
297
+ if not isinstance(verifier, dict):
298
+ return False
299
+ if verifier.get("image_id") != identity["grader_image_identity"].get("image_id"):
300
+ return False
301
+ harbor_backend.stopped_container_state(verifier.get("state"))
302
+ limits = identity["isolation"]["resources"]
303
+ resources = verifier.get("resources")
304
+ if not isinstance(resources, dict):
305
+ return False
306
+ return bool(
307
+ resources.get("NanoCpus") == limits["cpus"] * 1_000_000_000
308
+ and resources.get("Memory") == limits["memory_mb"] * 1024 * 1024
309
+ )
310
+
311
+
312
+ def _native_cleanup_matches(cleanup: object) -> bool:
313
+ if not isinstance(cleanup, dict) or cleanup.get("status") != "complete":
314
+ return False
315
+ projects = cleanup.get("projects")
316
+ if not isinstance(projects, dict) or not projects:
317
+ return False
318
+ return all(
319
+ isinstance(project, dict)
320
+ and project.get("status") == "complete"
321
+ and project.get("residuals") == {"containers": [], "networks": [], "images": []}
322
+ for project in projects.values()
323
+ )
324
+
325
+
326
+ def _native_export_matches(value: object, identity: dict[str, Any], digest: object) -> bool:
327
+ if not isinstance(value, dict) or not isinstance(identity.get("image_identity"), dict):
328
+ return False
329
+ image = identity["image_identity"]
330
+ if (
331
+ value.get("status") != "complete"
332
+ or value.get("stopped") is not True
333
+ or value.get("snapshot_digest") != digest
334
+ or not isinstance(digest, str)
335
+ or value.get("image_id") != image.get("image_id")
336
+ or value.get("workdir") != identity.get("workdir")
337
+ or value.get("resources")
338
+ != {key: identity["isolation"]["resources"][key] for key in ("cpus", "memory_mb")}
339
+ or not isinstance(value.get("container_id"), str)
340
+ or not re.fullmatch(r"[a-f0-9]{64}", value["container_id"])
341
+ or not isinstance(value.get("project"), str)
342
+ or not re.fullmatch(r"pluginbench-[a-f0-9]{32}__env", value["project"])
343
+ ):
344
+ return False
345
+ harbor_backend.stopped_container_state(value.get("state"))
346
+ return True
347
+
348
+
349
+ def cohort_contexts(
350
+ *, tasks: tuple[TaskSpec, ...], entries: list[dict[str, Any]], identity: dict[str, object]
351
+ ) -> dict[str, dict[str, Any]]:
352
+ """Each task's vanilla cohort context; its hash is the cache key. Holds no run paths."""
353
+ contexts = {}
354
+ for task in tasks:
355
+ coding = {
356
+ key: value.get(task.task_id)
357
+ if key in {"task_assets", "task_environment"} and isinstance(value, dict)
358
+ else value
359
+ for key, value in identity.items()
360
+ }
361
+ coding["native_task_identity"] = portable_native_identity(native_identity(task))
362
+ contexts[task.task_id] = {
363
+ # v4 caches each repeat in its own entry under the cohort key.
364
+ "protocol": "native-harbor-baseline-cohort-v4",
365
+ "source_policy": submissions.SOURCE_POLICY,
366
+ "coding": coding,
367
+ "task_id": task.task_id,
368
+ "task_digest": task.digest,
369
+ "prompt": task.prompt,
370
+ "fixture_digest": inspect_snapshot(task.fixture)["digest"],
371
+ "repeats": sorted(
372
+ entry["attempt"]
373
+ for entry in entries
374
+ if entry["arm"] == "baseline" and entry["task_id"] == task.task_id
375
+ ),
376
+ }
377
+ return contexts
378
+
379
+
380
+ def _cohort_contract(context: dict[str, Any]) -> dict[str, Any]:
381
+ """What a cached cohort must share with this run: its context with runtimes as contracts.
382
+
383
+ Runtime recorded and fit fields and launcher source hashes are left out, and saved
384
+ hashes become their launcher version. The task digest covers the full native identity,
385
+ runtime included, so it is left out too; `_same_cohort` checks it separately.
386
+ """
387
+ contract = copy.deepcopy({key: value for key, value in context.items() if key != "task_digest"})
388
+ coding = contract["coding"]
389
+ coding["isolation"] = harbor_backend.runtime_contract(coding["isolation"])
390
+ for key in ("native_task_identity", "task_environment"):
391
+ if isinstance(coding.get(key), dict) and "isolation" in coding[key]:
392
+ coding[key]["isolation"] = harbor_backend.runtime_contract(coding[key]["isolation"])
393
+ hashes = coding.pop("launcher_sha256", None)
394
+ if hashes is not None and "launcher_version" not in coding:
395
+ study = coding.get("study")
396
+ coding["launcher_version"] = components.saved_launcher_version(
397
+ study.get("harness") if isinstance(study, dict) else None, hashes
398
+ )
399
+ return contract
400
+
401
+
402
+ def _cohort_key(context: dict[str, Any], task: TaskSpec) -> str:
403
+ contract = _cohort_contract(context)
404
+ contract["task_digest"] = task_digest(task, contract["coding"]["native_task_identity"])
405
+ return _hash(contract)
406
+
407
+
408
+ def _same_cohort(saved: object, contract: dict[str, Any], task: TaskSpec) -> bool:
409
+ """A saved context matches when its contract is equal and its own task digest still holds.
410
+
411
+ Recomputing the digest from the saved native identity binds prompt, source, file modes and
412
+ provenance exactly. Anything unreadable, or saved by a build this one cannot vouch for,
413
+ does not match.
414
+ """
415
+ if not isinstance(saved, dict):
416
+ return False
417
+ try:
418
+ return bool(
419
+ _cohort_contract(saved) == contract
420
+ and task_digest(task, saved["coding"]["native_task_identity"]) == saved["task_digest"]
421
+ )
422
+ except (KeyError, TypeError, ValueError):
423
+ return False
424
+
425
+
426
+ def _saved_match(entry: Path, contract: dict[str, Any], task: TaskSpec) -> bool:
427
+ """A saved `<key>/<repeat>` entry whose manifest is readable and whose context matches."""
428
+ try:
429
+ context = json.loads((entry / "manifest.json").read_text())["context"]
430
+ except (OSError, ValueError, KeyError, TypeError):
431
+ return False
432
+ return _same_cohort(context, contract, task)
433
+
434
+
435
+ def _saved_cohort(cohort: Path, contract: dict[str, Any], task: TaskSpec) -> bool:
436
+ """A saved cohort directory with any matching repeat; one that cannot be listed has none."""
437
+ try:
438
+ entries = sorted(cohort.iterdir())
439
+ except OSError:
440
+ return False
441
+ return any(_saved_match(entry, contract, task) for entry in entries)
442
+
443
+
444
+ def _cohorts(path: Path, key: str, contract: dict[str, Any], task: TaskSpec) -> list[Path]:
445
+ """Directories that may hold this task's repeats: its own key, then saved cohorts.
446
+
447
+ A saved cohort, such as one an older PluginBench keyed by its full runtime, qualifies
448
+ when any of its repeats matches; its name then is that older key.
449
+ """
450
+ saved = [
451
+ cohort
452
+ for cohort in (sorted(path.iterdir()) if path.is_dir() else [])
453
+ if cohort.name != key
454
+ and not cohort.name.startswith(".")
455
+ and not cohort.is_symlink()
456
+ and cohort.is_dir()
457
+ and _saved_cohort(cohort, contract, task)
458
+ ]
459
+ return [path / key, *saved]
460
+
461
+
462
+ def _resolve(
463
+ cohorts: list[Path], repeat: int, contract: dict[str, Any], task: TaskSpec
464
+ ) -> Path | None:
465
+ """Where a repeat is found: the run's own key, else the first saved cohort that matches."""
466
+ own, *saved = (cohort / str(repeat) for cohort in cohorts)
467
+ if own.exists():
468
+ return own
469
+ return next((entry for entry in saved if _saved_match(entry, contract, task)), None)
470
+
471
+
472
+ def preview(
473
+ *, path: Path, tasks: tuple[TaskSpec, ...], repeats: int, identity: dict[str, object]
474
+ ) -> list[dict[str, object]]:
475
+ """Hit or miss for every vanilla attempt from saved entries alone; `reserve` validates."""
476
+ entries: list[dict[str, Any]] = [
477
+ {"task_id": task.task_id, "arm": "baseline", "attempt": repeat}
478
+ for task in tasks
479
+ for repeat in range(1, repeats + 1)
480
+ ]
481
+ contexts = cohort_contexts(tasks=tasks, entries=entries, identity=identity)
482
+ contracts = {task.task_id: _cohort_contract(contexts[task.task_id]) for task in tasks}
483
+ cohorts = {
484
+ task.task_id: _cohorts(
485
+ path, _cohort_key(contexts[task.task_id], task), contracts[task.task_id], task
486
+ )
487
+ for task in tasks
488
+ }
489
+ by_id = {task.task_id: task for task in tasks}
490
+ rows = []
491
+ for entry in entries:
492
+ task_id = entry["task_id"]
493
+ found = _resolve(cohorts[task_id], entry["attempt"], contracts[task_id], by_id[task_id])
494
+ rows.append(
495
+ {
496
+ "task_id": entry["task_id"],
497
+ "repeat": entry["attempt"],
498
+ "status": "hit" if found else "miss",
499
+ "cache_key": (found.parent if found else cohorts[entry["task_id"]][0]).name,
500
+ }
501
+ )
502
+ return rows
503
+
504
+
505
+ class BaselineCache:
506
+ """Reserve every cohort key before calls; publish each finished vanilla attempt as it ends.
507
+
508
+ A cohort key names one task's vanilla context. Each repeat is its own entry,
509
+ `<key>/<repeat>`, so a run that stops midway still caches the repeats it finished.
510
+ """
511
+
512
+ def __init__(
513
+ self,
514
+ *,
515
+ path: Path,
516
+ root: Path,
517
+ tasks: tuple[TaskSpec, ...],
518
+ entries: list[dict[str, Any]],
519
+ identity: dict[str, object],
520
+ ) -> None:
521
+ self.path = path.absolute()
522
+ self.root = root
523
+ self.tasks = {task.task_id: task for task in tasks}
524
+ self.entries = [entry for entry in entries if entry["arm"] == "baseline"]
525
+ self.contexts = cohort_contexts(tasks=tasks, entries=self.entries, identity=identity)
526
+ self.contracts = {
527
+ task_id: _cohort_contract(context) for task_id, context in self.contexts.items()
528
+ }
529
+ self.keys = {
530
+ task_id: _cohort_key(context, self.tasks[task_id])
531
+ for task_id, context in self.contexts.items()
532
+ }
533
+ self.hits: dict[tuple[str, int], dict[str, Any]] = {}
534
+ # Where each hit lives: this run's own key, or a saved cohort under an older key.
535
+ self.origins: dict[tuple[str, int], Path] = {}
536
+ self._resources = ExitStack()
537
+
538
+ def reserve(self) -> BaselineCache:
539
+ if any(path.is_symlink() for path in (self.path, *self.path.parents)):
540
+ raise ValueError("baseline cache must not follow symlinks")
541
+ (self.path / ".locks").mkdir(parents=True, exist_ok=True)
542
+ if (self.path / ".locks").is_symlink():
543
+ raise ValueError("baseline cache locks must not follow symlinks")
544
+ try:
545
+ for task_id, key in sorted(self.keys.items(), key=lambda item: item[1]):
546
+ self._reserve(task_id, key)
547
+ except BaseException:
548
+ self.close()
549
+ raise
550
+ return self
551
+
552
+ def _reserve(self, task_id: str, key: str) -> None:
553
+ """Lock this task's cohorts and validate each repeat where it is first found.
554
+
555
+ Repeats resolve one by one, own key first, so a cohort that a 942f12c run started and
556
+ this build finished is read from both places.
557
+ """
558
+ cohorts = _cohorts(self.path, key, self.contracts[task_id], self.tasks[task_id])
559
+ if any(path.is_symlink() for path in cohorts):
560
+ raise ValueError("baseline cache cohort must not follow symlinks")
561
+ repeats = self.contexts[task_id]["repeats"]
562
+ task, contract = self.tasks[task_id], self.contracts[task_id]
563
+ found = {repeat: _resolve(cohorts, repeat, contract, task) for repeat in repeats}
564
+ if any(path is not None and path.is_symlink() for path in found.values()):
565
+ raise ValueError("baseline cache entry must not follow symlinks")
566
+ # Exclusive on this run's key while it may still publish a missing repeat; shared on
567
+ # saved cohorts, which this build only reads.
568
+ complete = all(found.values())
569
+ own = self._lock(key, fcntl.LOCK_SH if complete else fcntl.LOCK_EX)
570
+ readable = [cohorts[0]]
571
+ for cohort in cohorts[1:]:
572
+ try:
573
+ self._lock(cohort.name, fcntl.LOCK_SH)
574
+ except OSError:
575
+ continue # A saved cohort whose lock cannot be opened is not read.
576
+ readable.append(cohort)
577
+ for repeat in repeats:
578
+ for cohort in readable:
579
+ entry = cohort / str(repeat)
580
+ if not entry.exists():
581
+ continue
582
+ if cohort == cohorts[0]:
583
+ manifest = self._validate(task_id, entry)
584
+ else:
585
+ try:
586
+ manifest = self._validate(task_id, entry)
587
+ except (OSError, ValueError, KeyError, TypeError, AttributeError):
588
+ # A saved entry that does not validate, malformed evidence included,
589
+ # is not this repeat.
590
+ continue
591
+ self.hits[(task_id, repeat)] = manifest
592
+ self.origins[(task_id, repeat)] = entry
593
+ break
594
+ if complete and not all((task_id, repeat) in self.hits for repeat in repeats):
595
+ self._flock(own, key, fcntl.LOCK_EX)
596
+
597
+ def _lock(self, key: str, mode: int) -> object:
598
+ lock_path = self.path / ".locks" / f"{key}.lock"
599
+ if lock_path.is_symlink():
600
+ raise ValueError("baseline cache lock must not follow symlinks")
601
+ lock = self._resources.enter_context(lock_path.open("a"))
602
+ self._flock(lock, key, mode)
603
+ return lock
604
+
605
+ @staticmethod
606
+ def _flock(lock: Any, key: str, mode: int) -> None:
607
+ try:
608
+ fcntl.flock(lock, mode | fcntl.LOCK_NB)
609
+ except BlockingIOError as exc:
610
+ raise ValueError(
611
+ f"baseline cache cohort is in use: {key}; wait for its owning run to finish"
612
+ ) from exc
613
+
614
+ def close(self) -> None:
615
+ self._resources.close()
616
+
617
+ def _validate(self, task_id: str, cached: Path) -> dict[str, Any]:
618
+ """Check one `<key>/<repeat>` entry: a single complete, eligible vanilla attempt."""
619
+ if cached.is_symlink() or not cached.is_dir():
620
+ raise ValueError("baseline cache entry must be a directory")
621
+ manifest: dict[str, Any] = json.loads((cached / "manifest.json").read_text())
622
+ if (
623
+ not _same_cohort(manifest["context"], self.contracts[task_id], self.tasks[task_id])
624
+ or manifest["inventory"] != _inventory(cached)
625
+ or manifest["origin_status"] != "complete"
626
+ ):
627
+ raise ValueError(f"baseline cache cohort changed: {cached.name}")
628
+ records = manifest["records"]
629
+ for record in records:
630
+ AttemptInput.model_validate(record)
631
+ for identifier in [record["id"], *(stage["id"] for stage in record["stages"])]:
632
+ if Path(identifier).name != identifier or identifier in {".", ".."}:
633
+ raise ValueError(
634
+ "baseline cache artifact identity must be a single path component"
635
+ )
636
+ if [record["attempt"] for record in records] != [int(cached.name)] or any(
637
+ record["task_id"] != task_id
638
+ or record["arm"] != "baseline"
639
+ # The evidence is checked against the identity it was cached under.
640
+ or not _eligible(
641
+ attempt=cached / "attempts" / record["id"],
642
+ record=record,
643
+ coding=manifest["context"]["coding"],
644
+ task=self.tasks[task_id],
645
+ )
646
+ for record in records
647
+ ):
648
+ raise ValueError(
649
+ "baseline cache cohort lacks complete isolated native baseline evidence"
650
+ )
651
+ return manifest
652
+
653
+ def decisions(self) -> dict[str, dict[str, Any]]:
654
+ decisions = {}
655
+ for entry in self.entries:
656
+ task_id = entry["task_id"]
657
+ hit = self.hits.get((task_id, entry["attempt"]))
658
+ origin = self.origins.get((task_id, entry["attempt"]))
659
+ key = origin.parent.name if origin else self.keys[task_id]
660
+ # Relative to the cache root, so moving the cache or this run keeps it resolvable.
661
+ # Historical decisions held an absolute `origin_run` path instead.
662
+ decisions[entry["id"]] = {
663
+ "status": "hit" if hit else "miss",
664
+ "cache_key": key,
665
+ "origin_cohort": f"{self.path.name}/{key}/{entry['attempt']}" if hit else None,
666
+ "origin_attempt": hit["records"][0]["id"] if hit else None,
667
+ }
668
+ return decisions
669
+
670
+ def import_attempt(self, entry: dict[str, Any]) -> dict[str, Any] | None:
671
+ if entry["arm"] != "baseline":
672
+ return None
673
+ manifest = self.hits.get((entry["task_id"], entry["attempt"]))
674
+ if manifest is None:
675
+ return None
676
+ record: dict[str, Any] = copy.deepcopy(manifest["records"][0])
677
+ old_id = record["id"]
678
+ cached = self.origins[(entry["task_id"], entry["attempt"])]
679
+ attempt = self.root / "attempts" / entry["id"]
680
+ attempt.parent.mkdir(parents=True, exist_ok=True)
681
+ shutil.copytree(cached / "attempts" / old_id, attempt, symlinks=True)
682
+ prefix = f"attempts/{old_id}/"
683
+ if _inventory(attempt) != {
684
+ name.removeprefix(prefix): metadata
685
+ for name, metadata in manifest["inventory"].items()
686
+ if name.startswith(prefix)
687
+ }:
688
+ raise ValueError("baseline cache artifacts changed while importing")
689
+ origin = self.root / "baseline-evidence" / entry["id"]
690
+ origin.mkdir(parents=True)
691
+ shutil.copyfile(cached / "native-starts" / f"{old_id}.json", origin / "native-starts.json")
692
+ # Retain observer evidence, but only the current verifier/collectors may grade this run.
693
+ for path in [
694
+ attempt / "before-evidence",
695
+ *[
696
+ attempt / stage["id"] / name
697
+ for stage in record["stages"]
698
+ for name in ("correctness.json", "verifier-result.json", "evidence")
699
+ ],
700
+ ]:
701
+ if path.exists():
702
+ target = origin / "observers" / path.relative_to(attempt)
703
+ target.parent.mkdir(parents=True, exist_ok=True)
704
+ path.rename(target)
705
+ native_artifacts = [
706
+ path
707
+ for stage in record["stages"]
708
+ for path in (attempt / stage["id"] / "artifacts").rglob("*")
709
+ if path.is_file()
710
+ ]
711
+ raw_archives = list(attempt.glob("*/unsupported-submission.tar"))
712
+ temporary = origin / "origin.tmp"
713
+ historical = [path for path in origin.rglob("*") if path.is_file()]
714
+ temporary.write_text(
715
+ json.dumps(
716
+ {
717
+ **self.decisions()[entry["id"]],
718
+ "context": manifest["context"],
719
+ "original_record": record,
720
+ "original_task_digest": manifest["task_digest"],
721
+ "historical_evidence": [
722
+ {
723
+ "path": path.relative_to(self.root).as_posix(),
724
+ "sha256": "sha256:" + hashlib.sha256(path.read_bytes()).hexdigest(),
725
+ }
726
+ for path in sorted([*historical, *native_artifacts, *raw_archives])
727
+ ],
728
+ },
729
+ indent=2,
730
+ sort_keys=True,
731
+ )
732
+ + "\n"
733
+ )
734
+ temporary.replace(origin / "origin.json")
735
+ record.update(id=entry["id"], before=None, before_evidence=None)
736
+ for stage in record["stages"]:
737
+ if stage["snapshot"]:
738
+ stage["snapshot"]["path"] = f"attempts/{entry['id']}/{stage['id']}/snapshot"
739
+ stage.update(correctness=None, evidence=None)
740
+ return record
741
+
742
+ def publish(self, record: dict[str, Any]) -> None:
743
+ """Cache one finished vanilla attempt now, unless it is a hit or not eligible."""
744
+ task_id = record["task_id"]
745
+ if record["arm"] != "baseline" or (task_id, record["attempt"]) in self.hits:
746
+ return
747
+ key = self.keys[task_id]
748
+ attempt = self.root / "attempts" / record["id"]
749
+ if not _eligible(
750
+ attempt=attempt,
751
+ record=record,
752
+ coding=self.contexts[task_id]["coding"],
753
+ task=self.tasks[task_id],
754
+ ):
755
+ return
756
+ # A session that never reached the model is no baseline. Runs from before the coding
757
+ # pass stopped on such a session recorded it as complete.
758
+ if any(
759
+ failed_before_first_turn(
760
+ json.loads((attempt / stage["id"] / "execution.json").read_text())
761
+ )
762
+ for stage in record["stages"]
763
+ ):
764
+ return
765
+ cohort = self.path / key
766
+ cohort.mkdir(exist_ok=True)
767
+ with tempfile.TemporaryDirectory(prefix=f".{record['attempt']}-", dir=cohort) as temporary:
768
+ staged = Path(temporary) / str(record["attempt"])
769
+ (staged / "attempts").mkdir(parents=True)
770
+ (staged / "native-starts").mkdir()
771
+ _copy_attempt(self.root / "attempts" / record["id"], staged / "attempts" / record["id"])
772
+ receipts = {
773
+ stage["id"]: json.loads(
774
+ (self.root / "invocations" / record["id"] / f"{stage['id']}.json").read_text()
775
+ )
776
+ for stage in record["stages"]
777
+ }
778
+ (staged / "native-starts" / f"{record['id']}.json").write_text(
779
+ json.dumps(receipts, indent=2, sort_keys=True) + "\n"
780
+ )
781
+ manifest = {
782
+ "context": self.contexts[task_id],
783
+ "records": [record],
784
+ "origin_status": "complete",
785
+ "task_digest": self.tasks[task_id].digest,
786
+ "inventory": _inventory(staged),
787
+ }
788
+ (staged / "manifest.json").write_text(
789
+ json.dumps(manifest, indent=2, sort_keys=True) + "\n"
790
+ )
791
+ self._validate(task_id, staged)
792
+ staged.rename(cohort / staged.name)