bench: 2026-07 public benchmark refresh harness with fail-closed publication gates#672
Conversation
…ication gates Machinery to re-run the published docs/benchmarks.rst comparisons (BasicDiD, TWFE, MultiPeriodDiD, CallawaySantAnna incl. MPDTA real data, SyntheticDiD vs R fixest/did/synthdid) fairly and reproducibly, with the timed run and page regeneration to follow as a second push on this PR. Harness (benchmarks/refresh_2026_07/): - run_refresh.py: released-wheel arm (pip diff-diff==3.7.0 in an isolated uv venv) vs R; fresh subprocess per replication, strictly sequential, untimed in-process warm-up on BOTH sides (R's byte-compiler JIT previously sat inside the timing window), median of counted reps (first excluded), CV>10% rerun-and-flag (skipped when a correctness failure is already observed, so a rerun can never mask it). - Fairness enforced, not assumed: DIFF_DIFF_* and thread-count env vars stripped from every benchmark subprocess, R under --vanilla, SDID placebo bootstrap at 200 draws on both sides (the legacy orchestrator gave Python 50 vs R's 200), R metadata captured under the same invocation contract as the timed arms. - Provenance hard-fails: wheel version/path (dev-tree sys.path shadowing is fully functional without this), backend-arm consistency, estimator self-identification per cell (a silently ignored flag can never publish numbers under the wrong label), environment fingerprints per cell (mixed partial reruns refuse to render), deterministic seed-42 dataset regeneration with byte-comparison against any on-disk CSV. - Publication gates (single hard-flag source of truth shared by runner and generator; gen_benchmark_tables.py refuses hard-gated, partial, or mixed-fingerprint artifacts): per-rendered-arm ATT/SE tolerances with CI overlap enforced independently; per-period / per-(g,t) / event-study / group effect surfaces vs R; SDID id-aligned unit/time weight identity at 1e-8; MPDTA known-answer (-0.039951); replication determinism and non-finite outputs. - SDID specifics: placebo-SE parity gated at a documented 35% Monte Carlo bound (R's placebo permutation is unseeded - SEs agree in distribution, not draw-by-draw; rep-to-rep R SE values recorded for audit; REGISTRY note). Weight comparison aligns by unit/period id: get_unit_weights_df() sorts by descending weight while R emits panel order, so a positional comparison fabricates ~1e-2 divergence out of ordering; id-aligned, weights reproduce R at <= 5e-13. - BasicDiD/TWFE honesty: the legacy path labeled an interaction-OLS cell "BasicDiD/TWFE" while --type twfe was accepted-and-ignored by both arms. The refresh benchmarks BasicDiD and absorbed-FE TWFE (dedicated script pair, TwoWayFixedEffects vs feols | unit + post) as separate gated cells; the no-op flag now fails loudly everywhere. - run_version_story.py: internal 3.5.3-vs-3.7.0 released-wheel comparison (repo-internal artifact; flagged cells banner + nonzero exit). - tests/test_benchmark_refresh_gates.py: 45 regression tests locking the gate behavior (overlap-cannot-waive-SE, both-arm gating, id alignment, non-finite replication gating, CV-rerun masking, fingerprint mixing, generator refusal, estimator wiring). Docs: marker regions installed on every numeric surface of docs/benchmarks.rst (content regenerated only from a gated results artifact); SDID weight-parity prose corrected to cite the machine verification; SE-criteria prose documents the SDID Monte Carlo exception; REGISTRY.md SyntheticDiD note documents the benchmark gate policy. Table numbers on the page are intentionally untouched in this commit - they regenerate atomically with the committed results artifact after the timed run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GPX5Rv8ozQXPdUV23QTfjr
Overall Assessment
Executive Summary
MethodologyFinding M1Severity: P1 Impact: The registry says SyntheticDiD unit/time weight gates are id-aligned and fail-closed on key mismatch/duplicates. But Concrete fix: Require id alignment for SDID publication gates. In Code QualityNo additional findings. PerformanceNo findings. The warm-up and sequential subprocess design are appropriate for the benchmark goal. MaintainabilityFinding MA1Severity: P3 Impact: Concrete fix: Replace the Tech DebtNo blocking findings. The deferred timed artifact/doc refresh is acceptable as long as it lands before publication or is tracked outside PR prose. SecurityNo findings. The provenance redaction path avoids committing full local paths and non-allowlisted Documentation/TestsFinding D1Severity: P3 Impact: The page now mentions the refresh policy but still contains old “3 replications” and combined “BasicDiD/TWFE” prose/tables until the results artifact is generated. This is not a correctness blocker, but it can confuse readers if merged before the second push. Concrete fix: When committing Path to Approval
|
…l fallback The registry documents the SDID weight-parity gate as id-aligned and fail-closed, but compare_weight_vectors() silently fell back to positional comparison when ids were absent, and the runner never checked aligned_by_ids - a benchmark-script regression that stopped emitting ids would have quietly weakened the documented contract. - compare_weight_vectors(require_ids=True): missing ids on either side now FAILS the comparison; the SDID publication gate passes it. Positional fallback remains available (require_ids=False) for legacy artifacts only. - ensure_mpdta() integrity checks use explicit raises instead of assert (asserts vanish under python -O). - TODO row tracks the intentionally deferred phase-2 artifact/prose push (removed by that push itself). - Tests: 48 total (missing-ids-fail-closed with positionally identical weights, valid-ids pass, non-required fallback preserved). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GPX5Rv8ozQXPdUV23QTfjr
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment✅ Looks good — no unmitigated P0 or P1 findings. Executive Summary
MethodologyFinding M1Severity: P3 informational Impact: SyntheticDiD benchmark SE parity uses a wider 35% Monte Carlo-bounded gate for placebo SEs, while ATT and id-aligned unit/time weights remain tightly gated. This is explicitly documented with a registry Note and is therefore an accepted documented deviation. Concrete fix: None required. Previous P1 check: resolved. Code QualityNo findings. The prior P3 concern about PerformanceNo findings. Warm-up, sequential subprocess execution, CV flagging, and per-arm provenance checks are consistent with the benchmark-refresh goal. MaintainabilityNo findings. The runner and table generator share hard-flag classification through Tech DebtFinding T1Severity: P3 informational Impact: The timed Concrete fix: Complete the tracked phase-2 item: run the gated timed refresh, commit SecurityNo findings. Provenance redaction keeps local path details out of committed artifacts, and unexpected Documentation/TestsFinding D1Severity: P3 informational Impact: Some benchmark page content remains pre-refresh until the phase-2 artifact lands, but this is explicitly tracked in Concrete fix: No immediate approval-blocking fix. Follow the tracked phase-2 TODO. Verification note: I attempted |
CI's Python-test jobs copy tests/ to a temp directory and run against the installed wheel, where benchmarks/refresh_2026_07/ does not exist - the module-level harness import raised ModuleNotFoundError at collection and errored the entire session on every OS/Python matrix job. Module-level pytest.skip when the harness directory is absent (the harness is repo tooling, not wheel content - same convention as the benchmarks/data golden-file skips). Verified: temp-copied layout skips (1 skipped), repo layout passes (48 passed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GPX5Rv8ozQXPdUV23QTfjr
|
🔁 AI review rerun (requested by @igerber) Head SHA: Overall Assessment✅ Looks good — no unmitigated P0 or P1 findings. Executive Summary
MethodologyFinding M1Severity: P3 informational Impact: SyntheticDiD placebo SE parity uses a documented 35% Monte Carlo-bounded gate because R Concrete fix: None required. Finding M2Severity: P3 informational Impact: Prior SDID positional-weight comparison risk is resolved. The Python and R scripts emit IDs, and the publication gate calls Concrete fix: None required. Code QualityNo findings. The hard-flag taxonomy is centralized in PerformanceNo findings. Warm-up, sequential subprocess execution, fresh replications, CV rerun behavior, and thread-env stripping are consistent with the benchmark-refresh protocol. MaintainabilityNo findings. The generator refuses hard-gated, mixed-fingerprint, or partial publication artifacts by default at Tech DebtFinding T1Severity: P3 informational Impact: Timed Concrete fix: Complete the tracked phase-2 TODO and remove the row. SecurityNo findings. Provenance handling redacts non-allowlisted Documentation/TestsFinding D1Severity: P3 informational Impact: The new tests cover the key gates: SE mismatch despite CI overlap, documented SDID 35% bound, ID-aligned weights, and generator refusal on hard flags. I could not run them locally because Concrete fix: Ensure CI runs |
Summary
benchmarks/refresh_2026_07/harness for a fair, reproducible re-run of the publisheddocs/benchmarks.rstcomparisons (BasicDiD, TWFE, MultiPeriodDiD, CallawaySantAnna incl. MPDTA real data, SyntheticDiD vs R fixest/did/synthdid). Two-phase PR by design: this push is the measurement machinery; the timed run's committed results artifact (results/refresh_results.json), the regenerated page tables/environment block, and the remaining protocol-prose reconciliation land together as a second push on this same PR once the run completes on an idle machine. Until then the page's numeric content is intentionally untouched (the marker comments are inert), so the currently published tables remain self-consistent with the protocol prose that produced them.pip install diff-diff==3.7.0in an isolated uv venv), never the dev tree: provenance hard-fails on wheel version/path (the benchmark scripts'sys.path.insert(0, repo_root)made dev-tree shadowing fully functional and invisible), backend-arm mismatch, or unexpectedDIFF_DIFF_*knobs reaching the subprocess.Sys.timewindow on every replication), fresh subprocess per replication, strictly sequential, thread-count env vars stripped and R run under--vanilla, SDID placebo bootstrap at 200 draws on both sides (the legacy orchestrator gave Python 50 vs R's default 200 - unfair to R), median of counted reps published with full distributions retained.gen_benchmark_tables.py(which refuses to render hard-gated, partial, or mixed-environment-fingerprint artifacts into the marker-bounded doc regions): per-rendered-arm ATT/SE/CI-overlap gates; per-period, per-(g,t), event-study, and group effect surfaces vs R; SDID id-aligned unit/time weight identity at 1e-8; MPDTA known-answer (-0.039951); replication determinism and non-finite-output gates (a CV rerun can never mask a correctness failure).--type twfewas accepted-and-ignored by both arms; the refresh benchmarks BasicDiD and genuine absorbed-FE TWFE as separate gated cells, and the no-op flag now fails loudly everywhere.get_unit_weights_df()sorts by descending weight while R emits panel order, so a naive positional comparison fabricates ~1e-2 divergence out of pure ordering; id-aligned, weights reproduce R at <= 5e-13, and the page's weight-parity prose now cites the machine verification.run_version_story.py: repo-internal released-wheel 3.5.3-vs-3.7.0 comparison runner (not part of the docs site).Methodology references (required if estimator / math changes)
vcov(method="placebo")permutation is unseeded (draw-by-draw parity holds only when the permutation sequence is threaded explicitly, verified at <1e-8 intests/test_methodology_sdid.py); see the REGISTRY.md SyntheticDiD note.Validation
tests/test_benchmark_refresh_gates.py(45 regression tests: CI-overlap-cannot-waive-SE, both-arm gating, id-aligned weight comparison, non-finite replication gating, CV-rerun masking, environment-fingerprint mixing, generator refusal, estimator-field wiring, hard/soft flag classification).Security / privacy
Generated with Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01GPX5Rv8ozQXPdUV23QTfjr