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,171 @@
1
+ # Contributing to EquiFrame
2
+
3
+ [Handbook](index.md) · [Compatibility](compatibility.md) · [Release process](releasing.md)
4
+
5
+ The 0.12.4 release contract uses MIT for project-owned code; third-party materials
6
+ retain their notices and licenses. Redistribution rights follow those licenses.
7
+ Hernán Troya (`htroya`) maintains the project and owns releases/security
8
+ coordination on a best-effort basis without a promised backup or response-time SLA.
9
+
10
+ The public repository contains the library source, user documentation and tests.
11
+ Open a nonsensitive issue or contribute a focused patch using your own account.
12
+ Never include credentials, private datasets, models or execution results.
13
+ The benchmark remains a separate package in this repository.
14
+
15
+ The owner has authorized manual TestPyPI/PyPI preparation and publication subject
16
+ to the [release gates](releasing.md). This is not permission for a contributor or
17
+ an ordinary CI run to publish. Historical private candidate notes retain their
18
+ original versions and outcomes.
19
+
20
+ ## Boundaries to preserve
21
+
22
+ - Read the API, compatibility and semantic-contract guides before a change
23
+ - Preserve Decimal, currency, keys, multiplicity, NULL/NaN and temporal distinctions
24
+ according to contract; never normalize differences just to pass a test
25
+ - Keep R, M, B, H and O independent. Detectors/profiles must not receive labels,
26
+ mutation definitions or another engine's output. pandas is not an oracle
27
+ - Never map failure, invalid input, missing support or not-estimable output to
28
+ success or no alert. Test fail-closed behavior, not just the happy path
29
+ - Native engines execute their own implementations; do not compute once and
30
+ convert the result to simulate independent implementations
31
+ - Core code must not depend on OCI, SERCOP, the experimental package or a specific
32
+ external service. Use explicit typed ports and trusted registrations
33
+ - Honor component ownership: core/benchmark work does not authorize infrastructure,
34
+ data-gateway or domain-warehouse redesign
35
+ - Do not execute untrusted callbacks, SQL, plugins or models. The library is not a
36
+ sandbox. Do not introduce shared DuckDB files with independent process writers
37
+ - Keep credentials, real datasets, private reports, databases, model binaries and
38
+ environments out of Git. Small identified synthetic fixtures are appropriate
39
+
40
+ ## Reproducible local checks
41
+
42
+ The commands below describe checks to run; they do not assert this artifact has
43
+ already passed them. Use an authorized source checkout or the identified sdist,
44
+ Python 3.12 and a suitable `uv` installation. Dependency installation may access
45
+ your configured package index unless a reviewed offline cache is supplied.
46
+ From the core package directory:
47
+
48
+ ```bash
49
+ uv sync --locked --all-extras --python 3.12
50
+ uv run --locked --all-extras ruff check .
51
+ uv run --locked --all-extras ruff format --check .
52
+ uv run --locked --all-extras mypy --python-version 3.12
53
+ uv run --locked --all-extras python tools/generate_assets.py --check
54
+ uv run --locked --all-extras pytest -q
55
+ uv run --locked --all-extras python -m build --no-isolation
56
+ uv run --locked --all-extras twine check --strict dist/*.whl dist/*.tar.gz
57
+ uv run --locked --all-extras python tools/verify_distribution.py
58
+ ```
59
+
60
+ The distribution verifier uses isolated installed consumers outside the checkout,
61
+ checks runtime bytes, exercises examples and compares the rebuilt wheel from
62
+ sdist. It is distinct from source tests and from a remote CI job. The first-use
63
+ kit verifier preserves deliberate negative recipe exits; do not turn them into
64
+ unconditional success.
65
+
66
+ The candidate driver provides a byte-bound aggregate receipt:
67
+
68
+ ```bash
69
+ python tools/check_candidate.py --package . --output /absolute/path/new-checks.json
70
+ ```
71
+
72
+ Every invocation needs a fresh receipt name and matching log/pytest paths. The
73
+ driver refuses existing output evidence before starting checks and reserves its
74
+ receipt exclusively. After a failed or interrupted attempt, keep that output
75
+ and choose another `--output` name for the retry.
76
+
77
+ The aggregate candidate driver and `release_manifest.py` require a Git checkout
78
+ to bind the complete source tree to a commit. An extracted sdist can run the
79
+ package's lint, types, assets, tests and build checks, but it cannot create that
80
+ Git-bound release receipt by itself. Keep the original source revision and
81
+ artifact receipt from the provider. The benchmark distribution verifier also
82
+ expects the monorepo layout, including the adjacent matching core source and
83
+ built wheel; an isolated benchmark sdist does not supply those inputs.
84
+
85
+ For the benchmark, work from `packages/equiframe-benchmark`, build the matching
86
+ core wheel first, and run the same lint/type/assets/test/build/Twine/distribution
87
+ sequence after:
88
+
89
+ ```bash
90
+ uv sync --locked --all-extras --reinstall-package equiframe
91
+ ```
92
+
93
+ Reinstallation matters when editing a local unreleased candidate with the same
94
+ version string: dependency caches
95
+ can otherwise retain an old local core. The candidate driver checks runtime
96
+ source hashes and reinstalls local dependencies; installed artifact verification
97
+ still consumes the exact wheels, not an editable checkout.
98
+ Published distribution files remain immutable; a local development reinstall
99
+ does not authorize replacing them.
100
+
101
+ Changing dependencies requires reviewed lock changes and the relevant
102
+ `tools/verify_compatibility.py` cells. Minimum/recent direct resolutions, platforms,
103
+ extras, corpus, hashes, failures and skips must be recorded accurately. A narrower
104
+ focused test does not replace all affected checks. Never update a compatibility
105
+ fingerprint automatically just to silence a regression.
106
+
107
+ ## Reviewable change procedure
108
+
109
+ 1. Describe the problem, affected contract/capability and evidence of failure
110
+ 2. Work on a branch based on the agreed candidate/base; identify dependencies
111
+ on unmerged changes. Do not assume the candidate is already on main
112
+ 3. Implement real behavior with minimal synthetic regression fixtures, including
113
+ positive, negative, invalid and not-evaluable cases
114
+ 4. Update public English usage, reference/limits, schemas and compatibility
115
+ classification when affected. Record observable changes and migration guidance
116
+ 5. Preserve historical fixture bytes and unknown-version rejection. New incompatible
117
+ writes need a new format, not retrospective reinterpretation
118
+ 6. Run affected source and installed-artifact checks against final bytes and retain
119
+ receipts. Distinguish passed, failed, blocked and unexecuted checks
120
+ 7. Request review with scope, rationale, artifacts, test outcomes and known limits.
121
+ A PR convention is not proof of enforced branch protection or independent approval
122
+
123
+ Methodological changes require explicit rationale, version and consequences in
124
+ the repository's decision/traceability documents. Do not describe synthetic
125
+ fixtures, untrained models, planned APIs, pending pilots or unexecuted tests as
126
+ scientific evidence or external acceptance.
127
+
128
+ Public-facing current documentation is English. Historical design and the
129
+ reinforced roadmap can remain Spanish with their original context. Keep the
130
+ installed handbook self-contained; essential instructions must not depend on
131
+ private monorepo links. Run examples against the exact distribution, and label
132
+ signature sketches or consumer-specific request templates as such.
133
+
134
+ ## CI and publishing permissions
135
+
136
+ The public repository ships no GitHub Actions workflows, and this release uses
137
+ local manual checks. Pushing a branch or creating a PR does not execute or prove
138
+ a compatibility matrix. Identify the actual command, source, environment and
139
+ artifacts before claiming results. Any future hosted CI requires separate review
140
+ and least privilege; publication remains a separate protected owner decision.
141
+
142
+ Building locally, checking Twine metadata, producing an SBOM or installing by
143
+ local wheel does not publish, reserve a project name or approve licensing.
144
+ Do not upload to PyPI, deploy a service, grant sharing access or activate a remote
145
+ release flow without the specific authorization and completed gates in
146
+ [releasing.md](releasing.md).
147
+
148
+ ## Adoption and maintenance evidence
149
+
150
+ The [adoption record checker](adoption.md#check-the-structured-record-locally)
151
+ is a developer-only sdist tool. Its exit 0 establishes input consistency, never
152
+ consent, human independence or adoption. Keep the example registry empty and
153
+ real observations outside Git. The [maintenance process](maintenance.md) requires
154
+ minimal permitted reproductions and separate technical/consumer confirmation;
155
+ Hernán Troya (`htroya`) is the confirmed owner; no backup, response deadline or
156
+ SLA is promised.
157
+
158
+ ## Fast source feedback
159
+
160
+ Use the [quick CI layer](fast-ci.md) for bounded PR/change feedback and the
161
+ `tools/check_architecture.py --check` from the source distribution.
162
+ Its smoke receipt is separate from the full candidate/distribution/matrix checks.
163
+ The installed [product journeys](journeys.md) make positive, different and
164
+ unavailable outcomes reproducible without a source checkout.
165
+
166
+ Type-check each selected Python with its own resolved dependency/stub set and
167
+ matching `mypy --python-version X.Y`. The aggregate driver observes that version
168
+ after sync and supplies it automatically. The checked-in minimum target remains
169
+ 3.10 and strict mode remains enabled; no error codes or imports are suppressed.
170
+ A 3.11+ stub package requiring newer typing syntax is not evidence about the
171
+ 3.10 dependency branch. Each branch must pass under its actual interpreter.
@@ -0,0 +1,237 @@
1
+ # Dependency inventory, licenses and advisory evidence
2
+
3
+ The package metadata declares allowed version ranges; `uv.lock` records exact
4
+ resolutions, including conditional branches for supported environments. Neither
5
+ means that every possible version combination has passed the test matrix.
6
+ Keep the exact Python/platform marker environment with the resolved inventory.
7
+
8
+ The `pandas` and `engines` extras exclude Pandas 3.0.3–3.0.6. Their upstream
9
+ `ZoneInfo.from_file` regression can ignore the supplied rules and silently alter
10
+ historical timestamps; the correction is documented in
11
+ [Pandas 3.1.0](https://pandas.pydata.org/pandas-docs/version/3.1.0/whatsnew/v3.1.0.html).
12
+ The minimum supported Pandas range remains available on Python 3.10. A changed
13
+ resolved profile requires fresh native evidence; previous 3.0.6 receipts retain
14
+ their actual version and outcome.
15
+
16
+ Runtime is deliberately separated from optional engines and ML: base contracts,
17
+ models and CLI depend on Pydantic, Typer and tzdata; Arrow and each native engine
18
+ are opt-in; ML is opt-in; the benchmark is a separate distribution. Build and
19
+ validation tools are development dependencies, not required by a normal core
20
+ import. The benchmark's dependency must resolve to the exact core candidate
21
+ wheel supplied with its evidence, not another private wheel with the same
22
+ version string.
23
+
24
+ TLS trust is also part of the build and advisory-query environment. The
25
+ [Python security notice for the emSign incident](https://discuss.python.org/t/information-for-python-users-regarding-emudhra-emsign-ca-certificate-security-incident/109576)
26
+ requires corrected CA bundles and identifies minimum repaired versions including
27
+ `certifi>=2026.10.19`, `pip>=26.2.2` and `uv>=0.13.4`. The release locks use
28
+ `certifi==2026.10.23`. A standalone tool can embed its own CA bundle, so inspecting
29
+ installed Python distributions alone does not cover `uv` or other native tools.
30
+ Do not disable certificate verification to work around a download failure.
31
+
32
+ Use a maintained Python security patch release and an updated operating-system
33
+ trust store. The same notice recommends Python 3.13.18 (and corrected releases of
34
+ other maintained branches) as an additional mitigation independent of CA bundles.
35
+ The package's Python compatibility floor is not a claim that an end-of-life
36
+ interpreter still receives upstream security fixes. Updating a QA runtime must
37
+ not silently replace a consumer's system interpreter or certificate store.
38
+
39
+ The optional `files` profile adds Pandas/Arrow, openpyxl for XLSX/XLSM, xlrd for
40
+ legacy XLS and defusedxml for XML. Imports occur only when a selected operation
41
+ needs that reader; no automatic installation occurs. Target conversion additionally
42
+ uses the existing `polars` or `duckdb` profile. The exact allowed ranges are in
43
+ the [compatibility table](compatibility.md#declared-runtime-dependencies).
44
+ `equiframe doctor --extra files` diagnoses installation metadata without reading
45
+ user data. File discovery, inference and conversion are documented in
46
+ [the file guide](lectura-conversion.md).
47
+
48
+ The optional [language-R review](r_review.md) uses a user-installed `Rscript`
49
+ and base R. It does not add an R package dependency or install a runtime.
50
+ R's observed version and the reviewer source hash belong to each returned
51
+ review; Python wheel metadata alone cannot identify the external R executable.
52
+ Validation of the 26 October source additions and lock updates is reported
53
+ separately; lock resolution alone does not establish native or semantic behavior.
54
+
55
+ ## Reproducible evidence workflow
56
+
57
+ The source distribution carries `tools/dependency_inventory.py` and the pinned
58
+ CycloneDX schemas. Run the tool with the locked core development interpreter.
59
+ `generate` is offline: it inspects the selected package, exact wheel and sdist,
60
+ lock profiles and installed metadata. `scan` is explicitly networked, querying
61
+ version-specific PyPI metadata and OSV for registry dependencies. `verify`
62
+ rederives the inventory, validates the artifact bindings and schemas, and checks
63
+ snapshot consistency/freshness. Consult `--help` for exact options.
64
+
65
+ Run these from the repository root after both packages have been built and their
66
+ locked development environments have been prepared. The following paths use
67
+ POSIX virtual environments; on Windows use the corresponding `Scripts/python.exe`.
68
+ Output directories must be new so an earlier receipt cannot be overwritten.
69
+
70
+ ```sh
71
+ PY=.venv/bin/python
72
+ TOOL=tools/dependency_inventory.py
73
+ CORE=.
74
+ BENCH=packages/equiframe-benchmark
75
+
76
+ $PY $TOOL generate --package "$CORE" \
77
+ --artifact "$CORE/dist/equiframe-0.12.4-py3-none-any.whl" \
78
+ --artifact "$CORE/dist/equiframe-0.12.4.tar.gz" \
79
+ --output evidence/dependencies/core
80
+
81
+ $PY $TOOL generate --package "$BENCH" \
82
+ --artifact "$BENCH/dist/equiframe_benchmark-0.12.4-py3-none-any.whl" \
83
+ --artifact "$BENCH/dist/equiframe_benchmark-0.12.4.tar.gz" \
84
+ --environment-python "$BENCH/.venv/bin/python" \
85
+ --local-artifact "$CORE/dist/equiframe-0.12.4-py3-none-any.whl" \
86
+ --output evidence/dependencies/benchmark
87
+ ```
88
+
89
+ Each directory contains `inventory.json` (`equiframe.dependencies/1.0`) and two
90
+ `<artifact filename>.cdx.json` files. The BOMs are CycloneDX 1.6, validated against
91
+ the unmodified official draft-07 JSON schemas with pinned SHA-256 and a closed,
92
+ local schema registry. They contain a clearly labelled union of profile closures;
93
+ they do not imply that build/dev dependencies are embedded in, or required by,
94
+ the wheel. Each component lists its profile membership. Only explicitly stated,
95
+ valid SPDX expressions are represented as license expressions; other source
96
+ metadata is retained for review rather than converted to an invented license.
97
+
98
+ The tool needs `packaging` and `jsonschema` from the existing core development
99
+ lock. It installs nothing and does not add runtime dependencies. It reads
100
+ installed metadata and license files without importing dependency/project code.
101
+ Declared `License-File` members, including names such as `AUTHORS` and legacy
102
+ `mit.LICENSE`, are collected alongside conventional `LICENSE`, `LICENCE`, copying
103
+ and notice files inside the distribution metadata boundary. Paths outside that
104
+ boundary are not followed. Missing files or declarations still require review;
105
+ collecting a file does not establish a compatible license.
106
+ The interpreter selected by `--environment-python` supplies the observed marker
107
+ environment and distribution metadata. Other-platform lock branches are not
108
+ claimed to have been installed or scanned. Missing installed metadata is recorded
109
+ as unknown; exact lock versions are never relabelled as installed when absent.
110
+
111
+ An advisory refresh requires an inspected public-only allowlist and an offline
112
+ request preview. Create `public-allowlist.json` containing only exact reviewed
113
+ public `name`/`version` objects from the artifact-bound inventory; exclude the
114
+ project, all private consumers/internal packages and any ambiguous provenance.
115
+ A registry label by itself is insufficient. This file and the preview remain
116
+ local; neither the complete inventory nor its metadata is uploaded.
117
+
118
+ ```sh
119
+ $PY $TOOL prepare-scan --inventory evidence/dependencies/core/inventory.json \
120
+ --public-allowlist public-allowlist.json --output public-requests.json
121
+ # Inspect public-requests.json: URLs, bodies, anonymous headers and exclusions.
122
+ $PY $TOOL scan --inventory evidence/dependencies/core/inventory.json \
123
+ --public-allowlist public-allowlist.json --request-plan public-requests.json \
124
+ --output evidence/dependencies/core-advisories
125
+ ```
126
+
127
+ Repeat with the benchmark inventory and its own reviewed allowlist/preview.
128
+ Preparation is offline and proves no provider coverage. Execution rejects a
129
+ changed preview, names absent from the allowlist, private/local sources, extra
130
+ payload fields, URL query/userinfo/insecure destinations and all redirects.
131
+ No project-identifying User-Agent, credentials, cookies or referer is supplied.
132
+ Provider pagination tokens are validated and remain bound to the same query.
133
+ Platform-denied requests must stop; changing a route is not authorization.
134
+
135
+ `scan` writes `advisories.json` (`equiframe.advisories/1.0`) and one snapshot per
136
+ public package/version/provider. It preserves the exact UTF-8 JSON response,
137
+ its SHA-256, parsed content, request, source URL, time and failures. It queries
138
+ PyPI's release endpoint and OSV, retaining GHSA IDs/advisories supplied through
139
+ those sources. It does not claim an independent direct GitHub Advisory API scan.
140
+ Private/local project packages are explicitly excluded from public name lookups.
141
+ The command exits 0 for complete `no_known_advisories`, 2 for `findings` or
142
+ `unknown`, and 1 for invalid inputs. Failure receipts remain available.
143
+
144
+ Reverify the final artifact bytes, lock, selected environment, BOMs and audit:
145
+
146
+ ```sh
147
+ $PY $TOOL verify --package "$CORE" \
148
+ --artifact "$CORE/dist/equiframe-0.12.4-py3-none-any.whl" \
149
+ --artifact "$CORE/dist/equiframe-0.12.4.tar.gz" \
150
+ --inventory evidence/dependencies/core/inventory.json \
151
+ --advisories evidence/dependencies/core-advisories/advisories.json \
152
+ --max-age-hours 24 --require-clean
153
+
154
+ $PY $TOOL verify --package "$BENCH" \
155
+ --artifact "$BENCH/dist/equiframe_benchmark-0.12.4-py3-none-any.whl" \
156
+ --artifact "$BENCH/dist/equiframe_benchmark-0.12.4.tar.gz" \
157
+ --environment-python "$BENCH/.venv/bin/python" \
158
+ --local-artifact "$CORE/dist/equiframe-0.12.4-py3-none-any.whl" \
159
+ --inventory evidence/dependencies/benchmark/inventory.json \
160
+ --advisories evidence/dependencies/benchmark-advisories/advisories.json \
161
+ --max-age-hours 24 --require-clean
162
+ ```
163
+
164
+ For an offline-only verification, omit `--advisories` and `--require-clean`;
165
+ that result establishes no advisory coverage. `--require-clean` always needs a
166
+ complete fresh audit. Freshness defaults to 24 hours; an explicit larger window
167
+ is bounded at 168 hours and must be justified by release policy. Missing snapshots,
168
+ changed hashes, changed local core wheel, stale timestamps, omitted packages,
169
+ inconsistent summaries and changed source declarations fail verification.
170
+ The verifier reconstructs the expected inventory and BOM instead of trusting
171
+ positive fields in a receipt. Receipts are unsigned: consistent total replacement
172
+ of artifacts and evidence is outside their authenticity guarantee.
173
+
174
+ The evidence distinguishes runtime base, individual extras, all extras, dev and
175
+ build profiles. It records declared requirements, resolved versions, lock and
176
+ artifact hashes, observed dependency metadata and license-file hashes. A
177
+ CycloneDX BOM is tied to each artifact rather than to an unspecified checkout.
178
+ Local project dependencies require an explicit candidate wheel. The schemas
179
+ are retained with source URLs and hashes for offline validation; their upstream
180
+ license notices apply to those schemas, not to EquiFrame.
181
+
182
+ An installed dependency's license expression, classifiers, free-form metadata
183
+ and files may disagree or be incomplete. Preserve those observations and their
184
+ provenance; do not guess a missing license or treat metadata as legal clearance.
185
+ Native wheels can bundle third-party libraries that are not separate Python
186
+ distributions. The Python dependency inventory is not an exhaustive inventory
187
+ of every bundled native component, the operating system or build infrastructure.
188
+ Redistribution review must inspect the applicable binary notices and conditions.
189
+
190
+ The optional legacy XLS reader `xlrd==2.0.2` illustrates why a generic `BSD`
191
+ metadata label is insufficient: its [license text](https://github.com/python-excel/xlrd/blob/2.0.2/LICENSE)
192
+ contains both a later three-clause notice and an earlier notice with advertising
193
+ and retained-acknowledgement conditions. Preserve both when redistributing that
194
+ dependency and retain the required acknowledgement:
195
+
196
+ > This product includes software developed by David Giffin <david@giffin.org>.
197
+
198
+ SciPy binary notices separately identify OpenBLAS/LAPACK, the GCC runtime with
199
+ its runtime exception, and libquadmath under their respective terms. Docutils
200
+ identifies its public-domain files and separately licensed exceptions. These
201
+ notices must not be replaced by EquiFrame's MIT license or collapsed into a
202
+ guessed single SPDX expression. Normal dependency installation retains upstream
203
+ notices; repackaging dependencies requires a separate review of the actual files.
204
+
205
+ ## Interpreting advisory results
206
+
207
+ Retain the query time, exact name/version, provider, URL, returned bytes/hash and
208
+ errors. A successful response with no listed advisories means only that the
209
+ provider returned none for that version at that time. Missing releases, blocked
210
+ networks, HTTP errors, incomplete provider coverage, stale or malformed evidence
211
+ are **unknown**, never zero vulnerabilities. A provider identifier is not a
212
+ confirmed exploitability assessment of EquiFrame's usage.
213
+
214
+ Inspect primary advisory records for affected/fixed ranges, severity and relevant
215
+ runtime paths. Record each correction, justified exception or unresolved result.
216
+ Changing a dependency requires rebuilding the lock/first-use pins as applicable,
217
+ rerunning the affected profiles and full release checks, and regenerating all
218
+ artifact-bound evidence. Do not suppress an advisory merely to obtain a green
219
+ release status. Recheck close to publication; an old snapshot cannot establish
220
+ current safety. `--require-clean` fails closed if coverage is unknown, findings
221
+ remain or the snapshot is stale.
222
+
223
+ ## Release limits
224
+
225
+ The inventory and advisory scan are separate from source review and the partial
226
+ historical STEP9 assessment. They do not prove publisher identity, grant rights
227
+ over dependencies or establish that the software is secure. EquiFrame's own
228
+ license and bundled notices do not replace third-party redistribution conditions.
229
+ Publication follows the [release decisions and gates](releasing.md), including
230
+ resolution of real findings and explicit treatment of unknown coverage. Bind the
231
+ completed checks to the final four artifact hashes; do not transfer acceptance
232
+ from an earlier wheel, sdist, dependency resolution or source snapshot.
233
+
234
+ Standards and provider documentation:
235
+ [CycloneDX 1.6](https://cyclonedx.org/docs/1.6/json/),
236
+ [PyPI JSON API](https://docs.pypi.org/api/json/),
237
+ [OSV query API](https://google.github.io/osv.dev/api/)
equiframe/docs/eda.md ADDED
@@ -0,0 +1,166 @@
1
+ # Optional standalone dataset EDA
2
+
3
+ [Handbook](index.md) · [Outcome gates](outcome-gates.md) · [Compatibility](compatibility.md)
4
+
5
+ Private Alpha **0.12.0a14** introduced `equiframe.eda`; that subpackage remains
6
+ experimental in 0.12.4, separately from the stable comparison/migration interfaces.
7
+ It diagnoses one fixed dataset independently of ETL or a migration. It never
8
+ changes a plan, tolerance, expected output, detector bundle or publication gate.
9
+ Install the identified wheel and its `eda` extra (DuckDB and Arrow),
10
+ or `eda-charts` for portable Plotly HTML. No public PyPI release is implied.
11
+ Imports remain lazy: importing EquiFrame, EDA contracts or CLI does not import
12
+ DuckDB, Arrow, Plotly or the ML engines.
13
+
14
+ ## Python API
15
+
16
+ ```python
17
+ from pathlib import Path
18
+ from equiframe.eda import EdaConfig, NumericRule, Source, analyze, read_result
19
+
20
+ result = analyze(
21
+ Source(path="/absolute/input/numbers.parquet", snapshot_id="numbers-v1"),
22
+ config=EdaConfig(numeric=(NumericRule(column="value", outliers="iqr"),)),
23
+ output=Path("/absolute/private-runs/numbers-run-001"),
24
+ )
25
+ print(result.state, result.reason) # Inspect each metric's availability as well.
26
+ checked = read_result(Path("/absolute/private-runs/numbers-run-001"))
27
+ ```
28
+
29
+ Paths above are placeholders, not proof of execution. The supplied synthetic
30
+ example uses real generated CSV, independent expected counts and exact money:
31
+
32
+ ```bash
33
+ python examples/eda_generic.py --work-dir /absolute/new-private-example
34
+ ```
35
+
36
+ The wheel includes `equiframe/examples/integrations/eda_generic.py`; extract
37
+ that resource into a fresh project to run it outside the checkout. Files and
38
+ reports remain private. Use a new work directory every time.
39
+
40
+ `analyze_artifact(TableArtifact, config=..., output=...)` reuses a captured
41
+ table's SHA/size without making an execution. `analyze_migration(path,
42
+ side="source"|"target", config=..., output=...)` first calls the existing verified
43
+ migration/bundle reader. It resolves only an inventoried captured output and
44
+ records its original evidence identity/state separately. Revalidation precedes
45
+ the EDA commit marker. A changed evidence document makes EDA fail; it does not
46
+ rewrite the original. Existing index artifacts are not profiled; the link says so.
47
+ Standalone ETL input/output files can be diagnosed in separate runs with the
48
+ same explicit config. Their profiles are diagnostics, not a second verifier.
49
+
50
+ ## CLI
51
+
52
+ The same implementation is exposed as `equiframe eda` and `equiframe-eda`:
53
+
54
+ ```bash
55
+ equiframe eda run input.parquet --output /private/runs/new-run \
56
+ --snapshot-id snapshot-v1 --expected-sha256 ACTUAL_TRUSTED_SHA \
57
+ --config eda-config.json
58
+ equiframe eda inspect /private/runs/new-run
59
+ equiframe eda migration /private/migration --side target \
60
+ --output /private/runs/new-migration-eda --config eda-config.json
61
+ ```
62
+
63
+ `run`/`migration`: 0 completed diagnostics, 2 invalid configuration/preflight,
64
+ 3 persisted unavailable/failed/cancelled diagnostics. `inspect`: 0 intact
65
+ closed evidence, including stored unavailable results; 3 integrity/read failure.
66
+ None of these codes authorizes a DW or establishes equivalence/correctness.
67
+ API results reject boolean evaluation. Compare full content through the existing
68
+ `diff`/`compare`/Verify APIs and use their explicit assertions/outcomes.
69
+
70
+ ## Original representation and conversion
71
+
72
+ Parquet and Arrow IPC file snapshots admit scalar representations the initial
73
+ backend can preserve; CSV is explicit UTF-8/header/delimiter text, including
74
+ empty fields and textual `NaN`. Original names are checked before DuckDB can
75
+ deduplicate them. Case-insensitive/reserved-name collisions are rejected.
76
+ The MVP admits raw null, boolean, integer, float32/64, string/large string,
77
+ date32, naive second/millisecond/microsecond timestamps and decimals with
78
+ precision at most 38 and nonnegative scale within precision. Other raw types
79
+ (including time, duration, nested/binary/dictionary types, date64, float16,
80
+ nanosecond/zoned timestamps and wider decimals) remain explicitly unavailable
81
+ because the report/transport cannot preserve those types faithfully.
82
+ Timestamp strings remain strings in the raw profile.
83
+
84
+ `Conversion` creates a new column with original text retained. Decimal requires
85
+ plain decimal text within configured precision/scale; excess fraction digits,
86
+ overflow and invalid lexemes are counted, never rounded. Integer requires
87
+ integral text; date requires ISO date; timestamp requires a valid explicit
88
+ offset/Z and at most six fraction digits. Original offsets/text remain intact,
89
+ with derived instants in UTC. Blank is invalid unless `empty="missing"` is
90
+ declared. No identifiers are converted. Unknown NULL distinctions lost by an
91
+ export cannot be reconstructed. `typed.parquet` is a separate derived artifact.
92
+
93
+ ## Meaning of results
94
+
95
+ Raw profiles distinguish NULL, numeric NaN, empty text, numeric zero and
96
+ nonfinite values. Valid excludes NULL/NaN/empty/nonfinite; distinct present
97
+ excludes NULL/NaN and retains empty text. Each count/rate records its row
98
+ denominator; an empty denominator has no rate. Nonfinite includes NaN, so
99
+ these two categories overlap and must not be summed as disjoint populations.
100
+ Frequencies are bounded top categories with complete counts, not sampled counts.
101
+
102
+ Full-row repetitions and declared keys report repeated groups, involved rows,
103
+ excess rows and denominator. Key policy includes/excludes/rejects incomplete
104
+ NULL/NaN and optionally empty text keys. `require_unique=False` records an
105
+ observation: repeated OCID/history is not automatically an error. Nothing is
106
+ removed. Examples and zero-based row references are bounded and tied to the
107
+ snapshot hash; counts scan the complete table.
108
+
109
+ `NumericRule` selects a native numeric or explicitly converted column and
110
+ comparable groups. Money requires Decimal and text currency grouping; missing
111
+ currency groups report unavailable counts without pooling amounts. Group types
112
+ are text/bool/64-bit integer/Decimal/finite float; other types are explicit
113
+ capability limitations. Groups above `max_groups` are not silently dropped.
114
+
115
+ Integer/Decimal sum, min/max and **discrete quantiles** are exact. Quantiles use
116
+ DuckDB `quantile_disc`, Hyndman–Fan type 1, including the discrete median. Mean
117
+ also records the exact sum/count rational for integer/Decimal. Float64 mean,
118
+ stddev and visualization/histogram binning are labeled approximate. Population
119
+ or sample stddev is explicit. NULL/nonfinite are excluded with counts; failed
120
+ conversions are recorded separately. Approximate display never changes money.
121
+
122
+ IQR uses `[Q1 − multiplier×IQR, Q3 + multiplier×IQR]`; unscaled MAD uses median
123
+ ± multiplier×median absolute deviation. Bounds are strict: equality is not an
124
+ outlier. Support minimum, method and multiplier are recorded. Insufficient
125
+ support, zero dispersion, missing comparable group or unrepresentable exact
126
+ bound leaves a reason and **count=null**, never zero alerts. Decimal bindings
127
+ use the existing typed transport guard; no float fallback for exact money.
128
+ Only transient bounds may remove exact trailing zeros to fit native precision.
129
+ An outlier remains a case to investigate, including legitimate extremes.
130
+
131
+ Unavailable requested metrics give run state `not_evaluable` while preserving
132
+ calculated quality and available statistics. Failure, cancellation and source
133
+ changes remain distinct. A completed run without findings does not prove a
134
+ transformation correct. Two different tables can have identical statistics;
135
+ two engines can agree on the same error. R/M/B/H/O and model fitting are unchanged.
136
+
137
+ ## Resources and evidence
138
+
139
+ Source, output, manifest and evidence paths reject symlinks and Windows reparse
140
+ points, including directory ancestors, before resolving the path. Source-delivery
141
+ members must be normalized relative paths inside that delivery. The delivery
142
+ manifest is limited to 16 MiB and 2,000 entries; its trusted SHA-256 identifies
143
+ the exact bytes read. These checks do not grant operating-system access or make
144
+ this library a sandbox for untrusted callbacks.
145
+
146
+ `Resources` records memory, threads, timeout, temp spill, input bytes/rows,
147
+ columns/groups, top categories, examples, histogram bins and chart rows/bytes.
148
+ DuckDB memory controls its buffer manager, not process RSS. Temporary-disk cap
149
+ covers DuckDB spill; fixed snapshots, typed files and reports use extra disk.
150
+ Byte/row/group caps and callback cancellation are cooperative. A timer
151
+ interrupts DuckDB queries; callbacks are checked before/after operations and
152
+ chart exports, without promising immediate preemption or hard RSS quotas.
153
+
154
+ Every exclusive run writes config/source, fixed snapshot, optional typed
155
+ Parquet, findings/summary JSON, HTML and a closed hashed manifest. A partial
156
+ snapshot is labeled `snapshot_complete=False`. The final manifest is the
157
+ commit marker: no readable completed run without it. Publication I/O failure
158
+ can leave an interrupted directory that the reader rejects; do not reuse it.
159
+ `calculation_elapsed_seconds` and elapsed time through report preparation are
160
+ separate; filesystem commit overhead is not a performance guarantee. Report
161
+ budgets can produce an unavailable HTML stub with full JSON diagnostics.
162
+
163
+ HTML embeds Plotly JavaScript locally. Full histograms and quality counts are
164
+ distinct from the declared first-finite-row sample used for box/scatter display;
165
+ that sample is not representative and its box quartiles are not population
166
+ quantiles. Plotly is never required by migration, comparison or Verify.