Skip to content

Gate the ACS local release on district ESS collapse (report-only by default) - #1088

Merged
MaxGhenis merged 5 commits into
mainfrom
acs-local-ess-collapse-gate
Nov 11, 2026
Merged

MaxGhenis merged 5 commits into
mainfrom
acs-local-ess-collapse-gate

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Why

#1078 measured Kish ESS by state and district for the ACS local release ("ESS floor as a release gate" in experiments/us-acs-local-l2-basis-20260928/README.md). At the release's ACS share of 0.5:

  • the release's three solves (full surface and two holdout folds) leave 15, 22 and 24 congressional districts below 25% of their design-weight ESS;
  • every chi-square-penalized solve leaves none;
  • an absolute district floor of 15 also separates them: the release's smallest district ESS is 7.8-11.7, and the penalized solves' smallest is at least 18.5 (18.58, soft_chi_s050_0.01).

Whether the gate should block a release is still open for review, so this PR builds the gate and records its result, but does not block by default. The tool already records the evidence: calibration_summary.json carries weight_origin.{design,calibrated}.effective_sample_size_by_district.

What changes

tools/build_us_acs_local_release.py, finalize stage:

  • district_ess_collapse_gate reads both per-district ESS maps from the summary's weight_origin. It counts:

    • the districts that collapse: calibrated ESS below --district-ess-relative-floor (default 0.25) × design-weight ESS;
    • the districts below the floor: calibrated ESS below --district-ess-floor (default 15).

    A floor of 0 turns its check off. Each district list is sorted worst first. The gate also records the smallest district ESS and the smallest ratio.

  • The result is recorded in three places:

    • gate_summary.json under gates.district_ess_collapse;
    • build_manifest.json, which embeds gates unchanged;
    • a reviewed-limitations entry, district_effective_sample_size_gate, which reaches release_manifest.json. Its text states the counts, the smallest district and whether the gate blocked.
  • Blocking is opt-in (--district-ess-gate-blocking, default off).

    • Off: the gate is report-only. It passes and carries its verdict in criteria_met. This is required, not cosmetic: _check_local_area_gates in microcosm.data.contract refuses to publish a local-area release with any gate whose passed is not true, so a report-only gate recording passed: false would block publication anyway.
    • On: finalize adds the gate to simulation_readiness_blockers when any district collapses or falls below the floor. It does the same when the summary records no per-district ESS (fails closed).
    • Package under the flag refuses, before any release directory exists, a gate report that finalize did not evaluate as blocking at the same floors. Otherwise running --stage package --district-ess-gate-blocking after a report-only finalize would ship a collapse the flag exists to stop.
  • The release refresh recipe names --district-ess-relative-floor, --district-ess-floor and --district-ess-gate-blocking when the recorded gate differs from the defaults, so a refresh rebuilds the recorded gate. A default report-only build's recipe is unchanged.

  • The default solve is unchanged. The tool edit adds one self-contained block (constants, the gate, its limitation, the package check, the recipe flags), a gate call, a limitation line and a hard-failure check in do_finalize (10 lines), two calls in do_package, three flags with range checks, and a docstring line. Other open PRs edit this tool, so it touches no shared hunk. In particular it leaves Chi-square design-weight penalty and softmax mass parametrization for calibration; ACS local ESS frontier #1078's _ess_concentration_limitation and penalty flags alone.

The README and a changelog fragment document the flags.

Evidence: the gate on #1078's solves

I replayed district_ess_collapse_gate on the per-district ESS in #1078's 59 run receipts (results/runs/*.json, metrics.concentration.per_cd, kish_ess and kish_ess_design). On every one of the 57 solves in results/frontier.csv it reproduces exactly:

  • cds_below_0.25_of_prior;
  • cd_ess_min;
  • n_cds (436).

There were 0 mismatches. The replay was a local check and isn't committed: the receipts live on #1078's branch, and the replay script lived in a session scratchpad that has since been cleared, so nothing in either repo re-runs it. frontier.csv has no count below 15, so the "Below 15" column below comes only from that replay. At share 0.5:

Solve Collapsed (full / fold 0 / fold 1) Below 15 Smallest district ESS
Release (projection, λ 0) 15 / 22 / 24 9 / 15 / 10 11.7 / 10.7 / 7.8
Softmax, λ 0 8 / 5 / – 2 / 1 / – 13.1 / 14.8 / –
Projection, chi-square λ 0.03 0 / 0 / 0 0 / 0 / 0 22.4 / 22.9 / 24.2
Softmax, chi-square λ 0.03 0 / 0 / 0 0 / 0 / 0 23.0 / 23.0 / 24.1

#1078's rerun of the release solve (dup_release_repro) gives 16 collapsed, not 15, so the count moves by at least 1 between reruns. At ACS share 0.9 the unpenalized solves leave 261-328 districts collapsed and none below 15. So with share 0.9, the relative gate is the one that binds.

Invariants

These are property-tested with Hypothesis in packages/microcosm-build/tests/engine_free/us/test_us_acs_local_district_ess_gate.py. The weight-origin evidence is built by cd_surface.weight_origin_summary, the function calibration_evidence uses.

  1. Monotone thresholds. The collapsed districts are nested in the relative floor, and the below-floor districts in the absolute floor. Both counts are therefore monotone in their threshold.
  2. A design-weight solve collapses nothing. With calibrated = design weights, no district collapses at any relative floor in [0, 1]. The same holds for a uniform rescaling of the design weights at any floor up to 0.99. The absolute floor is not relative: in a design-weight solve it flags exactly the design's own districts below the floor.
  3. Bounds. Both counts are at most the number of districts, which equals the number of distinct district codes. criteria_met holds exactly when both counts are 0. The entry survives a JSON round trip unchanged.
  4. Blocking changes only the pass flag. Blocking alters only passed, blocking and report_only. A report-only gate always passes. A blocking gate passes exactly when criteria_met, and the limitation's calibration_blocker is not passed.
  5. Differential. The collapsed districts read from the summary equal those an independent np.bincount Kish ESS finds from the raw weights. Districts within 1e-9 relative of the threshold are excluded as floating-point ties, because the two compute sums in a different order.

The replay above is a sixth, differential check against #1078's analysis code. It was executed, not committed.

Intended violation. A report-only gate records passed: true even when districts collapse. This is pinned by test_the_release_contract_publishes_a_report_only_gate_whatever_it_measured, because the local-area contract publishes only passing gates.

Tests

  • Example tests in the new module: the strict < at both floors, worst-first ordering (collapsed by ratio, below-floor by ESS, with an example where the two orders differ), the tie-break for the smallest ESS and ratio, zero floors, a district with zero design ESS, integer ESS from JSON, 16 kinds of missing or malformed evidence (including an integer too large for a float), out-of-range floors (in the function and the parser), the limitation text, the contract interaction, and refresh-recipe flags that parse back to the recorded gate.
  • End-to-end tests in test_us_acs_local_release_tool.py:
    • report-only finalize records the gate without blocking;
    • blocking finalize fails on a collapse;
    • blocking finalize fails closed without evidence;
    • blocking package refuses five kinds of non-blocking or mismatched report;
    • package accepts a passing blocking gate and ignores a report-only one;
    • finalize to package ships the gate in gate_summary.json and build_manifest.json, with its limitation in release_manifest.json, accepted by the contract; both manifests' refresh recipes name --district-ess-gate-blocking exactly when the build blocked.

Since the first review

  • First review: Opus 5.5 via Subfleet (job 20261002-074454-mc1088-review-1) at 29921fb2: APPROVE, with 3 low findings and 5 nits. Lanes ran read-only, so it executed nothing. 5cee50ab1 addresses them:
    • the refresh recipe now carries the gate's non-default flags;
    • the PR body cites where the below-15 counts come from and says the replay is uncommitted;
    • "at least 18.6" becomes 18.58 here and "at least 18.5" in the tool comment;
    • below-floor ordering and tie-break tests;
    • no subnormal factors in the property strategy;
    • an overflowing JSON integer is malformed evidence;
    • the target-surface doc lists the gate.
  • Second review (in-session Opus 5.5, since the Subfleet lanes are closed) at 5cee50ab1: REQUEST_CHANGES on one blocker. The end-to-end test still wrote the pre-Gate reviewed default fills on their means-tested consumer notes (#1022) #1071 null-fill fixture, which CI engine-free (3.14) also caught. 7fc818de3 fixes it. 8d8b75036 fixes the nits: a ratio-versus-ESS ordering example, the parse-error message check, and the docstring's 0.99. That review also re-implemented the gate's rule in stdlib Python on Chi-square design-weight penalty and softmax mass parametrization for calibration; ACS local ESS frontier #1078's committed receipts and matched every share-0.5 number in the table, including the below-15 column. Record: ~/reviews/mc1088-review-2026-11-11/runs/r2/reviewer.md.
  • Merge of main (f61f64e98, 2026-11-11): one conflict hunk in do_finalize. It keeps main's consumer_export=consumer_export argument to finalize_reviewed_limitations and the gate call before it.
  • Blocking ruling: still open (d797, parked). This PR lands report-only.

axiom: n/a: ACS local release tool gate; no policy rules change.

🤖 Generated with Claude Code

…efault

Finalize reads the calibration summary's Kish ESS by congressional district at
the design and the calibrated weights (weight_origin), counts districts whose
calibrated ESS is below 0.25 x their design ESS and below an absolute floor of
15, and records the result as the district_ess_collapse gate (gate_summary.json
and the build manifest) and the district_effective_sample_size_gate reviewed
limitation. Whether it blocks releases awaits review, so blocking is the
explicit --district-ess-gate-blocking flag, off by default; under it finalize
fails a collapse or missing evidence, and package refuses a report finalize did
not block on at the same floors. The default solve is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 4 commits November 11, 2026 00:11
…e-gate

# Conflicts:
#	tools/build_us_acs_local_release.py
- The release refresh recipe now names the district ESS gate's flags when
  the recorded gate differs from the defaults (non-default floors, or
  blocking), so a refresh rebuilds the recorded gate.
- _district_ess treats a JSON integer too large for a float as malformed
  evidence instead of raising OverflowError.
- Tests pin the below-floor order (smallest first, ties by district) and
  the tie-break for the smallest ESS and ratio; the property strategy
  draws factors of 0 or at least 1e-6, keeping subnormal squares out of
  the differential test's tie band.
- The docs list district_ess_collapse among gate_summary.json's entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now reads consumer_reviewed_null_fills.json as {columns_filled, fills}
for the acs_local_reviewed_fill_consumers gate, so the finalize -> package
test writes that shape, as main's own round-trip test does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- One example where ratio order and calibrated-ESS order differ, so the
  collapsed and below-floor sort keys can't be swapped unnoticed.
- The parse-time refusal test checks the flag's own error message, not
  argparse's usage line.
- The module docstring states the rescaling property at floors up to 0.99,
  as the test draws them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis merged commit 942e7ec into main Nov 11, 2026
11 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Merged at head 8d8b75036f288338de05d9c8a35ffdd58254838a (merge commit 942e7ecd5).

  • CI: gh pr checks exited 0 on this head. lint, wheels, select-countries, engine-free (3.13), engine-free (3.14), installed-emitter, engine-us (3.13) and engine-us (3.14) all pass; the UK jobs skip. The d2704 ruleset's six required checks are green, and it does not require an up-to-date branch.
  • Reviews:
    • r1: Opus 5.5 via Subfleet (20261002-074454-mc1088-review-1) at 29921fb2. APPROVE.
    • r2: in-session Opus 5.5 at 5cee50ab1. REQUEST_CHANGES on one blocker, a pre-Gate reviewed default fills on their means-tested consumer notes (#1022) #1071 null-fill fixture that CI also caught. Fixed in 7fc818de3.
    • r2b: delta review at 8d8b75036. APPROVE, all findings closed.
    • Record: ~/reviews/mc1088-review-2026-11-11/.
  • Main since the review: main moved ecf87ee24 → 2cfe3043d. None of those commits touch the tool, its tests or _check_local_area_gates; the contract.py changes are UK-only.
  • Blocking: the gate lands report-only. Whether it blocks a release stays with d797 (parked); --district-ess-gate-blocking turns it on.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant