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,212 @@
1
+ # Revisión independiente con el lenguaje R
2
+
3
+ EquiFrame ofrece `review_with_r`, `finalize_with_r` y
4
+ `finalize_execution_with_r` como una etapa opcional.
5
+ El revisor ejecuta una segunda comparación determinista sobre las dos tablas
6
+ observadas completas y el contrato declarado. Su implementación usa base R;
7
+ no recibe el veredicto del comparador Python ni los scores de los detectores
8
+ como respuesta esperada.
9
+
10
+ Esta independencia de implementación ayuda a encontrar discrepancias entre
11
+ comparadores. No demuestra por sí sola que la migración sea correcta para
12
+ cualquier entrada. Una alerta de Isolation Forest sigue siendo una anomalía,
13
+ no una prueba de diferencia semántica. Replicar el mismo Isolation Forest en
14
+ R no aportaría esa garantía y no forma parte de esta etapa.
15
+
16
+ La letra `r` de `HybridDecision.r` sigue significando **reglas**. El lenguaje R
17
+ no reemplaza reglas, M (Isolation Forest), B (MAD), H (reglas OR M) ni O
18
+ (oráculo científico). El revisor tampoco modifica perfiles, entrenamiento,
19
+ calibración o inferencia de los detectores.
20
+
21
+ ## Uso después de reglas e Isolation Forest
22
+
23
+ R debe estar instalado por el usuario y `Rscript` debe estar en `PATH`, o se
24
+ debe indicar su ejecutable. EquiFrame no instala R ni paquetes R. Sólo necesita
25
+ base R. Los ejemplos siguientes describen llamadas de uso; no son ejecuciones
26
+ ni resultados de validación.
27
+
28
+ ```python
29
+ import equiframe as ef
30
+
31
+ # Valores obtenidos previamente de las API reales:
32
+ # execution: ExecutionResult con la salida capturada que se está analizando.
33
+ # rules_result: RuleEvaluation para la ejecución que se está analizando.
34
+ # profile: ProfileResult producido para esa ejecución.
35
+ # detector_bundle: bundle Isolation Forest previamente ajustado y calibrado.
36
+ # python_comparison: ComparisonResult que conserva esa ejecución en uno de sus lados.
37
+ detection = ef.detect_component(detector_bundle, (profile.vector,), method="isolation_forest")
38
+
39
+ final = ef.finalize_execution_with_r(
40
+ execution,
41
+ rules_result,
42
+ profile,
43
+ detection,
44
+ comparison=python_comparison,
45
+ options=ef.RReviewOptions(rscript="Rscript", timeout_seconds=30.0),
46
+ )
47
+
48
+ # Consultar los componentes y su disponibilidad de forma explícita.
49
+ hybrid_decision = final.hybrid.result
50
+ independent_comparison = final.r_review
51
+ previous_comparison = final.comparison
52
+ comparators_disagree = final.comparison_disagreement
53
+ final_decision = final.result
54
+ may_continue = final.accepted
55
+ execution_binding = final.execution_binding
56
+ ```
57
+
58
+ Ésta es la ruta vinculada: comprueba IDs, huellas de ejecución, contrato,
59
+ artefactos del perfil y el resultado IF de exactamente un vector. Exige que
60
+ la comparación conserve esa ejecución y su salida en uno de sus lados; lee
61
+ directamente los artefactos comprobados de ambos lados. Una asociación inválida
62
+ se rechaza antes de iniciar R, mediante un error de validación. La vinculación
63
+ no autentica al autor humano ni convierte una etiqueta declarada en verdad.
64
+
65
+ `final.execution_binding` es un `RExecutionBinding` con `execution_id`,
66
+ `execution_sha256`, `profile_id`, `profile_sha256`, `detection_sha256` y
67
+ `output_sha256`. Identifica los documentos y la salida realmente asociados por
68
+ el finalizador. El ejemplo no entrena un modelo ni proporciona un bundle ficticio.
69
+
70
+ `finalize_with_r` permanece como API de bajo nivel para quien ya tiene decisiones
71
+ operacionales y observaciones eager. Recibe `rules`, una `ComponentDecision` de
72
+ Isolation Forest, `left`, `right`, `contract` y `comparison` opcional. En esa ruta
73
+ el llamante debe asegurar la asociación de las decisiones; una decisión aislada
74
+ no contiene toda la procedencia de su ejecución. No se declara
75
+ `execution_binding` por deducirla del nombre de una variable.
76
+
77
+ Para solicitar exclusivamente la segunda comparación:
78
+
79
+ ```python
80
+ from equiframe.r_review import review_with_r
81
+
82
+ review = review_with_r(
83
+ pandas_output,
84
+ polars_output,
85
+ contract=diff_contract,
86
+ comparison=python_comparison,
87
+ )
88
+ ```
89
+
90
+ `review_with_r` admite salidas eager de Pandas, Polars, Arrow, `ArrowInput`,
91
+ artefactos locales `ArtifactRef` y `TableArtifact`, y objetos reales
92
+ `DuckDBPyRelation`. Pasar una relación DuckDB autoriza consumir su plan nativo
93
+ una vez por entrada suministrada, con comprobación de tipos y límites de filas
94
+ y bytes. La captura rechaza el exceso de filas, sin truncarlo para aceptar la
95
+ comparación, y conserva una observación separada de la relación. El llamante
96
+ mantiene la propiedad de su conexión, configuración y plan nativo.
97
+ No se infieren cadenas SQL, conexiones, callbacks, lectores consumibles ni
98
+ `LazyFrame` de Polars como entradas tabulares. También puede capturarse DuckDB
99
+ previamente como Arrow/artefacto para revisar una observación ya materializada.
100
+ `comparison` es opcional para la revisión aislada. Cuando se aporta,
101
+ comprueba los SHA-256 de sus artefactos y que todo el transporte observado de
102
+ `left` y `right` corresponda a esos mismos datos antes de enviarlos a R.
103
+ Una especificación distinta bloquea la finalización; el veredicto Python no se
104
+ envía como respuesta esperada.
105
+
106
+ Una revisión vinculada conserva `review.binding`, un `RReviewBinding` con
107
+ `comparison_id`, `left_artifact_sha256` y `right_artifact_sha256`. Una revisión
108
+ aislada sin `comparison` conserva su dictamen, sin inventar ese vínculo.
109
+
110
+ Los modelos de opciones, vínculos y resultados tienen esquemas versionados estables
111
+ `1.0` incluidos en el sdist y como recursos `equiframe/schemas` del wheel:
112
+ `r-review-options`, `r-review-binding`,
113
+ `r-execution-binding`, `r-review-result` y `reviewed-decision`. Su registro
114
+ explícito de compatibilidad conserva la huella del productor actual; no cambia
115
+ los esquemas o recibos anteriores de comparación, reglas y detectores.
116
+ La estabilidad protege la interfaz y los estados de error dentro del subconjunto
117
+ admitido; no acredita precisión científica, todos los contratos posibles ni
118
+ cualquier versión externa de R. Véanse los límites de [compatibilidad](compatibility.md).
119
+
120
+ `contract` acepta `Contract`, `DiffContract` o `ComparisonSpec`; no se inventa
121
+ un esquema o una tolerancia a partir del veredicto anterior. Si se necesita
122
+ inferir un contrato de metadatos, se puede construir antes mediante la API
123
+ existente `infer_diff_contract` y revisar sus políticas.
124
+
125
+ ## Resultado final
126
+
127
+ | Condición | Resultado final |
128
+ |---|---|
129
+ | Alguna regla o M alerta | Se conserva `alert`, aunque R encuentre equivalencia. |
130
+ | El comparador Python previo encuentra diferencias | Se conserva `alert`, aunque R encuentre equivalencia. |
131
+ | R encuentra diferencias o violaciones del contrato | `alert`. |
132
+ | H está completo sin alertas, R termina equivalente y la comparación Python aportada también es completa/equivalente con la misma especificación y artefactos vinculados | `no_alert`, `accepted=True`. |
133
+ | Falta R, hay timeout/cancelación/fallo o una política no está admitida | `accepted=False`; se conserva cualquier alerta y se indica evaluación incompleta. |
134
+ | El comparador Python aportado está incompleto o no es evaluable | `accepted=False`; se conserva cualquier alerta y se indica evaluación incompleta. |
135
+
136
+ El campo `final.r_review.state` mantiene por separado `equivalent`,
137
+ `different` o `not_evaluable`. Una comparación equivalente en R y una alerta
138
+ de M no se cancelan: son observaciones distintas. `final.result.complete`
139
+ requiere disponibilidad de todos los componentes solicitados. `no_alert`
140
+ no se convierte en una etiqueta científica de corrección.
141
+
142
+ `comparison` es opcional. Si se aporta, EquiFrame conserva el documento Python
143
+ completo, comprueba las observaciones y lo combina después de ejecutar R, sin
144
+ usar su veredicto como esperado para R.
145
+ `comparison_disagreement=True` indica que ambos comparadores terminaron con
146
+ dictámenes distintos; no decide cuál tiene razón ni borra diferencias.
147
+ Una especificación diferente también se conserva y bloquea la aceptación.
148
+ Si se omite `comparison`, la finalización mantiene su comportamiento H más R;
149
+ no presupone que antes se ejecutó el comparador Python.
150
+ Esta opción corresponde a `finalize_with_r`; la ruta
151
+ `finalize_execution_with_r` exige una comparación vinculada.
152
+
153
+ ## Alcance exacto del revisor 1.0
154
+
155
+ Se compara contenido completo, sin muestreo y sin decidir igualdad mediante
156
+ hashes. Se mantienen multiplicidad y claves; puede compararse como multiconjunto
157
+ o como secuencia, comprobando las columnas de orden y sus empates. El orden
158
+ de columnas puede ignorarse sólo cuando el contrato lo autoriza. Un índice
159
+ significativo de Pandas exige la autorización explícita existente para ignorarlo
160
+ o su promoción previa a columnas del contrato.
161
+
162
+ Tipos admitidos: booleanos, cadenas UTF-8, enteros de 8–64 bits con signo o
163
+ sin signo, decimales de precisión física admitida por Arrow, flotantes de
164
+ 32/64 bits, fechas `date32` y timestamps. Los enteros y decimales no se
165
+ convierten a `double` en R. Se distingue NULL de NaN; NaN puede ser distinto
166
+ o estar prohibido. Se preservan signos de infinito; cero positivo y negativo
167
+ se comparan según la igualdad numérica exacta existente.
168
+
169
+ Los timestamps zonificados sólo se admiten para comparación de instantes con
170
+ `naive="reject"`; las observaciones ya contienen ticks y zona. Los timestamps
171
+ sin zona se admiten con `naive="preserve"`, sin interpretación local.
172
+ El revisor no resuelve DST ni convierte cadenas de fecha en instantes.
173
+
174
+ Las siguientes configuraciones devuelven `not_evaluable` en esta versión:
175
+
176
+ - Tipos físicos relajados mediante `physical_types="lossless"`.
177
+ - Tolerancias numéricas, redondeo, alias de NULL o NaN convertido a NULL.
178
+ - Restricciones `allowed_values` e intervalos temporales con límites.
179
+ - Resolución de hora local (`assume_zone`), comparación zonificada por hora
180
+ de pared y tipos `date64`.
181
+ - Tipos tabulares anidados o metadatos que la captura fiel actual no admite.
182
+
183
+ Estas limitaciones afectan únicamente a la revisión opcional en R. No
184
+ redefinen ni reducen las capacidades del comparador Python. Una política
185
+ no admitida no se sustituye por otra para obtener una aprobación.
186
+
187
+ ## Recursos, privacidad y procedencia
188
+
189
+ Los límites predeterminados son 100.000 filas por entrada, 256 columnas,
190
+ 64 MiB de captura por entrada y 64 MiB de transporte acumulado, con 30
191
+ segundos cooperativos para la operación y un proceso R terminable. No son
192
+ cuotas duras de memoria RSS: las copias y los objetos de R tienen sobrecoste.
193
+ La captura de un plan nativo DuckDB precede al proceso R y puede bloquear
194
+ dentro del motor; el tiempo cooperativo no constituye una interrupción dura
195
+ de esa captura.
196
+
197
+ El proceso usa `Rscript --vanilla --default-packages=NULL`, una carpeta
198
+ temporal privada por operación y argumentos estructurados sin shell. Los
199
+ archivos temporales se retiran al terminar, incluso ante fallo o cancelación.
200
+ El resultado devuelve versión de R, estado, conteos y SHA-256 del código,
201
+ especificación y transportes observados. Los hashes sirven para identificar
202
+ entradas; R decide con filas completas. No se incluyen valores de las filas
203
+ en los canales de diagnóstico ni se guarda un modelo adicional.
204
+
205
+ La captura Arrow es un límite compartido con el resto del producto: R vuelve
206
+ a implementar comparación y validaciones admitidas, pero no reinterpreta los
207
+ archivos fuente sin pasar por esa captura. Esta independencia es técnica,
208
+ no una revisión humana ni un oráculo de negocio independiente.
209
+
210
+ Referencias del lanzador y transporte: [Rscript](https://www.stat.ethz.ch/R-manual/R-devel/library/utils/html/Rscript.html),
211
+ [readLines](https://www.stat.math.ethz.ch/R-manual/R-devel/library/base/html/readLines.html)
212
+ y [writeLines](https://www.stat.math.ethz.ch/R-manual/R-devel/library/base/html/writeLines.html).
@@ -0,0 +1,248 @@
1
+ # Three reproducible installed recipes
2
+
3
+ [Handbook](index.md) · [Quick start](quickstart.md) · [Semantics](semantics.md)
4
+
5
+ A8 introduced the interpreter-aware builder. It supplies offline kits when
6
+ generated for an identified artifact. Retained 0.11.1 first-use results remain
7
+ historical; they do not validate a new release. Also see the offline wheelhouse guide.
8
+
9
+ These recipes describe an identified EquiFrame **0.12.4** first-use kit, when
10
+ provided, and **the Python minor selected in its manifest (3.10-3.13)**.
11
+ First-use kits are generated separately from the source tree. The kit contains
12
+ the exact wheel, three recipe scripts and their
13
+ pytest fixture, pinned dependencies with hashes, an integrity verifier and
14
+ English guides. No checkout, real dataset, credential, remote SQL, notebook or
15
+ service is required. Inputs are small synthetic fixtures defined in the scripts.
16
+
17
+ The target of a first useful diagnosis within five minutes is **not yet validated
18
+ by human observation**. Automated independent artifact checks are technical
19
+ evidence, not a substitute for that observation. Preserve the kit's unfilled
20
+ `OBSERVATION.md` and record a real attempt separately if you participate.
21
+
22
+ ## 1. Obtain, verify and install the kit
23
+
24
+ Obtain the candidate kit through the authorized private channel. Compare its
25
+ archive SHA-256 with the separately supplied trusted receipt **before extraction**.
26
+ Extract it into a new directory named `kit`, outside the repository, and open a
27
+ terminal there. Keep the kit unchanged: environments, outputs and completed
28
+ observation records belong in sibling directories. The internal manifest detects
29
+ byte changes; it does not authenticate the sender.
30
+
31
+ The commands below show 3.12. If the manifest selects 3.10, 3.11 or 3.13,
32
+ replace each python3.12 command with that exact interpreter; the supplied
33
+ requirements.lock must belong to that same minor.
34
+
35
+ ```bash
36
+ python3.12 --version
37
+ python3.12 verify_kit.py
38
+ python3.12 -m venv ../first-use-env
39
+ ```
40
+
41
+ On Windows use `py -3.12` in place of `python3.12`. Activate the environment:
42
+
43
+ - Linux/macOS shell: `source ../first-use-env/bin/activate`
44
+ - PowerShell: `..\first-use-env\Scripts\Activate.ps1`. If local policy prevents
45
+ activation, use `..\first-use-env\Scripts\python.exe` directly; do not change
46
+ security policy
47
+
48
+ These are platform command conventions, not new platform acceptance results.
49
+ Install into the new environment:
50
+
51
+ ```bash
52
+ python -m pip install --require-hashes --only-binary=:all: -r requirements.lock
53
+ python -m pip install --no-index wheels/equiframe-0.12.4-py3-none-any.whl
54
+ python -m pip check
55
+ python -I -c "import equiframe, sys; print(sys.version); print(equiframe.__file__)"
56
+ ```
57
+
58
+ The **first command deliberately downloads dependencies from your configured pip
59
+ index**; it does not download datasets. Pins and hashes come from the candidate's
60
+ `uv.lock`. The second command installs the verified local EquiFrame wheel with normal
61
+ dependency validation against the already pinned environment.
62
+ The import must resolve to the environment, not the source checkout. Do not add
63
+ `PYTHONPATH` or use an editable install.
64
+
65
+ For offline installation, first obtain a reviewed wheelhouse containing those
66
+ same pinned dependency wheels and add
67
+ `--no-index --find-links /absolute/path/wheelhouse` to the first install command.
68
+ Dependency binaries are not inside the kit. Do not remove hashes or force other
69
+ versions to bypass a failure. If your platform has no compatible wheel, record
70
+ the blocker; this guide does not certify a new platform matrix.
71
+
72
+ A maintainer with the source distribution and built wheel can prepare a kit with
73
+ `python tools/build_first_use_kit.py --python-minor 3.12 --output /absolute/path/new-kit` from the
74
+ core package directory. That builder does not resolve new dependencies or
75
+ publish a package. Consumers continue using only the resulting kit.
76
+
77
+ ## 2. Diagnose a pandas refactor
78
+
79
+ Create a sibling working directory and run all commands below from there:
80
+
81
+ ```bash
82
+ python -c "from pathlib import Path; Path('../first-use-work').mkdir()"
83
+ cd ../first-use-work
84
+ python -I ../kit/refactor_comparison.py --output refactor-different
85
+ ```
86
+
87
+ **Expected process exit: 1**, a deliberate regression. The report identifies:
88
+
89
+ - `different`, complete evaluation and coverage, strategy `key`
90
+ - Key `id="B"`, column `total_cents`
91
+ - Candidate `integer(2001)` versus expected `integer(2000)`, both `int64`
92
+ - Exact numeric policy, declared unit `USD_cent`, one modified row
93
+
94
+ Open `refactor-different/report/report.html` or read `report.txt`. The inspectable
95
+ contract is `refactor-different/contract.json`. The vectorized refactor added one
96
+ cent. Reordered rows are allowed because the contract ignores order and verifies
97
+ a unique key. The reference preserves the earlier implementation and is also
98
+ checked against three literal fixture values. This does not make pandas or a
99
+ historical output a universal oracle.
100
+
101
+ Run the correction **without changing the policy**:
102
+
103
+ ```bash
104
+ python -I ../kit/refactor_comparison.py --case equivalent --output refactor-equivalent
105
+ ```
106
+
107
+ Expected exit 0, `equivalent`. Try the no-key extension:
108
+
109
+ ```bash
110
+ python -I ../kit/refactor_comparison.py --without-key --output refactor-bag
111
+ python -I ../kit/refactor_comparison.py --without-key --case equivalent --output refactor-bag-ok
112
+ ```
113
+
114
+ Expected exits 1 and 0. Without row identity, EquiFrame compares multisets and
115
+ preserves occurrences; it reports excess rows on each side rather than claiming
116
+ which business row changed. Do not deduplicate to make it pass. This fixture's
117
+ integers are small: it does not promise overflow-free int64 arithmetic for
118
+ arbitrary inputs, fractional currency handling or currency conversion.
119
+
120
+ This script displays only its known synthetic values. `--redacted` hides values
121
+ and metadata in the report. Raw captures are temporary unless you supply
122
+ `--work-dir private-refactor`, which retains private snapshots and full differences.
123
+ Keep that directory separate from reports and do not upload it automatically.
124
+ The attached contract still contains synthetic names/units in redacted mode;
125
+ share only `report/` for the minimized view.
126
+
127
+ ## 3. Validate a bounded pandas → DuckDB migration
128
+
129
+ ```bash
130
+ python -I ../kit/migrate_duckdb_recipe.py --case equivalent --output migration-ok
131
+ python -I ../kit/migrate_duckdb_recipe.py --case different --output migration-regression
132
+ python -I ../kit/migrate_duckdb_recipe.py --case not_evaluable --output migration-limited
133
+ ```
134
+
135
+ Expected exits **0, 1 and 3**. The script uses the real `ef.migrate` API with a
136
+ `Scan` + `Aggregate` relational plan. It does not translate arbitrary Python or
137
+ SQL. Both engines execute their native implementation over the same snapshot;
138
+ the literal expected output is captured separately. Decimal type/scale, currency,
139
+ NULL amounts, empty-sum behavior and counts are declared before execution.
140
+
141
+ The negative case deliberately changes empty-sum semantics. The engines may
142
+ therefore be `equivalent` to each other while `verification="divergent"` against
143
+ the unchanged expected output. This is a shared error, not a fabricated
144
+ cross-engine discrepancy.
145
+
146
+ Each output contains `source-target/`, `source-expected/` and `target-expected/`.
147
+ Each directory retains `report.json`, `report.txt`, `report.html` and
148
+ `evidence-manifest.json`, verified before publication is announced. The limited
149
+ case permits one row per comparison; partial counts are not final totals. Do not
150
+ change the expected output to resolve an evaluation limit.
151
+
152
+ Use `--show-synthetic-values` to inspect the literal expected output and both
153
+ captured results. Expected values are EUR: NULL total/2 rows, JPY: 0.00/1 row and
154
+ USD: exact Decimal 1000000000000000.03/3 rows. The negative case changes only EUR
155
+ to 0.00 in both engines. Each comparison against the expected output retains one
156
+ difference. Values are not printed by default and reports are redacted.
157
+
158
+ `--work-dir private-migration` retains inputs, expected output, native outputs,
159
+ configuration and evidence instead of deleting temporary originals. Never mix
160
+ it with the shareable report directory. The DuckDB adapter owns an in-memory
161
+ connection per execution, uses one thread, an explicit 128 MB engine memory
162
+ limit and UTC session setting, then materializes and closes before returning.
163
+ The script rereads and verifies `read_migration` after that return.
164
+
165
+ The recipe does not open a shared DuckDB file, download extensions or execute
166
+ external SQL. The plan has no temporal values: UTC does not prove unsupported
167
+ date operations. Budgets remain cooperative, not a hard process-RSS quota.
168
+ Passing an actual `DuckDBPyRelation` directly to `diff` authorizes bounded native
169
+ materialization; SQL strings and connections are not accepted as data inputs.
170
+ Explicit capture or an adapter remains available when session ownership and SQL
171
+ execution controls are required. The example does not expand the IR or prove all
172
+ possible inputs valid.
173
+
174
+ ## 4. Keep evidence before failing pytest or CI
175
+
176
+ ```bash
177
+ python -I ../kit/assertion_ci.py --case equivalent --output ci-ok
178
+ python -I ../kit/assertion_ci.py --case different --output ci-different
179
+ python -I ../kit/assertion_ci.py --case not_evaluable --output ci-limited
180
+ ```
181
+
182
+ Expected exits **0, 1 and 3**. The script exports and verifies a report **before**
183
+ calling `ef.assert_equivalent`. Failures retain JSON/text/HTML diagnostics and
184
+ `evidence-manifest.json`. `assert result` and `if result` raise `TypeError` in all
185
+ three comparison states.
186
+
187
+ Run the real pytest fixture with a new output location for each selected case:
188
+
189
+ ```bash
190
+ python -I -B -m pytest -q -o addopts= -p no:cacheprovider ../kit/test_assertion_recipe.py -k equivalent
191
+ python -I -B -m pytest -q -o addopts= -p no:cacheprovider ../kit/test_assertion_recipe.py -k different
192
+ python -I -B -m pytest -q -o addopts= -p no:cacheprovider ../kit/test_assertion_recipe.py -k not_evaluable
193
+ ```
194
+
195
+ Pytest exits **0, 1 and 1** respectively. The final two are deliberately failing
196
+ tests, not `xfail`. Reports are under `ci-reports/<case>/`. Repeat from another
197
+ clean working directory that still points to the kit; never overwrite evidence.
198
+ `-B` prevents bytecode being created alongside the fixture. In your real pipeline,
199
+ provide your actual candidate/reference pair rather than a synthetic case
200
+ selector masquerading as validation.
201
+
202
+ Before uploading a report directory, verify the closed inventory with the
203
+ provided script:
204
+
205
+ ```bash
206
+ python -I -c "import runpy; from pathlib import Path; recipe = runpy.run_path('../kit/assertion_ci.py'); recipe['verify_upload_directory'](Path('ci-different'))"
207
+ ```
208
+
209
+ Run this check even if the test fails (`always()` or your CI equivalent). Upload
210
+ only the four verified report files, and preserve the test's nonzero exit status.
211
+ If publication or manifest verification fails, do not publish a success artifact.
212
+ Do not upload `--work-dir`, environments, snapshots, checkout files or raw JSONL.
213
+ This recipe produces a real local artifact; it does not claim a remote CI job ran.
214
+
215
+ ## 5. Change a baseline or contract deliberately
216
+
217
+ There is no automatic "accept all" or reference-update operation. Inspect types,
218
+ values, keys, coverage and policy; fix an erroneous candidate. If a business
219
+ requirement genuinely changed:
220
+
221
+ 1. Preserve the failed report, prior reference and their hashes
222
+ 2. Record the precise change, rationale, responsible person and review
223
+ 3. Create a new contract revision before comparison, changing only the approved
224
+ policy. Generate/review a new expected output independently; never blindly
225
+ copy the failed candidate
226
+ 4. Test success, a real difference, an invalid expected result and interruption
227
+ 5. Version the approved baseline and contract; review the change in a PR
228
+
229
+ `ef.read_diff_contract(result)` returns the inspectable declaration.
230
+ `DiffContract.model_validate_json(...)` rereads a saved declaration, and
231
+ `ef.diff(candidate, expected, contract=declaration)` reuses it exactly. Neither a
232
+ baseline change nor a new tolerance should be silently learned from a failure.
233
+ The recipes do not modify your external baselines.
234
+
235
+ ## Troubleshooting and recording the attempt
236
+
237
+ Use Python 3.12, a clean environment, unchanged pins and fresh directories. Check
238
+ `pip check` and the import location. Exit 1 is expected in deliberate negative
239
+ examples; exit 3 means the requested judgment was not established; exit 2 denotes
240
+ invalid configuration/execution/publication in these scripts. Missing permissions,
241
+ disk space or a finalized report never mean equivalence. Do not publish tracebacks
242
+ containing environment variables or real data.
243
+
244
+ Keep `kit` intact and recheck it with `python ../kit/verify_kit.py`. Save a completed
245
+ copy of `OBSERVATION.md` in the work directory. Record installation time separately,
246
+ include failed attempts and help received, and distinguish engine agreement from
247
+ correctness against a reference. [Semantics](semantics.md) and
248
+ [upgrading](upgrading.md) explain the guarantees and failures in more detail.
@@ -0,0 +1,183 @@
1
+ # Candidate, release, withdrawal and rollback
2
+
3
+ ## Current state
4
+
5
+ The release target is `equiframe 0.12.4` with the exactly paired
6
+ `equiframe-benchmark 0.12.4`, identified by commit and artifact SHA-256.
7
+ Source preparation does not establish validation or publication: verify the
8
+ artifact-bound receipts and official index inventory. The public 0.12.2 tag and
9
+ its receipts retain their original identities. The local 0.12.3 candidate tag
10
+ and checks are also preserved; that candidate was not uploaded. The new patch
11
+ must pass its own checks and must not replace previous candidate bytes.
12
+ On 2026-10-27 the owner authorized manual publication to TestPyPI followed by
13
+ PyPI after the release checks pass. Publication is established by the observed
14
+ index inventories and clean downloaded consumers, not by this guide or a build.
15
+
16
+ Hernán Troya (`htroya`) accepted maintenance, release and security responsibility.
17
+ No response-time SLA or staffed on-call coverage is promised. Accounts, 2FA,
18
+ name control, public documentation and private security intake must be verified
19
+ for the selected target. A name lookup does not reserve a package name.
20
+
21
+ Original project code and accompanying materials use MIT. Each distribution
22
+ carries LICENSE and THIRD_PARTY_NOTICES.md. Vendored CycloneDX schemas retain
23
+ Apache-2.0; the core artifact metadata records the combined licensing terms.
24
+ See [license scope](licensing.md) and [offline wheel installation](installed-private.md).
25
+
26
+ The version-2 release policy separates the nine technical checks from factual
27
+ publication gates: security/dependency review, rights, public documentation,
28
+ account protection, independent artifact review and the owner's current task.
29
+ UNKNOWN, missing or failed gates block publication. A temporary packet binds
30
+ observations to the exact commit, four artifacts, destination and date; it does
31
+ not authenticate a human or grant permission by itself. The inspector never
32
+ uploads files or handles credentials. Publication remains a separate manual act.
33
+
34
+ TestPyPI is a public index with separate accounts. Upload only the four approved
35
+ files, download them again, verify every SHA-256 and exercise a clean consumer.
36
+ Promote those same four bytes to PyPI only after this succeeds. Rebuilding or
37
+ changing a file requires new checks. TestPyPI can use an ephemeral local API token
38
+ supplied through a hidden terminal prompt and revoked after upload. Never store
39
+ that token in a repository, a report or GitHub secrets.
40
+
41
+ On 2026-10-28 the owner explicitly selected local manual publication and
42
+ instructed this release not to use GitHub Actions. A newly prepared OIDC
43
+ workflow was withdrawn before use. GitHub jobs are not a prerequisite for this
44
+ manual release route; no environment or publisher configuration is implied.
45
+
46
+ For each index, use its official endpoint and a uniquely named temporary token
47
+ created under the authenticated maintainer account. Pass it through a native
48
+ hidden terminal prompt, upload only the four identified files, then revoke it.
49
+ Keep the exact artifact hashes, account protection and downloaded-consumer
50
+ checks; changing the authentication route does not waive them. Neither account
51
+ passwords nor API tokens belong in source, reports, GitHub secrets or shell
52
+ history.
53
+
54
+ ## Tagged versions
55
+
56
+ Hatch and `hatch-vcs` derive both distributions from the same reviewed Git tag
57
+ `vMAJOR.MINOR.PATCH`. Runtime versions come from installed distribution metadata.
58
+ There is no second manually maintained runtime version and no silent fallback
59
+ release number. A source archive retains its package metadata so rebuilding the
60
+ sdist does not require Git history. Release checks reject an absent/mismatched
61
+ tag, dirty or noncanonical source, and SCM version overrides.
62
+
63
+ Create the tag only after the complete source revision is reviewed and committed.
64
+ Use a new patch version for documentation or compatible fixes; use a minor
65
+ version for compatible new functionality and explicitly document breaking
66
+ changes. During the `0.x` series, read the compatibility and upgrade guides.
67
+ Never move an existing release tag or replace published TestPyPI/PyPI files.
68
+
69
+ Historical Alpha candidates keep their original results and closed version-1
70
+ policy. STEP10's real-data evaluation, external scientific acceptance and DW
71
+ integration remain separate research/integration deliverables; they are not
72
+ claimed complete by a library release.
73
+
74
+ ## Repeatable candidate checks
75
+
76
+ From the repository root, select the supported Python interpreter for the chosen
77
+ profile. The driver uses each package's exact `uv.lock` and supports the benchmark
78
+ with `--package`:
79
+
80
+ ```sh
81
+ python tools/check_candidate.py \
82
+ --package . --output /absolute/evidence/core-checks.json
83
+ python tools/check_candidate.py \
84
+ --package packages/equiframe-benchmark --output /absolute/evidence/benchmark-checks.json
85
+ ```
86
+
87
+ Each driver checks locked sync, Ruff check/format, mypy, generated assets, the
88
+ full pytest suite, build, strict Twine validation and installed distribution
89
+ consumption. The final distribution stage uses clean environments outside the
90
+ checkout, exercises optional profiles/recipes, runs shipped tests against
91
+ site-packages and compares both rebuilt wheel and sdist bytes. A passing wheel
92
+ rebuild alone is not a claim about every compiler, platform or dependency build.
93
+ Source snapshots, exact artifact hashes and loaded local dependency bytes bind
94
+ the receipts; they do not authenticate the executor. Keep receipts outside the
95
+ source tree so writing them does not change the source being checked.
96
+
97
+ Candidate checks and manifest generation also require every package and local
98
+ dependency file to match its raw Git HEAD blob. A clean `git status` is not
99
+ enough: automatic line-ending or other Git filter conversions can change the
100
+ bytes that a build packages. On Windows, use a fresh checkout with both
101
+ `core.autocrlf=false` and `core.eol=lf`, for example:
102
+
103
+ ```text
104
+ git clone --config core.autocrlf=false --config core.eol=lf <repository> <fresh-directory>
105
+ ```
106
+
107
+ Changing these settings in an existing checkout does not rewrite already converted
108
+ files. Rebuild from the canonical checkout; do not normalize an artifact to make
109
+ the verification pass.
110
+
111
+ After freezing a clean commit, run `tools/release_manifest.py` with the exact
112
+ receipts for both packages. Inspect the independent compatibility matrix, the
113
+ installed consumer report, dependency inventory, licenses and dated advisories,
114
+ source review coverage and all unresolved decisions. Build tools and versions
115
+ must be identical for a byte-for-byte reproduction claim.
116
+
117
+ A candidate directory containing both manifests and their adjacent wheel/sdist
118
+ files can be checked with:
119
+
120
+ ```sh
121
+ python tools/verify_publication.py \
122
+ --candidate-root /absolute/candidate --commit EXACT_40_CHARACTER_SHA \
123
+ --target testpypi --gate-packet /absolute/temporary/testpypi-gates.json \
124
+ --output /absolute/evidence/publication-readiness.json
125
+ ```
126
+
127
+ Malformed, stale, incomplete, linked or substituted artifacts are rejected.
128
+ For the current version-2 policy, exit **0** means that the technical receipts
129
+ and all factual gates for the selected index passed; missing, UNKNOWN or failed
130
+ gates block readiness with a nonzero exit. Use `--target pypi` and a separate
131
+ packet after verifying the TestPyPI downloads and consumer installation.
132
+ Historical version-1 candidates retain their closed policy and exit **3**.
133
+ There is no `--force` or credential switch. The packet does not authenticate
134
+ human approval, and this command never uploads or grants account access.
135
+
136
+ ## Local checks and provenance
137
+
138
+ The public repository ships no GitHub Actions workflows. Run the local candidate
139
+ commands above and retain their exact source identities, environment details,
140
+ exit codes and artifact hashes. A local receipt establishes only the work it
141
+ records; it does not establish that a remote CI job ran. Dependency advisories
142
+ are separate networked checks and retain unknown or unavailable outcomes.
143
+
144
+ Publication is manual with a local ephemeral credential. A future adoption of
145
+ Trusted Publishing requires a separately reviewed protected workflow and real
146
+ publisher configuration; OIDC is not configured or used for this release.
147
+
148
+ ## Correcting a defect
149
+
150
+ 1. Record exact affected commits, wheel hashes, APIs/adapters, policies and
151
+ environments. Preserve a sanitized reproducer and independent expected result
152
+ 2. Assess exposure and whether the claim of equivalence, privacy or evidence
153
+ integrity is affected. Stop recommending the affected route if the judgment
154
+ can be wrong; fail closed rather than silently weakening the contract
155
+ 3. Assign an owner before promising a date. Implement a regression test and
156
+ narrow correction. Re-run the relevant historic corpus, full gates, installed
157
+ recipes and affected compatibility cells on new immutable artifacts
158
+ 4. Refresh SBOM/advisory/license evidence and publish release notes describing
159
+ behavior changes, affected versions, migration and remaining limitations
160
+ 5. Obtain the required release approval and distribute a new version. Public
161
+ artifacts are immutable: do not overwrite a released version with new bytes
162
+
163
+ ## Withdrawal and rollback
164
+
165
+ For a private candidate, stop distributing the affected hash and notify only
166
+ its authorized recipients through verified channels. Keep the incident record
167
+ and superseded evidence so results can be traced. For a future public release,
168
+ the assigned owner must approve the appropriate advisory and index action;
169
+ yanking is a selection signal, not deletion of all installed copies or pinned
170
+ references. Deletion is a separate, potentially irreversible action.
171
+
172
+ Test rollback in a **new environment** using the previous known-good exact
173
+ artifacts and their matching pinned dependencies. Verify hashes, imports,
174
+ `pip check`, recipes and historic evidence readers before replacing a consumer
175
+ service. Rollback must not rewrite user evidence or downgrade a versioned file
176
+ format silently. If a new format is unreadable by the old version, retain the
177
+ new reader in an isolated environment or use an explicitly reviewed migration.
178
+ Never infer compatibility from equal version numbers on private candidates.
179
+ Re-running old tests with a new hash is a new receipt, not a historic result.
180
+
181
+ Official background: [Python packaging](https://packaging.python.org/en/latest/tutorials/packaging-projects/),
182
+ [PyPI attestations](https://docs.pypi.org/attestations/),
183
+ [PyPI yanking](https://docs.pypi.org/project-management/yanking/)