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.
Files changed (170) hide show
  1. equiframe/__init__.py +832 -0
  2. equiframe/__main__.py +3 -0
  3. equiframe/_base.py +84 -0
  4. equiframe/_cli_support.py +125 -0
  5. equiframe/_comparison_io.py +161 -0
  6. equiframe/_dataset_provenance.py +146 -0
  7. equiframe/_decimal.py +22 -0
  8. equiframe/_diff_capture.py +433 -0
  9. equiframe/_doctor.py +123 -0
  10. equiframe/_document_io.py +61 -0
  11. equiframe/_documents.py +79 -0
  12. equiframe/_duckdb_transport.py +149 -0
  13. equiframe/_error_reporting.py +52 -0
  14. equiframe/_first_use.py +102 -0
  15. equiframe/_path_compat.py +26 -0
  16. equiframe/_physical.py +133 -0
  17. equiframe/_r_review.R +308 -0
  18. equiframe/_regular_io.py +167 -0
  19. equiframe/_report_format_v1.py +298 -0
  20. equiframe/_report_io.py +591 -0
  21. equiframe/_stability_inventory.py +1421 -0
  22. equiframe/_timezones.py +257 -0
  23. equiframe/_vendor/cyclonedx/LICENSE +201 -0
  24. equiframe/_vendor/cyclonedx/PROVENANCE.md +18 -0
  25. equiframe/_vendor/cyclonedx/bom-1.6.schema.json +5673 -0
  26. equiframe/_vendor/cyclonedx/jsf-0.82.schema.json +240 -0
  27. equiframe/_vendor/cyclonedx/spdx.schema.json +712 -0
  28. equiframe/adapters.py +454 -0
  29. equiframe/assertion_types.py +122 -0
  30. equiframe/assertions.py +271 -0
  31. equiframe/benchmark.py +101 -0
  32. equiframe/bundle.py +240 -0
  33. equiframe/cli.py +693 -0
  34. equiframe/comparison.py +937 -0
  35. equiframe/comparison_report.py +660 -0
  36. equiframe/comparison_types.py +257 -0
  37. equiframe/compatibility.py +546 -0
  38. equiframe/contracts.py +879 -0
  39. equiframe/datasets.py +1301 -0
  40. equiframe/decision.py +34 -0
  41. equiframe/destinations.py +295 -0
  42. equiframe/detector_types.py +431 -0
  43. equiframe/detectors.py +389 -0
  44. equiframe/diff.py +192 -0
  45. equiframe/diff_contracts.py +523 -0
  46. equiframe/docs/adoption.md +329 -0
  47. equiframe/docs/api.md +534 -0
  48. equiframe/docs/arquitecto.md +143 -0
  49. equiframe/docs/choosing-api.md +84 -0
  50. equiframe/docs/cientifico.md +180 -0
  51. equiframe/docs/compatibility.md +254 -0
  52. equiframe/docs/contributing.md +171 -0
  53. equiframe/docs/dependencies.md +237 -0
  54. equiframe/docs/eda.md +166 -0
  55. equiframe/docs/extending.md +158 -0
  56. equiframe/docs/fast-ci.md +52 -0
  57. equiframe/docs/first-use.md +78 -0
  58. equiframe/docs/index.md +142 -0
  59. equiframe/docs/ingestion.md +145 -0
  60. equiframe/docs/inicio.md +129 -0
  61. equiframe/docs/installed-private.md +97 -0
  62. equiframe/docs/journeys.md +362 -0
  63. equiframe/docs/lectura-conversion.md +211 -0
  64. equiframe/docs/licensing.md +17 -0
  65. equiframe/docs/maintenance.md +233 -0
  66. equiframe/docs/migration-ir.md +333 -0
  67. equiframe/docs/option-audit.md +45 -0
  68. equiframe/docs/outcome-gates.md +134 -0
  69. equiframe/docs/performance.md +263 -0
  70. equiframe/docs/piloto-simulado.md +56 -0
  71. equiframe/docs/quickstart.md +156 -0
  72. equiframe/docs/r_review.md +212 -0
  73. equiframe/docs/recipes.md +248 -0
  74. equiframe/docs/releasing.md +183 -0
  75. equiframe/docs/security.md +218 -0
  76. equiframe/docs/semantics.md +274 -0
  77. equiframe/docs/sqlite-snapshots.md +262 -0
  78. equiframe/docs/universal-sources.md +49 -0
  79. equiframe/docs/universalidad-cinco-pasos.md +107 -0
  80. equiframe/docs/upgrading.md +538 -0
  81. equiframe/duckdb_capture.py +410 -0
  82. equiframe/eda/__init__.py +36 -0
  83. equiframe/eda/__main__.py +3 -0
  84. equiframe/eda/_backend.py +612 -0
  85. equiframe/eda/_inventory.py +49 -0
  86. equiframe/eda/_paths.py +49 -0
  87. equiframe/eda/_report.py +291 -0
  88. equiframe/eda/api.py +535 -0
  89. equiframe/eda/cli.py +102 -0
  90. equiframe/eda/models.py +239 -0
  91. equiframe/errors.py +83 -0
  92. equiframe/evidence.py +375 -0
  93. equiframe/examples/integrations/eda_generic.py +60 -0
  94. equiframe/examples/integrations/outcome_gates.py +94 -0
  95. equiframe/examples/integrations/sqlite_snapshot.py +246 -0
  96. equiframe/examples/integrations/sqlite_snapshot_demo.py +226 -0
  97. equiframe/examples/integrations/universal_first_use.py +296 -0
  98. equiframe/examples/journeys/README.md +31 -0
  99. equiframe/examples/journeys/__init__.py +1 -0
  100. equiframe/examples/journeys/a_pandas_refactor.py +88 -0
  101. equiframe/examples/journeys/b_native_migration.py +181 -0
  102. equiframe/examples/journeys/c_retail_etl.py +460 -0
  103. equiframe/examples/journeys/common.py +136 -0
  104. equiframe/examples/journeys/d_financial_reconciliation.py +96 -0
  105. equiframe/examples/journeys/e_telemetry.py +135 -0
  106. equiframe/examples/journeys/f_data_science_preparation.py +84 -0
  107. equiframe/examples/journeys/g_external_snapshots.py +197 -0
  108. equiframe/examples/journeys/h_portable_ci.py +129 -0
  109. equiframe/examples/journeys/requirements.lock +249 -0
  110. equiframe/examples/journeys/verify_journeys.py +117 -0
  111. equiframe/examples/load_convert_compare.py +85 -0
  112. equiframe/execution.py +355 -0
  113. equiframe/execution_types.py +177 -0
  114. equiframe/ingestion.py +744 -0
  115. equiframe/local.py +126 -0
  116. equiframe/migration/__init__.py +94 -0
  117. equiframe/migration/_physical.py +8 -0
  118. equiframe/migration/backends/_timestamp_transport.py +109 -0
  119. equiframe/migration/backends/duckdb.py +651 -0
  120. equiframe/migration/backends/duckdb_decimal.py +125 -0
  121. equiframe/migration/backends/duckdb_time.py +87 -0
  122. equiframe/migration/backends/pandas.py +544 -0
  123. equiframe/migration/backends/polars.py +915 -0
  124. equiframe/migration/backends/polars_decimal.py +231 -0
  125. equiframe/migration/io.py +198 -0
  126. equiframe/migration/ports.py +96 -0
  127. equiframe/migration/runner.py +545 -0
  128. equiframe/migration/runner_types.py +405 -0
  129. equiframe/migration/types.py +354 -0
  130. equiframe/migration/validation.py +619 -0
  131. equiframe/normalization.py +358 -0
  132. equiframe/pipeline.py +274 -0
  133. equiframe/pipeline_types.py +247 -0
  134. equiframe/ports.py +39 -0
  135. equiframe/preprocessing.py +110 -0
  136. equiframe/profile_types.py +343 -0
  137. equiframe/profiles.py +447 -0
  138. equiframe/py.typed +0 -0
  139. equiframe/r_review.py +706 -0
  140. equiframe/reduction.py +518 -0
  141. equiframe/registry.py +121 -0
  142. equiframe/report_rendering.py +366 -0
  143. equiframe/report_types.py +294 -0
  144. equiframe/reporting.py +26 -0
  145. equiframe/rule_types.py +178 -0
  146. equiframe/rules.py +819 -0
  147. equiframe/schemas/r-execution-binding-1.0.schema.json +46 -0
  148. equiframe/schemas/r-review-binding-1.0.schema.json +28 -0
  149. equiframe/schemas/r-review-options-1.0.schema.json +38 -0
  150. equiframe/schemas/r-review-result-1.0.schema.json +207 -0
  151. equiframe/schemas/reviewed-decision-1.0.schema.json +3394 -0
  152. equiframe/serialization.py +152 -0
  153. equiframe/snapshot.py +296 -0
  154. equiframe/sources.py +283 -0
  155. equiframe/tables.py +459 -0
  156. equiframe/types.py +71 -0
  157. equiframe/values.py +252 -0
  158. equiframe/verification_request.py +35 -0
  159. equiframe/verify.py +799 -0
  160. equiframe/verify_types.py +255 -0
  161. equiframe/workflow.py +510 -0
  162. equiframe/workflow_request.py +38 -0
  163. equiframe-0.12.4.dist-info/METADATA +268 -0
  164. equiframe-0.12.4.dist-info/RECORD +170 -0
  165. equiframe-0.12.4.dist-info/WHEEL +4 -0
  166. equiframe-0.12.4.dist-info/entry_points.txt +3 -0
  167. equiframe-0.12.4.dist-info/licenses/LICENSE +21 -0
  168. equiframe-0.12.4.dist-info/licenses/THIRD_PARTY_NOTICES.md +42 -0
  169. equiframe-0.12.4.dist-info/licenses/tools/cyclonedx/LICENSE +201 -0
  170. 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