Repository navigation
Accept a restated age-band Ledger filter only where it agrees with the compiled bounds - #986
Merged
Merged
Conversation
…e compiled bounds Add "age" to RESTATED_LEDGER_FILTER_CONCEPTS under a third rule, age_band. ledger_filter_age_lower_bound / _upper_bound (stamped by ledger_targets._constraint_bound_filters from a fact's age >= / age < rows) are accepted only when the edge parses to the same number as the compiled age_lower_bound / age_upper_bound, and only on a spec whose materializer applies the band (population_age, or the age-banded policyengine_variable branch the SSA SSI by-age counts use). Refused, naming the values: a disagreeing edge, a missing compiled counterpart, an exact-age restatement, any other materializer, and a fact whose dimensions include age (the bound then came from the operator-less dimension stamp). >/<= rows keep their bare-key refusal (_lower_bound_exclusive / _upper_bound_inclusive), since fiscal_targets._age_bounds drops the operator when compiling the band. The AGI and age rules now share one half-open edge comparison, _restated_band_edge_refusal; the AGI rule's behaviour is unchanged. Tests: agreement accepted on both materializers, each disagreement refused with both values named, missing counterpart refused, operator-changed keys still refused by bare key, the real compile path (_dynamic_us_fiscal_target_references + compile_ledger_target_references) judged end to end, the population_age materialize test parametrized over no/agreeing/disagreeing restatements with an SSA age-band spec added, and an env-gated whole-registry test over the pinned feed. experiments/us-labelled-filter-support/measure_age_band_rule.py counts the whole-registry refusals under rule / without_age_band / reverted arms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d feeds age_band_rule_receipt.json (aggregate counts only), written by the committed measure_age_band_rule.py at b43aec6: over the whole compiled registry (32,866 targets) both pinned Chronicle exports go from 939 refused targets without the age-band rule to 0 with it; main's reverted behaviour refuses 3,208. REPORT.md section 9 records the mechanism read and the counts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…onstraint row
A ledger_filter_age_{lower,upper}_bound key keeps the operator only when
_constraint_bound_filters stamped it from a >= / < row; _ledger_metadata
stamps dimensions first and constraint edges through setdefault, so a
dimension named age, or age plus a bound suffix, can put an operator-less
value under the same key. The compile now records, on every age-banded
population_age and SSA SSI spec, age_bound_stamp_source: "constraint_rows"
unless such a dimension is present (ledger_targets.
constraint_bound_shadowing_dimensions), and the builder's restated age-band
rule accepts a restatement only with that attestation. age < 10 and
age <= 10 select different people, so an unattested bound is refused
rather than compared.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis
marked this pull request as ready for review
September 23, 2026 02:09
MaxGhenis
added a commit
that referenced
this pull request
Sep 23, 2026
No textual conflicts. build_us_fiscal_refresh_release.py (restated age-band ledger filters, #986) and test_us_spine_blindness.py (spm_universe_source.py, #977) auto-merged alongside this branch's spm_independence_role stage and module registrations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Sep 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #969 under Max's ruling (2), 2026-09-22: "add the age-band rule as a follow-up PR". This PR is stacked on
us-labelled-filter-support(#969, head01dc806f4), so the diff shows only this rule. GitHub retargets it tomainwhen #969 merges.Problem
On #969's head, the fatal guard
_assert_supported_ledger_filter_metadatastill refuses 939 of 32,866 compiled targets on the pinned Chronicle feed. Those are 936census_population/population_agetargets and 3ssa/ssa_ssi_age_band_recipientstargets. The refusals come fromledger_filter_age_lower_bound/ledger_filter_age_upper_bound, whichledger_targets._constraint_bound_filtersstamps from each fact's age constraint rows. As a result, any build that compiles age targets stops. REPORT.md section 7 describes this.What the materializer does (read this session at
01dc806f4)_population_age_household_values(tools/build_us_fiscal_refresh_release.py:4461-4471) and the age-bandedpolicyengine_variablebranch (:4958-4995, used by the SSA SSI by-age counts) both applylower <= age < upper. They readage_lower_bound/age_upper_boundthrough_as_bound.The compiled bounds come from
us_runtime/fiscal_targets.py::_age_bounds(:3199). It takes the value of any>/>=row as the lower edge and any</<=row as the upper edge, so it drops the operator. The SSA path rejects any operator other than>=/<at compile time.population_agehas no such check.ledger_targets.py::_constraint_bound_filters(:3473-3510) keeps the operator in the stamped key:>=becomesage_lower_bound<becomesage_upper_bound>becomesage_lower_bound_exclusive<=becomesage_upper_bound_inclusiveIt stamps nothing for a variable that is already a dimension.
The rule (
age_band, a third entry inRESTATED_LEDGER_FILTER_CONCEPTS)A restated
ledger_filter_age_{lower,upper}_boundis accepted only when both of these hold:age_{lower,upper}_boundon its own side.population_ageorpolicyengine_variable(RESTATED_AGE_BAND_MATERIALIZERS).The rule is refused, and the refusal names the values, when any of these is true:
ledger_filter_age_upper_bound=9 disagrees with age_upper_bound=10ledger_filter_age=<v>), which the materializer has no filter forirs_soislice or a spec with no materializerledger_filter_ageat all, soageis a dimension. In that case none of the age rows were stamped, and the bound came from the dimension stamp, which carries no operator. This is the same ambiguity that keeps restated qualifying-child upper bounds refused.The
_exclusive/_inclusivekeys are not restated concepts. They keep the bare-key refusal even when their value matches. They must: anage <= 9row compiles toage_upper_bound=9, and the materializer reads that asage < 9.Like the existing rules, this one is family-blind: it reads only metadata. The AGI and age rules now share one half-open edge comparison,
_restated_band_edge_refusal. The AGI rule's behaviour does not change; its tests are untouched and pass.Downstream listings. The two places that list unsupported
ledger_filter_*keys,_unsupported_ledger_filter_metadata(the fatal guard) and_unsupported_soi_ledger_filters(the SOI loop's silent skip), both go through_restated_ledger_filter_refusal. So an accepted key is dropped from neither, and a refused key is listed by both. I greppedpackages/*/srcandtools/and found no other US listing.target_materialization.pyis UK-only.Measured
experiments/us-labelled-filter-support/age_band_rule_receipt.json(aggregate counts only) was written by the committedmeasure_age_band_rule.pyatb43aec644, with a clean tree apart from the receipt. It covers the whole compiled registry,compile_us_fiscal_target_registry(..., age_targets=True), on 32,866 targets:ageremoved from the concepts)main(rule reverted)consumer_facts_us_c5e5bf8.jsonl(b8543739…) andchronicle_us_b571381/artifact/consumer_facts.jsonl(4d1dba8c…).population_age(936) orpolicyengine_variable(3)._lower_bound(939) and_upper_bound(886).role: filter,>=or<, with an integer value, at most one per side. No fact has an age-like dimension.Tests
packages/microcosm-build/tests/test_us_fiscal_refresh_builder.py:test_restated_age_bounds_pass_the_guard_only_where_they_agreecovers both materializers, each side alone, an open top, and float spellings. Neither listing names these keys. As a non-vacuity check, removing the concept brings back the bare-key refusal on every spec.test_disagreeing_restated_age_bounds_are_refused_by_valuecovers each disagreement with both values named, a missing counterpart, an exact age, an age dimension, the SOI and absent materializers, and an unknown-key control.test_operator_changed_age_bounds_stay_refused_by_bare_keyis the negative control:_inclusive/_exclusivekeys are still refused when their values match.test_restated_age_bounds_are_judged_on_the_real_compileruns real facts through_dynamic_us_fiscal_target_references+compile_ledger_target_references.test_population_age_targets_materialize_person_age_countsis now parametrized over none, agreeing and disagreeing restatements, and gains two SSA age-bandpolicyengine_variablespecs. With agreeing restatements the materialized columns are identical and no target is dropped. With disagreeing ones the build stops at the guard.test_pinned_chronicle_feed_whole_registry_compiles_no_unsupported_filtersis gated onMICROCOSM_US_CHRONICLE_FACTS.Local runs are listed in the PR thread's final report. Note that
.github/workflows/test.ymlruns only on PRs intomain, so CI will not run here until this PR is retargeted.Operator attestation at compile (added 2026-09-22,
622486abb)A
ledger_filter_age_{lower,upper}_boundkey carries the mask's operator only when_constraint_bound_filtersstamped it from a>=/<row;_ledger_metadatastamps dimensions first and constraint edges throughsetdefault, so a dimension namedage, orageplus a bound suffix, can write an operator-less value under the same key. The compile now recordsage_bound_stamp_sourceon every compiledpopulation_ageand SSA SSI age-band spec (constraint_rows, ordimensions:<names>whenledger_targets.constraint_bound_shadowing_dimensionsfinds a shadowing dimension), and the rule accepts a restatement only withconstraint_rows. Re-measured on both pinned feeds at the committed tree (age_band_rule_receipt.json, clean worktree): all 939 targets readconstraint_rows, so therulearm stays at 0 refused. Tests:test_ledger_targets.py,test_us_fiscal_targets.pyandtest_us_fiscal_refresh_builder.py(pinned feed set, no skips) — 794 passed; ruff andci_test_groups.py --verifyclean.Not changed / follow-ups
population_agecompile still drops the operator in_age_boundsand does not reject<=/>rows the way the SSA path does. This guard catches those rows through the_inclusive/_exclusivekeys, and the operator-attestation layer below now also refuses the case whereageis a dimension whose value isNoneor""(the compile'sage_bound_stamp_sourcechecks dimension keys whatever their value). A compile-side operator check forpopulation_agewould still be the cleaner fix.🤖 Generated with Claude Code