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.
- equiframe_benchmark/PROTOCOL_V2.md +172 -0
- equiframe_benchmark/__init__.py +284 -0
- equiframe_benchmark/__main__.py +3 -0
- equiframe_benchmark/_scale_workload.py +375 -0
- equiframe_benchmark/calibration_types.py +112 -0
- equiframe_benchmark/cli.py +196 -0
- equiframe_benchmark/commands.py +149 -0
- equiframe_benchmark/commands_scale.py +38 -0
- equiframe_benchmark/commands_step10.py +275 -0
- equiframe_benchmark/commands_v2.py +206 -0
- equiframe_benchmark/cost_table.py +62 -0
- equiframe_benchmark/costs.py +788 -0
- equiframe_benchmark/distribution.py +932 -0
- equiframe_benchmark/evaluation.py +614 -0
- equiframe_benchmark/evaluation_v2.py +428 -0
- equiframe_benchmark/fixtures.py +299 -0
- equiframe_benchmark/freeze.py +869 -0
- equiframe_benchmark/freeze_v2.py +412 -0
- equiframe_benchmark/integration.py +655 -0
- equiframe_benchmark/io.py +29 -0
- equiframe_benchmark/models.py +313 -0
- equiframe_benchmark/models_v2.py +13 -0
- equiframe_benchmark/mutations.py +34 -0
- equiframe_benchmark/oracle.py +264 -0
- equiframe_benchmark/pairwise.py +248 -0
- equiframe_benchmark/pilot.py +254 -0
- equiframe_benchmark/py.typed +0 -0
- equiframe_benchmark/reconciliation.py +294 -0
- equiframe_benchmark/reproduction.py +542 -0
- equiframe_benchmark/scale_io.py +19 -0
- equiframe_benchmark/scale_process.py +1077 -0
- equiframe_benchmark/scale_types.py +120 -0
- equiframe_benchmark/scaling.py +529 -0
- equiframe_benchmark/schemas/protocol-v2/CommonSupportV2-2.0.schema.json +327 -0
- equiframe_benchmark/schemas/protocol-v2/ContrastSpecV2-2.0.schema.json +37 -0
- equiframe_benchmark/schemas/protocol-v2/EvaluationConfigV2-2.0.schema.json +184 -0
- equiframe_benchmark/schemas/protocol-v2/EvaluationReportV2-2.0.schema.json +1237 -0
- equiframe_benchmark/schemas/protocol-v2/EvaluationStratumV2-2.0.schema.json +747 -0
- equiframe_benchmark/schemas/protocol-v2/FreezeDefinitionV2-2.0.schema.json +5951 -0
- equiframe_benchmark/schemas/protocol-v2/FreezeManifestV2-2.0.schema.json +6222 -0
- equiframe_benchmark/schemas/protocol-v2/PrincipalContrastV2-2.0.schema.json +578 -0
- equiframe_benchmark/schemas/protocol-v2/ProtocolReviewV2-2.0.schema.json +56 -0
- equiframe_benchmark/schemas/protocol-v2/SignedMetricV2-2.0.schema.json +51 -0
- equiframe_benchmark/schemas/protocol-v2/StudyCaseResultV2-2.0.schema.json +4881 -0
- equiframe_benchmark/schemas/protocol-v2/StudyResultV2-2.0.schema.json +8370 -0
- equiframe_benchmark/schemas/protocol-v2/TableExportV2-2.0.schema.json +75 -0
- equiframe_benchmark/selection.py +178 -0
- equiframe_benchmark/snapshot.py +24 -0
- equiframe_benchmark/splits.py +92 -0
- equiframe_benchmark/study.py +526 -0
- equiframe_benchmark/study_bundle.py +379 -0
- equiframe_benchmark/study_bundle_v2.py +388 -0
- equiframe_benchmark/study_v2.py +544 -0
- equiframe_benchmark/tables.py +347 -0
- equiframe_benchmark/tables_v2.py +340 -0
- equiframe_benchmark/training_types.py +52 -0
- equiframe_benchmark-0.12.4.dist-info/METADATA +343 -0
- equiframe_benchmark-0.12.4.dist-info/RECORD +62 -0
- equiframe_benchmark-0.12.4.dist-info/WHEEL +4 -0
- equiframe_benchmark-0.12.4.dist-info/entry_points.txt +3 -0
- equiframe_benchmark-0.12.4.dist-info/licenses/LICENSE +21 -0
- 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
|
+
)
|