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,149 @@
1
+ """Readiness and reproduction commands; JSON never chooses executable code."""
2
+
3
+ import json
4
+ from collections.abc import Callable
5
+ from pathlib import Path
6
+ from typing import cast
7
+
8
+ import typer
9
+
10
+
11
+ def register(app: typer.Typer) -> None:
12
+ """Attach commands to the caller's app without an eager circular CLI import."""
13
+ from equiframe_benchmark.cli import _factory, _failure
14
+
15
+ @app.command("distribution-readiness")
16
+ def distribution_command(
17
+ package_directory: Path,
18
+ wheel: Path | None = typer.Option(None),
19
+ name_check: Path | None = typer.Option(None),
20
+ output: Path | None = typer.Option(None),
21
+ ) -> None:
22
+ """Inventory local metadata; no PyPI network requests or publication actions."""
23
+ from equiframe_benchmark.distribution import NameCheck, inspect_distribution
24
+ from equiframe_benchmark.io import load_document, save_document
25
+
26
+ try:
27
+ observation = load_document(NameCheck, name_check) if name_check else None
28
+ result = inspect_distribution(package_directory, wheel, name_check=observation)
29
+ if output is not None:
30
+ save_document(result, output)
31
+ except Exception as error:
32
+ _failure(error)
33
+ return
34
+ typer.echo(result.model_dump_json())
35
+
36
+ @app.command("verify-bundle")
37
+ def verify_command(
38
+ directory: Path,
39
+ expected_sha256: str | None = typer.Option(None),
40
+ ) -> None:
41
+ """Verify closed bundle bytes and inventory without importing a regenerator."""
42
+ from equiframe_benchmark.reproduction import read_bundle
43
+
44
+ try:
45
+ result = read_bundle(directory, expected_sha256=expected_sha256)
46
+ except Exception as error:
47
+ _failure(error, input_validation=False)
48
+ return
49
+ typer.echo(
50
+ json.dumps(
51
+ {
52
+ "bundle_id": result.bundle_id,
53
+ "evidence_verified": True,
54
+ "files": len(result.files),
55
+ "reproduction_record_available": result.record_path is not None,
56
+ "independent_person_reproduced": False,
57
+ },
58
+ sort_keys=True,
59
+ )
60
+ )
61
+
62
+ @app.command("reproduce-bundle")
63
+ def reproduce_command(
64
+ directory: Path,
65
+ regenerator: str = typer.Option(
66
+ ..., help="Explicit trusted module:factory, never from JSON."
67
+ ),
68
+ expected_sha256: str | None = typer.Option(None),
69
+ output: Path | None = typer.Option(None),
70
+ ) -> None:
71
+ """Run an explicitly supplied trusted callable; compare complete logical content."""
72
+ from equiframe_benchmark.io import save_document
73
+ from equiframe_benchmark.reproduction import (
74
+ ReproductionRecord,
75
+ read_bundle,
76
+ reproduce_bundle,
77
+ )
78
+
79
+ try:
80
+ # Check persisted evidence before executing even the explicit factory.
81
+ read_bundle(directory, expected_sha256=expected_sha256)
82
+ except Exception as error:
83
+ _failure(error, input_validation=False)
84
+ return
85
+ try:
86
+ supplied = _factory(regenerator)()
87
+ if not callable(supplied):
88
+ raise ValueError("regenerator factory must return a callable")
89
+ callback = cast(Callable[[ReproductionRecord], ReproductionRecord], supplied)
90
+ except Exception as error:
91
+ _failure(error)
92
+ return
93
+ try:
94
+ receipt = reproduce_bundle(
95
+ directory,
96
+ regenerator=callback,
97
+ expected_sha256=expected_sha256,
98
+ )
99
+ if output is not None:
100
+ save_document(receipt, output)
101
+ except Exception as error:
102
+ _failure(error, input_validation=False)
103
+ return
104
+ typer.echo(receipt.model_dump_json())
105
+ if receipt.status == "different":
106
+ raise typer.Exit(code=1)
107
+
108
+ @app.command("bundle-study")
109
+ def bundle_study_command(directory: Path, output: Path = typer.Option(...)) -> None:
110
+ """Copy a verified study into a closed bundle without rewriting original evidence."""
111
+ from equiframe_benchmark.study_bundle import bundle_study
112
+
113
+ try:
114
+ result = bundle_study(directory, output=output)
115
+ except Exception as error:
116
+ _failure(error)
117
+ return
118
+ typer.echo(
119
+ json.dumps(
120
+ {
121
+ "bundle_id": result.bundle_id,
122
+ "files": len(result.files),
123
+ "independent_person_reproduced": False,
124
+ "etl_repeated": False,
125
+ },
126
+ sort_keys=True,
127
+ )
128
+ )
129
+
130
+ @app.command("reproduce-study")
131
+ def reproduce_study_command(
132
+ directory: Path,
133
+ expected_sha256: str | None = typer.Option(None),
134
+ output: Path | None = typer.Option(None),
135
+ ) -> None:
136
+ """Regenerate metrics/tables from captured study decisions; never rerun ETL."""
137
+ from equiframe_benchmark.io import save_document
138
+ from equiframe_benchmark.study_bundle import reproduce_study_bundle
139
+
140
+ try:
141
+ result = reproduce_study_bundle(directory, expected_sha256=expected_sha256)
142
+ if output is not None:
143
+ save_document(result, output)
144
+ except Exception as error:
145
+ _failure(error, input_validation=False)
146
+ return
147
+ typer.echo(result.model_dump_json())
148
+ if result.status == "different":
149
+ raise typer.Exit(code=1)
@@ -0,0 +1,38 @@
1
+ """Engineering scale commands do not change the scientific benchmark protocol."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import typer
7
+ from equiframe._cli_support import exit_with_failure
8
+ from equiframe.evidence import load_json
9
+
10
+ from equiframe_benchmark.scale_types import ScaleSpec
11
+ from equiframe_benchmark.scaling import measure_scale, read_scale, summarize_scale
12
+
13
+
14
+ def register(app: typer.Typer) -> None:
15
+ @app.command("scale")
16
+ def scale_command(spec: Path, output: Path = typer.Option(...)) -> None:
17
+ """Measure one bounded synthetic case with safe process completion checks."""
18
+ try:
19
+ result = measure_scale(load_json(ScaleSpec, spec), work_dir=output)
20
+ except Exception as error:
21
+ exit_with_failure(error, code="scale_operation_failed", exit_code=3)
22
+ typer.echo(json.dumps(summarize_scale(result), sort_keys=True))
23
+ raise typer.Exit(code=0 if result.complete else 3)
24
+
25
+ @app.command("verify-scale")
26
+ def verify_scale_command(directory: Path) -> None:
27
+ """Verify all retained bytes and completion; never rerun a workload."""
28
+ try:
29
+ result = read_scale(directory)
30
+ except Exception as error:
31
+ exit_with_failure(
32
+ error,
33
+ code="scale_evidence_invalid_or_incomplete",
34
+ exit_code=3,
35
+ input_validation=False,
36
+ )
37
+ typer.echo(json.dumps(summarize_scale(result), sort_keys=True))
38
+ raise typer.Exit(code=0 if result.complete else 3)
@@ -0,0 +1,275 @@
1
+ """Explicit experimental commands; persisted JSON never selects executable code."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+
9
+ def register(app: typer.Typer) -> None:
10
+ from equiframe_benchmark.cli import _factory, _failure
11
+
12
+ @app.command("freeze")
13
+ def freeze_command(definition: Path, output: Path = typer.Option(...)) -> None:
14
+ """Audit and seal a declared pilot/final protocol before test execution."""
15
+ from equiframe_benchmark.freeze import FreezeDefinition, audit_freeze, create_freeze
16
+ from equiframe_benchmark.io import load_document, save_document
17
+
18
+ try:
19
+ configuration = load_document(FreezeDefinition, definition)
20
+ audit = audit_freeze(configuration)
21
+ if not audit.ready:
22
+ typer.echo(
23
+ json.dumps(
24
+ {
25
+ "mode": configuration.mode,
26
+ "technical_ready": audit.technical_ready,
27
+ "scientific_ready": audit.scientific_ready,
28
+ "blockers": audit.blockers,
29
+ "sealed": False,
30
+ },
31
+ sort_keys=True,
32
+ )
33
+ )
34
+ raise typer.Exit(code=2)
35
+ manifest = create_freeze(configuration)
36
+ save_document(manifest, output)
37
+ except typer.Exit:
38
+ raise
39
+ except Exception as error:
40
+ _failure(error)
41
+ return
42
+ typer.echo(
43
+ json.dumps(
44
+ {
45
+ "mode": manifest.definition.mode,
46
+ "technical_ready": manifest.audit.technical_ready,
47
+ "scientific_ready": manifest.audit.scientific_ready,
48
+ "blockers": manifest.audit.blockers,
49
+ "sealed": True,
50
+ },
51
+ sort_keys=True,
52
+ )
53
+ )
54
+
55
+ @app.command("verify-freeze")
56
+ def verify_freeze_command(path: Path) -> None:
57
+ from equiframe_benchmark.freeze import read_freeze
58
+
59
+ try:
60
+ manifest = read_freeze(path)
61
+ except Exception as error:
62
+ _failure(error, input_validation=False)
63
+ return
64
+ typer.echo(
65
+ json.dumps(
66
+ {
67
+ "evidence_verified": True,
68
+ "mode": manifest.definition.mode,
69
+ "technical_ready": manifest.audit.technical_ready,
70
+ "scientific_ready": manifest.audit.scientific_ready,
71
+ "blockers": manifest.audit.blockers,
72
+ },
73
+ sort_keys=True,
74
+ )
75
+ )
76
+
77
+ @app.command("run-study")
78
+ def study_command(
79
+ freeze: Path,
80
+ registry: str = typer.Option(...),
81
+ output: Path = typer.Option(Path("equiframe-studies")),
82
+ ) -> None:
83
+ """Run sealed cases with an explicitly trusted registry, without fitting."""
84
+ import equiframe as ef
85
+
86
+ from equiframe_benchmark.freeze import read_freeze
87
+ from equiframe_benchmark.study import read_study, run_study
88
+
89
+ try:
90
+ manifest = read_freeze(freeze)
91
+ except Exception as error:
92
+ _failure(error, input_validation=False)
93
+ return
94
+ try:
95
+ registered = _factory(registry)()
96
+ if not isinstance(registered, ef.TransformationRegistry):
97
+ raise ValueError("registry factory must return a TransformationRegistry")
98
+ result = run_study(manifest, registry=registered, work_dir=output)
99
+ if read_study(output.resolve() / result.study_id) != result:
100
+ raise ValueError("published study differs from its verified receipt")
101
+ except Exception as error:
102
+ _failure(error)
103
+ return
104
+ typer.echo(
105
+ json.dumps(
106
+ {
107
+ "study_id": result.study_id,
108
+ "scientific": result.scientific,
109
+ "complete": result.complete,
110
+ "cases": len(result.evaluation.records),
111
+ "evidence_verified": True,
112
+ },
113
+ sort_keys=True,
114
+ )
115
+ )
116
+ raise typer.Exit(code=result.exit_code)
117
+
118
+ @app.command("verify-study")
119
+ def verify_study_command(directory: Path) -> None:
120
+ from equiframe_benchmark.study import read_study
121
+
122
+ try:
123
+ result = read_study(directory)
124
+ except Exception as error:
125
+ _failure(error, input_validation=False)
126
+ return
127
+ typer.echo(
128
+ json.dumps(
129
+ {
130
+ "study_id": result.study_id,
131
+ "scientific": result.scientific,
132
+ "complete": result.complete,
133
+ "cases": len(result.evaluation.records),
134
+ "evidence_verified": True,
135
+ },
136
+ sort_keys=True,
137
+ )
138
+ )
139
+
140
+ @app.command("evaluate")
141
+ def evaluate_command(study: Path, output: Path = typer.Option(...)) -> None:
142
+ """Regenerate a verified study's report from its complete captured records."""
143
+ from equiframe_benchmark.evaluation import evaluate_records
144
+ from equiframe_benchmark.io import save_document
145
+ from equiframe_benchmark.study import read_study
146
+
147
+ try:
148
+ captured = read_study(study).evaluation
149
+ except Exception as error:
150
+ _failure(error, input_validation=False)
151
+ return
152
+ try:
153
+ report = evaluate_records(captured.records, config=captured.config)
154
+ if report != captured:
155
+ raise ValueError("evaluation must reproduce the captured report")
156
+ save_document(report, output)
157
+ except Exception as error:
158
+ _failure(error)
159
+ return
160
+ typer.echo(json.dumps({"scientific": report.scientific, "regenerated": True}))
161
+
162
+ @app.command("tables")
163
+ def tables_command(study: Path, output: Path = typer.Option(...)) -> None:
164
+ from equiframe_benchmark.study import read_study
165
+ from equiframe_benchmark.tables import write_tables
166
+
167
+ try:
168
+ report = read_study(study).evaluation
169
+ except Exception as error:
170
+ _failure(error, input_validation=False)
171
+ return
172
+ try:
173
+ receipt = write_tables(report, output)
174
+ except Exception as error:
175
+ _failure(error)
176
+ return
177
+ typer.echo(json.dumps({"tables": len(receipt.tables), "scientific": report.scientific}))
178
+
179
+ @app.command("costs")
180
+ def costs_command(
181
+ spec: Path,
182
+ bundle: Path = typer.Option(...),
183
+ output: Path = typer.Option(Path("equiframe-costs")),
184
+ ) -> None:
185
+ """Measure profile+M and comparison on previously captured artifacts."""
186
+ import equiframe as ef
187
+
188
+ from equiframe_benchmark.costs import CostSpec, ProfileMCostResult, measure_costs
189
+ from equiframe_benchmark.io import load_document
190
+
191
+ try:
192
+ configuration = load_document(CostSpec, spec)
193
+ detector = ef.read_detector_bundle(bundle)
194
+ if ef.detector_fingerprint(detector) != configuration.detector_bundle_sha256:
195
+ raise ValueError("costs require the declared frozen detector bundle")
196
+
197
+ def profile_m(
198
+ directory: Path, cancellation: ef.CancellationToken
199
+ ) -> ProfileMCostResult:
200
+ profile = ef.profile_execution(
201
+ configuration.execution,
202
+ spec=configuration.profile,
203
+ options=configuration.profile_options,
204
+ work_dir=directory,
205
+ cancellation=cancellation,
206
+ )
207
+ detection = (
208
+ ef.detect_component(detector, (profile.vector,), method="isolation_forest")
209
+ if profile.complete
210
+ else None
211
+ )
212
+ return ProfileMCostResult(profile=profile, detection=detection)
213
+
214
+ def comparison(
215
+ directory: Path, cancellation: ef.CancellationToken
216
+ ) -> ef.ComparisonResult:
217
+ return ef.compare(
218
+ configuration.execution,
219
+ configuration.reference,
220
+ contract=configuration.execution.plan.contract,
221
+ policy=configuration.comparison_policy,
222
+ reference=configuration.comparison_reference,
223
+ options=configuration.comparison_options,
224
+ work_dir=directory,
225
+ cancellation=cancellation,
226
+ )
227
+
228
+ result = measure_costs(
229
+ configuration, profile_m=profile_m, comparison=comparison, work_dir=output
230
+ )
231
+ except Exception as error:
232
+ _failure(error)
233
+ return
234
+ typer.echo(
235
+ json.dumps(
236
+ {
237
+ "cost_id": result.cost_id,
238
+ "complete": result.complete,
239
+ "scientific_h3_confirmed": False,
240
+ },
241
+ sort_keys=True,
242
+ )
243
+ )
244
+ raise typer.Exit(code=0 if result.complete else 3)
245
+
246
+ @app.command("verify-costs")
247
+ def verify_costs_command(directory: Path) -> None:
248
+ from equiframe_benchmark.costs import read_costs
249
+
250
+ try:
251
+ result = read_costs(directory)
252
+ except Exception as error:
253
+ _failure(error, input_validation=False)
254
+ return
255
+ typer.echo(
256
+ json.dumps(
257
+ {
258
+ "cost_id": result.cost_id,
259
+ "complete": result.complete,
260
+ "evidence_verified": True,
261
+ },
262
+ sort_keys=True,
263
+ )
264
+ )
265
+
266
+ @app.command("cost-table")
267
+ def cost_table_command(directory: Path, output: Path = typer.Option(...)) -> None:
268
+ from equiframe_benchmark.cost_table import write_cost_table
269
+
270
+ try:
271
+ write_cost_table(directory, output=output)
272
+ except Exception as error:
273
+ _failure(error, input_validation=False)
274
+ return
275
+ typer.echo(json.dumps({"observations": 12, "scientific_h3_confirmed": False}))
@@ -0,0 +1,206 @@
1
+ """Explicit experimental commands; persisted JSON never selects executable code."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ import typer
7
+
8
+
9
+ def register_v2(app: typer.Typer) -> None:
10
+ from equiframe_benchmark.cli import _factory, _failure
11
+
12
+ @app.command("freeze-v2")
13
+ def freeze_command(definition: Path, output: Path = typer.Option(...)) -> None:
14
+ """Audit and seal a declared pilot/final protocol before test execution."""
15
+ from equiframe_benchmark.freeze_v2 import (
16
+ FreezeDefinitionV2,
17
+ audit_freeze_v2,
18
+ create_freeze_v2,
19
+ )
20
+ from equiframe_benchmark.io import load_document, save_document
21
+
22
+ try:
23
+ configuration = load_document(FreezeDefinitionV2, definition)
24
+ audit = audit_freeze_v2(configuration)
25
+ if not audit.ready:
26
+ typer.echo(
27
+ json.dumps(
28
+ {
29
+ "mode": configuration.mode,
30
+ "technical_ready": audit.technical_ready,
31
+ "scientific_ready": audit.scientific_ready,
32
+ "blockers": audit.blockers,
33
+ "sealed": False,
34
+ },
35
+ sort_keys=True,
36
+ )
37
+ )
38
+ raise typer.Exit(code=2)
39
+ manifest = create_freeze_v2(configuration)
40
+ save_document(manifest, output)
41
+ except typer.Exit:
42
+ raise
43
+ except Exception as error:
44
+ _failure(error)
45
+ return
46
+ typer.echo(
47
+ json.dumps(
48
+ {
49
+ "mode": manifest.definition.mode,
50
+ "technical_ready": manifest.audit.technical_ready,
51
+ "scientific_ready": manifest.audit.scientific_ready,
52
+ "blockers": manifest.audit.blockers,
53
+ "sealed": True,
54
+ },
55
+ sort_keys=True,
56
+ )
57
+ )
58
+
59
+ @app.command("verify-freeze-v2")
60
+ def verify_freeze_command(path: Path) -> None:
61
+ from equiframe_benchmark.freeze_v2 import read_freeze_v2
62
+
63
+ try:
64
+ manifest = read_freeze_v2(path)
65
+ except Exception as error:
66
+ _failure(error, input_validation=False)
67
+ return
68
+ typer.echo(
69
+ json.dumps(
70
+ {
71
+ "evidence_verified": True,
72
+ "mode": manifest.definition.mode,
73
+ "technical_ready": manifest.audit.technical_ready,
74
+ "scientific_ready": manifest.audit.scientific_ready,
75
+ "blockers": manifest.audit.blockers,
76
+ },
77
+ sort_keys=True,
78
+ )
79
+ )
80
+
81
+ @app.command("run-study-v2")
82
+ def study_command(
83
+ freeze: Path,
84
+ registry: str = typer.Option(...),
85
+ output: Path = typer.Option(Path("equiframe-studies")),
86
+ ) -> None:
87
+ """Run sealed cases with an explicitly trusted registry, without fitting."""
88
+ import equiframe as ef
89
+
90
+ from equiframe_benchmark.freeze_v2 import read_freeze_v2
91
+ from equiframe_benchmark.study_v2 import read_study_v2, run_study_v2
92
+
93
+ try:
94
+ manifest = read_freeze_v2(freeze)
95
+ except Exception as error:
96
+ _failure(error, input_validation=False)
97
+ return
98
+ try:
99
+ registered = _factory(registry)()
100
+ if not isinstance(registered, ef.TransformationRegistry):
101
+ raise ValueError("registry factory must return a TransformationRegistry")
102
+ result = run_study_v2(manifest, registry=registered, work_dir=output)
103
+ if read_study_v2(output.resolve() / result.study_id) != result:
104
+ raise ValueError("published study differs from its verified receipt")
105
+ except Exception as error:
106
+ _failure(error)
107
+ return
108
+ typer.echo(
109
+ json.dumps(
110
+ {
111
+ "study_id": result.study_id,
112
+ "scientific": result.scientific,
113
+ "complete": result.complete,
114
+ "cases": len(result.evaluation.records),
115
+ "evidence_verified": True,
116
+ },
117
+ sort_keys=True,
118
+ )
119
+ )
120
+ raise typer.Exit(code=result.exit_code)
121
+
122
+ @app.command("verify-study-v2")
123
+ def verify_study_command(directory: Path) -> None:
124
+ from equiframe_benchmark.study_v2 import read_study_v2
125
+
126
+ try:
127
+ result = read_study_v2(directory)
128
+ except Exception as error:
129
+ _failure(error, input_validation=False)
130
+ return
131
+ typer.echo(
132
+ json.dumps(
133
+ {
134
+ "study_id": result.study_id,
135
+ "scientific": result.scientific,
136
+ "complete": result.complete,
137
+ "cases": len(result.evaluation.records),
138
+ "evidence_verified": True,
139
+ },
140
+ sort_keys=True,
141
+ )
142
+ )
143
+
144
+ @app.command("evaluate-v2")
145
+ def evaluate_command(study: Path, output: Path = typer.Option(...)) -> None:
146
+ """Regenerate a verified study's report from its complete captured records."""
147
+ from equiframe_benchmark.evaluation_v2 import evaluate_records_v2
148
+ from equiframe_benchmark.io import save_document
149
+ from equiframe_benchmark.study_v2 import read_study_v2
150
+
151
+ try:
152
+ captured = read_study_v2(study).evaluation
153
+ except Exception as error:
154
+ _failure(error, input_validation=False)
155
+ return
156
+ try:
157
+ report = evaluate_records_v2(captured.records, config=captured.config)
158
+ if report != captured:
159
+ raise ValueError("evaluation must reproduce the captured report")
160
+ save_document(report, output)
161
+ except Exception as error:
162
+ _failure(error)
163
+ return
164
+ typer.echo(json.dumps({"scientific": report.scientific, "regenerated": True}))
165
+
166
+ @app.command("tables-v2")
167
+ def tables_command(study: Path, output: Path = typer.Option(...)) -> None:
168
+ from equiframe_benchmark.study_v2 import read_study_v2
169
+ from equiframe_benchmark.tables_v2 import write_tables_v2
170
+
171
+ try:
172
+ report = read_study_v2(study).evaluation
173
+ except Exception as error:
174
+ _failure(error, input_validation=False)
175
+ return
176
+ try:
177
+ receipt = write_tables_v2(report, output)
178
+ except Exception as error:
179
+ _failure(error)
180
+ return
181
+ typer.echo(json.dumps({"tables": len(receipt.tables), "scientific": report.scientific}))
182
+
183
+ @app.command("bundle-study-v2")
184
+ def bundle_command(study: Path, output: Path = typer.Option(...)) -> None:
185
+ """Copy the V2 captured study and original sources without rewriting provenance."""
186
+ from equiframe_benchmark.study_bundle_v2 import bundle_study_v2
187
+
188
+ try:
189
+ result = bundle_study_v2(study, output=output)
190
+ except Exception as error:
191
+ _failure(error, input_validation=False)
192
+ return
193
+ typer.echo(json.dumps({"protocol_version": "2.0", "bundle_id": result.bundle_id}))
194
+
195
+ @app.command("reproduce-study-v2")
196
+ def reproduce_command(directory: Path) -> None:
197
+ """Regenerate paired tables from captured decisions, without executing native ETL."""
198
+ from equiframe_benchmark.study_bundle_v2 import reproduce_study_bundle_v2
199
+
200
+ try:
201
+ result = reproduce_study_bundle_v2(directory)
202
+ except Exception as error:
203
+ _failure(error, input_validation=False)
204
+ return
205
+ typer.echo(json.dumps({"protocol_version": "2.0", "status": result.status}))
206
+ raise typer.Exit(code=0 if result.status == "reproduced" else 1)