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,255 @@
|
|
|
1
|
+
"""Reusable verification dimensions and declared independent-review provenance."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from typing import Annotated, Literal
|
|
7
|
+
|
|
8
|
+
from pydantic import BeforeValidator, Field, model_validator
|
|
9
|
+
from typing_extensions import Self
|
|
10
|
+
|
|
11
|
+
from equiframe._base import (
|
|
12
|
+
FrozenModel,
|
|
13
|
+
Identifier,
|
|
14
|
+
NonNegativeInt,
|
|
15
|
+
PositiveInt,
|
|
16
|
+
Text,
|
|
17
|
+
_ExplicitOutcome,
|
|
18
|
+
strict_bool_literal,
|
|
19
|
+
)
|
|
20
|
+
from equiframe.comparison_types import ComparisonOptions, ComparisonPolicy, ComparisonResult
|
|
21
|
+
from equiframe.contracts import Contract
|
|
22
|
+
from equiframe.execution_types import ExecutionResult
|
|
23
|
+
from equiframe.pipeline_types import EvidenceFile
|
|
24
|
+
from equiframe.rule_types import RuleEvaluation
|
|
25
|
+
from equiframe.serialization import contract_fingerprint
|
|
26
|
+
from equiframe.types import ArtifactRef, Sha256
|
|
27
|
+
|
|
28
|
+
VerificationState = Literal["passed", "failed", "not_evaluable", "not_requested", "pending_review"]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class VerificationCheck(_ExplicitOutcome, FrozenModel):
|
|
32
|
+
_truth_value_guidance = (
|
|
33
|
+
"inspect .state explicitly; not_requested and pending_review are not approval"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
state: VerificationState
|
|
37
|
+
reason: Text
|
|
38
|
+
output: Identifier | None = None
|
|
39
|
+
comparison_id: Identifier | None = None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class VerificationBinding(FrozenModel):
|
|
43
|
+
"""Content identities; locations and names never authenticate a producer."""
|
|
44
|
+
|
|
45
|
+
contract_sha256: Sha256
|
|
46
|
+
snapshots: tuple[tuple[Identifier, Sha256], ...] = ()
|
|
47
|
+
plan_sha256: Sha256 | None = None
|
|
48
|
+
outputs: tuple[tuple[Identifier, Sha256], ...]
|
|
49
|
+
expected_sha256: Sha256 | None = None
|
|
50
|
+
|
|
51
|
+
@model_validator(mode="after")
|
|
52
|
+
def unique(self) -> Self:
|
|
53
|
+
for entries in (self.snapshots, self.outputs):
|
|
54
|
+
if len({name for name, _ in entries}) != len(entries):
|
|
55
|
+
raise ValueError("verification identities must have unique names")
|
|
56
|
+
return self
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class ExpectedProvenance(FrozenModel):
|
|
60
|
+
expected_sha256: Sha256
|
|
61
|
+
method: Text
|
|
62
|
+
authors: tuple[Text, ...]
|
|
63
|
+
implementation_authors: tuple[Text, ...] = ()
|
|
64
|
+
derived_from_engines: tuple[Identifier, ...] = ()
|
|
65
|
+
independence_declared: bool = False
|
|
66
|
+
|
|
67
|
+
@model_validator(mode="after")
|
|
68
|
+
def unique(self) -> Self:
|
|
69
|
+
if not self.authors or len(set(self.authors)) != len(self.authors):
|
|
70
|
+
raise ValueError("expected authors must be nonempty and distinct")
|
|
71
|
+
return self
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def independently_constructed(self) -> bool:
|
|
75
|
+
return (
|
|
76
|
+
self.independence_declared
|
|
77
|
+
and not self.derived_from_engines
|
|
78
|
+
and not set(self.authors).intersection(self.implementation_authors)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class VerificationReview(FrozenModel):
|
|
83
|
+
packet_sha256: Sha256
|
|
84
|
+
reviewer: Text
|
|
85
|
+
label: Literal["normal", "divergent", "not_evaluable"]
|
|
86
|
+
reason: Text
|
|
87
|
+
recorded_at: Text
|
|
88
|
+
blind_attestation: Annotated[Literal[True], BeforeValidator(strict_bool_literal)]
|
|
89
|
+
|
|
90
|
+
@model_validator(mode="after")
|
|
91
|
+
def date(self) -> Self:
|
|
92
|
+
value = datetime.fromisoformat(self.recorded_at.replace("Z", "+00:00"))
|
|
93
|
+
offset = value.utcoffset()
|
|
94
|
+
if offset is None or offset.total_seconds() != 0:
|
|
95
|
+
raise ValueError("review time requires UTC")
|
|
96
|
+
return self
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class IndependentReviewEvidence(FrozenModel):
|
|
100
|
+
binding: VerificationBinding
|
|
101
|
+
output: Identifier
|
|
102
|
+
packet_sha256: Sha256
|
|
103
|
+
source_document_sha256: Sha256
|
|
104
|
+
reviewers: tuple[Text, Text]
|
|
105
|
+
arbitrator: Text
|
|
106
|
+
assigned_at: Text
|
|
107
|
+
reviews: tuple[VerificationReview, ...] = ()
|
|
108
|
+
resolution: VerificationReview | None = None
|
|
109
|
+
|
|
110
|
+
@model_validator(mode="after")
|
|
111
|
+
def consistent(self) -> Self:
|
|
112
|
+
assigned = datetime.fromisoformat(self.assigned_at.replace("Z", "+00:00"))
|
|
113
|
+
offset = assigned.utcoffset()
|
|
114
|
+
if offset is None or offset.total_seconds() != 0:
|
|
115
|
+
raise ValueError("assignment time requires UTC")
|
|
116
|
+
if len(set((*self.reviewers, self.arbitrator))) != 3:
|
|
117
|
+
raise ValueError("two distinct reviewers and an independent arbitrator required")
|
|
118
|
+
if self.output not in dict(self.binding.outputs):
|
|
119
|
+
raise ValueError("review output must belong to the bound evidence")
|
|
120
|
+
if len(self.reviews) > 2 or len({r.reviewer for r in self.reviews}) != len(self.reviews):
|
|
121
|
+
raise ValueError("at most two distinct primary reviews allowed")
|
|
122
|
+
for r in (*self.reviews, *((self.resolution,) if self.resolution else ())):
|
|
123
|
+
if r.packet_sha256 != self.packet_sha256:
|
|
124
|
+
raise ValueError("review packet identity mismatch")
|
|
125
|
+
if datetime.fromisoformat(r.recorded_at.replace("Z", "+00:00")) < assigned:
|
|
126
|
+
raise ValueError("assignment must precede review")
|
|
127
|
+
if any(r.reviewer not in self.reviewers for r in self.reviews):
|
|
128
|
+
raise ValueError("unassigned primary reviewer")
|
|
129
|
+
if self.resolution:
|
|
130
|
+
if (
|
|
131
|
+
self.resolution.reviewer != self.arbitrator
|
|
132
|
+
or len(self.reviews) != 2
|
|
133
|
+
or self.reviews[0].label == self.reviews[1].label
|
|
134
|
+
):
|
|
135
|
+
raise ValueError("resolution requires assigned arbitrator and disagreement")
|
|
136
|
+
if any(
|
|
137
|
+
datetime.fromisoformat(self.resolution.recorded_at.replace("Z", "+00:00"))
|
|
138
|
+
< datetime.fromisoformat(r.recorded_at.replace("Z", "+00:00"))
|
|
139
|
+
for r in self.reviews
|
|
140
|
+
):
|
|
141
|
+
raise ValueError("resolution must follow both primary reviews")
|
|
142
|
+
return self
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class VerificationCoverage(FrozenModel):
|
|
146
|
+
scope: Literal["complete_output_artifacts"] = "complete_output_artifacts"
|
|
147
|
+
outputs_requested: PositiveInt
|
|
148
|
+
outputs_inspected: NonNegativeInt
|
|
149
|
+
comparisons_requested: NonNegativeInt
|
|
150
|
+
comparisons_completed: NonNegativeInt
|
|
151
|
+
input_execution_evidence_available: bool
|
|
152
|
+
complete: bool
|
|
153
|
+
|
|
154
|
+
@model_validator(mode="after")
|
|
155
|
+
def consistent(self) -> Self:
|
|
156
|
+
if self.outputs_inspected > self.outputs_requested:
|
|
157
|
+
raise ValueError("inspected outputs exceed requested outputs")
|
|
158
|
+
if self.comparisons_completed > self.comparisons_requested:
|
|
159
|
+
raise ValueError("completed comparisons exceed requested comparisons")
|
|
160
|
+
if self.complete and (
|
|
161
|
+
self.outputs_inspected != self.outputs_requested
|
|
162
|
+
or self.comparisons_completed != self.comparisons_requested
|
|
163
|
+
):
|
|
164
|
+
raise ValueError("complete verification requires complete coverage")
|
|
165
|
+
return self
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class VerificationResult(_ExplicitOutcome, FrozenModel):
|
|
169
|
+
_truth_value_guidance = (
|
|
170
|
+
"inspect .operational_state, .publishable and the required verification "
|
|
171
|
+
"dimensions explicitly"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
schema_version: Literal["1.0"] = "1.0"
|
|
175
|
+
verification_id: Identifier
|
|
176
|
+
contract: Contract
|
|
177
|
+
binding: VerificationBinding
|
|
178
|
+
outputs: tuple[tuple[Identifier, ArtifactRef], ...]
|
|
179
|
+
expected_artifact: ArtifactRef | None
|
|
180
|
+
expected_source_engine: Identifier | None = None
|
|
181
|
+
comparison_policy: ComparisonPolicy = ComparisonPolicy()
|
|
182
|
+
comparison_options: ComparisonOptions = ComparisonOptions()
|
|
183
|
+
input_artifacts: tuple[tuple[Identifier, ArtifactRef], ...] = ()
|
|
184
|
+
expected_required: bool = False
|
|
185
|
+
rules_required: bool = False
|
|
186
|
+
review_requested: bool = False
|
|
187
|
+
technical_outcomes: tuple[VerificationCheck, ...] = ()
|
|
188
|
+
provenance: ExpectedProvenance | None
|
|
189
|
+
review_evidence: tuple[IndependentReviewEvidence, ...] = ()
|
|
190
|
+
inputs: VerificationCheck
|
|
191
|
+
technical: VerificationCheck
|
|
192
|
+
integrity: VerificationCheck
|
|
193
|
+
agreement: VerificationCheck
|
|
194
|
+
expected: tuple[VerificationCheck, ...]
|
|
195
|
+
rules: tuple[VerificationCheck, ...]
|
|
196
|
+
coverage: VerificationCoverage
|
|
197
|
+
independent_review: VerificationCheck
|
|
198
|
+
comparisons: tuple[ComparisonResult, ...]
|
|
199
|
+
input_comparisons: tuple[ComparisonResult, ...] = ()
|
|
200
|
+
executions: tuple[tuple[Identifier, ExecutionResult], ...] = ()
|
|
201
|
+
rule_evaluations: tuple[RuleEvaluation, ...] = ()
|
|
202
|
+
started_at: Text
|
|
203
|
+
elapsed_seconds: Annotated[float, Field(ge=0)]
|
|
204
|
+
evidence: tuple[EvidenceFile, ...] = ()
|
|
205
|
+
|
|
206
|
+
@model_validator(mode="after")
|
|
207
|
+
def consistent(self) -> Self:
|
|
208
|
+
if self.binding.contract_sha256 != contract_fingerprint(self.contract):
|
|
209
|
+
raise ValueError("verification contract identity mismatch")
|
|
210
|
+
if self.binding.outputs != tuple((name, ref.sha256) for name, ref in self.outputs):
|
|
211
|
+
raise ValueError("verification output identity mismatch")
|
|
212
|
+
if self.binding.expected_sha256 != (
|
|
213
|
+
self.expected_artifact.sha256 if self.expected_artifact else None
|
|
214
|
+
):
|
|
215
|
+
raise ValueError("verification expected identity mismatch")
|
|
216
|
+
if self.provenance and self.provenance.expected_sha256 != self.binding.expected_sha256:
|
|
217
|
+
raise ValueError("expected provenance identity mismatch")
|
|
218
|
+
if (
|
|
219
|
+
self.expected_source_engine is not None
|
|
220
|
+
and self.provenance is not None
|
|
221
|
+
and self.expected_source_engine not in self.provenance.derived_from_engines
|
|
222
|
+
):
|
|
223
|
+
raise ValueError("expected execution engine dependency must remain recorded")
|
|
224
|
+
if self.binding.snapshots != tuple(
|
|
225
|
+
(name, ref.sha256) for name, ref in self.input_artifacts
|
|
226
|
+
):
|
|
227
|
+
raise ValueError("verification snapshot identity mismatch")
|
|
228
|
+
from equiframe.verify import _validate_dimensions
|
|
229
|
+
|
|
230
|
+
_validate_dimensions(self)
|
|
231
|
+
return self
|
|
232
|
+
|
|
233
|
+
@property
|
|
234
|
+
def operational_state(self) -> VerificationState:
|
|
235
|
+
checks = (
|
|
236
|
+
self.inputs,
|
|
237
|
+
self.technical,
|
|
238
|
+
self.integrity,
|
|
239
|
+
self.agreement,
|
|
240
|
+
*self.expected,
|
|
241
|
+
*self.rules,
|
|
242
|
+
)
|
|
243
|
+
if any(c.state == "failed" for c in checks):
|
|
244
|
+
return "failed"
|
|
245
|
+
if not self.coverage.complete or any(c.state == "not_evaluable" for c in checks):
|
|
246
|
+
return "not_evaluable"
|
|
247
|
+
return "passed"
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def publishable(self) -> bool:
|
|
251
|
+
return self.operational_state == "passed"
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def exit_code(self) -> int:
|
|
255
|
+
return 0 if self.publishable else 1 if self.operational_state == "failed" else 3
|