diff --git a/CLAUDE.md b/CLAUDE.md index dc796a0..4cb0293 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,34 +1,54 @@ # views-frames The VIEWS platform's **data-contract layer**: small, stable, abstract, immutable -array+identifier value objects (`FeatureFrame`, `PredictionFrame`, `TargetFrame`, -and anticipated siblings) at the **root of the platform dependency DAG**. numpy -only; depends on nothing internal; every other repo depends *toward* it. +array+identifier value objects (`PredictionFrame`, `TargetFrame`, `FeatureFrame`) +at the **root of the platform dependency DAG**, plus two sibling operation packages +in the same wheel. numpy only; depends on nothing internal; every other repo +depends *toward* it. -> **Status:** **implemented — v0.1.0** (Epic 2). `src/views_frames/` realises the -> contract (index, frames, io, conformance suite). Consumer adoption (re-export -> shims, pandas migration) is Epic 3. +> **Status:** **released — v1.8.x on PyPI**, public API **frozen since v1.0.0** +> (ADR-018; everything after is additive, `CONFORMANCE_FLOOR` stays `1.0.0`). +> Consumers install `views-frames` and validate against the published conformance +> suite (`views_frames.conformance`, ADR-016). See `CHANGELOG.md` for the release +> history and `README.md` §status for the version chronicle. ## Architecture -**Two packages** under `src/` (datafactory multi-package pattern), strict one-way -dependency `views_frames_summarize → views_frames`, enforced by -`tests/test_import_enforcement.py`: +**Three packages** under `src/` (one wheel), strict one-way dependencies +`views_frames_summarize → views_frames` and `views_frames_reconcile → views_frames` +(siblings never import each other), enforced by `tests/test_import_enforcement.py`: -**`src/views_frames/`** — the pure data contract (numpy-only; depends on nothing): +**`src/views_frames/`** — the pure data contract (numpy-only; depends on nothing; frozen): -- `index.py` — `SpatioTemporalIndex` (`{time, unit, level}` + same-level numpy alignment). +- `index.py` — `SpatioTemporalIndex` (`{time, unit, level}`; same-level numpy alignment + + consumer-injected cross-level remap; identifier arrays write-protected). - `spatial_level.py` — `SpatialLevel` (cm/pgm identifier vocabulary; labels only). -- `protocols.py` — `Frame` / `SpatioTemporalIndexed` / `Sampled` (`sample_count`/`is_sample` only) / `Persistable`. +- `protocols.py` — `Frame` / `SpatioTemporalIndexed` / `Sampled` (`sample_count`/`is_sample` + only) / `Persistable` (four small segregated protocols). +- `metadata.py` — `FrameMetadata` (typed, frozen, generic-only provenance; ADR-020). - `_validation.py` — shared construction-time invariants. -- `feature_frame.py`, `prediction_frame.py`, `target_frame.py` — sibling frames (no shared base; ADR-011 Option C). -- `io/` — `npz` (native) + `arrow` (flat-columnar). **The only place `pyarrow` may be imported.** +- `feature_frame.py`, `prediction_frame.py`, `target_frame.py` — sibling frames + `(N,F,S)` / `(N,S)` / `(N,1)` (no shared base; ADR-011 Option C). +- `io/` — `npz` (native, mmap-capable) + `arrow` (flat-columnar parquet codec, + module-level by decision D-11). **The only place `pyarrow` may be imported.** +- `conformance/` — the published suite consumers run in *their* CI + (`assert_frame_contract`, `assert_frame_envelope`, alignment laws; `CONFORMANCE_FLOOR`). **`src/views_frames_summarize/`** — sample-axis posterior summarization *over* frames -(ADR-017; numpy-only; depends on `views_frames`). Point estimates -(`collapse(frame, reducer)`, `map_estimate`) return a `(N,…,1)` frame; intervals -(`hdi`, `quantiles`) return arrays aligned to the frame's index. **Never** owns IO, -domain data, scoring, or reconciliation. +(ADR-017; numpy-only; depends on `views_frames`). Point estimates (`collapse`, +`map_estimate`, `tower_point`) return `(N,…,1)` frames; intervals/arrays (`hdi`, +`quantiles`, `hdi_tower`, `exceedance`, `expected_shortfall`, `bimodality`) return +index-aligned arrays; `aggregate_distributions[_arrays]` sums sample distributions +across levels (joint sampling). Fail-loud config for the tower family (ADR-019). +**Never** owns IO, domain data, scoring, or reconciliation. + +**`src/views_frames_reconcile/`** — top-down proportional reconciliation of pgm grid +forecasts to cm country totals (ADR-023; numpy-only; depends on `views_frames`). +`ReconciliationModule(map_keys, map_vals)` with the geography **injected** (never +fetched); `reconcile` / `reconcile_result` (the latter reports the +`point-broadcast`/`aligned-draws` **mode** — returned, never stamped on the leaf +header, D-12). The per-draw method is a documented approximation; the principled +joint upgrade is designed and deferred (ADR-024, register C-62). ## Tooling (uv + hatchling) @@ -36,32 +56,46 @@ Always invoke via `uv run`: ```bash uv sync # install deps + the package (editable) -uv run pytest # tests (incl. import-enforcement + falsification stubs) +uv run pytest # tests (incl. import-enforcement + falsification suites) uv run ruff check . # lint uv run ruff format . # format -uv run mypy src/ # type check (strict) +uv run mypy src/ # type check (strict; also check with --python 3.11 before pushing) uv build # build wheel + sdist ``` +CI additionally gates 100% line+branch coverage +(`uv run pytest --cov --cov-fail-under=100`) and a numpy-floor job. + ## Design principles (the hard constraints) 1. **numpy only in the core.** Never import `pandas`, `polars`, `geopandas`, - `wandb`, `viewser`, `torch`, or any `views_*` package. `pyarrow` is allowed + `wandb`, `viewser`, `torch`, or any foreign `views_*` package. `pyarrow` is allowed *only* under `io/`. Enforced by `tests/test_import_enforcement.py` (ADR-002). 2. **Immutable value objects.** Operations return new frames; structural ops share - the buffer (zero-copy); only reductions allocate (ADR-013, register C-07). -3. **Fail loud at construction.** Invariants raise `ValueError`/`TypeError`; the - guarantee is *structural*, not temporal (`time` is opaque; register C-11). + the buffer (zero-copy); only reductions allocate (register C-07). Enforced for the + *index*; **by convention** for the value buffer (writeable on purpose to preserve + zero-copy/mmap — mutating `.values` in place is unsupported; ADR-025, C-66 rider). +3. **Fail loud.** Invariants raise `ValueError`/`TypeError` at construction and at + every validation guard; the guarantee is *structural*, not temporal (`time` is + opaque; register C-11). 4. **No shared frame base.** Frames are separate siblings (ADR-011); cm/pgm is a `SpatialLevel` *value*, never a class axis. -5. **No domain data.** Cross-level cm↔pgm alignment is a protocol with a - consumer-injected mapping; the leaf never embeds/fetches it (ADR-014). -6. **One concept per file**; explicit `__init__.py` re-exports (no `import *`). +5. **No domain data.** Cross-level cm↔pgm alignment takes a consumer-injected + mapping; the leaf and siblings never embed/fetch it (ADR-014/ADR-023). +6. **One concept per file** (test-enforced: ≤1 public class per module); explicit + `__init__.py` re-exports (no `import *`). +7. **Frozen surface + WET before DRY.** The v1 public surface only grows additively + (removal/tightening = MAJOR + cross-repo coordinated bump, GOVERNANCE.md); + deliberate duplication recorded in ADRs is a choice, not debt. ## Governance -Constitutional ADRs 000–010, project ADRs 011–016, CIC infrastructure, contributor -protocols, and standards live in `docs/`. The technical risk register is -maintained as an internal governance artifact. Run `bash docs/validate_docs.sh` to check -documentation consistency. Build *against* the README design bible — if code and -README disagree, reconcile before merging. +Constitutional ADRs 000–010, project ADRs 011–025, CICs for every non-trivial +surface (7 active incl. the package-level `Summarize.md` and `Reconcile.md`), +contributor protocols, and standards live in `docs/`. The technical risk register +(`reports/technical_risk_register.md`) is the curated concern/decision log. Run +`bash docs/validate_docs.sh` to check documentation consistency. Build *against* +the README design bible — if code and README disagree, reconcile before merging. +Releases: dev→main via **merge commit** (never squash — main carries squash release +commits dev lacks), then `gh release create vX.Y.Z` triggers the PyPI publish +(Trusted Publishing; see `docs/guides/publishing-to-pypi.md`). diff --git a/README.md b/README.md index 1a05f95..be9c706 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,18 @@ > containers (`FeatureFrame`, `PredictionFrame`, and their anticipated siblings) > that every other repo depends on and that depends on nothing internal. > -> **Status:** **v1.7.0 — frozen API** (frozen since v1.0.0, ADR-018; the v1.1 surface is +> **Status:** **v1.8.0 — frozen API, published to PyPI** (frozen since v1.0.0, ADR-018; the +> v1.1 surface is > purely additive — the coherent posterior summary, ADR-019; v1.2.0 rebuilt the tower > `outside-in`, C-44; v1.3.0 makes the tower summary distribution-agnostic — no magnitude > zeroing by default, register C-45; v1.4.0 adds generic provenance to `FrameMetadata` > (`run_id`/`data_version`) and publishes the shared `assert_frame_envelope` checker, > ADR-020; v1.5.0 adds the threshold **exceedance** estimator `P(Y > c)`, ADR-021; v1.6.0 > adds the worst-case **expected_shortfall** estimator, ADR-022; v1.7.0 adds a third -> sibling package **`views_frames_reconcile`** — forecast reconciliation, ADR-023). This +> sibling package **`views_frames_reconcile`** — forecast reconciliation, ADR-023; v1.8.0 +> adds the native point-country broadcast + the self-describing reconciliation mode, +> the three showcase notebooks, and the frames-family hardening pass — Reconcile.md CIC, +> ADR-025 immutability-by-convention, the adversarial red-test batch). This > README is the design > bible; the contract it specifies is realised in `src/views_frames/` (index, frames, > io, conformance suite) plus the `src/views_frames_summarize/` sibling package diff --git a/docs/ADRs/013_metadata_and_identifier_model.md b/docs/ADRs/013_metadata_and_identifier_model.md index 38e6852..33051b5 100644 --- a/docs/ADRs/013_metadata_and_identifier_model.md +++ b/docs/ADRs/013_metadata_and_identifier_model.md @@ -81,6 +81,17 @@ the index aligns rows; the header describes the frame. ## Implementation Notes +> **As-built amendment (2026-07-02, four-axis audit / register C-70).** Two details landed +> differently from the notes below, both consistent with the decision's substance: +> (1) **`feature_names` is not a header field** — it is a `FeatureFrame` constructor +> argument/attribute (`feature_frame.py`), validated there against the feature axis +> (`len(feature_names) == values.shape[1]`) and serialized with the frame; `FrameMetadata` +> stays purely generic provenance (which ADR-020 later ratified as generic-*only*). +> (2) "Validated at construction" applies to the **frames and identifiers**, not the header +> fields themselves — `FrameMetadata` is a frozen dataclass of all-optional fields; +> `from_dict` tolerates unknown keys (forward-compatible). The typed-optional-extensible +> header and fixed `{time, unit}` identifiers — the decision's core — are implemented as decided. + - Model the header as a frozen dataclass (`FrameMetadata`) with all-optional fields + validation; `feature_names` lives here for `FeatureFrame`. - `_validation.py` enforces required identifiers `{time, unit}`; optional identifiers validated diff --git a/docs/ADRs/README.md b/docs/ADRs/README.md index baf4184..c288b05 100644 --- a/docs/ADRs/README.md +++ b/docs/ADRs/README.md @@ -2,16 +2,17 @@ # ADR README and Governance Map — views-frames This repository uses Architecture Decision Records (ADRs) to govern structural, semantic, -and operational behavior. Because `views-frames` is currently a **design bible** (a -README, consumer-review findings, design critiques, and falsification stubs — no `src/` yet), these -ADRs describe the **intended** architecture; when the leaf is stood up, code must conform -to them (and to the README, which is itself authoritative). +and operational behavior. The architecture these ADRs describe is **implemented and +released** (three packages under `src/`, public API frozen since v1.0.0); code must +conform to them (and to the README design bible, which is itself authoritative — if code +and README disagree, reconcile before merging). ADRs are divided into: 1. **Constitutional ADRs (000–009)** — foundational architectural rules. 2. **Governance ADRs (010)** — the technical risk register. -3. **Project-Specific ADRs (011–016)** — the ratified contract decisions. +3. **Project-Specific ADRs (011–025)** — the ratified contract, estimator, sibling-package, + and freeze/immutability decisions. --- @@ -50,10 +51,13 @@ Together, these define the invariant layer of the system. --- -## Project-Specific ADRs (011–016) — the ratified contract decisions +## Project-Specific ADRs (011–025) — the ratified project decisions -These ratify the six resolved decisions from the design bible (README §13a). All **Accepted** -2026-06-21; each cites the risk-register IDs it resolves. +**011–016** ratify the six founding contract decisions from the design bible (README §13a, +all Accepted 2026-06-21); **017–025** are the post-v1 decisions — sibling packages, the API +freeze, estimators, contract homes, and the immutability convention (Accepted 2026-06-22 +through 2026-07; ADR-024 is a design-direction ADR with implementation deferred). Each cites +the risk-register IDs it resolves. - **ADR-011** — [Twin-unification model (Option C)](011_twin_unification_option_c.md). Share only `SpatioTemporalIndex` + `_validation` + protocols + `io/`; relocate the frames as separate sibling classes; reject the `_BaseFrame` god-class (A); defer the composed header (B). Resolves D-03; relates to C-16, C-03. - **ADR-012** — [Sample axis convention](012_sample_axis_convention.md). Always an explicit trailing axis (`S ≥ 1`); `is_sample = S > 1`; `collapse` reduces the trailing axis; one shape contract. Relates to C-02, C-16, C-17. diff --git a/docs/CICs/FeatureFrame.md b/docs/CICs/FeatureFrame.md index ce1cdbe..9da00f9 100644 --- a/docs/CICs/FeatureFrame.md +++ b/docs/CICs/FeatureFrame.md @@ -3,7 +3,7 @@ **Status:** Active **Owner:** VIEWS platform maintainers -**Last reviewed:** 2026-06-24 +**Last reviewed:** 2026-07-02 **Related ADRs:** ADR-001, ADR-008, ADR-011, ADR-012, ADR-013 > Implemented in v0.1.0 (`src/views_frames/feature_frame.py`). This contract governs @@ -59,7 +59,7 @@ Violations raise at construction (ADR-008). ## 5. Outputs and Side Effects -- New frames from operations; `save` writes `y_features.npy` + `identifiers.npz` +- New frames from operations; `save` writes `values.npy` + `identifiers.npz` + `feature_names`/`metadata` (via the frame-state round-trip contract). --- diff --git a/docs/CICs/PredictionFrame.md b/docs/CICs/PredictionFrame.md index 1c8cedd..e623213 100644 --- a/docs/CICs/PredictionFrame.md +++ b/docs/CICs/PredictionFrame.md @@ -3,7 +3,7 @@ **Status:** Active **Owner:** VIEWS platform maintainers -**Last reviewed:** 2026-06-24 +**Last reviewed:** 2026-07-02 **Related ADRs:** ADR-001, ADR-008, ADR-011, ADR-012, ADR-013, ADR-017 > Implemented in v0.1.0 (`src/views_frames/prediction_frame.py`), relocated from @@ -61,16 +61,19 @@ Violations raise at construction (ADR-008) — never log-and-continue. ## 5. Outputs and Side Effects -- New frames from operations; `save` writes `y_pred.npy` + `identifiers.npz` +- New frames from operations; `save` writes `values.npy` + `identifiers.npz` (+ header). No other side effects. --- ## 6. Failure Modes and Loudness -- Raises `TypeError` on non-`float32`/object-dtype values; `ValueError` on shape, +- Raises `ValueError` on object-dtype values (list-in-cell is banned), on shape, length, or completeness violations, and on `reindex(other)` when `other` is not a - subset of this frame's index. The structural guarantee is **not temporal** (register C-11). + subset of this frame's index. Other numeric dtypes (e.g. float64) are **coerced** + to float32 by copy at construction — accepted, not rejected (the no-copy fast path + is float32-only, register C-07). The structural guarantee is **not temporal** + (register C-11). --- diff --git a/docs/CICs/README.md b/docs/CICs/README.md index 30ff9c4..995a687 100644 --- a/docs/CICs/README.md +++ b/docs/CICs/README.md @@ -49,17 +49,18 @@ Contracts must be clear enough that: --- -## Status: infrastructure only (no contracts yet) +## Status: fully contracted -`views-frames` currently contains no code — it is a design bible (see `README.md`). This -directory holds the template (`cic_template.md`) and this README. **Contracts are authored -as each class is implemented**, when the twins are relocated and the leaf is stood up. +Every non-trivial surface across the three shipped packages (`views_frames`, +`views_frames_summarize`, `views_frames_reconcile`) is governed by an active CIC below. +New contracts are authored **with** the class/package that introduces them (the +`Reconcile.md` gap was the last, closed 2026-06-28, register C-64). --- ## Active Contracts -These CICs govern the classes implemented in v0.1.0 (`src/views_frames/`). +These CICs govern the shipped surface (`src/`, three packages, frozen since v1.0.0). - `SpatioTemporalIndex.md` — the genuinely-reused alignment primitive; same-level logic owned, cross-level mapping injected (ADR-014). diff --git a/docs/CICs/Reconcile.md b/docs/CICs/Reconcile.md index bd657e2..8565a93 100644 --- a/docs/CICs/Reconcile.md +++ b/docs/CICs/Reconcile.md @@ -255,14 +255,20 @@ gate); these are the contract-bearing suites: both-points, pre-tiled cm) — `TestReconciliationResult`. - **Red (it fails loud):** `tests/test_reconciliation_validation.py` — `validate_reconciliation_inputs` raises on wrong cm level, wrong grid level, sample-count - mismatch, time mismatch, and a missing country forecast; the `(M, 2)` map-keys guard - (`test_bad_mapping_shape_raises`). The conformance-suite **negative** (a deliberately - non-conforming impl/input makes `assert_reconcile_contract` raise) and the - `ReconciliationResult` frozen-ness assertion are the red gaps being closed alongside this - contract (register C-65 batch, epic #179 / S3). - -Each §3 guarantee maps to a green/beige test; each §6 failure mode maps to a red test in -`test_reconciliation_validation.py`. + mismatch, time mismatch, a missing country forecast, and a missing + `(time, priogrid_gid)` mapping entry (`test_missing_mapping_entry_raises`); the `(M, 2)` + map-keys guard (`test_bad_mapping_shape_raises`). The conformance-suite **negative** (a + deliberately non-conforming impl makes `assert_reconcile_contract` raise) and the + `ReconciliationResult` frozen-ness assertion live in `test_reconcile_conformance.py` / + `test_reconciliation_e2e_parity.py` (the C-65 batch, epic #179 / S3). The + share-proportionality **law** (`test_shares_are_preserved_within_each_draw`) and the + 2026-07 audit regressions — exact conservation for tiny nonzero sums, the negative-total + raise — are pinned in `test_reconciliation_parity.py` and + `test_falsification_safety_audit_2026_07.py` (register C-68/C-70). + +Each §3 guarantee maps to a green/beige test; each §6 failure mode maps to a red test +(the validation guards in `test_reconciliation_validation.py`; the negative-total and +tiny-sum modes in the audit-regression file above). --- diff --git a/docs/CICs/SpatioTemporalIndex.md b/docs/CICs/SpatioTemporalIndex.md index 28387e0..5ddfd60 100644 --- a/docs/CICs/SpatioTemporalIndex.md +++ b/docs/CICs/SpatioTemporalIndex.md @@ -77,8 +77,11 @@ Assumptions that do not hold **must raise** at construction (ADR-009), never fal ## 6. Failure Modes and Loudness -- Raises `TypeError` if any identifier array is not integer dtype. -- Raises `ValueError` if arrays differ in length, contain NaN, or `level` is invalid. +- Raises `TypeError` if any identifier array is not integer dtype — which is also what + a NaN-carrying array hits (NaN forces a float dtype; integers cannot be NaN, so the + "no NaN" guarantee is enforced *via* the dtype check, not a separate `ValueError`). +- Raises `ValueError` if arrays differ in length; `TypeError` if `level` is not a + `SpatialLevel`. - `cross_level_align` raises if called without an injected mapping — it must **never** silently fetch or assume one (the defining boundary of ADR-014). - Nothing fails silently. diff --git a/docs/CICs/Summarize.md b/docs/CICs/Summarize.md index adf692f..958597a 100644 --- a/docs/CICs/Summarize.md +++ b/docs/CICs/Summarize.md @@ -3,7 +3,7 @@ **Status:** Active **Owner:** VIEWS platform maintainers -**Last reviewed:** 2026-06-24 +**Last reviewed:** 2026-07-02 **Related ADRs:** ADR-002, ADR-008, ADR-009, ADR-011, ADR-012, ADR-014, ADR-017, ADR-019, ADR-021 > The `views_frames_summarize` package (functions, not a class). Implemented in diff --git a/docs/CICs/TargetFrame.md b/docs/CICs/TargetFrame.md index 6764704..00956eb 100644 --- a/docs/CICs/TargetFrame.md +++ b/docs/CICs/TargetFrame.md @@ -3,7 +3,7 @@ **Status:** Active **Owner:** VIEWS platform maintainers -**Last reviewed:** 2026-06-24 +**Last reviewed:** 2026-07-02 **Related ADRs:** ADR-001, ADR-008, ADR-011, ADR-012, ADR-013 > Implemented in v0.1.0 (`src/views_frames/target_frame.py`). This contract governs @@ -56,7 +56,7 @@ Violations raise at construction (ADR-008). ## 5. Outputs and Side Effects -- New frames from operations; `save` writes `y_true.npy` + `identifiers.npz`. No other +- New frames from operations; `save` writes `values.npy` + `identifiers.npz`. No other side effects. --- diff --git a/docs/validate_docs.sh b/docs/validate_docs.sh index 8bd3b9b..6f24d86 100755 --- a/docs/validate_docs.sh +++ b/docs/validate_docs.sh @@ -84,6 +84,26 @@ echo "--- Checking template status markers ---" template_count=$(grep -rl '\-\-template\-\-' --include='*.md' . 2>/dev/null | wc -l) echo " INFO: $template_count files still have --template-- status (expected in template repo)" +# 6. README status banner tracks the released MAJOR.MINOR (guards the narrative +# epoch-lag found by the 2026-07 audit, register C-70: the banner sat at v1.7.0 +# while 1.8.0 was on PyPI). Patch releases are exempt — the banner tracks the +# surface, which only MINORs change. +echo "--- Checking README status banner vs pyproject version ---" +if [ -f "../pyproject.toml" ] && [ -f "../README.md" ]; then + pyver=$(grep -m1 '^version = ' ../pyproject.toml | sed 's/version = "\(.*\)"/\1/') + pymm=$(echo "$pyver" | cut -d. -f1,2) + banner=$(grep -m1 -oE '\*\*Status:\*\* \*\*v[0-9]+\.[0-9]+' ../README.md | grep -oE '[0-9]+\.[0-9]+') + if [ -z "$banner" ]; then + echo " ERROR: could not find a '**Status:** **vX.Y' banner in README.md" + errors=$((errors + 1)) + elif [ "$banner" != "$pymm" ]; then + echo " ERROR: README banner says v$banner but pyproject version is $pyver (MAJOR.MINOR $pymm)" + errors=$((errors + 1)) + else + echo " OK (banner v$banner ~ pyproject $pyver)" + fi +fi + echo "" if [ "$errors" -gt 0 ]; then echo "=== FAILED: $errors issue(s) found ===" diff --git a/reports/technical_risk_register.md b/reports/technical_risk_register.md index e84d92d..22a8c81 100644 --- a/reports/technical_risk_register.md +++ b/reports/technical_risk_register.md @@ -6,8 +6,8 @@ | Owner | VIEWS platform maintainers | | Last Updated | 2026-07-02 | | Total Concerns | 67 | -| Open Concerns | 13 | -| Resolved Concerns | 54 | +| Open Concerns | 12 | +| Resolved Concerns | 55 | | Disagreements | 12 | --- @@ -35,9 +35,7 @@ > and formalised by ADRs 011–016, all of which merged and shipped/froze in **v1.0.0** > (ADR-018) — they are now in **Resolved Concerns**. C-01/C-08/C-12 are resolved-by-decision > and persist only as **frozen-invariant guards** (their triggers protect the frozen scope). -> The **13 currently open** concerns fall into five clusters plus a cross-cutting theme -> and one bundle (**C-70**, the 2026-07 audit's docs/tests polish list — one docs PR + one -> tests PR clears it) +> The **12 currently open** concerns fall into five clusters plus a cross-cutting theme > (detailed under *Causal clusters* in Register Conventions): **(1) summarize-estimator > coherence (#89)** — C-32, C-34, C-43, C-57 (the under-determined frozen MAP/bimodality > estimators); **(2) reconcile method + governance** — C-58, C-62 (+ D-12): a pragmatic @@ -239,21 +237,6 @@ C-63 was resolved by **correcting the contract** (ADR-025): the value buffer is --- -### C-70: audit polish bundle — docs narrative epoch-lag + three small test adds (four-axis audit 2026-07) - -| Field | Value | -|-------|-------| -| ID | C-70 | -| Tier | 3 | -| Source | four-axis audit 2026-07-02 (review-base-docs Phase 2 + test-review Phase 3) | -| Trigger | The next docs or tests PR — fold this bundle in rather than letting the narrative lag compound (a new contributor/agent onboarding from `CLAUDE.md` today is told a two-package v0.1.0 architecture). | -| Location | **Docs:** `CLAUDE.md` (epoch-stale: "Two packages", "Status: v0.1.0", no `views_frames_reconcile`); `README.md:7` (banner says v1.7.0; chronicle ends at 1.7.0); `docs/ADRs/013_*.md` (claims `feature_names` lives in `FrameMetadata` — it is a `FeatureFrame` constructor arg, `feature_frame.py:32`); `docs/CICs/{PredictionFrame,TargetFrame,FeatureFrame}.md` §5 (say `y_pred/y_true/y_features.npy` — actual artifact is `values.npy`, `io/npz.py:34`); `PredictionFrame.md` §6 ("TypeError on non-float32" — float64 is *coerced*, object dtype raises *ValueError*); `docs/CICs/README.md:52-56` ("no contracts yet" contradicting its own Active list); `docs/ADRs/README.md:6-55` (design-bible framing, "011–016", "six decisions"); `Reconcile.md` §10 (blanket "each §6 mode maps to a red test" — the missing-map-entry mode is pinned only at the leaf); stale `Last reviewed` dates on 3 frame CICs. **Tests:** a share-proportionality *law* test (the method's essence is otherwise pinned only by the frozen oracle; the falsify F5 probe proved the law holds — commit it); an mmap read-only pin (`frame.values.flags.writeable is False` after `load(mmap=True)` — F2 proved it); a reconcile-suite test for the missing-`(time,gid)`-mapping-entry raise. | -| Cross-refs | The systemic pattern: all doc drift is in narrative text `validate_docs.sh` does not check (banners, framing, filenames), while every mechanically-validated element is current. C-46 (the "verification surface depends on usage" family), C-58 (test-realism, registered), resolved C-67/C-68/C-69 (the fixed half of the same audit). | - -The 2026-07 four-axis audit found the code↔contract agreement strong (30/30 public symbols CIC-covered, 15/16 project ADRs accurate, ~57/60 CIC guarantee items pinned) but the **narrative documentation an epoch behind** and three cheap test additions open. None affects correctness; the CLAUDE.md/ADR-013 items are the material ones (they misinform onboarding). One small docs PR + one small tests PR clears the whole entry. **Tier 3** — maintainability/onboarding accuracy, multiple contributors affected via CLAUDE.md. **Open.** - ---- - ## Disagreements ### D-01: `SpatioTemporalIndex` domain-purity fork (where does cross-level alignment live?) @@ -394,6 +377,21 @@ Cross-refs: C-47 (eval provenance kept out of the generic header — the precede ## Resolved Concerns +### C-70: audit polish bundle — docs narrative epoch-lag + three small test adds (four-axis audit 2026-07) — RESOLVED + +| Field | Value | +|-------|-------| +| ID | C-70 | +| Tier | 3 | +| Source | four-axis audit 2026-07-02 (review-base-docs Phase 2 + test-review Phase 3) | +| Trigger | The next docs or tests PR — fold this bundle in rather than letting the narrative lag compound (a new contributor/agent onboarding from `CLAUDE.md` today is told a two-package v0.1.0 architecture). | +| Location | **Docs:** `CLAUDE.md` (epoch-stale: "Two packages", "Status: v0.1.0", no `views_frames_reconcile`); `README.md:7` (banner says v1.7.0; chronicle ends at 1.7.0); `docs/ADRs/013_*.md` (claims `feature_names` lives in `FrameMetadata` — it is a `FeatureFrame` constructor arg, `feature_frame.py:32`); `docs/CICs/{PredictionFrame,TargetFrame,FeatureFrame}.md` §5 (say `y_pred/y_true/y_features.npy` — actual artifact is `values.npy`, `io/npz.py:34`); `PredictionFrame.md` §6 ("TypeError on non-float32" — float64 is *coerced*, object dtype raises *ValueError*); `docs/CICs/README.md:52-56` ("no contracts yet" contradicting its own Active list); `docs/ADRs/README.md:6-55` (design-bible framing, "011–016", "six decisions"); `Reconcile.md` §10 (blanket "each §6 mode maps to a red test" — the missing-map-entry mode is pinned only at the leaf); stale `Last reviewed` dates on 3 frame CICs. **Tests:** a share-proportionality *law* test (the method's essence is otherwise pinned only by the frozen oracle; the falsify F5 probe proved the law holds — commit it); an mmap read-only pin (`frame.values.flags.writeable is False` after `load(mmap=True)` — F2 proved it); a reconcile-suite test for the missing-`(time,gid)`-mapping-entry raise. | +| Cross-refs | The systemic pattern: all doc drift is in narrative text `validate_docs.sh` does not check (banners, framing, filenames), while every mechanically-validated element is current. C-46 (the "verification surface depends on usage" family), C-58 (test-realism, registered), resolved C-67/C-68/C-69 (the fixed half of the same audit). | + +The 2026-07 four-axis audit found the code↔contract agreement strong (30/30 public symbols CIC-covered, 15/16 project ADRs accurate, ~57/60 CIC guarantee items pinned) but the **narrative documentation an epoch behind** and three cheap test additions open. None affects correctness; the CLAUDE.md/ADR-013 items are the material ones (they misinform onboarding). **Tier 3** — maintainability/onboarding accuracy, multiple contributors affected via CLAUDE.md. **Resolved** (2026-07-02, the option-3 cleanup): the tests half by PR #195 (share-proportionality law, mmap read-only pin, missing-map-entry raise); the docs half in the follow-up docs PR (CLAUDE.md rewritten for the three-package released reality; README banner → v1.8.0 + chronicle; ADR-013 as-built amendment; the three CIC §5 filenames → `values.npy`; PredictionFrame §6 coercion wording; SpatioTemporalIndex §6 NaN-via-dtype wording; CICs/ADRs README framing refreshed; `Last reviewed` dates bumped; Reconcile.md §10 updated to name the actual pinning files). Plus a **recurrence guard**: `validate_docs.sh` now checks the README banner's MAJOR.MINOR against `pyproject.toml`, so the narrative epoch-lag pattern fails validation instead of accumulating. + +--- + ### C-68: `reconcile_proportional` silently violated sum-to-country for tiny nonzero draw sums (the `+ 1e-8` epsilon), and silently clamped negative totals — RESOLVED | Field | Value |