Skip to content

Latest commit

 

History

History
159 lines (142 loc) · 56.8 KB

File metadata and controls

159 lines (142 loc) · 56.8 KB

Deferral & Decision Registry

Blocked, parked, and version-gated work, plus decisions on the record. Not a backlog — do not pull from here without first clearing the named blocker. Rows are retained for provenance and AI-review deviation-documentation: a row here (or in TODO.md's Actionable Backlog) marks a limitation as tracked for the PR reviewer.

  • Shippable work lives in TODO.md; the public feature roadmap in ROADMAP.md; monitoring and current-state notes in docs/dev-status.md.
  • Version-gated lifecycle items (deprecated-kwarg removals, v4 default flips such as SyntheticDiD lambda_reg/zeta and the clustered-df convention) are canonically tracked in the CI-enforced ledger docs/v4-deprecations.yaml; rows here cross-link their M-xxx ids where applicable.
  • The PR column points to the originating PR number or review tag.

Paper-gated / needs methodology derivation

Issue Location PR Priority
Chang (2020) Case 3 (multilevel treatment intensity) DML estimator: the paper prints no Case 3 regularity assumptions or variance theorem (Theorems 2/4 cover Cases 1-2 only; "same arguments" p. 13), the printed multilevel algorithm carries typos (review Gaps items 5-6), and no oracle exists (DoubleMLDIDMulti is staggered TIMING, not intensity) — needs the variance derivation + overlap assumptions before implementation docs/methodology/papers/chang-2020-review.md dml-b0 Low
LWDiD PSM matching variance: implement the Abadie-Imbens (2006) matching variance (matched-control reuse + first-stage matching uncertainty) so estimation_method='psm' can report valid inference instead of the current fail-closed NaN tuple (point retained; naive var(diffs)/n was invalid under with-replacement reuse) diff_diff/lwdid.py #588 Low
LWDiD cohort-relative sensitivity exclusions: robustness_pre_periods / sensitivity_no_anticipation currently reject multi-cohort staggered inputs because their exclusion windows are defined relative to the EARLIEST adoption (later cohorts' own pre periods fall inside the global post window and survive every restriction, mislabeling the specification). Supporting staggered inputs needs per-cohort window semantics (exclude the last k periods of each cohort's own t < g window before its transformation), which the current row-subset design cannot express — a per-cohort masking derivation + its aggregation contract diff_diff/lwdid_sensitivity.py #588 Low
LWDiD estimation-time anticipation offset (CS-parity gap): no anticipation= parameter exists — no-anticipation is hard-assumed and the only surface is the single-cohort sensitivity_no_anticipation refit diagnostic. The LW device for anticipation is the anchor-window shift — drop the last k pre-treatment periods from the transformation/anchor window (Ybar_{i,S0} with S0 < S-1, per the REGISTRY LWDiD Anticipation edge case; NOT a cohort recode g → g − k) — and exposing an anticipation= parameter needs that derivation checked against LW 2025 NAC/CNAS before implementation diff_diff/lwdid.py LWDiD release audit Low
HonestDiD rejects LWDiD event studies: LWDiDResults.aggregate('event_study') emits the unified EventStudyResults surface but the honest_did.py allowlist admits CS/Stacked sources only. Admission needs a methodology decision, not an allowlist edit: LWDiD pre-period cells are transformation-based placebos on the Appendix D control pools, and Rambachan-Roth requires those coefficients plus a joint pre/post vcov to stand as valid pre-trend evidence diff_diff/honest_did.py, diff_diff/lwdid_results.py LWDiD release audit Low
HonestDiD non-chronological declared partitions (native MultiPeriodDiDResults route): the Rambachan-Roth restriction matrices are built POSITIONALLY over the concatenated declared pre/post lists assuming one chronological boundary, but the native route accepts non-suffix post_periods / non-last-pre references and returns bounds whose restriction system does not match the Registry equations (pre-existing; surfaced by the Phase 3(a) calendar-route review, which fails closed instead). Fix = transform the declared partition into boundary form where a valid mapping exists, else reject on the native route too - needs the restriction-geometry derivation. REGISTRY HonestDiD Note records the limitation. diff_diff/honest_did.py 3(a) Medium
PlaceboTests boundary_gap — a permutation randomization-inference margin (SE-audit item (b)); NOT computed anywhere in code today, so this is a new feature + result field, not a coverage lock. User-locked 2026-07-09: defer until a derivation/paper source exists — do not design or implement from scratch. tests/test_methodology_placebo.py, diff_diff/diagnostics.py SE-audit Low
TwoStageDiD honest/pretrends container admission DEFERRED (decision revised from "widen" during the 2(b) PR-3b plan review): analytical fits carry the joint Gardner-GMM event-study covariance (M-092), but the pre-period coefficients are stage-1 residual MEANS — the reference horizon is dropped from the no-intercept Stage-2 design and the zero anchor row appended mechanically — not contrasts against a reference period, while HonestDiD's Δ^RM/Δ^SD arithmetic hard-codes the delta_0 = 0 normalization into its boundary/bridge constraints. Admission needs either a Stage-2 re-estimation with the reference horizon in the design or a derived residual-to-reference normalization mapping (+ its variance transform). Both consumers' TypeErrors state the deferral; see the REGISTRY TwoStageDiD Note (d). diff_diff/honest_did.py, diff_diff/pretrends.py, diff_diff/two_stage_aggregation.py 2(b) PR-3b Low
RDDensityTest binomial windows test (R rddensity's bino= block: exact binomial tests of side counts in shrinking windows around the cutoff): methodology is sourced from Cattaneo, Frandsen & Titiunik (2015, JCI) and Cattaneo, Frandsen & Vazquez-Bare (2017), NOT the reviewed CJM 2020 paper, so implementing it needs its own paper review(s) first (the 2-PR methodology convention). diff_diff/rddensity.py rddensity PR-B Low
RDDensityTest rdplotdensity companion (density plots with pointwise/uniform confidence bands): requires an lpdensity port (Cattaneo, Jansson & Ma 2022 JSS) - a separate estimation surface (evaluation grids, CDF/derivative estimation) well beyond the manipulation test. diff_diff/rddensity.py rddensity PR-B Low
CBWSDID covariate balancing (StackedDiD(balance="entropy")) v1 supports only balanced event windows + weighting="aggregate"; unbalanced/ragged panels fail closed (unit-count vs observation-count corrector convention unresolved off balanced panels). Matching-based balancing and the repeated 0→1/1→0 episode extension are also deferred. Documented in REGISTRY StackedDiD "Covariate balancing (CBWSDID)" Notes. stacked_did.py, balancing.py, REGISTRY follow-up Low
dCDH: Phase-1 per-period placebo DID_M^pl has NaN SE (no IF derivation for the per-period aggregation path). Multi-horizon placebos (L_max ≥ 1) have valid SE. chaisemartin_dhaultfoeuille.py #294 Low
dCDH: survey cell-period allocator's post-period attribution is a library convention, not derived from the observation-level survey linearization. MC coverage is empirically close to nominal; a formal derivation (or covariance-aware two-cell alternative) is deferred. Documented in REGISTRY survey IF expansion Note. chaisemartin_dhaultfoeuille.py, REGISTRY #408 Medium
dCDH by_path: survey-aware backward-horizon (placebo + predict_het + survey_design) raises NotImplementedError (and _compute_heterogeneity_test warn-and-skips to forward-horizon-only heterogeneity) — the Binder TSL cell-period allocator's REGISTRY justification is tied to post-period attribution; backward horizons would put ψ_g mass on a pre-period cell. Needs the pre-period cell allocator derived. chaisemartin_dhaultfoeuille.py, REGISTRY follow-up Medium
HonestDiD Δ^RM ARP confidence sets : uses a naive FLCI instead of the paper's ARP conditional/hybrid sets (Sections 3.2.1-3.2.2). ARP infrastructure exists but the moment-inequality transformation needs calibration; CIs are conservative (valid coverage). Sub-items folded here: improved C-LF via direct optimization instead of grid search (honest_did.py:947); hybrid inference methods; event-study-specific bounds per post-period; simulation-based power analysis for honest bounds. honest_did.py #248 Medium
Conley vcov_type for IF / GMM estimators (consolidates 8 near-identical rows). No reference implementation exists for any of these spatial-HAC × influence-function/GMM compositions; each was rejected at __init__ with a deferral pointer here. SunAbraham + WooldridgeDiD-OLS conley are already supported (within-transform via solve_ols) and are not part of this row. Per estimator:
CallawaySantAnna — Conley kernel × per-(g,t) IF aggregation (staggered.py).
TripleDifference — × the 3-pairwise-DiD IF decomposition w3·IF_3 + w2·IF_2 - w1·IF_1 (triple_diff.py).
ImputationDiD — × Theorem-3 per-unit IF sigma_sq = (cluster_psi_sums**2).sum() (imputation.py).
EfficientDiD — × per-unit EIF _compute_se_from_eif (efficient_did_aggregation.py).
TwoStageDiD — thread into the GMM sandwich meat _compute_gmm_variance; the SpilloverDiD _compute_gmm_corrected_meat machinery could be adapted to score S_g = gamma_hat' c_g - X'_{2g} eps_{2g} but two-stage-GMM × Conley has no reference (two_stage.py).
StackedDiDmethodology-blocked, not plumbing: the stacked design replicates each control unit across sub-experiments, so Conley's distance matrix sees same-unit copies at distance 0 (K(0)=1); needs a per-stack spatial identifier (stacked_did.py).
SyntheticDiD — uses variance_method ∈ {bootstrap, jackknife, placebo}, no analytical sandwich for Conley to plug into; needs an analytical-sandwich path or a spatial-block bootstrap (Politis-Romano 1994) (synthetic_did.py).
• Conley + survey weights / survey_design — score-reweighting is mechanical but the PSU×spatial-kernel interaction and replicate-weight spatial variance are non-trivial (Bertanha-Imbens 2014 covers cluster-sample, not Conley); raises NotImplementedError at the linalg validator (linalg.py::_validate_vcov_args).
(per sub-item) follow-up · Phase 1b · Phase 5 Low-Med
HeterogeneousAdoptionDiD Phase 4.5 C still-open: (a) replicate-weight designs (BRR/Fay/JK1/JKn/SDR) — per-replicate weight-ratio rescaling for the OLS-on-residuals refit isn't covered by the multiplier-bootstrap composition; each linearity-family helper raises NotImplementedError on replicate weights. (b) lonely_psu='adjust' + singleton-strata on the Stute family — the pseudo-stratum centering transform isn't derived for the Stute CvM functional. had_pretests.py Phase 4.5 C Low
HeterogeneousAdoptionDiD mass-point vcov_type in {hc2, hc2_bm} raises NotImplementedError — OLS leverage x_i'(X'X)^{-1}x_i is wrong for 2SLS; needs the x_i'(Z'X)^{-1}(...)(X'Z)^{-1}x_i correction plus an R/Stata (ivreg2 small robust) parity anchor. had.py::_fit_mass_point_2sls Phase 2a Medium
HeterogeneousAdoptionDiD trends_lin × survey_design: per-group linear-trend slope under survey weighting is not derived from the paper. Raises NotImplementedError across all 3 trends_lin surfaces. had.py, had_pretests.py #389 Low
SpilloverDiD(survey_design=...) replicate-weight variance (BRR/Fay/JK1/JKn/SDR): Wave E.1 ships Taylor-linearization only. Per Gerber (2026) Appendix A the IF-reweighting shortcut does NOT apply to TwoStageDiD-class estimators (gamma_hat is weight-sensitive); correct support needs per-replicate full re-fit of both stages. spillover.py, survey.py::compute_replicate_refit_variance follow-up Low
SpilloverDiD(vcov_type="conley", conley_lag_cutoff>0, survey_design=...) no-effective-PSU serial Bartlett HAC: weights-only / strata-only designs without a cluster fallback raise NotImplementedError (each pseudo-PSU appears in one period, so the serial cross-period loop contributes zero). Needs a unit-level serial fallback derivation or routing through conley_unit with documented IF-allocator asymmetry. spillover.py, two_stage.py::_compute_stratified_serial_bartlett_meat Wave E.2 tail Low
SpilloverDiD data-driven d_bar selection (Butts 2021b / 2023 JUE Insight cross-validation). spillover.py follow-up Low
LPDiD non-absorbing exit-event dynamics (Dube et al. 2025 online Appendix C eta_h^{g,n}): the shipped non_absorbing modes estimate the entry-effect estimands (Eq. 12/13) only; separate dynamic event-studies for treatment switch-offs are not implemented. Needs the exit-event clean-sample derivation + estimand contract. lpdid.py, REGISTRY PR-C follow-up Low
MMM interop, survey/RC totals for the five aggregate('total') adopters (CS/DMLDiD/EfficientDiD/ImputationDiD/TwoStageDiD; the shipped v1 is panel non-survey only and fails closed with NotImplementedError on repeated-cross-section-routed fits, fits declaring a survey_design=, and the CS/DMLDiD bare-cluster= divergent corner where the cohort-mass weighting disagrees with the complete-case count): needs (a) DESIGN-AWARE raw-mass recovery - analytic pweight/aweight resolved weights are normalized to sum = n and CS retains no raw record (fit-time RAW retention required there; CS accepts pweight only), while EDiD's analytic fweight fits and all replicate-design fits already retain raw scale in the kits and Imputation/TwoStage retain raw SurveyMetadata.sum_weights - and (b) the mass-uncertainty att*dC variance derivation (the shipped relay conditions on the realized mass and omits it). Never ship a survey/RC total from normalized masses. diff_diff/staggered_results.py, diff_diff/survey.py mmm-interop Low
LPDiD non-absorbing interior-gap support: non-absorbing modes require a gap-free panel within each unit's observed span and raise on interior time gaps (the [t-L, t+h] window conditions can't be verified across a gap). The absorbing path already reindexes interior gaps to the calendar grid; extending that fail-closed handling (per-window gap masking) to non-absorbing is deferred. lpdid.py::_prepare_panel PR-C follow-up Low

Needs external reference (R / Stata / Julia)

Blocked on tooling absent from CI (no workflow installs R/Stata/Julia). A clear path exists but parity can't be verified without a local toolchain.

Issue Location PR Priority
Chang (2020) published-version cross-check of the arXiv p_hat_k typo: the library adopted the global treated-share convention on DoubleML parity evidence (REGISTRY DML-infrastructure Note; spike benchmarks/doubleml/chang_case1_parity.py), but the published Econometrics Journal text (paywalled) has not been checked against the arXiv printing to see whether it resolves the algorithm-vs-proof contradiction docs/methodology/papers/chang-2020-review.md dml-b0 Low
StaggeredTripleDifference R cross-validation (the engine is shared with TripleDifference's staggered mode since 3(b), so this covers both surfaces): CSV fixtures not committed (gitignored); tests skip without local R + triplediff. Commit fixtures or generate deterministically. tests/test_methodology_staggered_triple_diff.py #245 Medium
Staggered DDD R parity (both surfaces - one shared engine since 3(b)): benchmark only tests the no-covariate path (xformla=~1). Add covariate-adjusted scenarios + aggregation-SE parity assertions. benchmarks/R/benchmark_staggered_triplediff.R #245 Medium
Staggered DDD per-cohort group-effect SEs include WIF (both surfaces - one shared engine since 3(b)) (conservative vs R's wif=NULL); documented in REGISTRY. Could override the mixin for an exact R match (verification needs R triplediff). _staggered_triple_diff_engine.py #245 Low
WooldridgeDiD follow-up cluster (PR-B Stage D/E fail-closed surfaces; re-enable after R/Stata validation):
• QMLE sandwich uses aweight cluster adjustment (G/(G-1))·(n-1)/(n-k) vs Stata's G/(G-1) (conservative); add a qmle weight type if Stata goldens confirm a material difference (wooldridge.py, linalg.py).
• response-scale APE / log-link coefficient bridge for R `etwfe(family=poisson
logit)cell-level parity — needsemfx() APE extraction or link-inversion with baseline-mean adjustment (generate_wooldridge_golden.R, test_methodology_wooldridge.py). <br>• aggregate(weights="cohort_share")on survey-weighted fits:_n_g_per_cohortuses rawunit.nunique(); implement design-weighted unit totals per cohort (paper W2025 §7) and lift the ValueError gate (wooldridge.py, wooldridge_results.py). <br>• unconditional inference for cohort_share accounting for ω̂_g sampling uncertainty (W2025 §7.5); currently NaN-closed (wooldridge_results.py). <br>• cohort_trends=True × survey_designand× control_group="never_treated"raiseNotImplementedError (unvalidated TSL variance / trend columns spanned jointly by the placebo cells and the unit FE, which absorb the cohort indicator and recover the omitted reference) (wooldridge.py). <br>• ~~Stata jwdidgolden-valueTestReferenceValues~~ RESOLVED: the golden ships four arms pinned by tests/test_etwfe_cs_stata_parity.py(noTestReferenceValuessymbol was ever added). The QMLE bullet above REMAINS OPEN -- every golden arm is linearjwdid`, so no QMLE cluster-SE reference exists, and SEs are pinned only as a ratio. wooldridge.py, wooldridge_results.py, linalg.py, benchmarks #216 · PR-B
Extend WooldridgeDiD method ∈ {logit, poisson} with vcov_type ∈ {classical, hc2, hc2_bm}: composing HC2 leverage + Bell-McCaffrey DOF with the QMLE pseudo-residual sandwich needs derivation + R parity vs clubSandwich::vcovCR(glm, type="CR2"). Rejected at __init__. wooldridge.py follow-up Medium
Multi-constraint CR2 parallel-trends test (AHT/HTZ) for hc2_bm fits: DiagnosticReport's PT check routes vcov_type="hc2_bm" sources to Bonferroni over the BM-adjusted per-row p-values because the generic chi-square joint Wald would discard the CR2 small-sample correction (see REPORTING.md "hc2_bm parallel-trends policy"). The proper joint test is the AHT/HTZ Wald with a Satterthwaite-style denominator df over the pre-period contrast block; needs derivation for the stacked/pooled WLS-CR2 layout + parity vs clubSandwich::Wald_test(..., test="HTZ"). diagnostic_report.py, linalg.py vcov/df round-trip PR Low
PreTrendsPower CS/SA anticipation=1 R-parity fixture: R pretrends has no anticipation parameter, so the Python _extract_pre_period_params anticipation filter isn't R-parity-locked. Build a synthetic CS/SA result with anticipation=1 and assert γ_p matches R's slope_for_power(). (Mechanism already covered by MC + full-VCV tests.) tests/test_methodology_pretrends.py, generate_pretrends_golden.R PR-C Low
Rust multiplier-bootstrap weight RNG (generate_bootstrap_weights_batch) seeds Xoshiro256PlusPlus::seed_from_u64(seed+i) per row; audit Python callers (sdid.py, efficient_did_bootstrap.py, bootstrap_utils.py) for parity-test gaps and, where a numpy-canonical equivalent exists, pre-generate in Python and pass through PyO3 (same fix shape as TROP RNG parity #354). rust/src/bootstrap.rs, bootstrap_utils.py follow-up Medium
SyntheticDiD bootstrap cross-language parity anchor vs R synthdid::vcov(method="bootstrap") or Julia Synthdid.jl (refit-native). Same-library validation is in place; Julia is the cleanest target. Tolerance ~1e-6 (BLAS+RNG paths preclude 1e-10). benchmarks/R/, benchmarks/julia/, tests/ follow-up Low
CS R helpers hard-code xformla = ~1; no covariate-adjusted R benchmark for the IRLS path. tests/test_methodology_callaway.py #202 Low
CallawaySantAnna bootstrap: align p-value computation with R did's symmetric-percentile method (former "CallawaySantAnna Bootstrap Improvements" section). staggered.py Low
bias_corrected_local_linear (lprobust) Phase-1c follow-ups: extend golden parity to kernel ∈ {triangular, uniform} (epa-only today); expose vce ∈ {hc0,hc1,hc2,hc3} on the public wrapper once R goldens exist (port supports all four; needs a per-mode generator + a hc2/hc3 q-fit-leverage decision); clustered-DGP auto-bandwidth parity is blocked upstream on an nprobust singleton-cluster bug in lpbwselect.mse.dpi (Phase-1c DGP 4 uses manual h=b=0.3). _nprobust_port.py, local_linear.py, generate_nprobust_lprobust_golden.R Phase 1c Low-Med
HeterogeneousAdoptionDiD Stute-family Stata-bridge parity: no public R Stutetest package exists; would add benchmarks/stata/generate_stute_golden.do + a Stata dependency. benchmarks/stata/, tests/test_stute_test_parity.py follow-up Low
LPDiD regression-adjustment SE — Stata-teffects anchored; optional R follow-up only. The RA influence-function cluster SE is canonically Stata teffects ra ... atet vce(cluster) (no R package computes it — alexCardazzi/lpdid does direct covariate inclusion, not RA). Resolved: benchmarks/stata/generate_lpdid_ra_golden.do reconstructs each horizon's clean sample independently and anchors the SE against teffects to ~1e-16 at all 7 horizons (golden lpdid_ra_stata_golden.json; tests/test_lpdid_ra_stata_parity.py, which also gates the Stata row count vs the library n_obs); the point is triple-anchored (Python/R/Stata). Stata is node-locked (not in CI), so the CI-runnable guards remain the regression pin (RA_SE_PIN) + MC coverage (coverage_lpdid_ra.py). Optional residual (no longer a blocker): contribute an RA path to alexCardazzi/lpdid so a runnable R reference also exists. benchmarks/stata/generate_lpdid_ra_golden.do, tests/test_lpdid_ra_stata_parity.py #B2 · Stata arm Low
LPDiD survey scope gaps (PR-D1 deferrals). Survey support covers the variance-weighted default path only. (a) survey_design + reweight=True (the equally-weighted / regression-adjustment IF path) is rejected: the weighted RA influence-function variance has no runnable survey reference (same class as the RA-SE row above - survey::svyglm anchors only the OLS/WLS path). (b) Replicate-weight survey designs (BRR/Fay/JK1/JKn/SDR) and (c) non-pweight (fweight/aweight) types are rejected pending demand. lpdid.py, REGISTRY #8 PR-D1 Low
LPDiD non-absorbing SE/RA external-reference gap. The authors' canonical non-absorbing SE/RA is Stata lpdid/teffects only - no faithful R analogue exists (alexCardazzi/lpdid's nonabsorbing_lag proved NOT a faithful Eq. 13; recorded as a divergent reference, not a gate). Resolved (SE half, scoped): benchmarks/stata/generate_lpdid_nonabsorbing_golden.do runs the authors' SSC lpdid package end-to-end (golden lpdid_nonabsorbing_stata_golden.json; tests/test_lpdid_nonabsorbing_stata_parity.py) - Eq. 12 (nonabsorbing(, firsttreat notyet) == first_entry) anchored on ALL surfaces of the full committed panel (ES incl. placebos + both pooled windows, vw + rw; att ~1e-14/1.3e-8, SE ~6e-17/2.3e-9), Eq. 13 (nonabsorbing(L) == effect_stabilization) anchored at post horizons + pooled post on a convention-neutral 47-unit subsample (att ~1.1e-14/1.9e-8, SE ~1.8e-16/1.6e-9); first external anchors for the rw SE, the pooled windows, and the Eq. 12 rw point. Three package convention differences measured, paper-adjudicated and divergence-gated (REGISTRY ## LPDiD Deviation 4). Residual: (a) non-absorbing RA (rw + covariates) - the package's RA engine is listreg (not teffects) and the committed non-absorbing panel has no covariate; needs a covariate panel + a listreg IF-SE convention check. (b) Eq. 13 placebo/pooled-pre inference - not anchored by this arm (the package's placebo-window construction differs, convention class 3; a dedicated late-entry/never-treated subsample could in principle align the two placebo constructions - a possible follow-up): the placebo ES SEs remain R-anchored (vw) / RW_SE_PIN-pinned (rw, h=-3/-2), and the pooled-pre SE currently has no pin or anchor of any kind (every non-absorbing R-parity fit is event-study-only and the R golden has no pooled block). benchmarks/stata/generate_lpdid_nonabsorbing_golden.do, tests/test_lpdid_nonabsorbing_stata_parity.py, tests/test_methodology_lpdid.py PR-C2 · Stata arm Low
HeterogeneousAdoptionDiD Phase-3 R-parity: ships coverage-rate validation on synthetic DGPs, not tight point parity vs chaisemartin::stute_test / yatchew_test (needs bootstrap-seed-semantics + B alignment across numpy/R). tests/test_had_pretests.py Phase 3 Low

Parked — pending user demand / out of scope

Doable in principle, but no current caller and/or explicitly out of paper scope. For survey-specific limitations (NotImplementedError paths), see the Current Limitations section of survey-roadmap.md.

Issue Location PR Priority
RDDensityTest public bandwidth-selector helper (an rdbwdensity-equivalent exposed function) and a Tutorial-28 executed density-test demo cell (Act 4d; needs notebook re-execution + drift-suite updates): the selector ships internal-only and the tutorial carries a prose pointer for now. diff_diff/rddensity.py, docs/tutorials/28_rdd_scholarship_illusion.ipynb rddensity PR-B Low
LW 2025 Sec 4.3 all-eventually-treated mode for LWDiD (drop D_infinity, effects relative to the last cohort as reference, last cohort's own effect not estimable). Current behavior (by decision, see the REGISTRY LWDiD Sec 4.3 note): such designs raise ValueError under both control strategies rather than silently truncating diff_diff/lwdid_staggered.py #588 Low
LWDiD sampling weights / survey_design=: fit() accepts no weight argument on any path (passing survey_design= raises TypeError) — the LW papers derive the rolling transformation and the collapsed exact/HC/CR1 inference for unweighted panels, and no weighted counterpart of the transformation or exact-inference layer has been derived. Documented scope exclusion (REGISTRY LWDiD Edge-cases Note, docs/api/lwdid.rst Scope limitations note, survey-support matrix row, survey-roadmap Current Limitations row) diff_diff/lwdid.py LWDiD release audit Low
EfficientDiD/ImputationDiD/TwoStageDiD kit retention has no memory opt-out: EfficientDiD's kit always retains the per-(g,t) EIF dict (M-023), ImputationDiD's references the _fit_data panel objects (M-021, pre-existing retention), and TwoStageDiD's retains a column-subset working-frame copy (M-022, the first new panel retention); a store_kit=False constructor knob would restore an opt-out at the cost of post-fit aggregation on that result diff_diff/efficient_did.py, diff_diff/imputation.py, diff_diff/two_stage.py 2(b) PR-3a Low
MMM interop, StackedDiD totals (remainder of the estimator-owned-totals delivery - aggregate('total') shipped for CS/DMLDiD/EfficientDiD/ImputationDiD/TwoStageDiD on panel non-survey fits): StackedDiD is STAGED OUT because the total estimand is ill-defined under weighting="population"/"sample_share" (a weighted estimand for which raw treated exposure is not the right mass), and its results class is view-only (a total would be its first computed level). Needs an estimand decision per weighting variant before any implementation. diff_diff/mmm.py, diff_diff/stacked.py mmm-interop Low
SyntheticControl fit-snapshot residency (_SyntheticControlFitSnapshot) — investigated 2026-07-07, parked: the snapshot ALIASES the fit's own working pivots (zero extra construction cost); the retained residency implements the documented freeze contract (post-fit mutation of estimator inputs must not change in_space_placebo() / leave_one_out() / conformal output on an already-returned results object, and __getstate__ already excludes it from pickles). A compact array representation saves only pandas overhead (the float panel dominates); releasing residency needs new API surface (release/opt-out flag) or a freeze-contract change. Revisit on user demand for very large donor panels. synthetic_control.py, synthetic_control_results.py follow-up Low
Stratified survey-PSU multiplier-weight draw-tiling — investigated 2026-07-07, parked: the stratified generator (generate_survey_multiplier_weights_batch) consumes ONE sequential rng stream stratum-major (rng.choice(size=(n_bootstrap, n_h)) per stratum, then lonely-PSU pooling), so draw-chunked assembly CANNOT reproduce the stream bit-identically (contra the old row's parenthetical) — it would need per-stratum generator state skipping (PCG64.advance + per-weight-type variate accounting; fragile) or a stream-layout change (MC-level SE changes → baseline/golden recapture + REGISTRY note). Stratified designs have few PSUs, so the full (n_bootstrap × n_psu) matrix rarely matters; unstratified (the large-n_units case) is already tiled. Revisit only if a large-PSU stratified design hits memory, as a documented stream change. diff_diff/bootstrap_chunking.py::iter_survey_multiplier_weight_blocks follow-up Low
ChangesInChanges FULL Melly-Santangelo covariate estimator (monotonized integrated-indicator conditional CDFs, treated-post `F_{X 11} integration, exchangeable bootstrap with variance-weighted KS bands, tail trimming, pre-period specification test). The simplified qte-xformla form of the MS pipeline is already supported (covariates=on both estimators, parity-tested vs qte 1.3.1); the FULL estimator has no reference implementation (the MS Stata code is the only one; distinct from Kranker'scic) and would need simulation-based validation. Reviewed: docs/methodology/papers/melly-santangelo-2015-review.md`. diff_diff/changes_in_changes.py #682
ChangesInChanges discrete-outcome bounds + DCIC point identification (Athey-Imbens Sections 4/5.2 incl. Imbens-Manski intervals; Kranker's Stata cic is the reference). The shipped ties warning marks the boundary of the continuous scope. diff_diff/changes_in_changes.py #682 Low
ChangesInChanges analytical SEs (Athey-Imbens Theorems 5.1-5.3 influence functions, panel 5.5-5.7, Appendix B covariances; needs the footnote-31 boundary density estimator - note the review's suspected half-range/midpoint typo). Bootstrap is the shipped inference. diff_diff/changes_in_changes.py #682 Low
Staggered/multi-period distributional DiD (Athey-Imbens Section 6 / Ciaccio arXiv:2408.01208v2; ecic is the staggered event-study CiC lineage - a distinct method from Ciaccio's copula approach, do not conflate). Reviewed: docs/methodology/papers/ciaccio-2024-review.md; ROADMAP row is reviewed-deferred pending demand. diff_diff/changes_in_changes.py #682 Low
ChangesInChanges treatment-on-the-controls (Athey-Imbens Theorem 3.2: group-label exchange + negation; no qte equivalent to anchor parity). diff_diff/changes_in_changes.py #682 Low
Rust-backend CR2 Bell-McCaffrey port (return_dof in the Rust vcov dispatch + CR2 algebra) — premise re-scoped 2026-07-09: the scores-based DOF + low-rank factored A_g changes made the NumPy CR2-BM path BLAS-bound (O(n_g k²) per cluster; 4.1s→38ms at n=100k/k=40), so a Rust port buys ~nothing and adds a parity surface. Revisit only if profiling shows CR2-BM hot again. rust/src/linalg.rs Low
CallawaySantAnna unbalanced-panel residual gaps: survey_design= × allow_unbalanced_panel= (per-obs vs per-unit weight resolution - currently fail-closed NotImplementedError), and covariate / ipw / dr × the flag R-parity verification (the RC path supports them; the committed golden covers reg no-cov). Context: allow_unbalanced_panel=True itself matches R did::att_gt(allow_unbalanced_panel=TRUE) / DRDID::reg_did_rc (ATT bit-exact on cells and dynamic aggregation via fixed pg + per-unit WIF; SE up to the documented CR1 sqrt(G/(G-1)) factor); the DEFAULT path keeps within-cell differencing as a documented design choice with a UserWarning on unbalanced input. staggered.py, staggered_aggregation.py SE-audit D3 Low
CallawaySantAnna event-study bucket/weight construction is duplicated between the analytical aggregator (staggered_aggregation.py::_aggregate_event_study) and the multiplier bootstrap (staggered_bootstrap.py): both group (g,t) by e = t - g, apply the finite/NaN/reference masks, and read cohort weights. Both already consume the same source-materialized universal reference cells (so they agree), but the bucket logic is copy-pasted. Extract one shared helper returning per-event-time buckets (finite cells, NaN cells, reference flags, cohort weights, combined-IF inputs) used by both. Pure refactor; gate on byte-identical analytical + bootstrap output. staggered_aggregation.py, staggered_bootstrap.py SE-audit D3 Low
solve_ols residual memory floor is inherent to SVD-based lstsq after the PR-E marshalling slim (rust-side allocator high-water 15.32 -> 7.81 GB on the 2.4M x 130 clustered solve; remaining = one fused equilibrated input copy + thin-SVD U transient + vcov scores block + LAPACK gelsd internals on the python path). Further reduction requires the tall-skinny-QR algorithm change - same trade-off as the parked QR-reuse row below (library-wide last-digit perturbation + loses the second independent collinearity detector); the two land together if ever reopened. Diagnosis + measurements in docs/performance-plan.md. (The 2026-07 opt-in DIFF_DIFF_SOLVE_OLS_FASTPATH Cholesky path sidesteps the floor when enabled — no thin-SVD U transient, scores reuse the one equilibrated buffer; this row tracks the DEFAULT path only.) rust/src/linalg.rs::solve_ols, diff_diff/linalg.py PR-E Low
SunAbraham-family fits are SOLVER-bound after the v3.6.x demeaning speedups: on the county-class shape (3.1k units x 60 months, ~100 interaction columns) solve_ols = ~60% of fit time (faer SVD 1.24s + pivoted-QR rank detection 0.70s of 3.27s), and the share grows further under the Rust demean kernel; the pivoted QR alone is ~20% of the whole fit (measured attribution in docs/performance-plan.md). Candidate fix is QR-reuse (one factorization for rank detection + solve). Parked per the 2026-07 correctness-first decision: the change perturbs every coefficient in the library at the last digits (golden/parity/bit-identity recapture) and removes the SVD's independent singular-value truncation - a second, different near-collinearity detector that is deliberate defense-in-depth for the no-silent-failures contract (the FE-span guard episode showed borderline cases are real). Re-open only on practitioner demand for wide event studies; any implementation gates on fixest/R end-to-end parity, not just internal consistency. (2026-07 updates: the pivoted-QR share quoted above predates #634's stage-0 Gram certification — rank detection is now ~0.03s on the county shape — and the opt-in DIFF_DIFF_SOLVE_OLS_FASTPATH Cholesky path is the certification-gated alternative that captures the solver win WITHOUT the library-wide perturbation: the default stays byte-identical and keeps the SVD truncation as the second collinearity detector.) linalg.py::solve_ols, linalg.py::_detect_rank_deficiency PR-C attribution Medium
dCDH parity-test SE/CI assertions only cover pure-direction scenarios; mixed-direction SE comparison is structurally apples-to-oranges (cell-count vs obs-count weighting). test_chaisemartin_dhaultfoeuille_parity.py #294 Low
HeterogeneousAdoptionDiD joint cross-horizon covariance / sup-t bands: per-horizon SEs use independent sandwiches (paper-faithful pointwise CIs per Pierce-Schott Fig 2). Follow-ups (low demand): IF-based stacking for joint cross-horizon inference; analytical H×H covariance on the weighted ES path. (A sup-t band on the unweighted ES path shipped via the clustered band — cluster= fires the simultaneous band on the unweighted path.) had.py::_fit_event_study Phase 2b / 4.5 B Low
HeterogeneousAdoptionDiD event-study staggered timing beyond the last cohort: Phase 2b auto-filters to the last cohort (paper App B.2); earlier-cohort effects aren't HAD-identified (redirect to dCDH). Full staggered HAD needs a different identification path (out of paper scope). had.py::_validate_had_panel_event_study Phase 2b Low
HeterogeneousAdoptionDiD survey-aware support-endpoint test (research, waits on literature): needs a calibrated support-infimum test under complex sampling (endpoint EVT × survey-aware functional CLT × tail-empirical-process theory). Permanent NotImplementedError on qug_test(survey_design=...); rationale in REGISTRY § "QUG Null Test" Note (Phase 4.5 C0). had_pretests.py::qug_test Phase 4.5 C0 Low
HeterogeneousAdoptionDiD Phase-4.5 weight-aware auto-bandwidth MSE-DPI selector (~300 LoC); users pass h/b explicitly today. Plus replicate-weight SurveyDesigns on the continuous-dose paths (Rao-Wu-style per-replicate weight-ratio rescaling for the local-linear intercept IF). _nprobust_port.py::lpbwselect_mse_dpi, had.py::_aggregate_unit_resolved_survey Phase 4.5 Low
HeterogeneousAdoptionDiD time-varying dose on event study: Phase 2b rejects panels where D_{g,t} varies within a unit for t≥F (constant-dose convention, App B.2). A time-varying-dose estimator is a future PR; current behavior is front-door rejection. had.py::_validate_had_panel_event_study Phase 2b Low
HeterogeneousAdoptionDiD repeated-cross-section support: paper §2 allows panel OR RCS, but Phase 2a is panel-only (RCS inputs rejected by the balanced-panel validator). Needs an RCS identification path (pre/post cell means) with its own validator + data_mode surface. had.py::_validate_had_panel Phase 2a Medium
HeterogeneousAdoptionDiD Phase-3 nprobust bandwidth for Stute variants on continuous regressors (currently OLS residuals from a 2-parameter linear fit, no bandwidth selection). Not in paper scope. had_pretests.py::stute_test Phase 3 Low
SpilloverDiD(ring_method="count"): count-of-treated-in-ring (paper §3.2) is methodologically supported by Butts but re-introduces functional-form dependence; expose behind an explicit kwarg gate + warning. spillover.py follow-up Low
TwoStageDiD paper-permitted estimand variants (Gardner 2022): the Eq.(5) P̄-period-average estimand and the fn.8 full-sample first-stage variant have no public parameter. Documented ⚠️ in gardner-2022-review.md; surface as estimand= / first_stage= if a use case arises. two_stage.py follow-up Low
bias_corrected_local_linear multi-eval grid (neval > 1) with cross-covariance (covgrid=TRUE). Not needed for HAD; useful for multi-dose diagnostics. _nprobust_port.py::lprobust Phase 1c Low
Rust local-method estimate_model → unify to solve_wls_svd (the global-method's SVD helper) for sub-1e-14 bootstrap-SE parity. The local-method bootstrap parity test passes at atol=1e-5; the residual ~1e-7 is roundoff, not a user-visible correctness bug. rust/src/trop.rs, rust/src/linalg.rs follow-up Low
Validate the .txt AI guides (llms-full.txt, llms-practitioner.txt) as executable snippets — not low-lift (re-scoped 2026-06-01): only ~20% of ~112 fenced blocks are standalone-runnable; the rest are signature pseudo-code, context fragments, or data-shape-specific. Needs signature-block detection + a context/data skip-allowlist + per-snippet fixtures. tests/test_doc_snippets.py #239 Low
TestWorkflowDoesNotExecutePRHeadCode (CodeQL #14 guard) doesn't model bash/sh/./source <script> execution, multi-line python3 -c bodies, shell-var indirection, eval, find -exec, xargs -I. Catches common accidental regressions (16 forms); closing the residuals needs multi-line shell parsing + script-exec allowlists — diminishing return given the documented threat model. tests/test_openai_review.py, .github/workflows/ai_pr_review.yml #436 Low
Calendar-time aggregation (R did feature gap) — blocks 1 ported test in test-att_gt.R. Low
Speculative / low-value performance notes (relocated from the old ## Performance Optimizations): numba JIT for bootstrap loops — blocked by the numpy/pandas/scipy-only dependency policy; generic sparse-matrix handling for large FE; QR+SVD rank-detection redundancy in solve_ols (QR overhead is minimal vs the SVD solve — correctness over micro-opt; skip_rank_check already exists for known-full-rank hot paths); incomplete check_finite=False bypass (scipy's QR in _detect_rank_deficiency() still validates; edge-case only). All Low, none correctness-affecting. linalg.py::solve_ols Low
SpilloverDiD covariates= support (Wave B MVP scope cut): the full covariate path mirroring TwoStageDiD._fit_untreated_model (stage-1 residualization on the untreated-and-unexposed subsample, then subtraction before stage 2) — appending covariates only at stage 2 would silently bias tau_total/delta_j; rejected at fit() with NotImplementedError. spillover.py Wave B Low
SpilloverDiD estimator-level end-to-end vcov reconstruction tests (bread @ meat @ bread against res.vcov): requires exposing the estimator's internal X_2_kept design arrays; the surface is currently pinned from different angles (uniform-weight bit-identity, drift goldens, manual lincom reconstruction at rtol=1e-6). spillover.py, tests/test_spillover.py follow-up Low
SpilloverDiD Wave E.3 finite_mask + design-subset hygiene not yet adopted for TwoStageDiD's analogous pattern (two_stage.py:567-601) — separate parity follow-up noted in the docs/api/spillover.rst Restrictions block. two_stage.py Wave E.3 Low
HeterogeneousAdoptionDiD covariates= (Theorem 6 multivariate-covariate extension) not implemented — fit(covariates=...) raises NotImplementedError via the shipped future-work trap (locked by the test_had.py / test_methodology_had.py L73 tests); the deferred work is the Theorem 6 extension itself. had.py Phase 2a Low
MultiPeriodDiD wild bootstrap not supported (falls back to analytical, n_bootstrap-independent) — user-facing edge-case limitation; the 4.0 removal replaces the fallback with a raise (v4-design §4.1), and the merged TWFE event-study mode already RAISES since 3.9 (Phase 3(a)) — this row now governs only the deprecated class itself. estimators.py (MultiPeriodDiD.fit wild-fallback block) Low
predict() raises NotImplementedError — rarely needed; user-facing limitation. estimators.py:890-911 Low

Version-gated (v4)

Gated on the next major version, not on demand or derivation. Lifecycle state and removal/flip targets live ONLY in the CI-enforced docs/v4-deprecations.yaml; rows here carry the remaining implementation work and cross-link their M-xxx ids.

Issue Location PR Priority
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

Decisions on the record, kept to stop re-litigation. Convention going forward: decisions that pin user-visible behavior or methodology are recorded as Notes in docs/methodology/REGISTRY.md (the reviewer-recognized labels); internal-engineering decisions (refactor waivers, perf trade-offs, test-infrastructure calls) are recorded here.

Decision Location Verified
utils.validate_pscore_trim ships without the allow_zero flag the retired TODO row proposed. The row (retired in the M-145 PR) suggested validate_pscore_trim(value, *, allow_zero); after ContinuousDiD's alignment to 0 < x < 0.5, no consumer needs allow_zero=True — every migrated copy (TripleDifference, DMLDiD, ContinuousDiD, LWDiD, CallawaySantAnna) wants the strict interval, and the deliberately-unmigrated StaggeredTripleDifference keeps its own bare check rather than a flagged call. A dead-on-arrival parameter on a shared validator is a drift magnet; re-add the flag only when a real caller needs a [0, 0.5) domain, with a distinct range message diff_diff/utils.py M-145 / 2026-08-27
Fixed-B Stata SE-golden comparisons in the LWDiD suite run unmarked in default CI (no ci_params.bootstrap() scaling, no slow marker): the SE tolerance derives from BOTH fixed rep counts (ours B=999, Stata's R from the golden meta) so scaling either side would invalidate the committed-golden comparison — the ci_params convention governs convergence-style tests, not fixed-B golden comparisons — and these tests are the PR #588 acceptance bar, which must run in default CI; module-scoped fit memoization bounds the cost (~1-2 s per fit locally) tests/test_methodology_lwdid.py 2026-08-16
The 4.0 migration guide's code blocks are not snippet-executed. tests/test_doc_snippets.py discovers a hardcoded list of .rst files and only .. code-block:: python / RST :: bodies, so docs/migration-4.0.md gets no coverage. Deliberate: the guide is a MIXED document - most "after" examples (the renames, results.att) run on the current release, but the field-flip and df-convention-flip examples describe 4.0 behaviour that cannot run until 4.0, so a blanket execution lane would fail by construction. Closing the gap fully means a markdown-fence extractor plus a skip-marker convention for the future-API blocks - a harness change, out of scope for a docs PR. What IS gated: the appendix's ledger parity (test_migration_guide_*), which pins the row set and every mechanically checkable cell; and, since the first local review found all three merge examples carrying invalid keywords, test_migration_guide_examples_bind_to_real_signatures, which ast-parses the guide's python blocks and asserts every constructor/fit() keyword exists on the target signature. That is signature binding, NOT execution - it deliberately skips calls whose owner it cannot resolve (e.g. results.aggregate(...)), and it cannot catch a wrong VALUE or a wrong sequence of calls. The hand-written Fix prose remains unverifiable by any available means. docs/migration-4.0.md, tests/test_doc_snippets.py Phase 4 / 2026-08-09
MMM launch carousel carries scoped marketing claims, not exporter-contract documentation. The deck (a LinkedIn marketing artifact, not a docs surface) states capabilities in scoped-but-punchy form: guardrail copy says "the easy mistakes fail loudly" / "you own the design, it owns the math" (the caller-owned estimand/population/window/outcome-scale alignment lives in REGISTRY.md's MMM section and the exporter docstrings, not on slides), and slide 8 shows tutorial 29's worked staggered-boost lift row WITHOUT an on-slide linearity qualifier - the compression's linear-channel scoping is the tutorial's job, which the CTA points to. Absolutes ("no silent mis-calibration", "any estimate exports", "anything with an estimate + SE") are ban-listed by tests/test_mmm_carousel_claims.py; local review R1/R2 pressed for compliance-style scoping language on the slides and the marketing-appropriate scoped copy was chosen instead. carousel/generate_mmm_carousel.py, tests/test_mmm_carousel_claims.py mmm-carousel / 2026-08-20
MultiPeriodDiD deprecation shim loses static constructor-arg checking (3.9 window). The M-010 shim is __init__(*args, **kwargs) + an import-time __signature__ mirror of DiD's constructor: runtime introspection (get_params/set_params, inspect.signature) and eager validation are fully preserved, but static type checkers / IDEs cannot check constructor arguments for the deprecated class until its 4.0 removal. Accepted: the alternative (hand-mirroring ~20 parameters) is a drift magnet on a class with one minor version of remaining life. diff_diff/estimators.py 3(a) / 2026-08-07
DCDH sklearn.base.clone param-identity failure won't-fix. ChaisemartinDHaultfoeuille._validate_paths_of_interest unconditionally canonicalizes paths_of_interest into a fresh List[Tuple[int, ...]], so sklearn clone()'s post-construction param1 is param2 identity check fails for configured instances - a pre-existing normalization the BaseEstimator mixin PR documented rather than changed (get_params/set_params signatures are clone-compatible; the dependency-free cls(**est.get_params()) config-equality contract is the enforced one, tests/test_base_estimator.py). Fixing would mean returning the caller's raw object from a validator whose job is canonicalization. chaisemartin_dhaultfoeuille.py mixin PR / 2026-08-01
scikit-learn stays out of dev deps; clone-identity tests remain importorskip-only. The sklearn-clone() round-trip tests (test_base_estimator.py, had/rdd/cic suites) run only where scikit-learn happens to be installed - deliberate, matching the numpy/pandas/scipy-only dependency posture; the always-running contract is the dependency-free re-instantiation config-equality test. tests/test_base_estimator.py mixin PR / 2026-08-01
Plan-review hash gate threat model: accident prevention, NOT adversarial defense. The ExitPlanMode content-hash gate (hook + plan_snapshot.py) exists to stop accidents — stale approvals, concurrent-worktree cross-talk, plans edited mid-review — all of which it closes by construction (snapshot identity + invocation-unique state tokens, 30+ behavioral tests). It does NOT and cannot defend against a malicious local process: nothing verifies review AUTHORSHIP, and such an actor can simply write a matching review file directly — no userland hook can prevent that short of signed reviews, which is out of scope for a personal workflow aid. Review findings that presuppose a hostile local actor against this gate are waived by this decision (2026-07-20, after 7 local AI-review rounds converged on ever-deeper adversarial-model refinements with no reachable fixpoint). Genuine accident vectors remain in scope and are fixed as found. .claude/hooks/check-plan-review.py, .claude/scripts/plan_snapshot.py 2026-07-20
StackedDiD survey re-resolution intra-file dedup not warranted. The cross-estimator ContinuousDiD/EfficientDiD panel-to-unit collapse consolidation landed in #226 (ResolvedSurveyDesign.subset_to_units_by_row_idx / build_unit_first_row_index); StackedDiD deliberately re-resolves at stacked granularity (control units are duplicated across sub-experiments). The residual intra-file dedup (raw-weight extraction ×3, compose-normalize ×3, resolve-on-stacked ×2) is stacked-specific, low value, and touches the numerically-sensitive composed-weight renormalization; cross-estimator unification was assessed and is not warranted (genuinely different mechanisms; all three already delegate to _resolve_survey_for_fit / compute_survey_metadata). stacked_did.py #226 review
HeterogeneousAdoptionDiD Phase-4 Pierce-Schott (2016) replication harness waived. R parity at atol=1e-8 on the same 3 DGPs is a strictly stronger anchor than reproducing Fig 2's pointwise CIs on the LBD-restricted PNTR panel (paper §5.2 self-acknowledges NP estimators too noisy there). Re-open only on user demand. See REGISTRY HAD Deviations Notes #3/#4. benchmarks/, tests/ Phase 2a / 2026-05-20
StackedDiD intercept SEs not parity-lockable (SE-audit C1/(c)). Recorded as the REGISTRY ## StackedDiD intercept-SE Note: measured ~0.3% nuisance-parameter reference-cell gap vs R (interaction SEs match ~2e-13); surfacing an intercept-SE field would add an unasserted, R-divergent public value. tests/test_methodology_stacked_did.py SE-audit / 2026-07-09
estimatr classical intercept SE excluded from parity (SE-audit (d)). Recorded as the REGISTRY HAD mass-point intercept-SE Note: same documented O(1/n) projection/DOF deviation as the slope; reference-only. tests/test_estimatr_iv_robust_parity.py SE-audit / 2026-07-09
bread_inv reuse not bit-identically achievable. "Factor (X'WX) once, reuse across HC2/HC2-BM" can't be done bit-identically (the bar for a pure perf refactor of the inference path). Internal bread ops solve against different RHS (X.T, eye, meat+temp.T, contrasts); only same-RHS results are bit-reusable. Measured: lu_solve(lu_factor(A),B) differs from solve(A,B) up to 6.4e-15; the inv(A)@meat@inv(A) sandwich differs up to 1.24e-14 — both nonzero and below the affected goldens' tolerances (1e-12/1e-10), so a broad reuse would silently shift SEs without tripping the suite. The one genuine bit-identical redundancy (a duplicated solve(bread, X.T) in the unweighted one-way hc2_bm+return_dof path) is dwarfed by that path's dense M=I−H build, so the saving is negligible. linalg.py::compute_robust_vcov 2026-06-01
R-script-per-test consolidation has no CI impact. No CI workflow installs R, so every R-parity test skips in CI behind a per-file availability gate — consolidating Rscript spawns yields zero CI speedup. test_methodology_twfe.py already session-caches its R fits. The only residual is a LOCAL-dev micro-opt for test_methodology_continuous_did.py / test_methodology_callaway.py (re-spawn library(...) per call). Low value; retained as a local-dev note. tests/test_methodology_continuous_did.py, tests/test_methodology_callaway.py #139 / 2026-06-07
HeterogeneousAdoptionDiD mass-point IV bread is non-symmetric — _rank_guarded_inv inapplicable. The structural rank-guard sweep (continuous_did / two_stage / spillover / conley) excluded had.py's ZtWX = Zd'WX ([1, instrument]' × [1, endogenous]): it is a non-symmetric 2×2 Wald-IV bread (V = ZtWX_inv @ Omega @ ZtWX_inv.T), and _rank_guarded_inv assumes a symmetric PSD Gram (symmetric D=diag(A) equilibration + eigendecomposition), so applying it would be methodologically wrong. The existing fallback already returns NaN SE on a singular bread; an IV-appropriate near-singular guard would need a different mechanism. had.py structural-rank-guard / 2026-06-28
ImputationDiD SE vcov is already rank-guarded upstream. Excluded from the structural rank-guard sweep: the lead/effect vcov comes from solve_ols(..., return_vcov=True, rank_deficient_action=...) at the OLS fit (imputation.py:~2316), which already drops rank-deficient columns. The only raw inverse (solve(V_gamma, gamma), imputation.py:~2530) is the pretrends Wald F-test statistic with a safe NaN fallback — a test statistic, not a sandwich bread — so there is no garbage-SE exposure. No structural rank-guard needed. imputation.py structural-rank-guard / 2026-06-28
TWFE HC2/HC2-BM full-dummy dedup: drift-prone duplication already resolved by the shared builder; full delegation waived. The former Actionable row (origin: follow-up review, citing pre-#655 line numbers) asked to extract a shared dummy-construction helper or delegate TWFE's HC2/HC2-BM path to DiD's fixed_effects= branch. The shared-helper half SHIPPED in #655: both sites now delegate dummy construction, drop-first convention, FE column naming, and the duplicate-term backstop to the single build_fe_dummy_blocks (utils.py) + validate_design_term_names implementation (twfe.py::fit full-dummy branch; estimators.py::DifferenceInDifferences.fit fixed_effects= branch) — the FE-naming / survey-behavior drift risk the row targeted is gone. What remains per site is ~4 lines of genuinely estimator-specific design-matrix assembly (TWFE stacks const/ATT/covariates; DiD stacks its formula terms), which is not drift-prone duplication. The remaining full-delegation option — routing TWFE.fit through DiD machinery with TWFE-specific cluster-default threading — would touch TWFE's user-visible result surface (coefficient-dict keys, cluster-label conventions, warning text) for near-zero residual benefit; waived on cost/benefit. twfe.py::fit, estimators.py::DifferenceInDifferences.fit, utils.py::build_fe_dummy_blocks #655 / 2026-07-10
Survey TSL SE intentionally counts genuine-subpopulation zero-weight PSUs (matches R, NOT a bug). Recorded as the REGISTRY § "Subpopulation Analysis" TSL-meat Note (Lumley 2004 §3.4 full-design domain convention; R survey::svyrecvar(subset()) parity); regression-locked by tests/test_survey.py::TestZeroWeightPsuConventionWaiver. survey.py (_compute_stratified_psu_meat) PR-B / 2026-06-30