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,143 @@
1
+ # Recorrido del arquitecto de datos
2
+
3
+ [Inicio](inicio.md) · [Instalación](installed-private.md) · [Científico](cientifico.md) · [API](api.md)
4
+
5
+ ## Qué resuelve y qué instalar
6
+
7
+ Compara la salida de tu código Python o una consulta SQL con un esperado revisado,
8
+ guarda el diagnóstico y detén la tarea si difiere o no puede evaluarse. El
9
+ orquestador actual conserva transporte, secretos, reintentos y cuotas. No hace
10
+ falta desplegar otro servicio, usar el benchmark o conocer todos los modelos.
11
+
12
+ Instala un wheel identificado con `[duckdb]` para este ejemplo; usa
13
+ `[engines]` para las seis direcciones. Los consumidores anteriores conservan
14
+ su pin de candidato y sus propios recibos. Sigue la receta
15
+ normal de pip y el ensayo separado con lock completo de [installed-private](installed-private.md).
16
+ El candidato vigente declara Python 3.10–3.13. Usa el recibo real de tu minor y
17
+ plataforma; el objetivo ARM64 no se acredita con un ensayo Linux x86_64.
18
+ No uses el antiguo wheel 0.11.1 de la receta histórica ni un homónimo público.
19
+
20
+ Para empezar sin copiar el script, sigue [primer uso por perfil](first-use.md):
21
+ `python -I -m equiframe doctor --extra duckdb` y
22
+ `python -I -m equiframe demo --role architect --output sql-good`.
23
+ La guía completa siguiente sirve para integrar tu propio código y pytest.
24
+
25
+ ## Python y SQL: evidencia antes de rechazar
26
+
27
+ Guarda lo siguiente como `integracion.py` fuera del checkout. Los datos son
28
+ sintéticos: la consulta buena tiene A=10, B=20; la variante cambia B a 21.
29
+ El esperado es literal y no lo calcula DuckDB. La suma por sí sola no es el gate.
30
+
31
+ ```python
32
+ import argparse
33
+ from pathlib import Path
34
+
35
+ import equiframe as ef
36
+ import pyarrow as pa
37
+
38
+ parser = argparse.ArgumentParser()
39
+ parser.add_argument("--output", type=Path, required=True)
40
+ parser.add_argument("--case", choices=("good", "different"), default="good")
41
+ args = parser.parse_args()
42
+ args.output.mkdir(parents=True, exist_ok=False)
43
+ expected = pa.table({"id": ["A", "B"], "quantity": [10, 20]})
44
+ # SQL del propio consumidor, fijo y confiable; valores enlazados, no interpolados.
45
+ snapshot = ef.capture_duckdb(
46
+ "SELECT 'A' AS id, 10::BIGINT AS quantity UNION ALL SELECT 'B', $quantity::BIGINT",
47
+ parameters={"quantity": 20 if args.case == "good" else 21},
48
+ work_dir=args.output / "capture",
49
+ )
50
+ declaration = ef.infer_diff_contract(expected.schema, keys=["id"])
51
+ result = ef.diff(
52
+ snapshot,
53
+ ef.ArrowInput(expected),
54
+ contract=declaration,
55
+ work_dir=args.output / "private",
56
+ )
57
+ report = ef.write_comparison_report(result, args.output / "report")
58
+ print(report.to_text())
59
+ # Solo este fixture sintético autoriza mostrar nombres, clave y valores.
60
+ detail = ef.write_comparison_report(
61
+ result,
62
+ args.output / "synthetic-detail",
63
+ options=ef.ReportOptions(include_values=True, include_metadata=True),
64
+ )
65
+ print(detail.to_text())
66
+ # No capturar esta excepción para continuar una publicación o hacer verde el CI.
67
+ ef.assert_equivalent(result, report_path=args.output / "report" / "report.html")
68
+ ```
69
+
70
+ Ejecuta `python -I integracion.py --output sql-good` y luego
71
+ `python -I -O integracion.py --case different --output sql-different`.
72
+ La primera termina con 0; la segunda con una excepción `EquivalenceDifferentError`
73
+ y salida no cero, después de guardar ambos reportes. Conserva el retorno real.
74
+ La conexión propia ya está cerrada cuando se compara su snapshot desacoplado.
75
+ Un reporte no tiene `assert_equivalent()` ni `summary()`; usa las funciones de la
76
+ [API](api.md). `ComparisonReport.to_text()`, `.to_html()` y `._repr_html_()` sí
77
+ existen; el objeto ComparisonResult se acepta con `ef.assert_equivalent`.
78
+
79
+ Para CI local, conserva el script junto con este test `test_integracion.py`:
80
+
81
+ ```python
82
+ from pathlib import Path
83
+ import subprocess
84
+ import sys
85
+
86
+
87
+ def test_sql_regression(tmp_path):
88
+ script = Path(__file__).with_name("integracion.py")
89
+ subprocess.run(
90
+ [sys.executable, "-I", str(script), "--output", str(tmp_path / "sql")],
91
+ check=True,
92
+ )
93
+ ```
94
+
95
+ `python -m pytest -q test_integracion.py` comprueba la ruta de integración actual.
96
+ Para un proyecto real sustituye la consulta/esperado por versiones autorizadas;
97
+ una regresión real debe hacer fallar el test. Este comando es una prueba local,
98
+ no un job remoto de GitHub Actions. No modifiques el esperado para aprobar el fallo.
99
+
100
+ ## Fijar datos externos y migrar
101
+
102
+ - Ya tienes DataFrames eager/Arrow: `diff`; inspecciona el contrato inferido y
103
+ declara unidades, nulabilidad, claves y tolerancias justificadas del dominio
104
+ - Ya tienes artefactos: `compare` con contrato y `reference="expected"`
105
+ - Necesitas SQL confiable: `capture_duckdb`; no acepta cualquier statement ni
106
+ acredita consistencia entre sistemas diferentes
107
+ - Necesitas un snapshot externo fijo: [G](journeys.md#g-external-snapshot-integration)
108
+ usa `SnapshotDataSource` y cliente inyectado; [SQLite](sqlite-snapshots.md)
109
+ añade un productor DB-API transaccional. El transporte/permiso/retención sigue
110
+ en el consumidor. El mock no acredita el servicio de José
111
+ - Necesitas comparar migración: `check_migration` antes de `migrate`; consulta
112
+ [IR](migration-ir.md) por motor. Los seis pares ordenados se conservan en el
113
+ consumidor existente. No traduce Python/SQL arbitrarios ni convierte una
114
+ única salida para aparentar dos ejecuciones nativas
115
+ - Necesitas cargar localmente: [C](journeys.md#c-retail-csvjson-etl-and-local-parquet)
116
+ muestra esquema, quarantine y checkpoints. En producción, una exclusión
117
+ necesita autorización de dominio; el ejemplo no la concede
118
+
119
+ El consumidor independiente 0.1.9, distribuido aparte con sus datos permitidos,
120
+ provee los programas `reconciliation`, `refactor`, `migration`, `preparation`.
121
+ Su README contiene instalación y CLI exactos. `run --all` verifica 72 casos:
122
+ 24 aceptaciones y 48 rechazos esperados si todo coincide. El cero del arnés
123
+ no acepta los casos defectuosos. Los recibos distinguen cada artefacto y repetición.
124
+
125
+ ## Repetición, publicación y recuperación
126
+
127
+ Una repetición usa nueva carpeta de evidencia; nunca sobreescribe la entrada.
128
+ El destino Parquet local de un solo escritor publica una versión y puntero
129
+ atómico. Misma petición admitida puede ser idempotente; no es una transacción
130
+ remota, concurrencia del DW ni garantía de durabilidad frente a corte eléctrico.
131
+
132
+ Antes del commit, un fallo de validación/escritura conserva la versión anterior.
133
+ Después de cambiar el puntero, un fallo de escritura del recibo puede dejar los
134
+ datos publicados y la aplicación incompleta: consulta el puntero/recibos,
135
+ conserva el fallo y reconcilia el estado antes de reintentar. No declares rollback
136
+ ni éxito por no haber llegado un recibo. El consumidor 0.1.10 documenta y conserva
137
+ `committed_application_incomplete` cuando puede escribir ese diagnóstico.
138
+
139
+ El diagnóstico minimizado no borra los datos de `private/`, capturas ni bundles.
140
+ Comparte únicamente vistas revisadas y autorizadas. Para incidencias, aporta
141
+ versión/hashes/lock, entorno, caso sintético, contrato, esperado y observado;
142
+ [maintenance](maintenance.md) distingue las incidencias públicas de los reportes
143
+ privados de seguridad y confirma al mantenedor responsable. No se promete SLA.
@@ -0,0 +1,84 @@
1
+ # Choose the smallest existing API
2
+
3
+ [Handbook](index.md) · [Inicio en español](inicio.md) · [API reference](api.md) ·
4
+ [Eight runnable journeys](journeys.md)
5
+
6
+ The compatibility facade retains its public models and entry points; the current
7
+ name inventory is `ef.__all__`. Most comparison applications start with `diff`,
8
+ `assert_equivalent` and `comparison_report`; none require
9
+ adopting a new framework. Models, extension ports and advanced services stay
10
+ available under their current imports. No facade names are hidden, renamed or
11
+ deprecated by this navigation guide.
12
+
13
+ ## Start with the object you already have
14
+
15
+ | You have | Use | Inspect or accept |
16
+ |---|---|---|
17
+ | A local JSON, Excel, TXT, XML, CSV, Parquet or GeoJSON file | `load_dataset`, optionally `inspect_dataset` | Original Pandas frame, structure, inferred types and explicit read choices; no semantic approval |
18
+ | A `DatasetResult` currently holding Pandas, Polars or DuckDB data | `convert_dataset(..., target=...)` | Select any of those three destinations, consume the current native frame and preserve the captured source identity; no translated program |
19
+ | Two eager pandas/Polars DataFrames or Arrow tables | `diff` | `read_diff_contract`, then `assert_equivalent` |
20
+ | An explicitly supplied `DuckDBPyRelation` and another admitted native input | `diff` | Authorize bounded consumption of that relation; the caller owns its connection and native plan |
21
+ | Materialized Arrow/Parquet or captured `TableArtifact` | `compare` with explicit `Contract` | `comparison_report`, then `assert_equivalent` |
22
+ | A trusted DuckDB SELECT/WITH and exclusive connection ownership | `capture_duckdb`, then `compare` | Detached capture and explicit contract |
23
+ | A trusted native callback/SQL implementation | `execute` with registry/source | Execution only; compare or Verify separately |
24
+ | A supported relational IR for two engines | `check_migration`, then `migrate` | Separate `.state`, `.verification` and `.complete` |
25
+ | Outputs plus a reviewed expected and provenance | `verify` | Technical agreement, expected, rules and review dimensions |
26
+ | Two admitted eager outputs or explicitly supplied DuckDB relations, with an explicit contract | `review_with_r` | Independent base-R comparison of the captured data; relation submission authorizes bounded native-plan consumption; inspect `.state` and `.complete` |
27
+ | Rules/Isolation Forest decisions and the same observed outputs | `finalize_with_r` | Preserve alerts and comparator disagreement; inspect `.accepted` and component availability |
28
+ | A captured execution, its rules/profile, one-profile IF detection and a comparison including that output | `finalize_execution_with_r` | Verify identities/artifacts and inspect `.execution_binding`, `.accepted` and component availability |
29
+ | CSV/JSON/JSONL with declared source/schema rules | `ingest_file` | Accepted/quarantined records and source/conversion receipts |
30
+ | Several bounded migration checkpoints | `run_workflow` | Stage statuses, failure isolation and expected checks |
31
+ | A pinned external data-client snapshot | `SnapshotDataSource` | Manifest/bytes/schema validation; client owns transport |
32
+ | Finalized migration/Verify/workflow evidence | `export_bundle`, `import_bundle` | Closed-inventory integrity; no ETL reexecution |
33
+
34
+ `diff` compares captured outputs. Supplying an actual `DuckDBPyRelation`
35
+ explicitly authorizes bounded consumption of its native plan; SQL strings,
36
+ connections, consumable readers and Polars lazy frames are not inferred as data.
37
+ `compare` does not infer an arbitrary file's semantics. Native migration is not arbitrary Python
38
+ translation. Prefer the original engine/orchestrator when it already performs the
39
+ work, then compare its materialized result. [API](api.md) is the authoritative
40
+ signature reference; use `inspect.signature(ef.diff)` in the installed interpreter.
41
+
42
+ For file loading, [the file guide](lectura-conversion.md) explains conservative
43
+ inference, Excel sheets, nested records, text choices, optional dependencies and
44
+ DuckDB resource ownership. The [R review guide](r_review.md) lists its exact
45
+ admitted policies, input associations and explicit unavailable states. The R
46
+ language is separate from the letter R used for operational rules.
47
+
48
+ ## Four dimensions that must stay separate
49
+
50
+ 1. **Execution:** `completed` says registered code finished and was captured
51
+ 2. **Agreement:** `equivalent` says outputs satisfy the evaluated contract
52
+ 3. **Expected result:** `correct`/`divergent` concerns a declared reference whose
53
+ business correctness and independence still need justification
54
+ 4. **Evaluability:** every required incomplete/unavailable component stays visible
55
+
56
+ Do not use a report, successful process exit from an integrity reader, empty
57
+ findings, `bool(result)` or engine agreement as a replacement for the intended
58
+ acceptance gate. Anomalies R/M/B/H and independent verification O remain separate.
59
+ Pandas is never an oracle by virtue of being the first engine.
60
+
61
+ ## Integrate into an existing orchestrator
62
+
63
+ Call the Python API from a task that owns a new private output directory, then
64
+ persist its result location and apply the explicit gate before its downstream
65
+ publication step. Bind supplied files/snapshots to fixed identities. A retry gets
66
+ a new run directory; it must not overwrite evidence or silently rerun a callback
67
+ with external effects. Let the orchestrator own its process quotas, timeout,
68
+ secrets, concurrency and retry policy. EquiFrame's budgets are cooperative.
69
+
70
+ For a command task, preserve the CLI command's meaning. Comparison/run commands
71
+ separate 0 accepted/completed scope, 1 differences/alerts, 2 input/preflight errors,
72
+ and 3 failed/partial/unavailable evaluation as documented for that command.
73
+ Integrity readers can return zero for valid evidence describing a failed run;
74
+ apply the stored semantic gate separately. [API CLI reference](api.md) and
75
+ [fast CI](fast-ci.md) show the boundary without requiring a deployed service.
76
+
77
+ ## Discover advanced types without importing engines
78
+
79
+ `ef.get_stability_register()` classifies every public name and supported surface;
80
+ `ef.get_adapter_capabilities()` separates capture, comparison, native execution
81
+ and migration. These descriptions are not admission checks or platform test
82
+ results. `Model.model_fields` and `Model.model_json_schema()` support typed editing
83
+ and inspection. Use the [tested compatibility scope](compatibility.md) and each
84
+ actual preflight/result, rather than treating export count as capability count.
@@ -0,0 +1,180 @@
1
+ # Recorrido del científico de datos
2
+
3
+ [Inicio](inicio.md) · [Instalación](installed-private.md) · [Arquitecto](arquitecto.md) · [Semántica](semantics.md)
4
+
5
+ ## Qué comprobar antes de usar un dataset preparado
6
+
7
+ Una optimización no debe cambiar IDs, cobertura, ausencias, precisión o fechas
8
+ sin un requisito explícito. Compara la preparación candidata con valores
9
+ esperados independientes; guarda diferencias y rechaza los casos no evaluables.
10
+ Equivalencia no demuestra ausencia de sesgo, calidad del modelo o validez científica.
11
+
12
+ Este script necesita un wheel identificado con el extra `[arrow]`.
13
+ La [instalación vigente](installed-private.md) usa resolución normal y `pip check`;
14
+ no requiere pandas, ML, benchmark ni otro orquestador. Las rutas A–H y el
15
+ consumidores identificados por sus propios pins y recibos amplían el ejercicio
16
+ a motores nativos y datos permitidos.
17
+
18
+ Puedes empezar sin copiar código: [primer uso por perfil](first-use.md) explica
19
+ `python -I -m equiframe doctor --extra arrow` y
20
+ `python -I -m equiframe demo --role scientist --output prep-good`.
21
+ El script siguiente permite integrar y modificar una preparación propia.
22
+
23
+ ## Preparación pequeña, precisa e inspeccionable
24
+
25
+ Guarda como `preparacion.py` fuera del checkout. El origen sintético conserva
26
+ IDs textuales, milisegundos decimales y fechas UTC; la transformación expresa
27
+ segundos exactos. El esperado usa literales Decimal e instantes enteros,
28
+ independientes del cálculo y parser de la preparación.
29
+
30
+ ```python
31
+ import argparse
32
+ from datetime import datetime, timedelta
33
+ from decimal import Decimal
34
+ from pathlib import Path
35
+
36
+ import equiframe as ef
37
+ import pyarrow as pa
38
+
39
+ parser = argparse.ArgumentParser()
40
+ parser.add_argument("--output", type=Path, required=True)
41
+ parser.add_argument(
42
+ "--case",
43
+ default="good",
44
+ choices=(
45
+ "good",
46
+ "different",
47
+ "dropped_row",
48
+ "changed_id",
49
+ "time_shift",
50
+ "missing_changed",
51
+ "invalid_expected",
52
+ "not_evaluable",
53
+ ),
54
+ )
55
+ args = parser.parse_args()
56
+ args.output.mkdir(parents=True, exist_ok=False)
57
+ source = [
58
+ ("0001", "1250", "1970-01-01T00:00:00+00:00"),
59
+ ("0002", None, "1970-01-01T00:00:01+00:00"),
60
+ ("0003", "1000000001", None),
61
+ ]
62
+ prepared = [
63
+ {
64
+ "sample_id": identifier,
65
+ "duration_s": Decimal(ms) / Decimal("1000") if ms is not None else None,
66
+ "observed_at": datetime.fromisoformat(at) if at is not None else None,
67
+ }
68
+ for identifier, ms, at in source
69
+ ]
70
+ if args.case == "different":
71
+ prepared[0]["duration_s"] = Decimal("1.251")
72
+ elif args.case == "dropped_row":
73
+ prepared = prepared[:2]
74
+ elif args.case == "changed_id":
75
+ prepared[0]["sample_id"] = "1"
76
+ elif args.case == "time_shift":
77
+ prepared[0]["observed_at"] += timedelta(hours=1)
78
+ elif args.case == "missing_changed":
79
+ prepared[1]["duration_s"] = Decimal("0.000")
80
+ expected = pa.table(
81
+ {
82
+ "sample_id": ["0001", "0002", "0003"],
83
+ "duration_s": pa.array(
84
+ [Decimal("1.250"), None, Decimal("1000000.001")], type=pa.decimal128(16, 3)
85
+ ),
86
+ "observed_at": pa.array([0, 1000000, None], type=pa.timestamp("us", tz="UTC")),
87
+ }
88
+ )
89
+ if args.case == "invalid_expected":
90
+ expected = expected.take([0, 1, 0]) # Clave duplicada: referencia inválida.
91
+ candidate = pa.Table.from_pylist(prepared, schema=expected.schema)
92
+ duration = ef.ColumnSpec(
93
+ name="duration_s",
94
+ logical_type=ef.DecimalType(precision=16, scale=3),
95
+ nullable=True,
96
+ null_policy=ef.NullPolicy(representations=("null",)),
97
+ numeric_policy=ef.NumericPolicy(comparison="exact", unit="seconds"),
98
+ )
99
+ declaration = ef.infer_diff_contract(
100
+ expected.schema,
101
+ keys=["sample_id"],
102
+ column_overrides=(duration,),
103
+ )
104
+ result = ef.diff(
105
+ candidate,
106
+ expected,
107
+ contract=declaration,
108
+ work_dir=args.output / "private",
109
+ options=ef.ComparisonOptions(max_rows=1 if args.case == "not_evaluable" else 100),
110
+ )
111
+ report = ef.write_comparison_report(result, args.output / "report")
112
+ # Solo los datos sintéticos de este ejercicio autorizan esta segunda vista.
113
+ detail = ef.write_comparison_report(
114
+ result,
115
+ args.output / "synthetic-detail",
116
+ options=ef.ReportOptions(include_values=True, include_metadata=True),
117
+ )
118
+ print(detail.to_text())
119
+ print(
120
+ "Estado:", result.state, "completo:", result.complete, "conteos finales:", result.counts.final
121
+ )
122
+ print("Contrato declarado/inferido:", declaration.model_dump_json())
123
+ ef.assert_equivalent(result, report_path=args.output / "report" / "report.html")
124
+ ```
125
+
126
+ Ejecuta `python -I preparacion.py --output prep-good`. Debe aceptar.
127
+ Repite con `--case time_shift --output prep-time`: debe rechazar después de
128
+ conservar el diagnóstico. Cada variante usa una carpeta nueva. También funciona
129
+ con `python -I -O`; el gate no es un `assert` de Python.
130
+
131
+ | Caso | Qué debe observarse | Consecuencia para el dataset |
132
+ |---|---|---|
133
+ | good | equivalent, completo, conteos finales, salida 0 | Conserva el contrato evaluado |
134
+ | different | different; 1.251 frente a 1.250 | Cambio de precisión/valor |
135
+ | dropped_row | different; falta 0003 | Pérdida de cobertura, incluso si era un valor grande |
136
+ | changed_id | different; 1 no es 0001 | Ruptura de identidad y posibles joins posteriores |
137
+ | time_shift | different; desplazamiento de una hora | Cambia ventana temporal y posibles features |
138
+ | missing_changed | different; NULL no es cero | Cambia significado de ausencia |
139
+ | invalid_expected | not_evaluable; clave duplicada | Reparar/revisar la referencia antes de aceptar |
140
+ | not_evaluable | not_evaluable; presupuesto de una fila | No hay evaluación completa; no usar recuentos parciales como totales |
141
+
142
+ Las siete variantes negativas terminan con excepción y retorno no cero. No se
143
+ capturan para aprobar datos. Orden de filas ignorado por clave verificada; si tu
144
+ tarea depende del orden usa `sequence_contract` con sus restricciones explícitas.
145
+ La inferencia no sabe qué IDs/unidades/ausencias son válidos en tu dominio:
146
+ inspecciona `.provenance` y sustituye los defaults con ColumnSpec/contrato revisados.
147
+ No relajes precisión, rellenes NULL o elimines duplicados tras ver el fallo.
148
+
149
+ ## Consola, HTML y notebook
150
+
151
+ `report/` es la vista minimizada por defecto. `synthetic-detail/report.html` y
152
+ el texto mostrado contienen la misma decisión, clave/columna y valores del
153
+ fixture. En un notebook puedes ejecutar el mismo script con argumentos o
154
+ conservar `result` y mostrar `detail` como última expresión: `_repr_html_()`
155
+ renderiza el mismo modelo. No hace falta leer manualmente toda la evidencia JSON.
156
+
157
+ `difference_records` cuenta registros de diferencia, no siempre filas distintas.
158
+ Los ejemplos mostrados son acotados; las diferencias completas y capturas siguen
159
+ en evidencia privada. Si un conteo es parcial/desconocido no es cero definitivo.
160
+ Una vista minimizada no anonimiza ni elimina los originales. No actives la vista
161
+ detallada con datos reales sin permiso para la audiencia prevista.
162
+
163
+ ## Continuar con evidencia existente
164
+
165
+ - [F](journeys.md#f-data-science-preparation-without-anomaly-claims): preparación
166
+ pandas, pérdida de fila/cambio de ID y clipping indebido
167
+ - [E](journeys.md#e-telemetry-zones-missingness-duplicates-and-order): NULL distinto
168
+ de NaN, multiplicidad, orden e instantes de nanosegundos. Captura/comparación
169
+ no equivale a admisión de todo plan temporal por todos los motores
170
+ - Consumidor independiente 0.1.9: `run --program preparation --variant good
171
+ --source pandas --target duckdb --data RUTA_DATOS --output CARPETA_NUEVA`;
172
+ su variante `defect` desplaza una hora. Usa su README para instalarlo. Incluye
173
+ datos USGS permitidos, esperado stdlib y seis direcciones, sin modelo entrenado
174
+
175
+ Perfiles/detectores, si los necesitas, requieren su propia definición de features,
176
+ normalidad/particiones, ajuste/calibración y soporte. Falta de soporte es
177
+ `not_estimable`/`not_evaluable`, nunca ausencia de alerta. R, M, B, H y O siguen
178
+ independientes y las salidas/etiquetas de otros motores no alimentan el detector.
179
+ Estos ejemplos no acreditan utilidad científica ni adopción humana; el
180
+ [protocolo](adoption.md) exige participantes reales y consentimiento.
@@ -0,0 +1,254 @@
1
+ # Compatibility and stability
2
+
3
+ [Handbook](index.md) · [Upgrading](upgrading.md) · [Dependencies](dependencies.md)
4
+
5
+ ## Admission is not a measured support matrix
6
+
7
+ The 0.12.4 release contract declares **Python `>=3.10,<3.14`**, meaning 3.10, 3.11, 3.12 and
8
+ 3.13. Dependency ranges describe installable admission, not every tested
9
+ combination. A lock captures one resolution; it does not prove every operating
10
+ system, architecture, Python patch or cross-product of package versions works.
11
+
12
+ The recorded STEP6 matrix tested the then-identified wheel on **Debian 13/Linux
13
+ x86_64, CPython 3.12.14 and 3.13.5**, with base, arrow, pandas, polars, duckdb,
14
+ engines, ml and all profiles at minimum-direct and recent-permitted resolutions.
15
+ Later STEP8 revalidation covered Arrow/all on those two interpreters at both
16
+ resolution ends, plus matching benchmark consumers. These are historical receipts
17
+ for their exact artifact hashes, not a blanket validation of every later wheel
18
+ that retains version `0.11.1`.
19
+
20
+ The following implementation checks actually ran from installed A14 artifacts
21
+ on 27 October 2026. They establish the listed workloads, rather than every input
22
+ or dependency combination. The final 0.12.4 artifact must still pass its own
23
+ [release gates](releasing.md); a version change does not inherit an artifact hash.
24
+
25
+ | OS / architecture | CPython actually exercised | Observed functional scope |
26
+ |---|---|---|
27
+ | Windows / x86_64 | 3.10.22 | Files, six native directions, semantic differences, complex ETL, IF/MAD/H and base R |
28
+ | Windows / x86_64 | 3.11.17 | Same functional scope |
29
+ | Windows / x86_64 | 3.12.4 | Same functional scope; the separate elevated symlink checks used 3.12.5 |
30
+ | Windows / x86_64 | 3.13.15 | Same scope, plus final MIT wheel/sdist, clean base/extras consumers and literal README checks |
31
+ | Linux / aarch64 | 3.10.12 | Files, six native directions, semantic differences, complex ETL, IF/MAD/H and base R |
32
+ | Linux / aarch64 | 3.11.17 | Same functional scope |
33
+ | Linux / aarch64 | 3.12.15 | Same functional scope |
34
+ | Linux / aarch64 | 3.13.16 | Same scope, plus final wheel/sdist rebuild and clean installation |
35
+
36
+ The functional scope used eight invented file formats (CSV, TXT, JSON, JSONL,
37
+ XML, Excel XLSX, Parquet and GeoJSON), three native targets and all six directed
38
+ conversions, with at least 30,000 rows per input. Thirty semantic cases combined
39
+ the six directions with equality, exact Decimal changes, NaN/NULL distinction,
40
+ an extra duplicate and declared-order changes. The complex ETL used two inputs
41
+ of 30,000 rows, six native directions, independent expected values and two
42
+ negative cases. ML/R checks kept eight training origins and four calibration
43
+ origins separate, checked IF against scikit-learn and MAD against NumPy, and
44
+ reviewed twelve native equal/different pairs with real R. These counts do not
45
+ establish statistical accuracy or a minimum useful scientific sample size.
46
+
47
+ Base R was 4.3.3 on Windows and 4.1.2 on Linux. The Python 3.13 runs used
48
+ Pandas 3.1.0, Polars 1.44.2, DuckDB 1.5.6, Arrow 25.0.1 and scikit-learn 1.9.1;
49
+ NumPy was 2.4.6 on Windows and 2.5.4 on Linux. Those pins are not asserted for
50
+ the other minors: each interpreter used a compatible resolved closure. This
51
+ matrix does not establish every legacy XLS, JSON nesting or XML dialect variant.
52
+ Windows' two symlink cases subsequently passed with real elevated permissions
53
+ on all four minors (eight passes, no skips); POSIX permission cases ran on Linux.
54
+ Platform-specific skips retain their meaning and are not successful checks.
55
+
56
+ No macOS, PyPy, other architecture, every Python patch or full minimum-to-maximum
57
+ dependency cross-product is established by this table. Earlier STEP6 x86_64
58
+ results above retain their own artifact identities; none is silently relabelled
59
+ as a 0.12.4 final-artifact result.
60
+
61
+ The first-use builder selects an explicit Python minor and its own hash-pinned
62
+ dependency closure. The legacy `requirements.lock` is the 3.12 closure;
63
+ `requirements-py310.lock`, `requirements-py311.lock` and
64
+ `requirements-py313.lock` identify the other minors. Do not treat one closure as
65
+ universal. A generated kit records its selected Python and still needs testing.
66
+
67
+ ## Declared runtime dependencies
68
+
69
+ The installed wheel metadata is authoritative. Current direct ranges are:
70
+
71
+ | Profile | Dependencies |
72
+ |---|---|
73
+ | Base | typing-extensions `>=4.14,<5`; pydantic `>=2.12,<3`; typer `>=0.20,<1`; tzdata `>=2025.2,<2027` |
74
+ | `arrow` | pyarrow `>=23.0.1,<26` |
75
+ | `pandas` | pandas `>=2.2,<4`, excluding `3.0.3`, `3.0.4`, `3.0.5`, `3.0.6`; pyarrow `>=23.0.1,<26` |
76
+ | `polars` | polars `>=1.30,<2`; pyarrow `>=23.0.1,<26` |
77
+ | `duckdb` | duckdb `>=1.5.6,<2`; pyarrow `>=23.0.1,<26` |
78
+ | `engines` | All three engines and the same Arrow range |
79
+ | `files` | The `pandas` and `arrow` dependencies above; openpyxl `>=3.1,<4`; xlrd `>=2,<3`; defusedxml `>=0.7,<1` |
80
+ | `ml` | scikit-learn `>=1.7,<1.10`; numpy `>=2,<3` |
81
+ | `eda` | duckdb `>=1.5.6,<2`; pyarrow `>=23.0.1,<26` |
82
+ | `eda-charts` | The `eda` dependencies plus plotly `>=6.3,<8` |
83
+
84
+ Pandas 3.0.3–3.0.6 are excluded because their `ZoneInfo.from_file` regression
85
+ can replace the supplied timezone rules with a named-key lookup. This can
86
+ silently change historical ETL timestamps even when the locked `tzdata` bytes
87
+ are correct. Pandas documents the correction in
88
+ [3.1.0 (GH 66828)](https://pandas.pydata.org/pandas-docs/version/3.1.0/whatsnew/v3.1.0.html).
89
+ The controlled ARM312 case preserves its original inputs and SDK: 3.0.6 shifts
90
+ three `EET` results by one hour; 3.1.0 passes the same complete native outputs
91
+ and readbacks. That diagnostic does not certify the complete environment matrix.
92
+
93
+ Minimum-direct testing means a mutually resolvable minimum direct set with
94
+ available binary wheels and compatible transitive dependencies. It does not mean
95
+ all historical transitive minima. Binary availability may raise a practical
96
+ minimum on a Python/platform. A recent-permitted resolution is time-bound and
97
+ must be identified by its pins and hashes.
98
+
99
+ Base imports remain light even when extras are installed: importing EquiFrame
100
+ or its compatibility registers does not import Arrow, pandas, Polars, DuckDB,
101
+ NumPy or scikit-learn. The selected operation loads its required dependencies.
102
+ Missing extras fail explicitly with a diagnostic identifying the dependency.
103
+
104
+ `files` supplies local file readers and the initial Pandas DataFrame. Select the
105
+ `polars` or `duckdb` extra as well when that engine is the conversion target.
106
+ `equiframe doctor --extra files` checks installed dependency metadata without
107
+ loading a dataset. The optional language-R reviewer requires a user-installed
108
+ `Rscript` executable and base R; it has no Python installation extra and does not
109
+ install R packages. Its result records the R version actually used.
110
+
111
+ File loading and language-R review were introduced on 26 October and exercised
112
+ in the implementation runs above. Their stabilization commits to the admitted
113
+ interfaces and failure semantics; it does not expand the admitted type/policy
114
+ subsets or reinterpret historical results.
115
+
116
+ Dependency changes require fresh artifact, semantic and environment checks.
117
+ Native engine/Arrow transport and timezone behavior must be rechecked, not just
118
+ `pip check`. ML parity is against the installed scikit-learn version; fitted
119
+ forests, thresholds and decisions are not promised identical across versions.
120
+ See [dependency/SBOM policy](dependencies.md) before upgrading transitives.
121
+
122
+ ## Public-surface classification
123
+
124
+ `get_stability_register()` is exhaustive **for the core**. It separately
125
+ classifies API, CLI, format, evidence, presentation and namespace surfaces.
126
+ `get_adapter_capabilities()` distinguishes capture, comparison, native execution
127
+ and migration; each entry gives conditions, known losses and implementation/test
128
+ references. This descriptive register does not execute a probe or admit a case.
129
+ The four returned capability records name Hernán Troya (`htroya`) as maintainer
130
+ of the internal EquiFrame adapter implementations, with best-effort support as
131
+ described in [maintenance](maintenance.md). This does not assign him maintenance
132
+ of Arrow, Pandas, Polars or DuckDB themselves. Unassigned defaults remain available
133
+ when callers construct their own capability records.
134
+
135
+ | Status | Main surfaces |
136
+ |---|---|
137
+ | Stable | Existing contracts/comparison/native execution/rules/assertions/Verify/bundles; local file reading and conversion; explicit DuckDB capture; closed IR and native pairwise migration, including admitted windows; profiles/preprocessing/IF/MAD/H; optional language-R review and finalization; their explicitly inventoried models/formats |
138
+ | Experimental | Standalone pipeline orchestration, ingestion/destinations/workflows, snapshot providers, pipeline reporting/reduction, first-use demo/doctor and the `equiframe.eda` namespace |
139
+ | Unsupported | Quarantined historical `equiframe.benchmark` helper |
140
+ | Outside this inventory | Private `_...` internals, dependency APIs and the separate benchmark package's experimental API |
141
+
142
+ Stable protects the documented signature and semantic/error contract within
143
+ admitted capabilities. It is not a certification, maturity claim for every
144
+ workflow, service-level guarantee or promise of speed. Experimental code still
145
+ must reject unsafe/unsupported input, preserve historical meaning and report
146
+ failures honestly. Adding an export requires an explicit reviewed classification.
147
+
148
+ Stable CLI includes contract validation, comparison, operational Verify, bundles,
149
+ `dataset load`, `dataset inspect` and migration validate/run/verify. Workflows,
150
+ ingestion, standalone pipeline execution/reporting, evidence inspection,
151
+ reduction and first-use commands retain their experimental classification. Inspect
152
+ the registry for exact command/parameter classifications and exit-code contracts.
153
+ Do not parse help formatting, diagnostic prose, HTML or console colors as a
154
+ machine interface.
155
+
156
+ The stable commitments are deliberately bounded:
157
+
158
+ - File conversion validates complete values, columns/order and admitted types;
159
+ ambiguous sheets, records and delimiters require a choice. Source bytes and
160
+ effective options identify the load; changing a captured frame requires a new
161
+ capture. GeoJSON structures can be transported without making nested geometry
162
+ a supported semantic-comparison type. Local loading is not a path sandbox.
163
+ - IR validation and both native compiler checks define the supported plan.
164
+ Each built-in engine executes independently; no arbitrary program translation, fallback
165
+ engine, implicit rounding or pruning of a failed guard is promised. Successful
166
+ compilation is not equivalence or proof of an expected result's independence.
167
+ - ML interface stability protects fit/calibration/inference separation, feature
168
+ admission and explicit unavailable states. Callers remain responsible for the
169
+ meaning of selected features and normal-training/calibration origins; validating
170
+ a vector is not automatic detection of every possible label encoding or leak.
171
+ Stability does not promise detection power,
172
+ identical trained forests across dependency versions or a throughput target.
173
+ Operational rules R and the external language R remain different components.
174
+ - The R reviewer admits its documented exact policy/type subset and external
175
+ Rscript. The bound finalizer verifies execution/profile/IF/artifact association;
176
+ the low-level finalizer requires the caller to associate component decisions.
177
+ A missing reviewer, unsupported contract or disagreement cannot produce an
178
+ accepted result. Its process timeout excludes earlier Python capture time.
179
+
180
+ `PipelineInput`, `EvidenceFile`, `StageRecord`, `PipelineSpec` and `PipelineResult`, and the
181
+ versioned spec/result formats, are protected shared models because migration
182
+ embeds them. This does not stabilize `run_pipeline`, its standalone request,
183
+ workflow APIs or the private local directory layout. Persisted semantic formats
184
+ and producer layout are separate commitments. The per-name register is decisive.
185
+
186
+ ## Deprecation and persisted-format commitments
187
+
188
+ This prospective policy began with the private STEP6 candidate. It does not
189
+ invent prior public releases or assert a deprecation has already happened.
190
+
191
+ 1. Incompatible stable changes require a changelog notice naming affected
192
+ surfaces, reason, replacement, migration example and earliest removal date/version
193
+ 2. Callable Python entry points emit `DeprecationWarning`; CLI invocations warn
194
+ on stderr without changing exit status merely because of the warning
195
+ 3. Removal waits for **both at least two subsequent minor releases and 90 days**.
196
+ A notice in 0.12 cannot be removed before 0.14 or day 90; time alone is insufficient
197
+ 4. During 0.x, a stable removal occurs in an announced minor; from 1.x it requires
198
+ a major, with the same notice window. Patch releases normally preserve it
199
+ 5. Experimental changes can occur in a minor with changelog and migration in that
200
+ release. Downgrading a stable label cannot evade the stable notice window
201
+ 6. Withdrawing a persisted-format reader follows the stable window even if its
202
+ producer is experimental. Historical evidence is not rewritten or reinterpreted
203
+
204
+ **Fail-closed exception:** security flaws, corrupted evidence, semantic loss or
205
+ false equivalence may be rejected immediately, including in a patch. Record
206
+ scope, reason, safe recovery/migration and a regression. Compatibility never
207
+ requires preserving a false success. An unrelated capability expansion is not
208
+ permission to change a valid old meaning silently.
209
+
210
+ Package version, format version and contract revision remain independent.
211
+ Incompatible writes need a new format and explicit migration; unknown versions
212
+ are rejected. Historical fixtures identify private commits and formats rather
213
+ than invented public releases. Their readers verify those admitted artifacts;
214
+ they do not promise arbitrary old data will be accepted.
215
+
216
+ HTML/text/notebook presentation may evolve while JSON semantic contracts remain.
217
+ Historical report verification uses the admitted frozen format behavior, not the
218
+ current renderer's bytes. Rehashing modified/corrupt evidence does not make it
219
+ valid. Preserve originals and create new, traceable migrated artifacts when an
220
+ explicit conversion exists. No automatic universal converter is promised.
221
+
222
+ ## Filesystems, concurrency and external integrations
223
+
224
+ Local evidence publication uses exclusive creation and, where specified, atomic
225
+ hard links. Verify your filesystem and permissions; network/object stores are not
226
+ implicitly supported. Do not make independent processes write a shared DuckDB
227
+ file. A production service owns snapshot publication, writer coordination,
228
+ security, concurrency and aggregate resource limits.
229
+
230
+ The core has no requirement for OCI, SERCOP, a data warehouse or the benchmark
231
+ package. Explicit DataSource/adapter integrations can provide external services;
232
+ that integration's authentication, network policy, snapshot consistency and
233
+ acceptance must be verified separately. A local synthetic test does not certify
234
+ a real service or another team's implementation.
235
+
236
+ ## Python 3.10 legacy-runtime boundary
237
+
238
+ EquiFrame compatibility with Python 3.10 does not provide upstream security
239
+ maintenance or vendor extended maintenance. Deployment owners must check their
240
+ runtime's [current lifecycle](https://devguide.python.org/versions/) and their
241
+ own maintenance policy; this does not remove 3.10 from the compatibility matrix.
242
+
243
+ The a8 compatibility changes preserve named type aliases through
244
+ `typing_extensions.TypeAliasType`, retain strict typing, use local Decimal
245
+ contexts without 3.11-only keyword arguments, and validate supported timestamp
246
+ literals consistently while retaining their original nanoseconds/text.
247
+ Windows junction checks use lstat reparse tags available since 3.8; simulated
248
+ branch tests alone are not actual Windows execution. The measured platform
249
+ scope is the table above; preserved roadmap cells do not add unexecuted support.
250
+
251
+ The benchmark declares `tomli` only below Python 3.11. Source-development tools
252
+ use the same fallback from the explicitly declared development dependency.
253
+ Normal pip resolution chooses compatible branches within unchanged native
254
+ dependency ranges; the recorded locks identify those branches per Python.