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