equiframe 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/__init__.py +832 -0
- equiframe/__main__.py +3 -0
- equiframe/_base.py +84 -0
- equiframe/_cli_support.py +125 -0
- equiframe/_comparison_io.py +161 -0
- equiframe/_dataset_provenance.py +146 -0
- equiframe/_decimal.py +22 -0
- equiframe/_diff_capture.py +433 -0
- equiframe/_doctor.py +123 -0
- equiframe/_document_io.py +61 -0
- equiframe/_documents.py +79 -0
- equiframe/_duckdb_transport.py +149 -0
- equiframe/_error_reporting.py +52 -0
- equiframe/_first_use.py +102 -0
- equiframe/_path_compat.py +26 -0
- equiframe/_physical.py +133 -0
- equiframe/_r_review.R +308 -0
- equiframe/_regular_io.py +167 -0
- equiframe/_report_format_v1.py +298 -0
- equiframe/_report_io.py +591 -0
- equiframe/_stability_inventory.py +1421 -0
- equiframe/_timezones.py +257 -0
- equiframe/_vendor/cyclonedx/LICENSE +201 -0
- equiframe/_vendor/cyclonedx/PROVENANCE.md +18 -0
- equiframe/_vendor/cyclonedx/bom-1.6.schema.json +5673 -0
- equiframe/_vendor/cyclonedx/jsf-0.82.schema.json +240 -0
- equiframe/_vendor/cyclonedx/spdx.schema.json +712 -0
- equiframe/adapters.py +454 -0
- equiframe/assertion_types.py +122 -0
- equiframe/assertions.py +271 -0
- equiframe/benchmark.py +101 -0
- equiframe/bundle.py +240 -0
- equiframe/cli.py +693 -0
- equiframe/comparison.py +937 -0
- equiframe/comparison_report.py +660 -0
- equiframe/comparison_types.py +257 -0
- equiframe/compatibility.py +546 -0
- equiframe/contracts.py +879 -0
- equiframe/datasets.py +1301 -0
- equiframe/decision.py +34 -0
- equiframe/destinations.py +295 -0
- equiframe/detector_types.py +431 -0
- equiframe/detectors.py +389 -0
- equiframe/diff.py +192 -0
- equiframe/diff_contracts.py +523 -0
- equiframe/docs/adoption.md +329 -0
- equiframe/docs/api.md +534 -0
- equiframe/docs/arquitecto.md +143 -0
- equiframe/docs/choosing-api.md +84 -0
- equiframe/docs/cientifico.md +180 -0
- equiframe/docs/compatibility.md +254 -0
- equiframe/docs/contributing.md +171 -0
- equiframe/docs/dependencies.md +237 -0
- equiframe/docs/eda.md +166 -0
- equiframe/docs/extending.md +158 -0
- equiframe/docs/fast-ci.md +52 -0
- equiframe/docs/first-use.md +78 -0
- equiframe/docs/index.md +142 -0
- equiframe/docs/ingestion.md +145 -0
- equiframe/docs/inicio.md +129 -0
- equiframe/docs/installed-private.md +97 -0
- equiframe/docs/journeys.md +362 -0
- equiframe/docs/lectura-conversion.md +211 -0
- equiframe/docs/licensing.md +17 -0
- equiframe/docs/maintenance.md +233 -0
- equiframe/docs/migration-ir.md +333 -0
- equiframe/docs/option-audit.md +45 -0
- equiframe/docs/outcome-gates.md +134 -0
- equiframe/docs/performance.md +263 -0
- equiframe/docs/piloto-simulado.md +56 -0
- equiframe/docs/quickstart.md +156 -0
- equiframe/docs/r_review.md +212 -0
- equiframe/docs/recipes.md +248 -0
- equiframe/docs/releasing.md +183 -0
- equiframe/docs/security.md +218 -0
- equiframe/docs/semantics.md +274 -0
- equiframe/docs/sqlite-snapshots.md +262 -0
- equiframe/docs/universal-sources.md +49 -0
- equiframe/docs/universalidad-cinco-pasos.md +107 -0
- equiframe/docs/upgrading.md +538 -0
- equiframe/duckdb_capture.py +410 -0
- equiframe/eda/__init__.py +36 -0
- equiframe/eda/__main__.py +3 -0
- equiframe/eda/_backend.py +612 -0
- equiframe/eda/_inventory.py +49 -0
- equiframe/eda/_paths.py +49 -0
- equiframe/eda/_report.py +291 -0
- equiframe/eda/api.py +535 -0
- equiframe/eda/cli.py +102 -0
- equiframe/eda/models.py +239 -0
- equiframe/errors.py +83 -0
- equiframe/evidence.py +375 -0
- equiframe/examples/integrations/eda_generic.py +60 -0
- equiframe/examples/integrations/outcome_gates.py +94 -0
- equiframe/examples/integrations/sqlite_snapshot.py +246 -0
- equiframe/examples/integrations/sqlite_snapshot_demo.py +226 -0
- equiframe/examples/integrations/universal_first_use.py +296 -0
- equiframe/examples/journeys/README.md +31 -0
- equiframe/examples/journeys/__init__.py +1 -0
- equiframe/examples/journeys/a_pandas_refactor.py +88 -0
- equiframe/examples/journeys/b_native_migration.py +181 -0
- equiframe/examples/journeys/c_retail_etl.py +460 -0
- equiframe/examples/journeys/common.py +136 -0
- equiframe/examples/journeys/d_financial_reconciliation.py +96 -0
- equiframe/examples/journeys/e_telemetry.py +135 -0
- equiframe/examples/journeys/f_data_science_preparation.py +84 -0
- equiframe/examples/journeys/g_external_snapshots.py +197 -0
- equiframe/examples/journeys/h_portable_ci.py +129 -0
- equiframe/examples/journeys/requirements.lock +249 -0
- equiframe/examples/journeys/verify_journeys.py +117 -0
- equiframe/examples/load_convert_compare.py +85 -0
- equiframe/execution.py +355 -0
- equiframe/execution_types.py +177 -0
- equiframe/ingestion.py +744 -0
- equiframe/local.py +126 -0
- equiframe/migration/__init__.py +94 -0
- equiframe/migration/_physical.py +8 -0
- equiframe/migration/backends/_timestamp_transport.py +109 -0
- equiframe/migration/backends/duckdb.py +651 -0
- equiframe/migration/backends/duckdb_decimal.py +125 -0
- equiframe/migration/backends/duckdb_time.py +87 -0
- equiframe/migration/backends/pandas.py +544 -0
- equiframe/migration/backends/polars.py +915 -0
- equiframe/migration/backends/polars_decimal.py +231 -0
- equiframe/migration/io.py +198 -0
- equiframe/migration/ports.py +96 -0
- equiframe/migration/runner.py +545 -0
- equiframe/migration/runner_types.py +405 -0
- equiframe/migration/types.py +354 -0
- equiframe/migration/validation.py +619 -0
- equiframe/normalization.py +358 -0
- equiframe/pipeline.py +274 -0
- equiframe/pipeline_types.py +247 -0
- equiframe/ports.py +39 -0
- equiframe/preprocessing.py +110 -0
- equiframe/profile_types.py +343 -0
- equiframe/profiles.py +447 -0
- equiframe/py.typed +0 -0
- equiframe/r_review.py +706 -0
- equiframe/reduction.py +518 -0
- equiframe/registry.py +121 -0
- equiframe/report_rendering.py +366 -0
- equiframe/report_types.py +294 -0
- equiframe/reporting.py +26 -0
- equiframe/rule_types.py +178 -0
- equiframe/rules.py +819 -0
- equiframe/schemas/r-execution-binding-1.0.schema.json +46 -0
- equiframe/schemas/r-review-binding-1.0.schema.json +28 -0
- equiframe/schemas/r-review-options-1.0.schema.json +38 -0
- equiframe/schemas/r-review-result-1.0.schema.json +207 -0
- equiframe/schemas/reviewed-decision-1.0.schema.json +3394 -0
- equiframe/serialization.py +152 -0
- equiframe/snapshot.py +296 -0
- equiframe/sources.py +283 -0
- equiframe/tables.py +459 -0
- equiframe/types.py +71 -0
- equiframe/values.py +252 -0
- equiframe/verification_request.py +35 -0
- equiframe/verify.py +799 -0
- equiframe/verify_types.py +255 -0
- equiframe/workflow.py +510 -0
- equiframe/workflow_request.py +38 -0
- equiframe-0.12.4.dist-info/METADATA +268 -0
- equiframe-0.12.4.dist-info/RECORD +170 -0
- equiframe-0.12.4.dist-info/WHEEL +4 -0
- equiframe-0.12.4.dist-info/entry_points.txt +3 -0
- equiframe-0.12.4.dist-info/licenses/LICENSE +21 -0
- equiframe-0.12.4.dist-info/licenses/THIRD_PARTY_NOTICES.md +42 -0
- equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/LICENSE +201 -0
- equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/PROVENANCE.md +18 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Frozen persisted report layouts for evidence schema 1.0.
|
|
2
|
+
|
|
3
|
+
Readers bind these functions to a validated 1.0 result, never to public/current
|
|
4
|
+
presentation functions. Do not restyle or extend these implementations in place:
|
|
5
|
+
a new persisted layout requires an explicit format/reader compatibility decision.
|
|
6
|
+
|
|
7
|
+
Migration 1.0 has two historically emitted closed layouts. The reader selects
|
|
8
|
+
legacy only when the stored JSON equals migration_legacy_summary_v1 exactly;
|
|
9
|
+
otherwise the complete migration_summary_v1 and its HTML must match exactly.
|
|
10
|
+
These checks complement closed inventories and hashes, not replace them.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import html
|
|
16
|
+
import json
|
|
17
|
+
from html import escape
|
|
18
|
+
from typing import TYPE_CHECKING
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
from equiframe.comparison_types import ComparisonResult
|
|
22
|
+
from equiframe.migration.runner_types import MigrationResult
|
|
23
|
+
from equiframe.pipeline_types import PipelineResult
|
|
24
|
+
from equiframe.verify_types import VerificationResult
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def pipeline_summary_v1(result: PipelineResult) -> dict[str, object]:
|
|
28
|
+
from equiframe.pipeline_types import PipelineResult
|
|
29
|
+
|
|
30
|
+
result = PipelineResult.model_validate(result)
|
|
31
|
+
return {
|
|
32
|
+
"schema_version": "1.0",
|
|
33
|
+
"pipeline_id": result.pipeline_id,
|
|
34
|
+
"contract_sha256": result.execution.contract_sha256
|
|
35
|
+
if result.execution is not None
|
|
36
|
+
else None,
|
|
37
|
+
"spec_sha256": result.spec_sha256,
|
|
38
|
+
"engine": result.spec.execution.engine,
|
|
39
|
+
"family": result.spec.execution.contract.family,
|
|
40
|
+
"complete": result.complete,
|
|
41
|
+
"cancelled": result.cancelled,
|
|
42
|
+
"exit_code": result.exit_code,
|
|
43
|
+
"elapsed_seconds": result.elapsed_seconds,
|
|
44
|
+
"execution_state": result.execution.state
|
|
45
|
+
if result.execution is not None
|
|
46
|
+
else "unavailable",
|
|
47
|
+
"stages": [stage.model_dump(mode="json") for stage in result.stages],
|
|
48
|
+
"R": {"decision": result.hybrid.r.decision, "complete": result.hybrid.r.complete},
|
|
49
|
+
"M": {"decision": result.hybrid.m.decision, "complete": result.hybrid.m.complete},
|
|
50
|
+
"B": {
|
|
51
|
+
"decision": result.detection.b[0].result.decision,
|
|
52
|
+
"complete": result.detection.b[0].result.complete,
|
|
53
|
+
}
|
|
54
|
+
if result.detection is not None
|
|
55
|
+
else None,
|
|
56
|
+
"H": {"decision": result.hybrid.result.decision, "complete": result.hybrid.result.complete},
|
|
57
|
+
"comparison": {
|
|
58
|
+
"state": result.comparison.state,
|
|
59
|
+
"complete": result.comparison.complete,
|
|
60
|
+
"reference": result.spec.comparison_reference,
|
|
61
|
+
}
|
|
62
|
+
if result.comparison is not None
|
|
63
|
+
else None,
|
|
64
|
+
"detector_bundle_sha256": result.detection.bundle_sha256
|
|
65
|
+
if result.detection is not None
|
|
66
|
+
else None,
|
|
67
|
+
"scientific_equivalence_confirmed": False,
|
|
68
|
+
"scope": "Operational evidence only; no oracle review or scientific label.",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def pipeline_html_v1(summary: dict[str, object]) -> str:
|
|
73
|
+
"""No scripts, remote assets, user URLs or raw HTML; every supplied value is escaped."""
|
|
74
|
+
body = html.escape(json.dumps(summary, ensure_ascii=False, allow_nan=False, indent=2))
|
|
75
|
+
rows = ""
|
|
76
|
+
for name in ("R", "M", "B", "H"):
|
|
77
|
+
component = summary.get(name)
|
|
78
|
+
if isinstance(component, dict):
|
|
79
|
+
decision = html.escape(str(component.get("decision", "unavailable")))
|
|
80
|
+
complete = html.escape(str(component.get("complete", False)))
|
|
81
|
+
else:
|
|
82
|
+
decision, complete = "not requested / unavailable", "False"
|
|
83
|
+
rows += f"<tr><th scope='row'>{name}</th><td>{decision}</td><td>{complete}</td></tr>"
|
|
84
|
+
stage_rows = ""
|
|
85
|
+
stages = summary.get("stages")
|
|
86
|
+
if isinstance(stages, list):
|
|
87
|
+
for stage in stages:
|
|
88
|
+
if isinstance(stage, dict):
|
|
89
|
+
stage_rows += (
|
|
90
|
+
"<tr>"
|
|
91
|
+
+ "".join(
|
|
92
|
+
"<td>" + html.escape(str(stage.get(key, ""))) + "</td>"
|
|
93
|
+
for key in ("stage", "state", "code", "elapsed_seconds")
|
|
94
|
+
)
|
|
95
|
+
+ "</tr>"
|
|
96
|
+
)
|
|
97
|
+
return (
|
|
98
|
+
'<!doctype html><html lang="en"><meta charset="utf-8">'
|
|
99
|
+
'<meta http-equiv="Content-Security-Policy" '
|
|
100
|
+
"content=\"default-src 'none'; style-src 'unsafe-inline'; base-uri 'none'\">"
|
|
101
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">'
|
|
102
|
+
"<title>EquiFrame evidence</title>"
|
|
103
|
+
"<style>body{max-width:1000px;margin:3rem auto;padding:0 1rem;"
|
|
104
|
+
"font:16px system-ui;color:#172b3a;background:#f6f8fa}"
|
|
105
|
+
"pre{white-space:pre-wrap;overflow-wrap:anywhere;background:white;padding:1.5rem;"
|
|
106
|
+
"border:1px solid #ccd5df;border-radius:8px}h1{color:#174b57}"
|
|
107
|
+
"table{width:100%;border-collapse:collapse;background:white;margin:1rem 0 2rem}"
|
|
108
|
+
"th,td{padding:.7rem;border-bottom:1px solid #ccd5df;text-align:left}"
|
|
109
|
+
"caption{text-align:left;font-weight:bold;padding:.5rem 0}"
|
|
110
|
+
"@media print{body{margin:0;background:white}details{display:block}}</style>"
|
|
111
|
+
"<h1>EquiFrame · Operational evidence</h1>"
|
|
112
|
+
"<p>Completion, alerts and equivalence are separate results. "
|
|
113
|
+
"An unavailable component never means absence of alert.</p>"
|
|
114
|
+
"<table><caption>Independent decisions</caption><thead><tr><th>Method</th>"
|
|
115
|
+
"<th>Decision</th><th>Complete</th></tr></thead><tbody>" + rows + "</tbody></table>"
|
|
116
|
+
"<table><caption>Stage trace</caption><thead><tr><th>Stage</th><th>State</th>"
|
|
117
|
+
"<th>Code</th><th>Seconds</th></tr></thead><tbody>" + stage_rows + "</tbody></table>"
|
|
118
|
+
"<details open><summary>Full operational summary</summary><pre>"
|
|
119
|
+
+ body
|
|
120
|
+
+ "</pre></details></html>"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def migration_legacy_summary_v1(result: MigrationResult) -> dict[str, object]:
|
|
125
|
+
from equiframe.migration.runner_types import MigrationResult
|
|
126
|
+
|
|
127
|
+
result = MigrationResult.model_validate(result)
|
|
128
|
+
return {
|
|
129
|
+
"schema_version": "1.0",
|
|
130
|
+
"migration_id": result.migration_id,
|
|
131
|
+
"source_engine": result.spec.source_engine,
|
|
132
|
+
"target_engine": result.spec.target_engine,
|
|
133
|
+
"complete": result.complete,
|
|
134
|
+
"state": result.state,
|
|
135
|
+
"independent_verification": result.verification,
|
|
136
|
+
"expected_requested": result.expected_requested,
|
|
137
|
+
"detection_requested": result.detection_requested,
|
|
138
|
+
"rules_required": result.spec.require_rules,
|
|
139
|
+
"exit_code": result.exit_code,
|
|
140
|
+
"cancelled": result.cancelled,
|
|
141
|
+
"stages": [s.model_dump(mode="json") for s in result.stages],
|
|
142
|
+
"source_pipeline_id": result.source.pipeline_id if result.source is not None else None,
|
|
143
|
+
"target_pipeline_id": result.target.pipeline_id if result.target is not None else None,
|
|
144
|
+
"source_exit_code": result.source.exit_code if result.source is not None else None,
|
|
145
|
+
"target_exit_code": result.target.exit_code if result.target is not None else None,
|
|
146
|
+
"difference_records": result.comparison.counts.difference_records
|
|
147
|
+
if result.comparison is not None
|
|
148
|
+
else None,
|
|
149
|
+
"equivalence_is_independent_correctness": False,
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def _comparison_summary_v1(
|
|
154
|
+
result: ComparisonResult | None, *, requested: bool
|
|
155
|
+
) -> dict[str, object]:
|
|
156
|
+
return {
|
|
157
|
+
"state": result.state
|
|
158
|
+
if result is not None
|
|
159
|
+
else "not_evaluable"
|
|
160
|
+
if requested
|
|
161
|
+
else "not_requested",
|
|
162
|
+
"complete": result.complete if result is not None else False,
|
|
163
|
+
"comparison_id": result.comparison_id if result is not None else None,
|
|
164
|
+
"difference_records": result.counts.difference_records if result is not None else None,
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def migration_summary_v1(result: MigrationResult) -> dict[str, object]:
|
|
169
|
+
"""Keep each comparison visible without attributing human validation to C."""
|
|
170
|
+
from equiframe.migration.runner_types import MigrationResult
|
|
171
|
+
|
|
172
|
+
result = MigrationResult.model_validate(result)
|
|
173
|
+
return {
|
|
174
|
+
**migration_legacy_summary_v1(result),
|
|
175
|
+
"expected_output_comparison": result.verification,
|
|
176
|
+
"comparisons": {
|
|
177
|
+
"source_target": _comparison_summary_v1(result.comparison, requested=True),
|
|
178
|
+
"source_expected": _comparison_summary_v1(
|
|
179
|
+
result.verification_source, requested=result.expected_requested
|
|
180
|
+
),
|
|
181
|
+
"target_expected": _comparison_summary_v1(
|
|
182
|
+
result.verification_target, requested=result.expected_requested
|
|
183
|
+
),
|
|
184
|
+
},
|
|
185
|
+
"equiframe_verify": {
|
|
186
|
+
"procedure": "EquiFrame Verify",
|
|
187
|
+
"identifier": "O",
|
|
188
|
+
"status": "not_established",
|
|
189
|
+
"reason": (
|
|
190
|
+
"Migration compares supplied expected content but does not establish "
|
|
191
|
+
"independent expected authorship or collect two independent human reviews."
|
|
192
|
+
),
|
|
193
|
+
},
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def migration_html_v1(result: MigrationResult, *, legacy: bool = False) -> str:
|
|
198
|
+
summary = migration_summary_v1(result)
|
|
199
|
+
rows = "".join(
|
|
200
|
+
"<tr><td>"
|
|
201
|
+
+ escape(s.stage)
|
|
202
|
+
+ "</td><td>"
|
|
203
|
+
+ escape(s.state)
|
|
204
|
+
+ "</td><td>"
|
|
205
|
+
+ escape(s.code)
|
|
206
|
+
+ "</td></tr>"
|
|
207
|
+
for s in result.stages
|
|
208
|
+
)
|
|
209
|
+
return (
|
|
210
|
+
'<!doctype html><html lang="en"><head><meta charset="utf-8">'
|
|
211
|
+
"<title>EquiFrame migration</title></head><body>"
|
|
212
|
+
"<h1>EquiFrame migration</h1><p>"
|
|
213
|
+
+ escape(result.spec.source_engine)
|
|
214
|
+
+ " to "
|
|
215
|
+
+ escape(result.spec.target_engine)
|
|
216
|
+
+ "</p><p>Observed equivalence: "
|
|
217
|
+
+ escape(result.state)
|
|
218
|
+
+ (". Independent verification: " if legacy else ". Supplied expected comparison: ")
|
|
219
|
+
+ escape(result.verification)
|
|
220
|
+
+ ". Complete: "
|
|
221
|
+
+ str(summary["complete"])
|
|
222
|
+
+ ".</p>"
|
|
223
|
+
"<p>Matching native outputs do not establish independent correctness.</p>"
|
|
224
|
+
+ (
|
|
225
|
+
""
|
|
226
|
+
if legacy
|
|
227
|
+
else "<p>EquiFrame Verify (O): not established. Comparing supplied expected "
|
|
228
|
+
"content does not establish independent expected authorship or collect "
|
|
229
|
+
"two independent human reviews.</p>"
|
|
230
|
+
)
|
|
231
|
+
+ "<table><thead><tr><th>Stage</th><th>State</th><th>Code</th></tr></thead><tbody>"
|
|
232
|
+
+ rows
|
|
233
|
+
+ "</tbody></table></body></html>"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def verification_summary_v1(result: VerificationResult) -> dict[str, object]:
|
|
238
|
+
return {
|
|
239
|
+
"schema_version": "1.0",
|
|
240
|
+
"verification_id": result.verification_id,
|
|
241
|
+
"operational_state": result.operational_state,
|
|
242
|
+
"publishable": result.publishable,
|
|
243
|
+
"exit_code": result.exit_code,
|
|
244
|
+
"contract_sha256": result.binding.contract_sha256,
|
|
245
|
+
"binding": result.binding.model_dump(mode="json"),
|
|
246
|
+
"dimensions": {
|
|
247
|
+
name: getattr(result, name).model_dump(mode="json")
|
|
248
|
+
for name in ("inputs", "technical", "integrity", "agreement", "independent_review")
|
|
249
|
+
},
|
|
250
|
+
"expected": [c.model_dump(mode="json") for c in result.expected],
|
|
251
|
+
"rules": [c.model_dump(mode="json") for c in result.rules],
|
|
252
|
+
"rule_evaluations": [r.model_dump(mode="json") for r in result.rule_evaluations],
|
|
253
|
+
"coverage": result.coverage.model_dump(mode="json"),
|
|
254
|
+
"provenance": result.provenance.model_dump(mode="json") if result.provenance else None,
|
|
255
|
+
"expected_source_engine": result.expected_source_engine,
|
|
256
|
+
"elapsed_seconds": result.elapsed_seconds,
|
|
257
|
+
"engines": [
|
|
258
|
+
{
|
|
259
|
+
"output": name,
|
|
260
|
+
"engine": e.plan.engine,
|
|
261
|
+
"versions": [v.model_dump(mode="json") for v in e.versions],
|
|
262
|
+
}
|
|
263
|
+
for name, e in result.executions
|
|
264
|
+
],
|
|
265
|
+
"comparisons": [
|
|
266
|
+
{
|
|
267
|
+
"comparison_id": c.comparison_id,
|
|
268
|
+
"state": c.state,
|
|
269
|
+
"reference": c.reference,
|
|
270
|
+
"counts": c.counts.model_dump(mode="json"),
|
|
271
|
+
"examples": [d.model_dump(mode="json") for d in c.examples],
|
|
272
|
+
"examples_truncated": c.counts.difference_records > len(c.examples),
|
|
273
|
+
"policy": c.spec.policy.model_dump(mode="json"),
|
|
274
|
+
"differences": c.differences.model_dump(mode="json"),
|
|
275
|
+
}
|
|
276
|
+
for c in (*result.comparisons, *result.input_comparisons)
|
|
277
|
+
],
|
|
278
|
+
"identity_authentication": (
|
|
279
|
+
"Declared identities are not authenticated; hashes prove integrity"
|
|
280
|
+
),
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def verification_html_v1(result: VerificationResult) -> str:
|
|
285
|
+
return (
|
|
286
|
+
'<!doctype html><html lang="en"><meta charset="utf-8"><title>EquiFrame Verify</title>'
|
|
287
|
+
"<h1>EquiFrame Verify</h1><pre>"
|
|
288
|
+
+ escape(
|
|
289
|
+
json.dumps(
|
|
290
|
+
verification_summary_v1(result),
|
|
291
|
+
sort_keys=True,
|
|
292
|
+
ensure_ascii=True,
|
|
293
|
+
allow_nan=False,
|
|
294
|
+
separators=(",", ":"),
|
|
295
|
+
)
|
|
296
|
+
)
|
|
297
|
+
+ "</pre></html>"
|
|
298
|
+
)
|