From 7da94e7f6bbe38ffc4f735d60e64d2775463d47a Mon Sep 17 00:00:00 2001 From: igerber Date: Fri, 31 Jul 2026 21:14:01 -0400 Subject: [PATCH 1/2] docs(v4): planning consolidation - alias diet (M-132..M-135), M-063 cancelled, section 9 de-staled + canonical 3.9 sequence Records the 2026-07-31 consolidation-scope decisions in the canonical pair (v4-design.md + the CI-enforced ledger): no staggered-family mega-merge, no ImputationDiD<->TwoStageDiD merge, and the moderate alias diet - CDiD, Stacked and Gardner deprecate at 3.9 (FutureWarning via a module-level __getattr__, M-135, since their surviving targets have no shim to ride) and are removed at 4.0 (M-132..M-134, code_refs carrying the grep-verified reader surfaces). EDiD was initially slated but is RETAINED: the Chen-Sant'Anna-Xie paper brands its estimator EDiD throughout (Tables 6/7; the repo's paper review records the same usage), which is the diet's own keep-the-literature-name criterion. The never-shipped Spillover introduction is cancelled (M-063 flipped terminal as an anti-resurrection pin). Ledger 110 -> 114 rows with matching matrix-test constants, plus the FORMAT CONTRACT comment rule qualified to line-leading-only. Section 9 is de-staled against the ledger: the phase-2 cell gains the shipped M-122..M-127 insertions, phase 3 gains M-064/M-082, and the phase-5 roster is completed (M-020..M-027/M-064/M-084/M-086..M-089/M-117 re-listed; M-031 carved out - no 4.0 lifecycle work; M-016 moved to a property-window clause - removal is 5.0) under an explicit citation semantic. A new "Remaining 3.9 sequence" subsection is the single canonical PR order (guard test -> 2c mixin-first -> 2c renames -> 2b aggregate()+consumability -> 2d -> phase-3 merges -> phase-4 cut). The TODO.md naming-guard row's phase-table agreement spec is amended to the enforceable two-direction form with a stated parser rule and derivation. Duplicated flip-plan prose in DEFERRED.md and the REGISTRY M-010 note is trimmed to cross-links; .claude/memory.md 4.0-TARGET blocks compressed to pointers; the stale "scheduled for PR C" comments in test_variance_conventions.py updated to the converged post-M-127 reality. --- .claude/memory.md | 14 +--- CHANGELOG.md | 18 +++++ DEFERRED.md | 2 +- TODO.md | 2 +- docs/methodology/REGISTRY.md | 11 +-- docs/v4-deprecations.yaml | 64 ++++++++++++++-- docs/v4-design.md | 115 +++++++++++++++++++++++------ tests/test_v4_matrix.py | 24 ++++-- tests/test_variance_conventions.py | 20 +++-- 9 files changed, 208 insertions(+), 62 deletions(-) diff --git a/.claude/memory.md b/.claude/memory.md index 1d32eda57..79d5acd03 100644 --- a/.claude/memory.md +++ b/.claude/memory.md @@ -90,12 +90,8 @@ everywhere; `to_dict()` / `to_dataframe()` on most classes. See `.claude/../docs/methodology/REGISTRY.md` per estimator. ### Results Objects — 4.0 TARGET (do not write against this pre-4.0) -Canonical quintet becomes the native fields on every class; `overall_att` -family becomes FutureWarning properties (removed 5.0); one unified -event-study representation; aggregation via post-fit -`results.aggregate(type=)`. Normative spec: `docs/v4-design.md`; per-surface -lifecycle: `docs/v4-deprecations.yaml` (CI-enforced by -`tests/test_v4_matrix.py`). +Target contract: `docs/v4-design.md` section 5; per-surface lifecycle: +`docs/v4-deprecations.yaml` (CI-enforced by `tests/test_v4_matrix.py`). ### Column Naming — CURRENT (3.x) - `unit` unit id (`unit_col` on HAD; `group` on dCDH — both slated for 4.0) @@ -107,10 +103,8 @@ lifecycle: `docs/v4-deprecations.yaml` (CI-enforced by (`cohort` on WooldridgeDiD — slated for 4.0) - `covariates` covariate list (`controls` on dCDH — slated for 4.0) -### Column Naming — 4.0 TARGET -`outcome` / `unit` / `time` (calendar) / `post` (0/1) / `treatment` (0/1) / -`first_treat` / `covariates` / `partition` (DDD), no `_col` suffixes. Rules: -`docs/v4-design.md` section 8. +### Column Naming — 4.0 TARGET (do not write against this pre-4.0) +Target vocabulary and rules: `docs/v4-design.md` section 8. ## Session Notes diff --git a/CHANGELOG.md b/CHANGELOG.md index 60c6fa554..9bb91098c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -215,6 +215,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 rejecting the design. ### Added +- **4.0-program planning consolidation + alias-diet decision** (internal; + no runtime change). Three consolidation-scope decisions locked 2026-07-31 + and recorded in `docs/v4-design.md` §8: no staggered-family mega-merge, no + ImputationDiD↔TwoStageDiD merge, and a moderate **alias diet** — `CDiD`, + `Stacked` and `Gardner` are deprecated at 3.9 and removed at 4.0 + (ledger rows M-132..M-134), with the 3.9 `FutureWarning` carried by a + module-level `__getattr__` (M-135, ships in phase 2d) because the + target classes survive; `EDiD` was initially slated but retained (review + evidence: it is the Chen-Sant'Anna-Xie paper's own estimator label); the + never-shipped `Spillover` alias introduction is + cancelled (M-063 → terminal). Ledger row count 110 → 114. `docs/v4-design.md` + §9 is de-staled against the ledger (the phase table gains the shipped + M-122..M-127 insertions, the M-064/M-082 phase-3 entries, and a completed + phase-5 roster with the M-031/M-016 carve-outs) and gains the canonical + "Remaining 3.9 sequence"; the TODO.md naming-guard row's phase-table + agreement spec is amended to the enforceable two-direction form; duplicated + flip-plan prose in DEFERRED.md and the REGISTRY M-010 note is trimmed to + cross-links. - **`df_convention` becomes a library-wide three-value knob** ([M-127]). `{"residual", "cluster", "normal"}` is now accepted everywhere the knob exists, and the knob extends to five standalone estimators: `SunAbraham`, diff --git a/DEFERRED.md b/DEFERRED.md index 5ca1fabf7..9b6976deb 100644 --- a/DEFERRED.md +++ b/DEFERRED.md @@ -113,7 +113,7 @@ here carry the remaining implementation work and cross-link their `M-xxx` ids. | Issue | Location | PR | Priority | |-------|----------|----|----------| -| Clustered inference df **default flip to `"cluster"` (G−1) at v4** — the `df_convention=` knob is now the library-wide THREE-VALUE surface (PR C / M-127, 3.9: DiD/TWFE/MPD/LinearRegression + SunAbraham/WooldridgeDiD-OLS/StackedDiD/ImputationDiD-pretrends at default `"residual"`, LPDiD already at `"cluster"`); the remaining work is the major-version default change (moves every clustered p-value/CI) + migration note + flipping `TestDfConvention`/`test_moderate_t_pins_residual_df_convention`/the per-estimator knob suites' expectations. Lifecycle tracked in docs/v4-deprecations.yaml (M-004..M-006 + M-128..M-131). | `diff_diff/linalg.py::LinearRegression`, `diff_diff/estimators.py`, `diff_diff/twfe.py`, `diff_diff/sun_abraham.py`, `diff_diff/wooldridge.py`, `diff_diff/stacked_did.py`, `diff_diff/imputation.py` | — | Medium | +| Clustered inference df **default flip at v4** (lifecycle + roster: docs/v4-deprecations.yaml M-004..M-006 + M-128..M-131; knob semantics: v4-design §7). Implementation work the ledger rows do not carry: the migration note, and flipping the test expectations that pin the current default (`TestDfConvention`, `test_moderate_t_pins_residual_df_convention`, the per-estimator knob suites). | `diff_diff/linalg.py::LinearRegression`, `diff_diff/estimators.py`, `diff_diff/twfe.py`, `diff_diff/sun_abraham.py`, `diff_diff/wooldridge.py`, `diff_diff/stacked_did.py`, `diff_diff/imputation.py` | — | Medium | ## Decision record — won't-fix / waived diff --git a/TODO.md b/TODO.md index 3d31d4731..9d3c7e3a2 100644 --- a/TODO.md +++ b/TODO.md @@ -61,7 +61,7 @@ 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** — every row's `phase` must appear in the matching `docs/v4-design.md` section 9 table entry and vice versa (the section 9 checklist asserts this agreement but nothing verifies it, and three separate rounds caught a stale table); (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. | `tests/`, `docs/v4-design.md` | gating-completeness amendment | Mid | Medium | +| 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` (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 | diff --git a/docs/methodology/REGISTRY.md b/docs/methodology/REGISTRY.md index 85a363d3f..09da6b835 100644 --- a/docs/methodology/REGISTRY.md +++ b/docs/methodology/REGISTRY.md @@ -226,14 +226,9 @@ under H0 of parallel trends, δ_e = 0 for all e < 0. `fixest::feols(... | unit + time)` parity target) and the pooled default produce identical point estimates only under balanced panels with no covariates and simultaneous adoption; with unbalanced panels or covariates - the two projections differ. The 4.0 program (see `docs/v4-design.md` - section 4.1 and `docs/v4-deprecations.yaml` [M-010]) migrates the - event-study default to the unit-FE spec on the merged TwoWayFixedEffects, - keeping the pooled model reachable via `spec="pooled"` (required for - repeated cross-sections and for reproducing 3.x MPD numbers exactly); the - Phase 3 PR gates on a balanced-equivalence test, an - unbalanced-or-covariate divergence test, and pooled bit-exact parity vs - 3.x MPD. + the two projections differ. The 4.0 merge changes the event-study default + to the unit-FE spec with the pooled model reachable via `spec="pooled"` — + plan and gates: `docs/v4-design.md` section 4.1, ledger row [M-010]. Post-treatment coefficients (e ≥ 0) estimate dynamic treatment effects. diff --git a/docs/v4-deprecations.yaml b/docs/v4-deprecations.yaml index f1abb6ead..c5db913ba 100644 --- a/docs/v4-deprecations.yaml +++ b/docs/v4-deprecations.yaml @@ -13,7 +13,8 @@ # # FORMAT CONTRACT (parsed by a purpose-built scanner, no PyYAML): top-level # `rows:` list; each row a flat mapping of single-line scalars; `code_refs` as -# an inline flow list; no anchors, no multi-line strings. Comments allowed. +# an inline flow list; no anchors, no multi-line strings. Line-leading comments +# only (a trailing `#` after a value is parsed as part of the value). # # Locator grammar: "diff_diff:Name" (top-level export), "diff_diff:Class[param]" # (__init__ param), "diff_diff:Class.method[param]" (method param), @@ -762,14 +763,14 @@ rows: old: "Spillover" new: null old_target: null - new_target: "diff_diff:SpilloverDiD" - introduced_in: "3.9" + new_target: null + introduced_in: null deprecated_in: null removed_in: null - status: planned + status: done phase: 2 code_refs: [diff_diff/__init__.py] - notes: "Introduce-only; follows the Stacked=StackedDiD precedent." + notes: "CANCELLED 2026-07-31 (alias-diet decision): the planned Spillover=SpilloverDiD introduction will never ship; row retained as an anti-resurrection pin (ids are never deleted; done with null new_target asserts the name stays absent)." # ---- Module-level function wrappers (class form is canonical) ------------ - id: M-070 @@ -1457,3 +1458,56 @@ rows: new_default: "'cluster'" code_refs: [diff_diff/imputation.py] notes: "PR C ([M-127]) extended the knob here with default 'residual' (pretrends lead regression only; BJS overall inference and the joint pretrend Wald F are knob-independent); flips with [M-004]..[M-006] at 4.0; covered by the same migration note." + + # ---- Alias diet (2026-07-31 consolidation-scope decision) ---------------- + - id: M-132 + kind: alias + group: alias-table + old: "CDiD" + new: null + old_target: "diff_diff:ContinuousDiD" + new_target: null + deprecated_in: "3.9" + removed_in: "4.0" + status: planned + phase: 5 + code_refs: [diff_diff/__init__.py, docs/api/estimators.rst, diff_diff/guides/llms-full.txt, diff_diff/guides/llms-practitioner.txt, tests/test_aliases.py] + notes: "Alias diet 2026-07-31: not an established literature name and one edit from CiC/DiD/DCDH. Target class survives, so no parent-class shim can carry the warning - the 3.9 FutureWarning rides the module __getattr__ shim ([M-135]); removal at 4.0. code_refs list the known reader surfaces (section 8 rule 11); the 2d/removal PRs also grep repo-wide." + - id: M-133 + kind: alias + group: alias-table + old: "Stacked" + new: null + old_target: "diff_diff:StackedDiD" + new_target: null + deprecated_in: "3.9" + removed_in: "4.0" + status: planned + phase: 5 + code_refs: [diff_diff/__init__.py, docs/api/estimators.rst, diff_diff/guides/llms-full.txt, diff_diff/guides/llms-practitioner.txt, tests/test_aliases.py, diff_diff/diagnostic_report.py] + notes: "Alias diet 2026-07-31: a vague English adjective saving three characters over StackedDiD, with zero lookup value. Target class survives - the 3.9 FutureWarning rides the module __getattr__ shim ([M-135]); removal at 4.0. code_refs list the known reader surfaces; the 2d/removal PRs also grep repo-wide." + - id: M-134 + kind: alias + group: alias-table + old: "Gardner" + new: null + old_target: "diff_diff:TwoStageDiD" + new_target: null + deprecated_in: "3.9" + removed_in: "4.0" + status: planned + phase: 5 + code_refs: [diff_diff/__init__.py, docs/api/estimators.rst, diff_diff/guides/llms-full.txt, diff_diff/guides/llms-practitioner.txt, tests/test_aliases.py, diff_diff/diagnostic_report.py] + notes: "Alias diet 2026-07-31: author surname as a class name; the ecosystem name for the method is did2s and 'Gardner (2022)' prose citations are unaffected. Target class survives - the 3.9 FutureWarning rides the module __getattr__ shim ([M-135]); removal at 4.0. code_refs list the known reader surfaces; the 2d/removal PRs also grep repo-wide." + - id: M-135 + kind: behavior + group: alias-table + old: "diff_diff:__getattr__" + new: null + introduced_in: "3.9" + deprecated_in: null + removed_in: null + status: planned + phase: 2 + code_refs: [diff_diff/__init__.py] + notes: "Alias-diet warning mechanism: CDiD/Stacked/Gardner served via a PEP 562 module __getattr__ emitting FutureWarning naming the surviving class. Names stay in __all__ through 3.9 but leave module globals: dir()/vars() shrink symmetrically (test_dir_returns_full_module_namespace unaffected) and star-import fires the three warnings - accepted diet behavior, documented in v4-design section 3.2. Ships in phase 2d; test_due_rows_are_terminal forces it terminal before any 3.9 bump. Removals are [M-132]..[M-134]." diff --git a/docs/v4-design.md b/docs/v4-design.md index 618218b68..a0d23b48f 100644 --- a/docs/v4-design.md +++ b/docs/v4-design.md @@ -20,8 +20,10 @@ restates per-row lifecycle data and instead cites rows as `[M-###]`), (program pointer). All decisions below were locked with the maintainer on 2026-07-18 (six program -decisions + eight naming-checkpoint decisions). Sections 3-8 are the target -surface; section 9 maps it onto PRs. +decisions + eight naming-checkpoint decisions), with dated addenda: 2026-07-19 +(the diagnostic-family decision, section 3.5) and 2026-07-31 (the four +consolidation-scope decisions appended to the section 8 checkpoint list). +Sections 3-8 are the target surface; section 9 maps it onto PRs. --- @@ -97,15 +99,26 @@ Derived from `diff_diff.__all__` at v3.8.0. 24 estimator classes -> 21. ### 3.2 Final alias table -Existing aliases keep their targets (DiD, TWFE, SDiD, CS, SA, BJS, Gardner, -DDD, Stacked, Bacon, EDiD, ETWFE, DCDH, CiC, CDiD, HAD, RDD). Changes: +Kept aliases (14, the literature-standard names): DiD, TWFE, SDiD, CS, SA, +BJS, DDD, Bacon, EDiD, ETWFE, DCDH, CiC, HAD, RDD (EDiD was initially slated +for the diet but RETAINED on review evidence - it is the Chen-Sant'Anna-Xie +paper's own label for its estimator, the same criterion that keeps SDiD). Changes: `EventStudy` is dropped, not retargeted [M-060] - "event study" names a design that CS/SA/BJS/LPDiD also produce, and retargeting it to a class whose default mode is the static ATT would make the name carry altered meaning. `SDDD` dies -with its class [M-013] [M-064]. `QDiDResults` dies with QDiD [M-061]. New: `SCM` for -SyntheticControl [M-062] ("SC" rejected - one transposition from CS) and -`Spillover` for SpilloverDiD [M-063]. TROP and LPDiD are self-aliased acronyms. -After 4.0: every estimator has exactly one class name and at most one alias. +with its class [M-013] [M-064]. `QDiDResults` dies with QDiD [M-061]. +**Alias diet (2026-07-31):** `CDiD`, `Stacked` and `Gardner` are +retired — [M-132]..[M-134] — deprecated 3.9, removed 4.0. Their target classes +survive, so no parent-class shim can carry the warning; the 3.9 FutureWarning +rides a module-level `__getattr__` [M-135]. The 3.9 consequence: the three +names leave module globals (gone from `dir()` and static autocomplete) while +staying importable and in `__all__`, and `from diff_diff import *` fires their +FutureWarnings — accepted diet behavior, owned by the phase-2(d) PR. New: +`SCM` for SyntheticControl [M-062] ("SC" rejected - one transposition from +CS). The planned `Spillover` alias for SpilloverDiD is CANCELLED [M-063] +(never shipped). TROP and LPDiD are self-aliased acronyms. +After 4.0: every estimator has exactly one class name and at most one alias — +15 aliases total. ### 3.3 Module-level function wrappers @@ -581,7 +594,8 @@ indicator that rule 3 explicitly reserves the name for. dCDH is the one estimator where `group` genuinely means a unit id, which is why [M-033] and [M-114] exist and the CS-family fields have no rows. -**Naming-checkpoint outcomes (2026-07-18), with losing candidates:** +**Naming-checkpoint outcomes (2026-07-18 + 2026-07-31 addendum), with losing +candidates:** `event_study=` bool (over `effects=` enum, `dynamic=` bool); `spec="within"|"pooled"` (over `unit_fe=` bool, `model=` string); `partition` (over `eligibility`); EventStudy alias dropped (over retarget - @@ -591,6 +605,17 @@ unified event-study representation with `period_effects` as a 4.0->5.0 property (over keeping the dict canonical, over hard removal); n_bootstrap semantic-only unification (over uniform 999, over uniform 200); panel auto-cluster-at-unit (over never-auto-cluster, over status quo). +2026-07-31 addendum (consolidation scope): staggered-family mega-merge +rejected (over a `StaggeredDiD(method=)` union class - altered-meaning trap on +the union-params surface, and literature discoverability); ImputationDiD <-> +TwoStageDiD merge rejected (different inference stacks - BJS conservative IF +SE vs GMM sandwich - and both independently cited); moderate alias diet +(section 3.2; over an aggressive diet dropping the author-initials shorthands +too, over no diet; EDiD was initially slated but retained - review evidence +showed it is the CSX paper's own estimator label); alias-diet 3.9 +FutureWarning via module `__getattr__` +[M-135] (over silent removal - the only 4.0 removals that would have shipped +without a deprecation window). ## 9. Phase -> PR breakdown @@ -600,12 +625,52 @@ above; anything only one PR cares about stays in that PR's plan.** | Phase | Ships in | PRs (each: dedicated shim/removal tests + matrix flips + CHANGELOG naming flipped row ids) | |---|---|---| | 1 (this PR) | - | Spec + matrix + enforcement test + support edits | -| 2: contract foundations | 3.9 | (a) results base + unified event-study representation [M-092] + to_dict completion + the Diagnostic marker base on the diagnostic result roster [M-091] (section 3.5); (b) `aggregate()` + fit(aggregate=) shims [M-020..M-027]; (c) param renames [M-030..M-047] [M-084] [M-086..M-089] + their results-field mirrors [M-094] [M-095] (section 8 rule 9) + the public-function completeness sweep [M-097..M-113] (section 8 rule 10) + the dCDH results mirror [M-114] + the fourth `robust` site [M-115] + BaseEstimator mixin + ContinuousDiD covariates move; (d) alias introductions [M-062] [M-063] + wrapper deprecations [M-070..M-077] + the two inference-surface policies: `n_bootstrap` semantic unification [M-081] and the wild-cluster-bootstrap roster guard [M-096] | -| 3: merges | 3.9 | (a) TWFE event-study mode [M-010] + EventStudy warn [M-060] (gates: section 4.1's equivalence/divergence/pooled-parity test triple); (b) TripleDifference facade [M-013]; (c) CiC method= [M-015] | +| 2: contract foundations | 3.9 | (a) results base + unified event-study representation [M-092] + to_dict completion + the Diagnostic marker base on the diagnostic result roster [M-091] (section 3.5); (b) `aggregate()` + fit(aggregate=) shims [M-020..M-027] (M-020's shim already shipped); (c) param renames [M-030..M-047] [M-084] [M-086..M-089] + their results-field mirrors [M-094] [M-095] (section 8 rule 9) + the public-function completeness sweep [M-097..M-113] (section 8 rule 10) + the dCDH results mirror [M-114] + the fourth `robust` site [M-115] + BaseEstimator mixin + ContinuousDiD covariates move; (d) alias introduction [M-062] (the Spillover introduction is cancelled [M-063]) + the alias-diet `__getattr__` warning shim [M-135] + wrapper deprecations [M-070..M-077] + the two inference-surface policies: `n_bootstrap` semantic unification [M-081] and the wild-cluster-bootstrap roster guard [M-096]; shipped insertions (all done): the aggregate contract [M-122], the ETWFE reference-period family [M-123] [M-124] [M-125], and the variance-consolidation program [M-126] [M-127] | +| 3: merges | 3.9 | (a) TWFE event-study mode [M-010] + EventStudy warn [M-060] + the fit `time`->`post` rename [M-082] (gates: section 4.1's equivalence/divergence/pooled-parity test triple); (b) TripleDifference facade [M-013] + the SDDD alias [M-064]; (c) CiC method= [M-015] | | 4: release + soak | 3.9 cut | Migration guide written (skeleton: section 10); maintainer cuts 3.9; maint/3.8 rule active | -| 5: enforcement | 4.0 | Removals [M-010..M-016, M-030..M-047 old names, M-060, M-061, M-070..M-077, M-001..M-003] + the amendment's old names [M-094] [M-095] [M-097..M-115] (incl. their consumer migrations and the `clean_control` serialized reporting key); storage flips [M-050..M-058]; default policies [M-004..M-006, M-128..M-131, M-080]; warning retirement [M-007]; fastpath go/no-go [M-008]; diagnostic-family docs/roster reorganization [M-090]; sentinel retirement [M-093]; docs/llms.txt/README refresh | +| 5: enforcement | 4.0 | Removals [M-010..M-015, M-020..M-027, M-030, M-032..M-047 old names, M-060, M-061, M-064, M-070..M-077, M-084, M-086..M-089, M-001..M-003, M-117] + the alias diet [M-132]..[M-134] + the amendment's old names [M-094] [M-095] [M-097..M-115] (incl. their consumer migrations and the `clean_control` serialized reporting key); M-031's old `time` name persists as the merged class's calendar column, so it is deliberately absent from the removal roster (its 4.0 enforcement is the M-085 behavior entry below); property window: [M-016] property-flips at 4.0 (removal at 5.0); storage flips [M-050..M-058]; default policies [M-004..M-006, M-128..M-131, M-080]; merged-class behavior enforcements [M-083] [M-085]; warning retirement [M-007]; fastpath go/no-go [M-008]; diagnostic-family docs/roster reorganization [M-090]; sentinel retirement [M-093]; docs/llms.txt/README refresh | | 6: front door | 4.1 | `event_study(data, outcome, unit, time, first_treat, estimator=...)` comparison entry point over the staggered family (sketch only; specified in its own plan) | +Citation semantic for the table: a cell may cite a row whose current `phase` +differs when that phase performs one of the row's lifecycle transitions (the +phase-5 removal roster cites rows still at their shim phase; the phase-2(b) +cell keeps M-020, whose shim shipped there). The row's `phase` field tracks +only the NEXT transition (section 11). Terminal rows (`done`/`removed`) are +exempt in both directions - citable for the historical record, never required. + +**Remaining 3.9 sequence (2026-07-31).** The single canonical statement of the +remaining PR order; it records order and rationale only and does not +re-enumerate the cells' M-id lists: + +1. Planning consolidation + the section 8 consolidation-scope decisions (the + PR that wrote this subsection). +2. The naming-completeness guard test (TODO.md row: phase-table agreement per + the amended spec there + consumer coverage) - lands BEFORE 2(c), so the + rename PR works from a mechanically-verified list. +3. 2(c)-i: the BaseEstimator mixin, front-loaded. Scope is section 7's + normative statement verbatim: replace the 24 hand-rolled + `get_params`/`set_params` copies library-wide (3.9-cut checklist item 1) - + not merely the standalone estimator classes. Rationale: the tail-df PR's + review exposed the non-atomic `set_params` pattern and fixed the five + estimators in its scope; others remain non-atomic today (verified examples: + CallawaySantAnna, TwoStageDiD, TripleDifference, TROP, SpilloverDiD - the + mixin PR's first task is the exhaustive inventory of all 24 copies), and + the renames should build on the transactional contract. +4. 2(c)-ii: the rename sweep (the phase-2(c) cell); may split by rename group. +5. 2(b): post-fit `aggregate()` + `fit(aggregate=)` shims (the (b) cell), + claiming reserved ids M-116/M-118..M-121 for any new rows; the + `EventStudyResults` downstream-consumability work (TODO.md row: the three + consumers currently reject the unified container) lands before or inside + this wave so the shims do not steer users into a dead end. +6. 2(d): wrapper deprecations, the SCM introduction, the alias-diet + `__getattr__` shim + dieted-alias surface sweep (reader surfaces are + recorded in M-132..M-135's `code_refs`, the ledger-native home - this doc + carries no file inventory; the 2(d) PR starts from those `code_refs` and + additionally greps each dieted alias repo-wide), `n_bootstrap`, and the + wild-cluster-bootstrap roster guard. +7. Phase 3 merges (a)/(b)/(c) per the phase-3 cell. +8. Phase 4: migration guide, the 3.9-cut checklist below, cut. + **3.9-cut checklist (un-rowed obligations).** `test_due_rows_are_terminal` gates everything that HAS a row; the following Phase 2 obligations are real but not expressible as ledger rows, so the 3.9 release PR asserts them by hand: @@ -617,8 +682,9 @@ not expressible as ledger rows, so the 3.9 release PR asserts them by hand: 2. The R-equivalents mapping table (section 8 rule 8) ships in the docs. 3. The migration guide exists (section 10) and its TL;DR table has a row per breaking change, generated against the matrix rather than hand-listed. -4. `docs/v4-deprecations.yaml` and this document agree on the phase breakdown - in the table above - any PR that re-scoped a phase edited both. +4. The ledger and this document agree on the phase breakdown in the table + above - any PR that re-scoped a phase edited both. Invariant and + enforcement spec: the naming-completeness guard row in TODO.md. Everything else queued for 3.9 is row-gated, by one of two mechanisms. Symbol rows that declare a `warning` gate on `deprecated_in` - the shim must have @@ -627,8 +693,8 @@ shipped ([M-010] [M-013] [M-015], [M-020]..[M-027], [M-030]..[M-047], [M-084], [M-086]..[M-089], [M-094] [M-095], [M-097]..[M-115]). Rows with no shim to assert gate on `introduced_in` instead - the new surface must have shipped: the -introduce-only aliases [M-062] [M-063] and the `behavior`-kind policies -[M-081] [M-091] [M-092] [M-096]. That second mechanism is deliberate for +introduce-only alias [M-062] and the `behavior`-kind policies +[M-081] [M-091] [M-092] [M-096] [M-135]. That second mechanism is deliberate for behavior rows: the early-flip guard keys off `deprecated_in`, so a flip version would fail the very PR that implements the obligation (it lands while `__version__` is still 3.8.x). Use `introduced_in`, not `deprecated_in`, for @@ -653,7 +719,8 @@ major"/"flip") to catch anything born outside the matrix. 5. Results fields (overall_att family -> canonical quintet; property window). 6. Inference defaults that moved numbers (df_convention, auto-cluster) - with how to reproduce 3.x numbers exactly. -7. Removed functions and aliases (wrappers, EventStudy, SDDD, QDiDResults). +7. Removed functions and aliases (wrappers, EventStudy, SDDD, QDiDResults, + and the alias-diet three: CDiD, Stacked, Gardner). 8. Codemod section: the mechanical renames as a script/regex table. ## 11. Matrix mechanics (normative schema for `docs/v4-deprecations.yaml`) @@ -745,15 +812,19 @@ forever - a removed symbol resurrecting is a test failure. (removal pins survive forever); behavior / default-flip / env-default rows REQUIRE a `test_ref` at `done` (semantic flips need ledger-linked behavioral evidence). Alias rows must NOT declare `warning` - an alias is - the same object as its target, so the deprecation warning rides the parent - class row (schema-enforced). Top-level `diff_diff:Name` class/function rows + the same object as its target, so the warning rides the parent class row + when the target is itself deprecated, and a `behavior`-row `__getattr__` + mechanism ([M-135]) when the target survives (schema-enforced either way: + the alias row itself never declares one). Top-level `diff_diff:Name` + class/function rows and alias rows also assert `__all__` membership consistent with their status (stale `import *` entries fail). The shipped row ids are a - committed snapshot in the enforcement test (110 as of the tail-df - consolidation family: Phase 1 + the diagnostic-family amendment + + committed snapshot in the enforcement test (114 as of the alias-diet + family: Phase 1 + the diagnostic-family amendment + the M-092/M-093 results-contract rows + the M-094..M-096 amendment rows + the M-097..M-115 completeness sweep + M-117/M-122 + the ETWFE - reference-period pair M-123/M-124 + M-125 + M-126 + M-127..M-131; + reference-period pair M-123/M-124 + M-125 + M-126 + M-127..M-131 + + the alias-diet family M-132..M-135; the snapshot extends by a new id range in the same diff that appends rows): ids are never deleted or reused, and the test fails if any snapshot id disappears. diff --git a/tests/test_v4_matrix.py b/tests/test_v4_matrix.py index e1e29d494..8813ebad0 100644 --- a/tests/test_v4_matrix.py +++ b/tests/test_v4_matrix.py @@ -117,11 +117,14 @@ # plus the ETWFE reference-period pair (M-123, M-124) = 103, plus the # comparison-support row (M-125) = 104, plus the clustered-CR1 K_reference # convergence row (M-126) = 105, plus the tail-df consolidation family -# (M-127 behavior + the four M-128..M-131 default-flips) = 110. +# (M-127 behavior + the four M-128..M-131 default-flips) = 110, plus the +# alias-diet family (M-132..M-134 alias deprecations + the M-135 __getattr__ +# warning mechanism; EDiD was initially slated but RETAINED - it is the CSX +# paper's own estimator label) = 114. # Ids are never reused and terminal rows are never # deleted, so the ledger only grows - raise the floor when rows are added; a # lower parse count means scanner/format drift or an illegal row deletion. -ROW_COUNT_FLOOR = 110 +ROW_COUNT_FLOOR = 114 # Committed snapshot of the shipped id set ("ids are never deleted or reused" # contract - a delete-one-add-one edit keeps the count above the floor but trips @@ -140,7 +143,11 @@ # variance-consolidation program); (127,131) = the tail-df consolidation # (M-127 behavior row = PR C's D4 + normal-theory defect fix + the # three-value knob extension, and M-128..M-131 = the four new standalone -# df-convention default-flips joining M-004..M-006 at 4.0). +# df-convention default-flips joining M-004..M-006 at 4.0); (132,135) = the +# alias diet (M-132..M-134 = the CDiD/Stacked/Gardner alias deprecations, +# M-135 = the module __getattr__ FutureWarning mechanism that carries their +# 3.9 warning, since the surviving target classes have no shim; EDiD was +# initially slated but retained - the CSX paper's own estimator label). # M-116 and # M-118..M-121 are reserved for the later 2b PRs, not deleted - ids are # never reused, so a gap here is intentional. @@ -161,6 +168,7 @@ (125, 125), (126, 126), (127, 131), + (132, 135), ] EXPECTED_INITIAL_IDS = frozenset( f"M-{n:03d}" for lo, hi in _INITIAL_ID_RANGES for n in range(lo, hi + 1) @@ -283,7 +291,9 @@ def validate_schema(rows): if kind == "alias" and row.get("warning") is not None: errors.append( f"{rid}: alias rows must not declare 'warning' - an alias is the same object " - "as its target, so the deprecation warning rides the parent class row" + "as its target, so the warning rides the parent class row when the target is " + "itself deprecated, or a behavior-row __getattr__ mechanism (M-135) when the " + "target survives" ) if ( kind in ("class", "function") @@ -557,13 +567,13 @@ def test_initial_ids_never_deleted(): """The shipped id set is immutable: ids are never deleted or reused (spec section 11). ROW_COUNT_FLOOR alone would let a delete-one-add-one edit pass; this snapshot cannot. - Extends as rows ship (110 as of the tail-df consolidation family: + Extends as rows ship (114 as of the alias-diet family: Phase 1 + diagnostic-family + M-092/M-093 + M-094..M-096 + the M-097..M-115 public-function completeness sweep + M-117/M-122 + M-123/M-124 + M-125 + - M-126 + M-127..M-131).""" + M-126 + M-127..M-131 + M-132..M-135).""" missing = sorted(EXPECTED_INITIAL_IDS - set(_ROW_IDS)) assert not missing, f"ledger rows deleted (ids are permanent): {missing}" - assert len(EXPECTED_INITIAL_IDS) == 110 + assert len(EXPECTED_INITIAL_IDS) == 114 def test_version_tuple_pads_to_three_components(): diff --git a/tests/test_variance_conventions.py b/tests/test_variance_conventions.py index 74e63450a..bab7c5de8 100644 --- a/tests/test_variance_conventions.py +++ b/tests/test_variance_conventions.py @@ -11,12 +11,14 @@ - ``tail_df`` — the sorted multiset of ``df`` values passed to ``safe_inference`` / ``safe_inference_batch`` (``None`` = normal theory). -The point is visibility, not endorsement: the clustered-CR1 ``k`` cells now -pin the converged K_reference accounting, while the remaining DOCUMENTED -DEFECT rows are the mixed tail-df conventions scheduled for PR C of the 3.9 -consolidation program. Every row carries ``status`` and, for legitimate -differences, a ``reason``. When a later PR changes a convention, the -expected literal changes HERE, in one reviewable table. +The point is visibility, not endorsement: the clustered-CR1 ``k`` cells pin +the converged K_reference accounting (M-126), the tail-df cells pin the +converged three-value ``df_convention`` resolution (M-127), and every row is +now ``status="legitimate"`` — the 3.9 consolidation program fixed the last +documented-defect family, so no ``defect`` row remains. Every row carries +``status`` and, for legitimate differences, a ``reason``. When a later PR +changes a convention, the expected literal changes HERE, in one reviewable +table. Instrumentation notes (each guards against a failure mode that produced wrong inventory numbers during planning): @@ -176,8 +178,10 @@ def snapshot(self): # --------------------------------------------------------------------------- # The matrix. One entry per (surface, configuration) cell. # -# status: "defect" — scheduled to change in the 3.9 consolidation program -# "legitimate" — a declared exception with its reason +# status: "legitimate" — a declared exception with its reason +# "defect" — historical value no current row uses (the 3.9 +# consolidation program fixed the last defect family); +# kept in the enum for any future documented defect # cr1_k = () means the row's CONTRACT is "no shared clustered-CR1 call". # --------------------------------------------------------------------------- From 737886df9b0b5bdd6f54102a03ee0457f54f477e Mon Sep 17 00:00:00 2001 From: igerber Date: Fri, 31 Jul 2026 21:28:15 -0400 Subject: [PATCH 2/2] docs(v4): complete alias reader inventories + decision_due in the guard predicate (CI review) Two review findings, both verified: (1) the M-133/M-134 code_refs missed label-usage surfaces - llms-autonomous.txt recommends "Stacked" and "(Gardner)" as estimator labels, practitioner.py's robustness-compare steps and business_report.py's diagnostic string recommend "Gardner", and business_report.py/power.py roster "Stacked" - now added (author-citation prose like "Gardner (2022)" and English phrases like "Stacked DiD" / "Stacked bar chart" are deliberately excluded; the mandated repo-wide grep in the notes covers the long tail). (2) The TODO.md guard predicate keyed only on introduced_in/deprecated_in/removed_in, which would reject M-008's legitimate phase-5 citation - that env-default row is scheduled solely via decision_due: "4.0"; the predicate now includes decision_due for env-default rows with M-008 named as a guard fixture. --- TODO.md | 2 +- docs/v4-deprecations.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/TODO.md b/TODO.md index 9d3c7e3a2..cdcc2ac0c 100644 --- a/TODO.md +++ b/TODO.md @@ -61,7 +61,7 @@ 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` (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 | +| 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 | diff --git a/docs/v4-deprecations.yaml b/docs/v4-deprecations.yaml index c5db913ba..447f80b2b 100644 --- a/docs/v4-deprecations.yaml +++ b/docs/v4-deprecations.yaml @@ -1484,7 +1484,7 @@ rows: removed_in: "4.0" status: planned phase: 5 - code_refs: [diff_diff/__init__.py, docs/api/estimators.rst, diff_diff/guides/llms-full.txt, diff_diff/guides/llms-practitioner.txt, tests/test_aliases.py, diff_diff/diagnostic_report.py] + code_refs: [diff_diff/__init__.py, docs/api/estimators.rst, diff_diff/guides/llms-full.txt, diff_diff/guides/llms-practitioner.txt, diff_diff/guides/llms-autonomous.txt, tests/test_aliases.py, diff_diff/diagnostic_report.py, diff_diff/business_report.py, diff_diff/power.py] notes: "Alias diet 2026-07-31: a vague English adjective saving three characters over StackedDiD, with zero lookup value. Target class survives - the 3.9 FutureWarning rides the module __getattr__ shim ([M-135]); removal at 4.0. code_refs list the known reader surfaces; the 2d/removal PRs also grep repo-wide." - id: M-134 kind: alias @@ -1497,7 +1497,7 @@ rows: removed_in: "4.0" status: planned phase: 5 - code_refs: [diff_diff/__init__.py, docs/api/estimators.rst, diff_diff/guides/llms-full.txt, diff_diff/guides/llms-practitioner.txt, tests/test_aliases.py, diff_diff/diagnostic_report.py] + code_refs: [diff_diff/__init__.py, docs/api/estimators.rst, diff_diff/guides/llms-full.txt, diff_diff/guides/llms-practitioner.txt, diff_diff/guides/llms-autonomous.txt, tests/test_aliases.py, diff_diff/diagnostic_report.py, diff_diff/practitioner.py, diff_diff/business_report.py] notes: "Alias diet 2026-07-31: author surname as a class name; the ecosystem name for the method is did2s and 'Gardner (2022)' prose citations are unaffected. Target class survives - the 3.9 FutureWarning rides the module __getattr__ shim ([M-135]); removal at 4.0. code_refs list the known reader surfaces; the 2d/removal PRs also grep repo-wide." - id: M-135 kind: behavior