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,333 @@
1
+ # Native relational contracts
2
+
3
+ [API](api.md) · [Semantics](semantics.md) · [Compatibility](compatibility.md)
4
+
5
+ These additions were introduced experimentally in 0.12.0a1 and retained in
6
+ 0.12.0a14. Their admitted public semantics join the stable 0.12.0
7
+ `RelationalPlan` and `migrate` contracts. They do not infer business rules, change comparison policy,
8
+ translate arbitrary code, or calculate one output for conversion into other
9
+ engines. Every supported compiler executes its own native transformation.
10
+ The tests use independently specified literal expectations; dated receipts
11
+ preserve failed attempts and separately establish installed-artifact
12
+ validation. See the existing `get_adapter_capabilities()` and
13
+ `get_stability_register()` records, not a second capability catalogue.
14
+
15
+ ## Literal column names
16
+
17
+ Column references and output names are exact, case-sensitive strings. A name
18
+ such as `*`, `^a$`, a dotted name, or a name containing quotes is a literal
19
+ column name, never a wildcard, regular expression, or SQL expression. Input
20
+ names, projected aliases, group keys, join keys, sort keys, and deduplication
21
+ keys obey the same rule. Names and column order are preserved; a missing name
22
+ is rejected by IR validation rather than expanded into matching columns.
23
+ Backends may use temporary internal names to execute a native operation, but
24
+ these do not appear in captured output. Existing `Text` validation, name
25
+ uniqueness and engine-specific identifier limits still apply: for example,
26
+ DuckDB rejects NUL names and case-insensitively colliding names. This contract
27
+ does not relax those boundaries or the separate table-identifier restrictions.
28
+
29
+ ## Deterministic windows
30
+
31
+ The admitted `Window` node appends `WindowMeasure` outputs to every input
32
+ row. `row_number` starts at 1 in each partition, or in the entire input when
33
+ `partition_by=()`. `lag` and `lead` select the preceding/following row at a
34
+ positive `offset` (default 1), retaining its exact logical type and unit.
35
+ They return NULL outside the partition and preserve NULL values inside it.
36
+ No arithmetic, rounding, default fill or NULL skipping occurs.
37
+
38
+ ```python
39
+ window = ef.Window(
40
+ node_id="history",
41
+ source="events",
42
+ partition_by=("account",),
43
+ by=(ef.SortKey(column="sequence", direction="ascending", nulls="last"),),
44
+ measures=(
45
+ ef.WindowMeasure(
46
+ name="position",
47
+ operation="row_number",
48
+ logical_type=ef.IntegerType(bits=64, signed=True),
49
+ unit="rows",
50
+ ),
51
+ ef.WindowMeasure(
52
+ name="previous_amount",
53
+ operation="lag",
54
+ column="amount",
55
+ logical_type=ef.DecimalType(precision=12, scale=2),
56
+ ),
57
+ ),
58
+ )
59
+ ```
60
+
61
+ NULL partition components group together. Every ordering term declares its
62
+ direction and NULL position; partition-plus-order tuples must be unique.
63
+ `ties="reject"` rejects even identical tied rows, so input order cannot choose
64
+ an arbitrary predecessor. Float partition/order keys remain unsupported.
65
+ `row_number` requires signed64, nonnullable output; lag/lead output is nullable
66
+ even for a nonnullable input. Row multiplicity remains unchanged. Output bag
67
+ order is unspecified until a final `Sort` requests it.
68
+
69
+ Each compiler uses its own native operations. DuckDB materializes the checked
70
+ window before executing later filters/projections, so they cannot prune its
71
+ error guard. See the eager checkpoint contract below.
72
+ Arbitrary frames, rank/dense_rank and window aggregates remain unsupported.
73
+ The additions extended the IR and nested migration/workflow schemas; historical
74
+ node defaults remain unchanged. IR/migration contracts are stable in 0.12.0;
75
+ standalone workflow orchestration retains its separate experimental status.
76
+
77
+ ## Outer joins and cardinality
78
+
79
+ ```python
80
+ join = ef.EquiJoin(
81
+ node_id="joined",
82
+ left="old",
83
+ right="new",
84
+ keys=(ef.JoinKey(left="id", right="id"),),
85
+ how="full",
86
+ null_keys="never",
87
+ cardinality="one_to_one",
88
+ )
89
+ ```
90
+
91
+ `how` admits `inner`, `left`, `right`, `full`, `semi` and `anti`. Joins preserve
92
+ all matching occurrences. Outer joins preserve unmatched occurrences on their
93
+ respective sides; the absent side has NULL in **every** column. Both original
94
+ key columns remain, with `right_prefix="right."` on the right. There is no
95
+ implicit coalescing. The output contract must include both sides except for
96
+ semi/anti joins, which retain only left columns and left multiplicity.
97
+ Unmatched sides must be nullable in the output contract.
98
+
99
+ `cardinality` describes uniqueness of the **input matchable key tuples**:
100
+
101
+ - `one_to_one`: both sides unique
102
+ - `one_to_many`: left unique
103
+ - `many_to_one`: right unique
104
+ - `many_to_many` (historical default): neither side required unique
105
+
106
+ With `null_keys="equal"`, NULL components compare as equal and participate in
107
+ uniqueness. With `"never"`, a tuple with any NULL component cannot match and is
108
+ excluded from cardinality uniqueness checks; it is still retained where outer
109
+ join semantics require it. A duplicate matchable key violates the declaration
110
+ even if the other input is empty or contains no matching key. Violations fail
111
+ execution, rather than producing a successful empty result. This declaration
112
+ is technical input cardinality, not an inferred business relationship.
113
+
114
+ ## Deterministic selection by key
115
+
116
+ ```python
117
+ selection = ef.Deduplicate(
118
+ node_id="latest",
119
+ source="events",
120
+ keys=("event_id",),
121
+ keep="first",
122
+ by=(ef.SortKey(column="revision", direction="descending", nulls="last"),),
123
+ )
124
+ ```
125
+
126
+ The existing `keep="identical"` still requires every column in `keys` and
127
+ forbids `by`. The new `keep="first"` requires a nonempty `by` tuple. Sort
128
+ semantics are applied within each key group, then exactly one row is selected.
129
+ NULL keys group together (`nulls="equal"`); each sort term explicitly declares
130
+ direction and NULL placement. The key-plus-sort tuple must be unique:
131
+ `ties="reject"` rejects all remaining ties, including identical tied rows.
132
+ No original input order or engine-dependent arbitrary first row resolves ties.
133
+ Input permutation therefore preserves the selected bag. Output sequence is
134
+ unspecified unless a final `Sort` establishes the order required by the contract.
135
+ Float key/tiebreak columns remain unsupported.
136
+
137
+ ## Exact integer operations
138
+
139
+ Integer `add`, `subtract` and `multiply` accept signed and unsigned 8/16/32/64-bit
140
+ operands of identical type with matching explicit units. The declared integer
141
+ output must contain the actual result; overflow and unsigned underflow fail.
142
+ NULL operands propagate. Integer division remains unsupported.
143
+
144
+ Pandas operates on native object-backed integer Series, DuckDB widens operands
145
+ to native HUGEINT and checks its final cast, and Polars uses exact native
146
+ Decimal(38,0) coefficients. Polars proves a bound before multiplying because
147
+ UInt64 squared can exceed both signed Int128 and Decimal128. Every integer
148
+ result is checked against its exact destination bounds before the native cast.
149
+ No floating intermediate or cross-engine result fallback is used. DuckDB checks
150
+ each nested arithmetic expression before a containing predicate or later node.
151
+
152
+ ## Exact Decimal operations
153
+
154
+ `Arithmetic(operation="multiply", left=..., right=..., logical_type=..., unit=...)`
155
+ admits Decimal operands with potentially different declared precision/scale.
156
+ The result scale must equal the sum of operand scales, at most 38; each operand
157
+ precision must be at most 38. Each actual intermediate result must fit its declared
158
+ precision. There is no implicit rounding. The explicit result `unit` declares
159
+ the intended product unit; the compiler does not infer dimensional algebra.
160
+ Existing add/subtract retain identical types/units, scale and a guard digit.
161
+
162
+ Polars multiplies native Decimal(38,0) coefficients and reconstructs the declared
163
+ summed scale with native string kernels. Before multiplying it checks each
164
+ non-NULL coefficient pair against `floor((10**precision - 1) / abs(right))`.
165
+ Zero and NULL operands are handled explicitly. The proof precedes native
166
+ multiplication, so valid large products are admitted without overflowing a
167
+ scaled intermediate. No float intermediate or Python row callback is used.
168
+
169
+ ```python
170
+ rounded = ef.DecimalRound(
171
+ argument=ef.Column(name="amount"),
172
+ logical_type=ef.DecimalType(precision=12, scale=2),
173
+ rounding="half_away_from_zero",
174
+ )
175
+ ```
176
+
177
+ `DecimalRound` requires a Decimal argument and an output scale no greater than
178
+ its input scale. It preserves the argument's unit and NULL values. Overflow
179
+ rejects. `half_away_from_zero` and `half_even` are supported by all three
180
+ compilers. DuckDB uses exact HUGEINT coefficient division for half-even rather
181
+ than its floating `round_even` function. Rounding is a requested transformation,
182
+ never a comparison repair.
183
+ Fixed-scale Arrow output does not preserve a signed zero representation.
184
+
185
+ `Arithmetic(operation="divide", ..., logical_type=DecimalType(...), unit=...,
186
+ rounding="exact" | "half_even" | "half_away_from_zero")` uses independent
187
+ native implementations in all three engines. Pandas uses isolated Decimal
188
+ arithmetic; Polars uses native Decimal(38,0) coefficient arithmetic with Int128
189
+ quotient/remainder kernels, and DuckDB uses native HUGEINT
190
+ SQL quotient/remainder steps. `exact` is the default and rejects any
191
+ nonrepresentable quotient at the declared scale, including recurring fractions.
192
+ The explicit rounding modes quantize to the declared scale. A non-NULL
193
+ zero denominator fails; NULL operands propagate without evaluating a division
194
+ by zero. Integer division is unsupported. Polars and DuckDB support Decimal
195
+ coefficients through precision 38. Modular doubling keeps each remainder below
196
+ its denominator without an overflowing multiplication by ten; scale adjustment
197
+ takes at most 76 native digit steps. No FLOAT/DOUBLE intermediate, Python row UDF
198
+ or common-result fallback is used. DuckDB materializes each fallible expression
199
+ before a containing expression or later relational node can discard it.
200
+ Large inputs and scales can require
201
+ substantial working memory/time; cooperative budgets are not hard RSS quotas.
202
+
203
+ ## Timestamp transport and DST localization
204
+
205
+ `TimestampType(unit=..., timezone=...)` is admitted in IR columns with the
206
+ existing matching `TimestampPolicy` on their input/output contracts. Units are
207
+ physical `s`, `ms`, `us` and `ns`. Transport does not silently change unit/zone:
208
+
209
+ All three compilers transport all four units with naive or named-zone timestamps.
210
+ Polars and DuckDB IR transformations explicitly encode timestamps as unchanged
211
+ signed int64 ticks before native execution and restore their unit/zone metadata
212
+ through Arrow buffer views. A decoder requires actual int64 output; it never
213
+ casts floats or incorrectly typed output to fit a contract. Each engine still
214
+ executes its own operations. The codec is not a common ETL result or an implicit
215
+ comparison repair. Native input/output types and the encoding are recorded.
216
+
217
+ This encoding belongs to compiler-generated IR transformations. General trusted
218
+ callbacks/SQL keep their existing native unit, zone, range and infinity-sentinel
219
+ restrictions. Non-NULL timestamp literals, calendar arithmetic and implicit
220
+ zone/unit conversion have not been added.
221
+
222
+ ```python
223
+ localized = ef.LocalizeTimestamp(
224
+ argument=ef.Column(name="local_time"),
225
+ timezone="America/New_York",
226
+ ambiguous="earlier",
227
+ nonexistent="reject",
228
+ )
229
+ ```
230
+
231
+ `LocalizeTimestamp` interprets a **naive** timestamp as local wall time in the
232
+ named IANA zone, preserving its physical unit and NULLs. The output contract
233
+ must declare that zone and the same unit. `ambiguous="reject"` rejects a fold;
234
+ `"earlier"` and `"later"` choose the earlier/later UTC instant, independent of
235
+ whether a provider labels an offset as daylight time. Gaps always reject;
236
+ `nonexistent="reject"` is the only admitted value. Already-zoned input is
237
+ rejected: conversion between zones is a different operation.
238
+
239
+ The implementations remain independent: pandas uses its native datetime route,
240
+ Polars computes UTC candidates with native expressions from the installed
241
+ `tzdata` TZif metadata, and DuckDB uses native ICU round trips. The Polars
242
+ compiler records the exact resource hashes in its compilation identity and
243
+ rejects a resource changed before execution. Explicit transition intervals and
244
+ POSIX continuation rules retain historical second offsets, negative daylight
245
+ adjustments and fractional-hour zones. Only timezone metadata is read in Python;
246
+ table rows, candidate counts and fold selection stay in Polars. Gaps, rejected
247
+ folds and output overflow remain errors, including inside a branch or filter.
248
+
249
+ DuckDB
250
+ enumerates every integral-second UTC offset within the IR's maximum one-day
251
+ displacement, counts matching instants and applies the requested policy. Integer
252
+ quotient/remainder preserves nanosecond residuals, including negative ticks.
253
+ The search includes half-hour folds, historical second offsets and skipped days;
254
+ it does not accept ICU's default fold/gap choice without checking it.
255
+
256
+ pandas rejects an int64-min localization input because its datetime route
257
+ reserves that tick for NaT. Polars seconds localization uses a checked native
258
+ millisecond view and rejects if it cannot preserve the value exactly. Polars and
259
+ pandas check NULL masks and tick displacement in an overflow-safe numeric domain
260
+ before a containing predicate can prune a bad value. DuckDB guards its native
261
+ microsecond calendar range with a two-day margin and checks final int64 ticks;
262
+ its search itself bounds displacement to one day. These localization limits do
263
+ not narrow raw IR tick transport. Native range exceptions remain failures.
264
+
265
+ DuckDB materializes the localized ticks before a containing predicate or later
266
+ relational operation. Searching 172,801 offsets per correlated scalar evaluation can
267
+ be expensive for many distinct wall times; the query uses the configured memory,
268
+ threads and cooperative interruption limits. Those limits are not a hard total
269
+ RSS quota. Provider/version differences remain recorded: agreement on tested
270
+ dates does not prove identical IANA databases in all engines.
271
+
272
+ Polars compiler `product-semantics-v13` adds this TZif route after a revised
273
+ Colombian transition in `tzdata` differed from the bundled Polars timezone
274
+ provider. General Polars callbacks still use their own provider. Updating
275
+ `tzdata` requires revalidation of affected plans; previous wheel results and
276
+ finite campaign coverage do not transfer automatically to a new artifact.
277
+
278
+ ## Eager failures and historical evidence
279
+
280
+ DuckDB compiler v8 produces a private program of native SELECT checkpoints.
281
+ The native adapter consumes each node's entire declared output into Arrow
282
+ before registering it for subsequent nodes. It also evaluates nested arithmetic,
283
+ rounding and localization from the innermost expression outward, before a
284
+ containing predicate can simplify them. Later filters, projections, joins and
285
+ aggregations therefore cannot hide overflow, forbidden ties or cardinality
286
+ errors. Joins and deterministic deduplication retain a separate error-producing
287
+ guard branch, including when the native result is empty.
288
+
289
+ These Arrow tables are in-memory transport from DuckDB back into the same
290
+ private DuckDB connection. No result comes from another engine, Python row UDF
291
+ or a shared database. A native ordinal preserves observed row order across
292
+ scalar expression kernels; join/group/window order remains unspecified until
293
+ `Sort`. Relations are released after their last consumer, including shared
294
+ branches. The connection closes on success, failure, cancellation or timeout.
295
+
296
+ Each materialized intermediate observes the cooperative Arrow byte budget and
297
+ checks cancellation/time; final row limits apply to the final output. Helper
298
+ columns can increase working memory and native checkpoint calls add overhead.
299
+ These checks are not a total process RSS cap or a hard deadline. DuckDB's
300
+ [order-preservation rules](https://duckdb.org/docs/current/sql/dialect/order_preservation)
301
+ explain why expression checkpoints explicitly restore their native ordinal.
302
+
303
+ Compiler-generated programs require `DuckDBAdapter` or an implementation that
304
+ retains that native adapter's program execution. An unrelated adapter is
305
+ rejected before resolving a provider. Replaying only the program's `.sql`
306
+ fails explicitly; it cannot bypass the checkpoints. Ordinary registered trusted
307
+ `DuckDBTransformation` SQL keeps its single SELECT/WITH interface. This changes
308
+ the private compiled IR implementation, not serialized IR/result formats or the
309
+ stable admitted semantic contract.
310
+
311
+ Fallible expressions inside `Coalesce`, `Conditional` or boolean short-circuit
312
+ forms still require preceding projected checkpoints in the common IR validator.
313
+ Integer division, conservative numeric bounds and the type/zone limits above
314
+ remain explicit. Pandas and Polars perform their own native checks. Pandas
315
+ both-empty full joins preserve an explicit typed empty frame. DuckDB Decimal
316
+ multiplication promotes each operand to precision 38 at its own scale before
317
+ multiplying; an outer result cast alone would overflow a DECIMAL(18)
318
+ intermediate for valid large inputs.
319
+
320
+ Format 1.0 readers remain compatible. Newly added historical defaults
321
+ (`many_to_many`, `rounding="exact"`, empty dedup `by` and its fixed tie policy)
322
+ are omitted from serialized old-shaped nodes, preserving existing IR and full
323
+ specification fingerprints. Nondefault new options and new expression kinds
324
+ are serialized and fingerprinted. New compiler implementations use new code
325
+ identities; old stored identities are not recomputed or rewritten.
326
+
327
+ The finite new technical matrix explicitly covers the 144 combinations of six
328
+ join kinds, four cardinalities, two NULL policies and three engines on one
329
+ composite-key fixture, plus 24 engine/unit/zone timestamp transport cells and
330
+ separate literal/migration/overflow/DST/permutation cases. It is not exhaustive
331
+ over all types, timezones, numeric values, plans, directions or interactions.
332
+ The semantic matrix records source execution separately from final installed
333
+ artifact results; neither establishes thesis outcomes or business truth.
@@ -0,0 +1,45 @@
1
+ # Audit options without inventing coverage
2
+
3
+ [API](api.md) · [Compatibility](compatibility.md) · [Private installation](installed-private.md)
4
+
5
+ The developer tool `tools/option_inventory.py`, shipped in the source
6
+ archive, inspects a **normally installed** candidate. Run it from an external
7
+ folder with the target venv's Python and `-I`. Give the exact wheel and source
8
+ commit. It refuses imports outside that environment's site-packages and checks
9
+ every shipped package file against the wheel before recording an inventory.
10
+
11
+ ```bash
12
+ python -I option_inventory.py --wheel equiframe-0.12.4-py3-none-any.whl \
13
+ --commit EXACT_40_CHARACTER_COMMIT --output inventory.json --matrix obligations.json
14
+ ```
15
+
16
+ To include the separate benchmark, install it normally and add `--benchmark
17
+ --benchmark-wheel equiframe_benchmark-0.12.4-py3-none-any.whl`.
18
+ The inspection harness uses only dependencies already declared by the inspected
19
+ packages. It never executes default factories merely to discover their names.
20
+
21
+ The output extends `get_stability_register()` and `get_adapter_capabilities()`;
22
+ it does not replace either with a competing support catalog. It records exports,
23
+ alias identity, function parameters/defaults, Pydantic fields/constraints,
24
+ Literal/Enum alternatives, boolean defaults and both branches, declared methods,
25
+ CLI paths/options and extras. Output/declarative types do not count as executable
26
+ features. Dependencies and operating system/Python are recorded explicitly.
27
+
28
+ Every obligation begins as NOT_RUN. Independent semantic evidence is necessary
29
+ to promote it; a discovered signature, matching schema, import, code mention or
30
+ line-coverage hit is not a correctness test. Defaults and explicit boolean values
31
+ are separate obligations. Numeric/text spaces require declared equivalence and
32
+ boundary classes; there is no exhaustive test of an infinite value space.
33
+
34
+ Execution states:
35
+
36
+ - PASS: the executed case satisfied an independent expected result
37
+ - EXPECTED_REJECTION: the rejection behaved correctly; application data was not accepted
38
+ - FAIL: observed behavior contradicted the expected result
39
+ - BLOCKED: a named external precondition prevented execution
40
+ - NOT_RUN: no qualifying execution receipt is linked
41
+
42
+ Count unique test IDs separately from option rows and repeated compatibility
43
+ runs. Record high-risk finite cross-products and constrained pairwise choices;
44
+ list any uncovered interactions. Do not report 100% behavioral option coverage
45
+ while obligations remain unlinked or their independent expectation is missing.
@@ -0,0 +1,134 @@
1
+ # Explicit outcome gates
2
+
3
+ Object presence, completed execution, observed equivalence, agreement with an
4
+ expected output and authorization to publish are different claims. A returned
5
+ object, empty output, empty alert list or generated report cannot supply the
6
+ missing claim.
7
+
8
+ ## Python migration
9
+
10
+ The original private consolidation changed Python truth testing for the
11
+ operational types inventoried below; 0.12.0 retains that boundary. Previously
12
+ these objects inherited Python's
13
+ ordinary object truthiness: even a failed or unavailable result was truthy. They
14
+ now raise `TypeError` for `bool(result)`, `if result`, `assert result`, `not result`,
15
+ truth-tested `and`/`or` operands and `any`/`all` over result objects, in every state.
16
+ The exception names the type and points to explicit fields or the assertion API.
17
+ `ComparisonResult` and `EquivalenceDecision` already rejected truth testing;
18
+ their existing behavior and messages are preserved.
19
+
20
+ Review callers that used object truthiness for a gate. Replace it with the actual
21
+ required verdict; do not mechanically change an approval predicate into a
22
+ presence check. Existing evidence accepted solely because an object existed
23
+ must be reevaluated from its states, coverage and required dimensions.
24
+
25
+ Only a real optional-presence check should become `result is not None`:
26
+
27
+ ```python
28
+ if pipeline.execution is not None:
29
+ print(pipeline.execution.state) # This merely displays available evidence.
30
+ ```
31
+
32
+ For comparisons, keep the result and use the established gate:
33
+
34
+ ```python
35
+ result = ef.diff(candidate, expected, keys=["id"], work_dir=work_dir)
36
+ report = ef.write_comparison_report(result, report_dir)
37
+ print(report.to_text()) # Reports can be generated for failed/incomplete results.
38
+ ef.assert_equivalent(result) # Raises for different and not_evaluable.
39
+ ```
40
+
41
+ A consumer of an `EquivalenceDecision` uses `.accepted` explicitly. A workflow
42
+ or migration consumer checks the dimensions its policy requires in addition to
43
+ technical completeness. The existing exit codes, defaults and semantic meanings
44
+ are unchanged; the guard introduces no implicit acceptance policy.
45
+
46
+ ## Exact guarded inventory
47
+
48
+ These public classes reject truth testing. No new public base class is exported.
49
+
50
+ | Types | Explicit interpretation |
51
+ |---|---|
52
+ | `ComparisonResult` | `assert_equivalent(result)`, or the explicit state/completeness and guarantee policy |
53
+ | `EquivalenceDecision` | `.accepted`, with guarantee and evidence diagnostics |
54
+ | `ComparisonReport` | Presentation only; gate the original comparison |
55
+ | `ExecutionResult` | `.state`; `completed` only establishes execution completion |
56
+ | `PipelineResult` | `.complete`, `.exit_code`, requested R/M/B/H and comparison outcomes |
57
+ | `MigrationResult` | `.state`, `.verification`, `.complete`, `.exit_code`; peer agreement alone is not expected-output correctness |
58
+ | `CompilationCheck` | `.supported`; support is preflight admission, not execution or equivalence |
59
+ | `VerificationResult` | `.operational_state`, `.publishable`, and explicitly required dimensions; `.publishable` is the existing operational result, not external authorization or scientific review |
60
+ | `VerificationCheck` | `.state`; `not_requested` and `pending_review` are not passed checks |
61
+ | `WorkflowResult` | `.complete`, `.exit_code` and required checkpoints |
62
+ | `WorkflowCheckpoint` | `.state` and the migration's explicit dimensions |
63
+ | `IngestionResult` | `.state`, accepted/quarantined counts and the declared ingestion policy |
64
+ | `RuleEvaluation` | `.decision`, `.complete`, `.coverage`; `no_alert` is not equivalence |
65
+ | `CheckResult` | `.state`, `.complete`, required/applicable coverage |
66
+ | `ProfileResult` | `.complete` and feature availability; a profile makes no approval decision |
67
+ | `DetectorScores` | `.state` and score values; scoring is not classification |
68
+ | `DetectorScoreResult` | `.m.state`, `.b.state`, `.rows`; M and B remain separate |
69
+ | `DetectionResult` | Individual `.m`/`.b` decisions and evaluated coverage |
70
+ | `ComponentDetectionResult` | `.decisions`, `.complete`, `.rows`; an empty batch cannot establish evidence about absent rows |
71
+ | `ComponentDecision` | `.decision`, `.complete`; `not_evaluable` is not `no_alert` |
72
+ | `DetectorDecision` | `.result.decision`, `.result.complete` and score/threshold |
73
+ | `HybridDecision` | `.result.decision`, `.result.complete`; H retains R OR M, not correctness |
74
+ | `RReviewResult` | `.state`, `.complete`, `.reason`; the language-R reviewer is independent of operational rules R, and unavailable R does not establish equivalence |
75
+ | `ReviewedDecision` | `.accepted`, `.result`, `.hybrid`, `.r_review`, optional `.comparison` and `.execution_binding`; existing alerts and comparator disagreement stay visible |
76
+ | `ReductionResult` | `.status`, `.baseline_verified`, `.minimality`; a reduction result is not validation of a transformation |
77
+
78
+ The scope is intentionally explicit. Contracts, options, schemas, artifact
79
+ references, table/profile data, compiled plans, fitted preprocessors/models,
80
+ calibration bundles, provenance declarations and stage bookkeeping keep their
81
+ existing behavior. They are not approval APIs. This change does not make
82
+ `FrozenModel` or every class with a `state` field reject truth testing. Fitted
83
+ bundles still require inspecting model/preprocessor/calibration availability
84
+ before their established inference APIs can be used.
85
+
86
+ `DatasetResult` holds loaded data and an optional owned DuckDB connection; it is
87
+ not an approval result and does not adopt these truth-value guards. Use its
88
+ `pandas`, `frame`, `target` and `info` fields for data access and `close()` or a
89
+ context manager for resource ownership. Successful file loading/conversion does
90
+ not establish equivalence of two independently written transformations.
91
+
92
+ ## Empty and incomplete evidence
93
+
94
+ - Two empty tables can legitimately be equivalent under a contract that permits
95
+ emptiness. This does not prove that the business expected any rows. Require an
96
+ independently expected output or an explicit cardinality rule when appropriate.
97
+ - Ingestion can produce zero accepted records, including an empty completed
98
+ input. Inspect counts and policy; an existing output artifact is not approval.
99
+ - Empty detector batches preserve their existing coverage semantics. They do not
100
+ demonstrate that a required population was evaluated. Check required row coverage
101
+ and each component's availability before interpreting absence of alerts.
102
+ - A comparison report preserves `different` or `not_evaluable`. Successful report
103
+ publication does not change the comparison's verdict.
104
+ - Missing rules, unavailable features, cancelled work and partial detector
105
+ availability retain their explicit states and diagnostics.
106
+
107
+ ## Compatibility and verification
108
+
109
+ The guard is a field-free private Python mixin. Public names, import paths,
110
+ model fields, JSON schema, serialization, versions, canonical bytes and
111
+ fingerprint algorithms are unchanged. Existing persisted documents load into
112
+ the same public models and acquire the safer Python behavior. No historical
113
+ fixture is regenerated to make this change pass.
114
+
115
+ `tests/test_operational_truthiness.py` exercises real positive, discrepant,
116
+ failed, unavailable, empty and cancelled routes, serialization round-trips,
117
+ explicit inventory and unchanged configuration behavior. Existing native,
118
+ verification, evidence, schema/digest and immutable historical-reader tests
119
+ exercise the migrated internal presence checks. The installed executable example
120
+ `equiframe/examples/integrations/outcome_gates.py` demonstrates the gate outside
121
+ the source checkout with the identified wheel and its `arrow` extra.
122
+
123
+ After installing that wheel and its reviewed dependencies, extract and run the
124
+ example in a fresh working directory:
125
+
126
+ ```bash
127
+ python -c "from importlib.resources import files; from pathlib import Path; Path('outcome_gates.py').write_bytes(files('equiframe').joinpath('examples/integrations/outcome_gates.py').read_bytes())"
128
+ python -I -O outcome_gates.py --work-dir outcome-evidence
129
+ ```
130
+
131
+ The script exits zero only after observing the expected assertion verdict for
132
+ all four synthetic cases and verifying that implicit approval is rejected. Its
133
+ checks also run with Python assertions disabled (`-O`). This is software behavior
134
+ validation, not a claim of business correctness or human adoption.