Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/docs-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- 'tests/test_doc_deps_integrity.py'
- 'tests/test_docs_ia.py'
- 'tests/test_v4_matrix.py'
- 'tests/test_naming_guard.py'
# tests/conftest.py is auto-loaded by pytest for the snippet
# test run and mutates sys.path + MPLBACKEND (conftest.py:14, 18);
# changes there can break snippet exec without touching the test
Expand All @@ -32,6 +33,7 @@ on:
- 'tests/test_doc_deps_integrity.py'
- 'tests/test_docs_ia.py'
- 'tests/test_v4_matrix.py'
- 'tests/test_naming_guard.py'
- 'tests/conftest.py'
- 'pyproject.toml'
# sphinx-build job mirrors RTD setup; trigger when RTD config drifts
Expand Down Expand Up @@ -102,6 +104,14 @@ jobs:
# the main test matrix does not trigger on docs-only diffs.
run: PYTHONPATH=. DIFF_DIFF_BACKEND=python pytest tests/test_v4_matrix.py -v

- name: Run naming-completeness guard
# The section-8/section-9 naming guard (spec in the module docstring):
# public-surface vocabulary sweep, phase-table agreement in both
# directions, and rename consumer coverage against code_refs. Runs
# here for the same reason as the matrix step - docs-only ledger/spec
# edits must not bypass it.
run: PYTHONPATH=. DIFF_DIFF_BACKEND=python pytest tests/test_naming_guard.py -v

sphinx-build:
name: Sphinx HTML build (-W warnings as errors)
# Skip unrelated label churn: a non-ready-for-ci label add/remove won't run this job.
Expand Down
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added
- **Naming-completeness guard test** (`tests/test_naming_guard.py`, internal):
mechanical enforcement of the three v4-program naming invariants that were
previously prose-only - (1) a public-surface sweep of every
`diff_diff.__all__` export's signatures, dataclass fields and property names
against the section-8 rename vocabulary (every hit must be a
`docs/v4-deprecations.yaml` row, structurally exempt, or allowlisted with a
stated reason - first run: 0 unrowed violations, 49 documented carve-outs);
(2) phase-table agreement between the ledger and `docs/v4-design.md`
section 9 in both directions (bracketed-citation expansion, ship-version
predicate, M-008/M-031 as named fixtures); (3) rename consumer coverage per
section-8 rule 11, with tiered regex/AST lanes (ambiguous tokens like `time`
match at call sites only) over `diff_diff/` source, the packaged
`guides/*.txt`, and `docs/methodology/`. The guard's first run completed 26
rows' `code_refs` with newly confirmed readers (e.g. `diagnostics.py`'s
`fit(time=...)` call sites on [M-030], `diagnostic_report.py`'s bare
`bacon_decompose(...)` call on [M-076], the packaged-guide readers of
`cohort`/`aggregation`/the wrapper functions), rule 11 gained the
token-family `code_refs` union clause, and the `plot_group_effects` cohort
selector joined the section-8 domain-vocabulary carve-outs. Wired into the
docs-tests workflow so docs-only ledger/spec edits cannot bypass it; the
completed TODO guard row is removed (spec now lives in the test module
docstring).

### Fixed
- **Normal-theory tail inference on clustered analytical paths — the D4 +
tail-df defect fix of the 3.9 variance-consolidation program** ([M-127]).
Expand Down
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ generic sparse-FE, QR+SVD rank-detection redundancy, `check_finite` bypass — m
| Tighten the mypy suppressions that back the enforced-zero posture: burn down `prep_dgp`'s per-module `[index]` override (needs a None-vs-array restructure that preserves the seeded RNG stream), and evaluate re-enabling the globally disabled codes (`arg-type`, `return-value`, `var-annotated`, `assignment`) one at a time — `assignment` alone hid several real annotation drifts found during the 2026-07 triage. | `pyproject.toml` `[tool.mypy]`, `diff_diff/prep_dgp.py` | lint-CI | Mid | Low |
| MMM interop follow-up: Meridian `roi_calibration_period` mask builder - accept the MMM's time index + channel order and emit the boolean `(n_media_times, n_media_channels)` mask so `.to_code()` scopes the prior to the experiment window automatically (today the caller passes a mask expression / `full_model_window=True`). | `diff_diff/mmm.py` | mmm-interop | Quick | Low |
| MMM interop PR-B: calibration tutorial notebook (fit DiD/CS -> scope -> `to_pymc_marketing_lift_test` / `to_meridian_roi_prior`) + a `llms-practitioner.txt` Step 8 pointer to the exporters as the MMM hand-off. | `docs/tutorials/`, `diff_diff/guides/llms-practitioner.txt` | mmm-interop | Mid | Low |
| Section-8 naming-completeness guard test: sweep the public surface (module-level functions, class `__init__`/`fit` params, results dataclass fields) for contract-rename violations and fail unless each hit is either a `docs/v4-deprecations.yaml` row or an allowlist entry carrying a stated reason (seed the allowlist with the documented domain-vocabulary exemptions: the staggered family's ATT(g,t) `group`/`groups`, `TripleDifference.fit[group]`, wrapper params dying with `M-070..M-077`, and params inherited from `DifferenceInDifferences.__init__`). Today the rules are normative prose and the ledger holds only what an audit remembered — two successive manual sweeps each found surfaces the prior one missed (`M-094`/`M-095`, then the 19-row `M-097..M-115` function sweep). Should land BEFORE Phase 2c so the rename PR works from a mechanically-verified list. Two further checks belong in the same guard, each having been missed by hand across successive review rounds: (a) **phase-table agreement** (spec amended 2026-07-31; three separate rounds caught a stale table and nothing verifies the section 9 checklist's assertion) — direction 1: every row's current `phase` appears in the matching `docs/v4-design.md` section 9 table entry; direction 2: every id cited in a cell resolves to a live ledger row whose lifecycle includes work in that phase, mechanically: a citation of row R in the cell of phase P is legitimate iff P's ship-version (phases 2/3/4 → "3.9", phase 5 → "4.0", phase 6 → "4.1") appears among R's `introduced_in`/`deprecated_in`/`removed_in` — or `decision_due` for `env-default` rows (M-008 is scheduled solely via `decision_due: "4.0"` and its phase-5 citation must pass; make it a guard fixture) — (accepts shim-phase rows re-listed in the phase-5 removal roster; rejects a row with no lifecycle work at that version, e.g. M-031 cited in phase 5). Terminal rows (`done`/`removed`) are exempt in both directions. Parsing rule: cells cite ids as single tokens (`[M-122]`), compound single brackets (`[M-030..M-047]`, comma compounds), and endpoint-bracket ranges (`[M-132]..[M-135]`) — the guard's parser expands BOTH range forms plus comma compounds when computing cell membership (single-token matching alone yields zero interior ids). Known accepted limitation: phases 2 and 3 both ship 3.9, so the predicate cannot catch a row cited in the wrong 3.9 phase; (b) **consumer coverage** (section 8 rule 11) — for each rename row, grep `diff_diff/` and `docs/methodology/` for the old name and require every hit to be either in the row's `code_refs` or allowlisted, since `getattr(obj, "old_name", default)` degrades silently rather than raising after removal. (Sequenced in v4-design §9.) | `tests/`, `docs/v4-design.md` | gating-completeness amendment | Mid | Medium |
| Tracking-file contract guard test: reject NEW active deferred-work pointers at `TODO.md` (deferred rows live in `DEFERRED.md`; allowlist for historical/past-tense prose and actionable-row pointers) and assert rows cross-linking a `docs/v4-deprecations.yaml` `M-xxx` id don't restate ledger status. Origin: tracking-split local review R2. | `tests/`, `TODO.md`, `DEFERRED.md` | tracking-split | Quick | Low |
| Real-data CI canary for dataset-backed replication tests: `test_methodology_lwdid.py`'s Prop 99 / Walmart goldens skip (visibly) when loaders fall back to synthetic; add a lane or canary asserting `df.attrs["source"] == "lwdid_ssc_ancillary"` in CI so network regressions cannot silently de-gate the replication tests. Follow-on from the loader-fallback repair (#723), which made provenance explicit but deliberately did not add a network-dependent CI lane. | `tests/test_methodology_lwdid.py`, `.github/workflows/` | LWDiD validation suite | Quick | Low |
| df-provenance completion follow-up (PR C descoping): a Wooldridge public per-row df channel (a per-key dict that can also carry the hc2_bm BM contrast dofs the scalar `_df_analytic_fallback` cannot represent — the unified surface's Wooldridge df column stays NaN until then); ImputationDiD lead-horizon `event_study_df` (needs the `_compute_lead_coefficients`/`_aggregate_event_study` return-path change plus bootstrap-clearing semantics for post rows); and the `inference_df` (DiD/MPD/SA/StackedDiD) vs `df_inference` (CS/dCDH) adapter-naming unification. | `diff_diff/wooldridge_results.py`, `diff_diff/imputation.py`, `diff_diff/results_base.py` | #variance-PR-C | Mid | Low |
Expand Down
Loading
Loading