equiframe-benchmark 0.12.4__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 (62) hide show
  1. equiframe_benchmark/PROTOCOL_V2.md +172 -0
  2. equiframe_benchmark/__init__.py +284 -0
  3. equiframe_benchmark/__main__.py +3 -0
  4. equiframe_benchmark/_scale_workload.py +375 -0
  5. equiframe_benchmark/calibration_types.py +112 -0
  6. equiframe_benchmark/cli.py +196 -0
  7. equiframe_benchmark/commands.py +149 -0
  8. equiframe_benchmark/commands_scale.py +38 -0
  9. equiframe_benchmark/commands_step10.py +275 -0
  10. equiframe_benchmark/commands_v2.py +206 -0
  11. equiframe_benchmark/cost_table.py +62 -0
  12. equiframe_benchmark/costs.py +788 -0
  13. equiframe_benchmark/distribution.py +932 -0
  14. equiframe_benchmark/evaluation.py +614 -0
  15. equiframe_benchmark/evaluation_v2.py +428 -0
  16. equiframe_benchmark/fixtures.py +299 -0
  17. equiframe_benchmark/freeze.py +869 -0
  18. equiframe_benchmark/freeze_v2.py +412 -0
  19. equiframe_benchmark/integration.py +655 -0
  20. equiframe_benchmark/io.py +29 -0
  21. equiframe_benchmark/models.py +313 -0
  22. equiframe_benchmark/models_v2.py +13 -0
  23. equiframe_benchmark/mutations.py +34 -0
  24. equiframe_benchmark/oracle.py +264 -0
  25. equiframe_benchmark/pairwise.py +248 -0
  26. equiframe_benchmark/pilot.py +254 -0
  27. equiframe_benchmark/py.typed +0 -0
  28. equiframe_benchmark/reconciliation.py +294 -0
  29. equiframe_benchmark/reproduction.py +542 -0
  30. equiframe_benchmark/scale_io.py +19 -0
  31. equiframe_benchmark/scale_process.py +1077 -0
  32. equiframe_benchmark/scale_types.py +120 -0
  33. equiframe_benchmark/scaling.py +529 -0
  34. equiframe_benchmark/schemas/protocol-v2/CommonSupportV2-2.0.schema.json +327 -0
  35. equiframe_benchmark/schemas/protocol-v2/ContrastSpecV2-2.0.schema.json +37 -0
  36. equiframe_benchmark/schemas/protocol-v2/EvaluationConfigV2-2.0.schema.json +184 -0
  37. equiframe_benchmark/schemas/protocol-v2/EvaluationReportV2-2.0.schema.json +1237 -0
  38. equiframe_benchmark/schemas/protocol-v2/EvaluationStratumV2-2.0.schema.json +747 -0
  39. equiframe_benchmark/schemas/protocol-v2/FreezeDefinitionV2-2.0.schema.json +5951 -0
  40. equiframe_benchmark/schemas/protocol-v2/FreezeManifestV2-2.0.schema.json +6222 -0
  41. equiframe_benchmark/schemas/protocol-v2/PrincipalContrastV2-2.0.schema.json +578 -0
  42. equiframe_benchmark/schemas/protocol-v2/ProtocolReviewV2-2.0.schema.json +56 -0
  43. equiframe_benchmark/schemas/protocol-v2/SignedMetricV2-2.0.schema.json +51 -0
  44. equiframe_benchmark/schemas/protocol-v2/StudyCaseResultV2-2.0.schema.json +4881 -0
  45. equiframe_benchmark/schemas/protocol-v2/StudyResultV2-2.0.schema.json +8370 -0
  46. equiframe_benchmark/schemas/protocol-v2/TableExportV2-2.0.schema.json +75 -0
  47. equiframe_benchmark/selection.py +178 -0
  48. equiframe_benchmark/snapshot.py +24 -0
  49. equiframe_benchmark/splits.py +92 -0
  50. equiframe_benchmark/study.py +526 -0
  51. equiframe_benchmark/study_bundle.py +379 -0
  52. equiframe_benchmark/study_bundle_v2.py +388 -0
  53. equiframe_benchmark/study_v2.py +544 -0
  54. equiframe_benchmark/tables.py +347 -0
  55. equiframe_benchmark/tables_v2.py +340 -0
  56. equiframe_benchmark/training_types.py +52 -0
  57. equiframe_benchmark-0.12.4.dist-info/METADATA +343 -0
  58. equiframe_benchmark-0.12.4.dist-info/RECORD +62 -0
  59. equiframe_benchmark-0.12.4.dist-info/WHEEL +4 -0
  60. equiframe_benchmark-0.12.4.dist-info/entry_points.txt +3 -0
  61. equiframe_benchmark-0.12.4.dist-info/licenses/LICENSE +21 -0
  62. equiframe_benchmark-0.12.4.dist-info/licenses/THIRD_PARTY_NOTICES.md +8 -0
@@ -0,0 +1,379 @@
1
+ """Portable study bytes and deterministic report regeneration from captured decisions.
2
+
3
+ This reproduces tables from recorded executions. It does not rerun ETL, authenticate
4
+ reviewers or attest that another researcher independently repeated the experiment.
5
+ Original manifests are copied verbatim; their absolute provenance is not rewritten.
6
+ """
7
+
8
+ import hashlib
9
+ import json
10
+ from pathlib import Path, PurePosixPath, PureWindowsPath
11
+ from tempfile import TemporaryDirectory
12
+
13
+ import equiframe as ef
14
+ from equiframe.evidence import parse_json, verify_events
15
+ from equiframe.local import local_path
16
+ from pydantic import BaseModel
17
+
18
+ from equiframe_benchmark.evaluation import EvaluationConfig, EvaluationRecord, evaluate_records
19
+ from equiframe_benchmark.io import save_document
20
+ from equiframe_benchmark.models import digest
21
+ from equiframe_benchmark.reproduction import (
22
+ BundleFile,
23
+ BundleLimits,
24
+ ComparisonReceipt,
25
+ ReproductionBundle,
26
+ ReproductionRecord,
27
+ _bounded_json,
28
+ create_bundle,
29
+ make_reproduction_record,
30
+ read_bundle,
31
+ reproduce_bundle,
32
+ )
33
+ from equiframe_benchmark.tables import table_payloads
34
+
35
+
36
+ def _original_uri(path: str) -> str:
37
+ """Preserve the producer's path syntax without resolving it on another OS."""
38
+ windows = PureWindowsPath(path)
39
+ if windows.is_absolute():
40
+ return windows.as_uri()
41
+ posix = PurePosixPath(path)
42
+ if posix.is_absolute():
43
+ return posix.as_uri()
44
+ raise ValueError("producer paths must have an absolute declared syntax")
45
+
46
+
47
+ def _portable_artifact(
48
+ original: ef.ArtifactRef, directory: Path, by_source: dict[str | None, BundleFile]
49
+ ) -> ef.ArtifactRef:
50
+ copied = by_source.get(original.uri)
51
+ if copied is None or (copied.sha256, copied.size_bytes, copied.artifact_format) != (
52
+ original.sha256,
53
+ original.size_bytes,
54
+ original.format,
55
+ ):
56
+ raise ValueError("portable artifact contradicts its original content binding")
57
+ return ef.ArtifactRef.model_validate(
58
+ {
59
+ **original.model_dump(mode="python"),
60
+ "uri": (directory.absolute() / copied.path).as_uri(),
61
+ }
62
+ )
63
+
64
+
65
+ def _check_document(path: Path, expected: BaseModel, item: BundleFile) -> None:
66
+ maximum = BundleLimits().max_record_bytes
67
+ with path.open("rb") as stream:
68
+ data = stream.read(maximum + 1)
69
+ if len(data) > maximum or (hashlib.sha256(data).hexdigest(), len(data)) != (
70
+ item.sha256,
71
+ item.size_bytes,
72
+ ):
73
+ raise ValueError("portable document exceeds its budget or changed during verification")
74
+ parse_json(data)
75
+ if type(expected).model_validate_json(data) != expected:
76
+ raise ValueError("portable source document contradicts its captured study")
77
+
78
+
79
+ def _labels(records: tuple[EvaluationRecord, ...]) -> list[dict[str, object]]:
80
+ return [
81
+ {
82
+ "case_id": r.case_id,
83
+ "oracle_sha256": r.oracle_sha256,
84
+ "state": r.oracle_state,
85
+ "label": r.oracle_label,
86
+ }
87
+ for r in records
88
+ ]
89
+
90
+
91
+ def regenerate_study_record(record: ReproductionRecord) -> ReproductionRecord:
92
+ """Recalculate every metric/CSV; retain the declared configuration and split."""
93
+ configuration = json.loads(record.configuration)
94
+ config = EvaluationConfig.model_validate_json(json.dumps(configuration["evaluation"]))
95
+ records = tuple(
96
+ EvaluationRecord.model_validate_json(json.dumps(item))
97
+ for item in json.loads(record.decisions)
98
+ )
99
+ report = evaluate_records(records, config)
100
+ if (
101
+ configuration["splits_sha256"]
102
+ != hashlib.sha256(
103
+ json.dumps(
104
+ json.loads(record.splits),
105
+ sort_keys=True,
106
+ separators=(",", ":"),
107
+ ensure_ascii=True,
108
+ allow_nan=False,
109
+ ).encode()
110
+ ).hexdigest()
111
+ ):
112
+ raise ValueError("recorded splits must match the sealed configuration")
113
+ return make_reproduction_record(
114
+ configuration=configuration,
115
+ splits=json.loads(record.splits),
116
+ labels=_labels(report.records),
117
+ decisions=[r.model_dump(mode="json") for r in report.records],
118
+ tables={
119
+ "evaluation_sha256": digest(report),
120
+ "csv": {
121
+ name: data.decode("utf-8") for name, (data, _) in table_payloads(report).items()
122
+ },
123
+ },
124
+ )
125
+
126
+
127
+ def _references(value: object) -> tuple[ef.ArtifactRef, ...]:
128
+ result: list[ef.ArtifactRef] = []
129
+ if isinstance(value, dict):
130
+ if {"uri", "sha256", "size_bytes", "format", "media_type"} <= value.keys():
131
+ result.append(ef.ArtifactRef.model_validate_json(json.dumps(value)))
132
+ else:
133
+ for child in value.values():
134
+ result.extend(_references(child))
135
+ elif isinstance(value, list):
136
+ for child in value:
137
+ result.extend(_references(child))
138
+ return tuple(result)
139
+
140
+
141
+ def bundle_study(directory: Path, *, output: Path) -> ReproductionBundle:
142
+ """Copy closed study evidence, original captures and all frozen source files."""
143
+ from equiframe_benchmark.study import read_study
144
+
145
+ study = read_study(directory)
146
+ directory = directory.absolute()
147
+ output = output.absolute()
148
+ if output.is_relative_to(directory):
149
+ raise ValueError("portable bundle must be outside its source study")
150
+ files = {
151
+ "study/" + path.relative_to(directory).as_posix(): path
152
+ for path in sorted(directory.rglob("*"))
153
+ if path.is_file()
154
+ }
155
+ by_uri = {path.absolute().as_uri(): name for name, path in files.items()}
156
+ artifacts: dict[str, ef.ArtifactRef] = {}
157
+ expected_files = {"study/" + i.path: (i.sha256, i.size_bytes) for i in study.evidence}
158
+ for ref in _references(study.model_dump(mode="json")):
159
+ if ref.sha256 is None or ref.size_bytes is None:
160
+ raise ValueError("portable source artifacts require hash and size")
161
+ if ref.uri not in by_uri:
162
+ name = "sources/" + hashlib.sha256(ref.uri.encode()).hexdigest() + "." + ref.format
163
+ files[name] = local_path(ref)
164
+ by_uri[ref.uri] = name
165
+ name = by_uri[ref.uri]
166
+ if name in artifacts and artifacts[name] != ref:
167
+ raise ValueError("one original URI must identify exactly one artifact")
168
+ artifacts[name] = ref
169
+ for source in study.freeze.definition.files:
170
+ path = Path(source.path)
171
+ if path.absolute().as_uri() not in by_uri:
172
+ name = (
173
+ "frozen/" + hashlib.sha256(path.absolute().as_uri().encode()).hexdigest() + ".bin"
174
+ )
175
+ files[name] = path
176
+ by_uri[path.absolute().as_uri()] = name
177
+ name = by_uri[path.absolute().as_uri()]
178
+ identity = (source.sha256, source.size_bytes)
179
+ if name in expected_files and expected_files[name] != identity:
180
+ raise ValueError("frozen input contradicts the closed study evidence")
181
+ expected_files[name] = identity
182
+ report = study.evaluation
183
+ record = make_reproduction_record(
184
+ configuration={
185
+ "scope": "captured_decisions_and_full_csv_regeneration",
186
+ "freeze": study.freeze.model_dump(mode="json"),
187
+ "evaluation": report.config.model_dump(mode="json"),
188
+ "splits_sha256": digest(study.freeze.definition.splits),
189
+ },
190
+ splits=study.freeze.definition.splits.model_dump(mode="json"),
191
+ labels=_labels(report.records),
192
+ decisions=[r.model_dump(mode="json") for r in report.records],
193
+ tables={
194
+ "evaluation_sha256": digest(report),
195
+ "csv": {
196
+ name: data.decode("utf-8") for name, (data, _) in table_payloads(report).items()
197
+ },
198
+ },
199
+ )
200
+ if regenerate_study_record(record) != record:
201
+ raise ValueError("study tables must be reproducible before portable publication")
202
+ with TemporaryDirectory(prefix="study-record-", dir=output.parent) as temporary:
203
+ path = Path(temporary) / "record.json"
204
+ save_document(record, path)
205
+ files["record.json"] = path
206
+ bundle = create_bundle(
207
+ files,
208
+ directory=output,
209
+ bundle_id=study.study_id,
210
+ artifacts=artifacts,
211
+ expected_files=expected_files,
212
+ record_path="record.json",
213
+ )
214
+ by_source = {item.source_uri: item for item in bundle.files}
215
+ for source in study.freeze.definition.files:
216
+ copied = by_source[_original_uri(source.path)]
217
+ if (copied.sha256, copied.size_bytes) != (source.sha256, source.size_bytes):
218
+ raise ValueError("a frozen source changed during portable copying")
219
+ return bundle
220
+
221
+
222
+ def reproduce_study_bundle(
223
+ directory: Path, *, expected_sha256: str | None = None
224
+ ) -> ComparisonReceipt:
225
+ """Verify portable bytes and regenerate the six CSV from captured decisions."""
226
+ bundle = read_bundle(directory, expected_sha256=expected_sha256)
227
+ if bundle.record_path != "record.json":
228
+ raise ValueError("study bundle requires its declared full reproduction record")
229
+ from equiframe_benchmark.freeze import FreezeManifest
230
+ from equiframe_benchmark.study import StudyResult
231
+ from equiframe_benchmark.tables import read_tables
232
+
233
+ record, record_sha256 = _bounded_json(
234
+ ReproductionRecord, directory / bundle.record_path, BundleLimits().max_record_bytes
235
+ )
236
+ if next(i.sha256 for i in bundle.files if i.path == bundle.record_path) != record_sha256:
237
+ raise ValueError("portable record changed after its inventory was verified")
238
+ configuration = json.loads(record.configuration)
239
+ frozen = FreezeManifest.model_validate_json(json.dumps(configuration["freeze"]))
240
+ captured, _ = _bounded_json(
241
+ StudyResult, directory / "study" / "study.json", BundleLimits().max_record_bytes
242
+ )
243
+ if captured.freeze != frozen or (
244
+ configuration["evaluation"] != captured.evaluation.config.model_dump(mode="json")
245
+ or json.loads(record.decisions)
246
+ != [r.model_dump(mode="json") for r in captured.evaluation.records]
247
+ ):
248
+ raise ValueError("portable logical record must match its captured study")
249
+ by_name = {item.path: item for item in bundle.files}
250
+ by_source = {item.source_uri: item for item in bundle.files}
251
+ copied: BundleFile | None
252
+ expected_study = {"study/study.json", *("study/" + i.path for i in captured.evidence)}
253
+ if {name for name in by_name if name.startswith("study/")} != expected_study:
254
+ raise ValueError("portable study evidence inventory is not closed")
255
+ for item in captured.evidence:
256
+ copied = by_name["study/" + item.path]
257
+ if (copied.sha256, copied.size_bytes) != (item.sha256, item.size_bytes):
258
+ raise ValueError("portable study bytes differ from the original study inventory")
259
+ for name, source_document in (
260
+ ("freeze.json", captured.freeze),
261
+ ("audit-before.json", captured.audit_before),
262
+ ("audit-after.json", captured.audit_after),
263
+ ("evaluation.json", captured.evaluation),
264
+ ):
265
+ name = "study/" + name
266
+ _check_document(directory / name, source_document, by_name[name])
267
+ for position, case in enumerate(captured.cases):
268
+ prefix = f"study/case-{position}/"
269
+ name = prefix + "case.json"
270
+ _check_document(directory / name, case, by_name[name])
271
+ if case.pipeline is not None:
272
+ pipeline = case.pipeline
273
+ folder = prefix + pipeline.pipeline_id + "/"
274
+ for name, pipeline_document in (
275
+ ("manifest.json", pipeline),
276
+ ("pipeline.json", pipeline.spec),
277
+ ("execution.json", pipeline.execution),
278
+ ("rules.json", pipeline.rules),
279
+ ("profile.json", pipeline.profile),
280
+ ("detection.json", pipeline.detection),
281
+ ("comparison.json", pipeline.comparison),
282
+ ("hybrid.json", pipeline.hybrid),
283
+ ):
284
+ if pipeline_document is not None:
285
+ name = folder + name
286
+ _check_document(directory / name, pipeline_document, by_name[name])
287
+ for item in pipeline.evidence:
288
+ copied = by_name.get(folder + item.path)
289
+ if copied is None or (copied.sha256, copied.size_bytes) != (
290
+ item.sha256,
291
+ item.size_bytes,
292
+ ):
293
+ raise ValueError("portable pipeline evidence contradicts its manifest")
294
+ events = verify_events(
295
+ directory / folder / "events.jsonl", operation_id=pipeline.pipeline_id
296
+ )
297
+ if (
298
+ len(events) != pipeline.event_count
299
+ or events[-1].sha256 != pipeline.event_head_sha256
300
+ ):
301
+ raise ValueError("portable pipeline journal differs from its captured head")
302
+ if case.output_replay is not None:
303
+ uri = case.output_replay.differences.uri.rsplit("/", 1)[0] + "/comparison.json"
304
+ copied = by_source.get(uri)
305
+ if copied is None:
306
+ raise ValueError("portable replay source manifest is missing")
307
+ _check_document(directory / copied.path, case.output_replay, copied)
308
+ if read_tables(directory / "study" / "tables", report=captured.evaluation) != captured.tables:
309
+ raise ValueError("portable CSV must reproduce its original full report")
310
+ if digest(frozen.definition.splits) != configuration["splits_sha256"] or json.loads(
311
+ record.splits
312
+ ) != frozen.definition.splits.model_dump(mode="json"):
313
+ raise ValueError("portable split must equal the sealed split")
314
+ for ref in _references(frozen.model_dump(mode="json")):
315
+ copied = by_source.get(ref.uri)
316
+ if copied is None or (copied.sha256, copied.size_bytes, copied.artifact_format) != (
317
+ ref.sha256,
318
+ ref.size_bytes,
319
+ ref.format,
320
+ ):
321
+ raise ValueError("portable input must preserve its original artifact binding")
322
+ for source in frozen.definition.files:
323
+ copied = by_source.get(_original_uri(source.path))
324
+ if copied is None or (copied.sha256, copied.size_bytes) != (
325
+ source.sha256,
326
+ source.size_bytes,
327
+ ):
328
+ raise ValueError("portable frozen source differs from its declared content")
329
+ definitions = {case.case_id: case for case in frozen.definition.cases}
330
+ scenarios = {scenario.scenario_id: scenario for scenario in frozen.definition.catalog.scenarios}
331
+ for case in captured.cases:
332
+ if case.record.oracle_state != "confirmed":
333
+ continue
334
+ declared = definitions[case.case_id]
335
+ if (
336
+ case.pipeline is None
337
+ or case.pipeline.execution is None
338
+ or (case.pipeline.execution.output is None)
339
+ or case.output_replay is None
340
+ ):
341
+ raise ValueError("a confirmed replay requires an actual captured output")
342
+ original = case.pipeline.execution.output
343
+ # An ephemeral TableArtifact retains index and physical metadata. Original
344
+ # serialized manifests and their fingerprints remain unchanged in the bundle.
345
+ rebased = ef.TableArtifact.model_validate(
346
+ {
347
+ **original.model_dump(mode="python"),
348
+ "data": _portable_artifact(original.data, directory, by_source),
349
+ "index": _portable_artifact(original.index, directory, by_source)
350
+ if original.index
351
+ else None,
352
+ }
353
+ )
354
+ scenario = scenarios[declared.scenario_id]
355
+ with TemporaryDirectory(prefix="portable-replay-") as temporary:
356
+ replay = ef.compare(
357
+ rebased,
358
+ _portable_artifact(declared.candidate.output, directory, by_source),
359
+ contract=scenario.comparison.contract,
360
+ policy=scenario.comparison.policy,
361
+ options=declared.pipeline.comparison_options,
362
+ reference="peer",
363
+ work_dir=Path(temporary),
364
+ )
365
+ if not replay.complete or replay.state != "equivalent":
366
+ raise ValueError("confirmed portable label requires equivalent actual captured rows")
367
+ if (
368
+ replay.counts,
369
+ replay.strategy,
370
+ replay.selected_key,
371
+ ) != (
372
+ case.output_replay.counts,
373
+ case.output_replay.strategy,
374
+ case.output_replay.selected_key,
375
+ ):
376
+ raise ValueError("portable replay observations differ from actual captured rows")
377
+ return reproduce_bundle(
378
+ directory, regenerator=regenerate_study_record, expected_sha256=expected_sha256
379
+ )