Skip to content

Encode the 2030 expiry of California's top income tax rates; add Prop 3 (2026) reform - #9601

Open
MaxGhenis wants to merge 9 commits into
mainfrom
ca-prop3-2030-expiry
Open

MaxGhenis wants to merge 9 commits into
mainfrom
ca-prop3-2030-expiry

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

What changes

California's rates above 9.3% expire after tax year 2030. All five filing-status schedules revert to 9.3% on January 1, 2031; Proposition 3's contributed reform extends the pre-sunset rates. The legal boundary is established by Prop 55 §4, page 16; Prop 3 removes that boundary in §4, page 6 and page 7. RTC §17045 supplies the joint and surviving-spouse treatment.

This round replaces hard-coded rates and bracket positions with the enacted schedules, snapshots rates before applying updates, respects dated app activation, and preserves future rate overrides that differ from the enacted baseline. It also adds coverage for every filing status, AMT, total tax including MHST, and both construction paths. The baseline retains the sunset; thresholds and their existing CA CPI forecasting assumptions remain in place.

DTrim99's review

The changes-requested review and program review remain open for re-review. Each requested item is addressed as follows:

Review item Change and coverage
Critical 1: HOH and shared citations HOH cites §36(f)(3); the shared toggle, reform comment and sunset fragment cover §36(f)(2)–(3).
Critical 2: literal rates and bracket indexes Determine sunset-affected brackets from the unreformed schedule and copy their user-adjusted 2030 rates. Regression covers a .104 override and bounded lower-rate edits.
Should 1: activation misses 2029+ and bounded toggle leaks Inspect dated in_effect.values_list; apply only true intervals intersecting 2031–2100. Reform.from_dict tests cover a 2031 start, 2026-only activation, delayed activation and true/false/true intervals.
Should 2: operative references Toggle cites Prop 3 §4 pages 6–7 and LAO Figure 1, page 2.
Should 3: joint/QSS statutory chain Both schedules cite RTC §17045; direct rate and scaling properties cover their relationship to single.
Should 4: missing statuses Add 2031 baseline/reform HOH, MFS and QSS cases, plus HOH 2030.
Should 5: feature changelog Retain .fixed.md for the sunset and add .added.md for the public reform and toggle.
Should 6: AMT Assert regular tax, max(.07 × reduced AMTI − regular tax, 0), and total tax with a $1.4M AMTI / $1M taxable-income case.
Suggestion 1: CPI-independent assertions and arithmetic Assert all statuses' rates at 2030, 2031 and 2035, unchanged thresholds/lower rates, and joint scaling; YAML comments state the hand derivations and forecast factors.
Suggestion 2: individual surcharge brackets Add $450,000 and $600,000 cases. The latter rounds to $54,432.81, correcting the review's $54,432.80.
Suggestion 3: meaningful MHST coverage Replace the isolated MHST assertion with regular and total tax stacking: $191,942.90 baseline versus $234,169.10 with Prop 3 at $2M.
Suggestion 4: test layout Move YAML to contrib/states/ca/prop3/ca_prop3.yaml; explain bypass and baseline controls; remove redundant toggle inputs.
Suggestion 5: registration/import style Group imports, factory creation and registration with the other CA reform; export create_ca_prop3; remove the unused import.
Suggestion 6: durable citations Add dated Prop 55 references to all five schedules and identify the applicable §17041 subsections; remove stale federal-indexing comments.
Suggestion 7: election follow-up Schedule comments track the certified November 2026 result and removal of sunset entries if approved. Follow-up will also retire the contributed reform once the baseline reflects enactment.

The additional independent review identified later-expiry intervals, bounded lower-rate edits, future overrides and endpoint coverage. The recovered implementation addresses those with interval splitting, enacted-schedule comparison, idempotence checks on both construction paths, and transition/adjacent-day probes. Implementing statutory nearest-dollar threshold rounding remains outside this PR.

Methodology choice for Max

The app toggle honors every dated true interval within 2031-01-01 through 2100-12-31. A 2026-only override has no out-year effect; delayed activation begins on its effective date; false gaps retain the sunset. The explicit ca_prop3 bypass used by YAML reforms: applies unconditionally within the model horizon.

Only brackets changed by the enacted sunset are extended. Their user-adjusted 2030 rates are copied where the future rate still equals the enacted baseline. Future overrides with a different rate survive, including on repeated application and both system-construction paths. A future user edit numerically equal to the enacted baseline cannot be distinguished from that baseline and is restored by Prop 3.

Validation

Current head: a72bca9, after a clean merge of main dd9cb3f. Both new commits were pushed immediately. make format passes; independent static review approves the recovered model changes and the timing-check adjustment.

Current single-file checks, run sequentially at nice 0:

Check Passed Pytest elapsed Wall including startup Peak RSS
Baseline regular tax 9 43.79s 491.36s 2.19 GB
Prop 3 YAML 11 106.25s 412.09s 3.95 GB
Baseline AMT 6 17.95s 372.46s 2.21 GB
Baseline total tax/MHST 4 18.10s 248.16s 2.18 GB
Window property, 300 examples 1 170.09s 648.59s Unavailable

30 YAML cases and the remaining Python property passed. Reused the 14 completed Python passes from the recovered checkpoint rather than rerunning them. The window property's macOS timing wrapper subsequently hit a sandbox-denied sysctl; pytest passed. Subsequent measurements used a timer that preserves the test exit code. Final-head partner CI passes; full CI is still running with no failures observed.

Recovered evidence is retained without claiming that older runs validate the final head:

  • Original head ea80e9d0ad: evidence-only compatible copy of the new activation/rate tests recorded 6 failed, 6 passed, 1 skipped in 403.56s. The failures expose the dated activation, bounded timing and rate-copy bugs; the helper property skipped because that head had no helper.
  • Recovered fix 279f541dd1: 14 passed, 1 failed in 1028.44s. The only failure was Hypothesis's input-generation health check on the loaded host, after 158.40s; model assertions passed.
  • Prior PR head ef290736bf: all 36 CI checks passed, including partner checks. The original human review separately recorded 11 targeted YAML cases passing at ea80e9d0ad.

The affected CI group is Contrib (other-shard-1) (make test-policy-contrib-python). Local macOS cost at 279f541dd1, including startup: 1205.73s wall, 4,060,102,656 bytes maximum RSS, 3,924,561,136 bytes peak footprint. The old-head evidence run took 565.10s wall, 3,921,494,016 bytes maximum RSS; different test contents and host contention prevent treating this as a controlled before/after delta. The Contrib CI log is now recovered: the Linux Python stage passed 72 tests in 1008.88s; the CA file occupied about 224s, inferred from log timestamps within that shared process. The entire job took 45m24s. Python-stage/file peak memory is not reported. These are existing CI measurements, not a controlled before/after comparison. The model-building tests protect activation, override replay and both construction paths; lightweight properties cover dates and interval restoration without constructing a model per example.

The PR diff against merged main changes zero partner test files or expected outputs. The current tree contains 144 YAML files, with periods 2024, 2025, 2026 and 2026-01, all before 2031. git ls-tree counts 143 at the reviewed head and 144 on merged main; the supplied spec's 145 count is not reproduced.

Impact and remaining follow-up

Population microsimulation (hub runner): merge base dd9cb3f683 against this head a72bca942f, on populace_us_2024.h5@populace-us-2024-spm-20260909. It ran under the shared heavy lock, one run per side, compared record by record.

Year CA income tax State income tax Household net income Tax units changed
2026 $0 $0 $0 0 (identical on every record)
2031 −$8.50bn (base $170.66bn) −$8.50bn +$8.50bn 254 records, 569k weighted

2026 is unchanged because the enacted top rates still apply. 2031 is the first year after they expire, and the drop is the scheduled sunset that the baseline now encodes: the higher rates stop applying, so the affected high-income units pay less. The Prop 3 (2026) reform is opt-in (gov.contrib.states.ca.prop3.in_effect), so it is not in the baseline.

The hub should compare main, corrected baseline and Prop 3 enabled for 2025, 2026, 2030, 2031 and 2035, using identical cached data, entity order and weights. Calculate ca_income_tax_before_credits, ca_amt, ca_mental_health_services_tax, ca_income_tax_before_refundable_credits, ca_income_tax, ca_withheld_income_tax, income_tax and household_net_income; exclude enum arrays from numerical comparison. Expect zero direct effects before 2031, a regular-tax reduction after the sunset, possible AMT offsets, unchanged MHST, and reversal of the regular-tax reduction when Prop 3 is enabled.

The existing forecast gives these hand-computed 2031 examples; they are household test expectations, not population estimates:

Filing status / taxable income Corrected regular tax Prop 3 regular tax Difference
Single or MFS / $1M $88,942.90 $101,169.10 $12,226.20
Joint or QSS / $2M $177,885.81 $202,338.20 $24,452.40
HOH / $1.5M $133,132.32 $153,959.95 $20,827.63

Differences are rounded from unrounded hand calculations; displayed taxes are rounded independently.

The $1.4M AMTI example has $98,000 tentative minimum tax: baseline AMT is $9,057.10, while Prop 3 AMT is zero. Dollar expectations still use gov.states.ca.cpi; #9621 remains OPEN and its IRS-uprating change requires reconciliation when it lands. Rate/date properties do not depend on forecasts. Check the certified election result after November 3, 2026.

axiom: CA Constitution XIII §36(f)(2)-(3) TheAxiomFoundation/rulespec-us#1565 queued | CA Prop 3 (2026) §4 TheAxiomFoundation/rulespec-us#1566 queued

MaxGhenis and others added 2 commits September 24, 2026 15:56
… 3 (2026) reform

Under Cal. Const. art. XIII, section 36(f)(2), the 10.3%, 11.3% and 12.3%
rates added by Proposition 30 and extended by Proposition 55 apply to taxable
years before January 1, 2031. The baseline carried them indefinitely. From
2031 the three brackets now revert to 9.3%, matching the Legislative
Analyst's Figure 1 in the 2026 voter guide.

Proposition 3 on the November 3, 2026 ballot would make those rates
permanent. gov.contrib.states.ca.prop3.in_effect switches on a
parameter-only reform that keeps them from 2031 onward. The 1% Mental
Health Services Tax is unaffected.

Tests: 3 baseline boundary cases (2030 vs 2031) and 6 reform cases,
expected values computed by hand from the 2031 thresholds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis requested a review from DTrim99 September 24, 2026 19:56
@DTrim99

DTrim99 commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Program Review

PR #9601: Encode the 2030 expiry of California's top income tax rates; add Prop 3 (2026) reform
Head reviewed: ea80e9d0ad577232990679162a1374489a6f4acc

The law is encoded correctly. All five filing-status schedules keep 10.3% / 11.3% / 12.3% through tax year 2030 and fall back to 9.3% from 2031-01-01. The Prop 3 reform restores exactly those three rates, on the right brackets, for all five statuses from 2031. Neither PDF audit found a value or date mismatch. The issues below are two citation and hard-coding problems that must be fixed, a reform activation window that cannot reach Prop 3's operative year, and test coverage for the three filing statuses that aren't tested yet.

Source Documents

Year(s): TY2025 thresholds (FTB); the TY2030 / TY2031 sunset boundary; Prop 3 reform 2031-01-01 to 2100-12-31.
Scope: PR changes only.


Critical (Must Fix)

1. The head-of-household rate file cites the wrong constitutional paragraph: § 36(f)(2) instead of § 36(f)(3)

  • Where: policyengine_us/parameters/gov/states/ca/tax/income/rates/head_of_household.yaml:116. This is the only new reference offered for the 2031 values at lines 73, 85 and 97.
  • Problem:
  • The values are correct. (f)(3) has the same 2031 cutoff, so only the citation is wrong. Four validators flagged this. It is classified critical because the reference title cites the wrong subsection.
  • Fix:
    - title: Cal. Const. art. XIII, § 36(f)(3) (rates above 9.3% apply to taxable years before January 1, 2031)
      href: https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=CONS&sectionNum=SEC.%2036.&article=XIII
  • Same root cause, fix in the same pass. These three places cover heads of household but cite only (f)(2). They are incomplete rather than wrong, so change each to "§ 36(f)(2)-(3)":
    • policyengine_us/parameters/gov/contrib/states/ca/prop3/in_effect.yaml:11
    • the comment at policyengine_us/reforms/states/ca/prop3/ca_prop3_reform.py:5-7
    • changelog.d/ca-prop3-2030-expiry.fixed.md:1

2. The reform hard-codes the restored rates and bracket indexes

  • Where:
    • policyengine_us/reforms/states/ca/prop3/ca_prop3_reform.py:8: TOP_BRACKET_RATES = {6: 0.103, 7: 0.113, 8: 0.123}
    • applied at :23-28
  • Why it is critical:
    • The literals are correct. They equal § 36(f)(2)(A)(i)-(iii) and (f)(3)(A)(i)-(iii), which Prop 3 leaves unamended (https://vig.cdn.sos.ca.gov/2026/general/pdf/prop3-text-proposed-laws.pdf#page=6 and #page=7). So this is not a value error.
    • It is classified critical because the repo standard for contrib reforms is "every amount from a parameter", and these are policy values with no reference anywhere in the repo.
    • They duplicate the baseline rate files. A later correction to those rates, or a user's app edit to them, would not carry into 2031+ under Prop 3.
    • The indexes 6/7/8 are tied to the current 9-bracket layout. Inserting a bracket would silently change the wrong ones, and the tests would only catch that for SINGLE and JOINT.
    • The fix is a few lines.
    • The validators split on severity: code said critical, references said should-address, regulatory said suggestion. There is one weak precedent for literals in modify_parameters: afa_other_dependent_credit.py:226 (value=18).
  • Fix: Prop 3 keeps the rates that apply before the sunset. So copy each bracket's 2030 rate forward and drop both the literals and the indexes. Reading the baseline at an earlier instant inside modify_parameters has precedent: reforms/states/ut/ut_hb210_s2.py:79 and reforms/states/ct/hb5114/ct_hb5114.py:65.
    def modify_parameters(parameters):
        rates = parameters.gov.states.ca.tax.income.rates
        for status in FILING_STATUSES:
            for bracket in getattr(rates, status).brackets:
                pre_sunset_rate = bracket.rate(instant("2030-01-01"))
                if bracket.rate(instant("2031-01-01")) != pre_sunset_rate:
                    bracket.rate.update(
                        start=instant("2031-01-01"),
                        stop=instant("2100-12-31"),
                        value=pre_sunset_rate,
                    )
        return parameters
    Then delete TOP_BRACKET_RATES.
  • Alternative: move the three rates into gov.contrib.states.ca.prop3.* parameters that carry the measure-text references from Should Address item 2. This is heavier, and only worth it if the extended rates should be adjustable in the app.

Should Address

1. The activation scan cannot see Prop 3's operative year, so a toggle that starts in 2029 or later silently does nothing

  • Where: policyengine_us/reforms/states/ca/prop3/ca_prop3_reform.py:44-51, the for i in range(5) lookahead.

  • Problem:

  • Measured via Reform.from_dict, single filer, ca_taxable_income $1M, TY2031:

    in_effect window 2031 ca_income_tax_before_credits
    2026-01-01.2100-12-31 101,169.10 (applied)
    2028-01-01.2100-12-31 101,169.10 (applied)
    2029-01-01.2100-12-31 88,942.90 (silently baseline)
    2031-01-01.2100-12-31 88,942.90 (silently baseline)
    2026-01-01.2026-12-31 only (false in 2031) 101,169.10 (applied anyway)
  • Answer to the PR description's question about the start date: today, an in_effect start date anywhere in 2024-2028 (including 2026) activates the reform, and any start date from 2029 on does not. The last row shows the reverse problem: once loaded, the reform restores 2031-2100 whatever in_effect says in those years.

  • Why this isn't critical: the YAML tests can't catch it, because they use the bypass=True instance (:59), which ignores in_effect. The default app path, starting in the current year, works today.

  • Fix:

    • (a) Detect activation from any dated true value in in_effect.values_list instead of a 5-year scan. Follow _first_true_instant in policyengine_us/reforms/states/id/ctc/id_ctc_reform.py:9-24.
    • (b) Optionally, start the restoration at max(2031-01-01, first true instant) so the toggle's timing is respected (the same file, :60-75).
    • (c) Add a pytest in the style of policyengine_us/tests/policy/contrib/states/test_id_ga_ctc_reform_activation.py:
      • Build Reform.from_dict({"gov.contrib.states.ca.prop3.in_effect": {"2031-01-01.2100-12-31": True}}, country_id="us").
      • Assert 101,169.10 for the single $1M TY2031 case.
      • Assert 88,942.90 with the toggle off.

2. in_effect.yaml does not cite the measure's operative text, and its Figure 1 link has no page anchor

  • Where: policyengine_us/parameters/gov/contrib/states/ca/prop3/in_effect.yaml:8-12
  • Problem:
    • The references are the LAO analysis, an official but secondary summary, and the current (pre-Prop 3) § 36(f)(2). The reform implements SEC. 4 of the Text of Proposed Laws, which isn't cited.
    • The LAO link opens on file page 1. Figure 1 is on file page 2 (printed p. 27).
    • The HOH extension comes from (f)(3), which isn't cited (see Critical item 1).
  • Why this isn't critical: the existing references do corroborate the policy (Figure 1's "Approved" column shows 10.3 / 11.3 / 12.3 after 2030), and the parameter's value is a default-off toggle that doesn't come from a source.
  • Fix: replace the reference block with:
    reference:
      - title: California Proposition 3 (2026), Official Voter Information Guide, Legislative Analyst's analysis, Figure 1
        href: https://vig.cdn.sos.ca.gov/2026/general/pdf/prop3.pdf#page=2
      - title: California Proposition 3 (2026), Text of Proposed Law, SEC. 4, amending Cal. Const. art. XIII, § 36(f)(2) (strikes "and before January 1, 2031,")
        href: https://vig.cdn.sos.ca.gov/2026/general/pdf/prop3-text-proposed-laws.pdf#page=6
      - title: California Proposition 3 (2026), Text of Proposed Law, SEC. 4, amending Cal. Const. art. XIII, § 36(f)(2)(D), (f)(3) and (f)(3)(D)
        href: https://vig.cdn.sos.ca.gov/2026/general/pdf/prop3-text-proposed-laws.pdf#page=7
      - title: Cal. Const. art. XIII, § 36(f)(2)-(3)
        href: https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?lawCode=CONS&sectionNum=SEC.%2036.&article=XIII

3. The joint and surviving-spouse files don't cite RTC § 17045, so the chain from § 36(f)(2) to their 2031 values is broken

  • Where: policyengine_us/parameters/gov/states/ca/tax/income/rates/joint.yaml:116 and .../rates/surviving_spouse.yaml:116
  • Problem:
    • § 36(f)(2) modifies only the § 17041(a)(1) schedule. Joint and QSS returns reach those rates only through § 17045: "twice the tax ... if the taxable income were cut in one-half", and a surviving spouse return "shall be treated as a joint return".
    • For 2021-2025, the FTB Schedule Y references backed the joint rates directly. No FTB schedule exists for 2031.
    • The (f)(2) citation is right; the chain is just incomplete.
  • Fix: add to both files, after line 117:
    - title: Cal. Rev. & Tax. Code § 17045 (joint and surviving spouse tax is twice the tax on one-half of taxable income)
      href: https://leginfo.legislature.ca.gov/faces/codes_displaySection.xhtml?sectionNum=17045.&lawCode=RTC

4. HEAD_OF_HOUSEHOLD, SEPARATE and SURVIVING_SPOUSE are never tested in 2031, under the baseline or the reform

  • Where:

    • policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/ca_income_tax_before_credits.yaml:25-55: SINGLE (2030, 2031) and JOINT (2031) only.
    • policyengine_us/tests/policy/contrib/states/ca/prop3.yaml:4-75: SINGLE and JOINT only.
  • Problem:

    • 9 of the 15 new 2031-01-01: 0.093 entries are never exercised: lines 73, 85 and 97 of separate.yaml, surviving_spouse.yaml and head_of_household.yaml.
    • Three of the five statuses in the reform's FILING_STATUSES loop (ca_prop3_reform.py:9-15) are never exercised either.
    • HOH matters most. It is governed by the separate paragraph (f)(3), its thresholds are not a multiple of single's, and a HOH filer at $1M sits in the 11.3% bracket rather than 12.3%.
    • Sources: FTB 2025 Schedule Z, https://www.ftb.ca.gov/forms/2025/2025-540-tax-rate-schedules.pdf#page=1; Const. § 36(f)(3).
  • Fix: append these cases (period: 2031, state_code: CA, absolute_error_margin: 1). Baseline cases go to the end of ca_income_tax_before_credits.yaml; reform cases go to the end of prop3.yaml, with the reforms: key.

    Filing status ca_taxable_income Baseline 2031 Prop 3 2031
    HEAD_OF_HOUSEHOLD 1,500,000 133,132.32 153,959.95
    SEPARATE 1,000,000 88,942.90 101,169.10
    SURVIVING_SPOUSE 2,000,000 177,885.81 202,338.20
    • Add a 2030 HOH baseline case at $1,500,000 = 154,557.25, so the HOH boundary is covered on both sides.
    • Two validators computed these values independently from the snapshot's uprated thresholds and they agree to the cent. Confirm them by running.
    • The 2031 HOH thresholds are 10.3% at 575,531.95, 11.3% at 690,639.94 and 12.3% at 1,151,065.04.

5. The changelog type covers the baseline fix but not the new contrib reform

  • Where: changelog.d/ca-prop3-2030-expiry.fixed.md:1
  • Problem: the PR also adds a public reform module (policyengine_us.reforms.states.ca.prop3) and a new gov.contrib.states.ca.prop3.in_effect parameter. Contrib-reform PRs use added (e.g. ca-ab2591.added.md). With fixed alone, towncrier makes a patch bump.
  • Fix: keep the .fixed.md fragment for the 2031 expiry, citing § 36(f)(2)-(3). Add changelog.d/ca-prop3-2030-expiry.added.md for the Prop 3 contributed reform. Same-stem multi-type fragments have precedent: ny-ccdf.added.md + ny-ccdf.fixed.md.

6. No test covers the new AMT interaction

  • Where: all new cases assert only ca_income_tax_before_credits or ca_mental_health_services_tax.
  • Problem:
    • ca_amt is max(TMT - ca_income_tax_before_credits, 0) (policyengine_us/variables/gov/states/ca/tax/income/alternative_minimum_tax/ca_amt.py:21-25). The 2031 drop in regular tax makes AMT bind for more high-preference filers, which offsets part of the sunset and part of Prop 3's effect.
    • That behavior is legally correct: RTC § 17062 compares against the § 17041 tax, and § 36(f)(2)(C)(ii) deems the higher rates part of § 17041. But it is new in 2031, and nothing tests it.
  • Fix: add these cases:
    • Baseline: in policyengine_us/tests/policy/baseline/gov/states/ca/tax/income/alternative_minimum_tax/ca_amt.yaml, period 2031, state_code: CA, filing_status: SINGLE, ca_taxable_income: 1_000_000, ca_amti: 1_400_000, ca_amt_exemption: 0. Expected ca_amt = 98,000 - 88,942.90 = 9,057.10.
    • Reform: the same case in prop3.yaml. Expected ca_amt = 0, because regular tax is 101,169.10.

Suggestions

  1. Add assertions that don't depend on the CPI projections.
    • All 9 new dollar expectations depend on CBO C-CPI-U projections (gov/bls/cpi/c_cpi_u.yaml:327-329). A 0.1-point revision moves them by about $1 to $5.50, which is more than the margin of 1.
    • Add a small pytest that asserts brackets 6-8 of all 5 schedules directly:
      • 0.103 / 0.113 / 0.123 at 2030-01-01.
      • 0.093 at 2031-01-01 and 2035-01-01 in the baseline.
      • 0.103 / 0.113 / 0.123 at 2031-01-01 and 2035-01-01 with ca_prop3 applied.
    • That pytest also shows the change is permanent after 2031.
    • The PR description says the arithmetic is in the comments, but only prop3.yaml:14 and ca_income_tax_before_credits.yaml:44 have it. Add one-line derivations to the other cases, noting the factors f2030 = 193.00 / 172.797 and f2031 = 196.85 / 172.797.
  2. Add incomes inside a single surcharge bracket, for diagnosis. 2031 SINGLE cases:
    • $450,000 (10.3% bracket only): baseline 37,792.90, Prop 3 38,061.02.
    • $600,000 (11.3% bracket only): baseline 51,742.90, Prop 3 54,432.80.
    • The $2M single case (prop3.yaml:17-27) only adds the 12.3% slope, which the $1M case already covers. It could be repurposed for one of these incomes.
  3. Make the MHST case able to fail.
    • prop3.yaml:65-75 asserts ca_mental_health_services_tax. That variable reads neither the rate schedules nor the reform, so the case can't fail because of this PR.
    • Assert ca_income_tax_before_refundable_credits instead, with 2031, SINGLE, ca_taxable_income: 2_000_000, ca_non_refundable_credits: 0 and ca_amt: 0. Expected: Prop 3 234,169.10, baseline 191,942.90. This shows MHST stacking on the extended rates.
  4. Test layout and clarity.
    • Move policyengine_us/tests/policy/contrib/states/ca/prop3.yaml to .../contrib/states/ca/prop3/ca_prop3.yaml to match the sibling ca/ab2591/ca_ab2591.yaml.
    • Add a header comment saying:
      • the YAML reforms: key applies the bypass=True instance, so the in_effect: true input is redundant;
      • the baseline cases at ca_income_tax_before_credits.yaml:36-55 are the reform-off controls.
  5. Import and registration style.
    • policyengine_us/reforms/states/ca/__init__.py:2-4: use from .prop3 import create_ca_prop3_reform, to match line 1.
    • policyengine_us/reforms/states/ca/prop3/__init__.py:1: also export create_ca_prop3, as ab2591/__init__.py does.
    • policyengine_us/reforms/reforms.py: move the import at :146 (currently in the RI block) next to the CA import at :291-293. Move the instantiation at :495 next to :566, and the list entry at :662 next to :717.
    • ca_prop3_reform.py:47: for _ in range(5). This is moot if Should Address item 1 replaces the scan.
  6. Harden the rate-file references against Prop 3 passing.
    • The leginfo § 36 page always shows the current text. If Prop 3 passes, it will no longer show "before January 1, 2031".
    • Add a dated source to each of the five rate files: "Proposition 55 (2016), Text of Proposed Law, SEC. 4, amending Cal. Const. art. XIII, § 36(f)(2) and (f)(3)", https://vig.cdn.sos.ca.gov/2016/general/en/pdf/text-proposed-laws.pdf#page=16. In the HOH file, cite (f)(3) only.
    • The line 118 RTC § 17041 reference could also name the subsections: "(a)(1), (h)" for single, separate, joint and surviving spouse; "(c)(1), (h)" for HOH.
  7. Plan the follow-up for after the November 3, 2026 election.
    • If Prop 3 passes, the 2031-01-01: 0.093 entries (lines 73, 85 and 97 of all five rate files) have to be removed, and the contrib reform retired.
    • If it fails, the baseline is already correct.
    • A tracking issue or YAML comment would keep this from being missed.

Pre-existing (out of PR scope)

  • Uprated thresholds aren't rounded to the nearest $1, as RTC § 17041(h)(2)(B) requires. For example, the 2031 single 9.3% threshold is 82,847.04. The tax effect is under $1. If rounding is added, round single and derive joint as 2x, so that joint stays exactly twice single.
  • There is a stale comment at line 120 of all five rate files: "Index according to federal tax parameter rules until we add California CPI trend." The thresholds already uprate with gov.states.ca.cpi.
  • The FTB schedule references at lines 106-115 don't name the schedule (X, Y or Z) and have no #page=1 anchor.
  • ca_income_tax_before_credits.yaml:14 is named "Single filing, income in 3rd tax bracket", but it tests HEAD_OF_HOUSEHOLD at $1M, which is in the 12.3% bracket.
  • The 2021-2024 thresholds weren't re-checked, because those FTB PDFs weren't in scope. The 2026-2030 thresholds are CPI projections with no primary source.

Verified correct

  • Sunset date. 2031-01-01: 0.093 matches "for any taxable year beginning on or after January 1, 2012, and before January 1, 2031" in § 36(f)(2) and (f)(3).
    • So TY2030 is the last year of 10.3 / 11.3 / 12.3.
    • The (D) "inoperative on December 1, 2031" clauses are correctly not used as the switch date.
    • The title's "2030 expiry" and the changelog's "after tax year 2030" agree with Prop 3 SEC. 2(c) and the LAO analysis.
  • All five filing statuses change consistently. Each file sets 2031-01-01: 0.093 on bracket indexes 6, 7 and 8 (lines 73, 85, 97), which hold exactly 0.103 / 0.113 / 0.123. Brackets 0-5 are untouched.
    • The resolved 2031, 2032 and 2035 rates are [.01, .02, .04, .06, .08, .093, .093, .093, .093] for every status. This is the base § 17041(a)(1) / (c)(1) ladder.
    • The redundant 9.3% brackets produce $0.00 difference from a merged schedule. They are needed for the reform.
  • Thresholds.
    • The 2025 thresholds in all five files match FTB 2025 Schedules X, Y and Z.
    • Joint and QSS thresholds are exactly 2x single in every year 2021-2035. MFS equals single, and QSS equals joint (§ 17045).
    • CPI uprating of the thresholds is unaffected by the new rate entries.
  • Prop 3 encoding.
    • The measure only strikes "and before January 1, 2031," from (f)(2) and (f)(3), plus both (D) clauses. No rate or threshold changes; this was confirmed from the page images at prop3-text-proposed-laws.pdf#page=6 and #page=7.
    • The reform restores 10.3 / 11.3 / 12.3 for all five statuses from 2031-01-01. It leaves 2030 unchanged and reproduces LAO Figure 1's "Approved" and "Rejected" columns.
    • The 2100-12-31 stop date follows the repo's horizon convention.
  • Registration and wiring.
    • The reform is registered at reforms/reforms.py:146 (import), :495 (instantiate) and :662 (list).
    • The YAML reforms: path resolves through prop3/__init__.py:1, and all 6 contrib cases use it.
    • in_effect uses the 0000-01-01: false sentinel and has complete metadata.
    • No programs.yaml change is needed.
  • The Mental Health Services Tax is unaffected. RTC § 17043 has its own parameter, which neither the PR nor the reform touches.
  • Downstream consumers pick up the change automatically. The consumers are ca_income_tax_before_credits.py, ca_withheld_income_tax.py (which uses rates.single, so high-earner withholding also drops from 2031 and Prop 3 restores it) and ca_amt.py. Nothing else references these rates or hard-codes 0.103 / 0.113 / 0.123.
  • Test values. All 9 new expected values were checked by hand to the cent.
  • Conflicting measures. Prop 3 SEC. 5 (conflicting measures) has no effect here: the only other November 2026 tax measure, Prop 40 (a one-time billionaire wealth tax), doesn't change § 17041 rates.

PDF Audit Summary

Neither audit reported a value mismatch; only citation and reference items came up. No cross-reference or external-PDF requests were raised, and the PR adds no #page= anchors. So the code-path, 600-DPI, cross-reference / external-document and page-number re-verification steps were not triggered.

Category Count Detail
Confirmed correct 30 items (about 190 individual value checks) Baseline audit: 16 items (the sunset date, 2030 and 2031+ rates for all 5 statuses, brackets 0-5, FTB 2025 thresholds X/Y/Z, the 2x joint relation 2021-2035, uprating). Prop 3 audit: 14 items (the four SEC. 4 strikes, rate values, bracket positions, all statuses, LAO Figure 1 columns, the AG summary thresholds, the 2031 test expectations, MHST).
Mismatches (verified) 0 value / date 3 reference items: the HOH § 36(f)(3) citation (Critical item 1); the missing measure text, #page=2 anchor and (f)(3) in in_effect.yaml (Should Address item 2); the missing § 17045 in the joint and QSS files (Should Address item 3).
Mismatches rejected 1 The reform stops at 2100-12-31 rather than being open-ended. This is accepted as the repo's horizon convention and has no effect within any projection window.
Unmodeled items 5 Correctly out of scope for a household tax model: Prop 3's § 36(e) Education Protection Account timeline; revenue allocation (89% K-12 / 11% community colleges, Medi-Cal, audit and penalties); SEC. 5 conflicting measures; SEC. 1-3 and 6-7 (findings, intent, severability, standing); § 36(f)(1) sales tax (inoperative since 2017).
Pre-existing issues 5 Threshold rounding; the stale indexing comment; FTB references without #page=1; 2021-2024 thresholds not re-checked; 2026-2030 projected thresholds have no primary source.

Validation Summary

Check Result
Regulatory Accuracy Pass. The sunset and the Prop 3 reform are encoded correctly for all 5 filing statuses, with 0 value mismatches. The activation window has a gap (Should Address item 1).
Reference Quality Issues. The HOH file cites (f)(2) instead of (f)(3) (critical). The contrib toggle lacks the measure text and #page=2, and the joint and QSS files lack § 17045.
Code Patterns Issues. The reform hard-codes rates and bracket indexes (critical). The range(5) activation scan misses 2031. The changelog has only the fixed type.
Formatting Pass. Parameter description, label, unit and period follow conventions. There are minor import-order and style nits (Suggestion 5).
Test Coverage Issues. All 9 new values are correct, but HOH, MFS and QSS are untested in 2031, and neither the activation path nor the AMT interaction is tested.
PDF Value Audit Pass. 30 items confirmed, with 0 value mismatches.
Partner contract tests SAFE. None of the 143 partner YAML files under policyengine_us/tests/policy/baseline/partners/** has a period after 2026 (all are 2024, 2025, 2026 or 2026-01). That includes every file with CA tax (amplifi/2025.yaml, amplifi/2026.yaml, and the analytics_coverage CA signatures and edge cases), so none of their outputs can move from a change dated 2031-01-01. The diff touches no partner file, and no partner gate is needed. Of the 20 other YAML files with periods in 2031 or later, none depends on high-earner CA tax.
Local tests A targeted policyengine-core test run of contrib/states/ca/prop3.yaml and baseline/gov/states/ca/tax/income/ca_income_tax_before_credits.yaml at the PR head: 11 passed (6 + 5).
CI Status At review time: 13 pass, 22 pending, 0 failing. The pending shards include Contrib (states), Baseline (states) and Household API Partners. Pending is not a failure.

Out-year impact (informational): from 2031, baseline CA income tax revenue falls for every filer above the 10.3% threshold. The LAO puts the rates at $5 billion to $15 billion a year. Saved app policies that edit brackets 6-8 will show larger 2031+ impacts against the new baseline.

Review Severity: REQUEST_CHANGES

The two critical items are both citation and encoding-hygiene problems with small, mechanical fixes: the HOH citation points to the wrong constitutional paragraph, and the reform hard-codes its rates and bracket indexes. The law and every value are correct. Should Address item 1 is the most substantive functional issue: enabling Prop 3 from its own operative year does nothing, with no warning.

Next Steps

To auto-fix issues: /fix-pr 9601

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes per the program review above (#9601 (comment)). The law is encoded correctly: 2030 is the last year with the 10.3/11.3/12.3% rates ("before January 1, 2031"), all five filing statuses switch on 2031-01-01, there are no value mismatches, and no partner test runs past 2026.

Items to address:

  • Head-of-household is governed by Cal. Const. art. XIII §36(f)(3), not (f)(2). The same (f)(2)-only citation appears in in_effect.yaml, the reform comment and the changelog.
  • ca_prop3_reform.py hard-codes the rates and bracket indexes {6: 0.103, 7: 0.113, 8: 0.123}. The values match the measure, but copying each bracket's 2030 rate forward would be safer.
  • The range(5) activation scan only looks at 2024–2028, so a user who switches in_effect on for 2029 or later silently gets the baseline. A Reform.from_dict pytest would catch this.
  • Prop 3 measure-text anchors are missing, and RTC §17045 is missing on joint/surviving-spouse.
  • HOH, MFS and QSS have no 2031 cases, and nothing tests the 2031 AMT interaction.
  • The changelog needs an .added.md fragment for the reform.

MaxGhenis and others added 3 commits October 6, 2026 09:18
- Copy each bracket's 2030 rate forward instead of hard-coding the rates
  and bracket indexes, so user 2030 edits and schedule changes carry over.
- Read activation from the dated in_effect values instead of a five-year
  scan, and restore the rates only in the years from 2031 to 2100 in which
  the toggle is true; the bypass instance stays unconditional.
- Cite Cal. Const. art. XIII, § 36(f)(3) for head of household and
  § 36(f)(2)-(3) where both apply; add Prop 3 SEC. 4 and LAO page anchors,
  dated Prop 55 references, RTC § 17045 on joint and surviving spouse,
  and RTC § 17041 subsections; drop the stale indexing comment.
- Add 2031 cases for every filing status, a 2030 head-of-household case,
  AMT and MHST stacking cases, and cases inside the first two surcharge
  brackets; move the contrib YAML to ca/prop3/ and drop redundant toggles.
- Add pytest coverage of the app activation path, delayed and bounded
  toggles, 2030 user rates, and rate, threshold and RTC § 17045 properties.
- Group the CA registration, export create_ca_prop3, and add an .added
  changelog fragment for the reform.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis and others added 4 commits October 6, 2026 20:18
The reform now reads each schedule's enacted rates from the file they were
loaded from and, inside each active window, gives the 2030 rate only to the
brackets the enacted sunset changes and only on intervals where the enacted
9.3% rate still applies. This fixes three cases the January 2031 snapshot
got wrong: a 2031-only override equal to the 2030 rate no longer stops the
restoration in later years; a bounded 2030 edit to a lower bracket is no
longer extended; and explicit future rates survive on both construction
paths, so applying the reform twice (as Simulation does) changes nothing.

Tests: enacted schedules match the baseline in every year 2021-2101; user
edits are kept the same way through CountryTaxBenefitSystem and Simulation;
a Hypothesis property checks the restoration against random edits and
toggle schedules, including idempotence; the window property now probes
both sides of every toggle change and the horizon.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DTrim99

DTrim99 commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

PR Review (round 2): Encode the 2030 expiry of California's top income tax rates; add Prop 3 (2026) reform

PR #9601 (author: MaxGhenis). Round 1 reviewed head ea80e9d0ad on 2026-09-24 and requested changes: 2 critical, 6 should-address items and 7 suggestions. This round reviews head a72bca942f. Since round 1 there are three fix commits and four merges of main:

  • 769ac916a6: answers the round-1 review. It fixes the citations, copies the 2030 rates forward instead of hard-coding them, reads the dated toggle, and adds tests for every filing status, AMT and MHST, a pytest file, the .added.md fragment and the registration cleanup.
  • 279f541dd1: compares each schedule with its enacted YAML file, so the reform restores only the brackets the statutory sunset changes and keeps a user's own rate edits. It adds a Hypothesis property for the restoration.
  • 8fb31d7218: suppresses Hypothesis's too_slow health check on the toggle-window property.

Summary

Every round-1 item is addressed: both critical items, all six should-address items and all seven suggestions.

  • Critical 1: the head-of-household schedule now cites § 36(f)(3), and the shared toggle, reform comment and changelog cite § 36(f)(2)-(3).
  • Critical 2: the reform no longer contains rates or bracket indexes. It finds the sunset brackets from the enacted schedule and carries each one's 2030 rate forward.
  • Should 1: activation reads the dated in_effect values. A 2031 start now works, and a 2026-only toggle no longer leaks into 2031. Both are tested through Reform.from_dict.
  • Should 2-6: the measure text and page anchors, RTC § 17045, the 2031 cases for HOH, MFS and QSS, the .added.md fragment and the AMT case are all in.
  • Max also corrected one of my round-1 numbers. The $600,000 Prop 3 case is $54,432.81 (54,432.8077), not $54,432.80.

The law and the year boundary hold. Cal. Const. art. XIII, § 36(f)(2) and (f)(3) apply the 10.3% / 11.3% / 12.3% rates "for any taxable year beginning on or after January 1, 2012, and before January 1, 2031". So tax year 2030 is the last year with the higher rates, and they stop on 2031-01-01. The title's "2030 expiry" (they expire after TY2030) and the commit's "2031 rate expiry" (the reversion takes effect in 2031) describe the same boundary. The parameters (2031-01-01: 0.093 on brackets 6-8 of all five schedules) and the tests (2030 cases keep the higher rates, 2031 cases do not) agree with it.

The new reform logic is correct. It restores the rates only inside the dated windows where the toggle is true, only on the three brackets the enacted sunset changes, and only where the enacted 9.3% still applies. So it keeps a user's explicit future rates and edits to other brackets, and applying it again changes nothing. No other reform touches the CA rate schedules. The one limit is that a future edit that equals 9.3% can't be told apart from the sunset, and the PR body discloses that.

Tests, CI and branch.

  • Locally, on the extracted head tree, the four changed YAML files ran in one process and 30/30 passed. The pytest file then ran on its own and 15/15 passed.
  • CI is green at a72bca942f: 35/35 checks. The pytest file ran in Contrib (other-shard-1), and all 15 of its tests passed, none skipped.
  • The branch is 82 commits behind main and merges cleanly. main has made no change to the CA rate schedules, the CA tax formulas these tests use, or reforms.py's CA entries.

There is one new should-address item, and it is small. The round-2 fix for the Hypothesis health-check flake was applied to only one of the two properties that draw from the same toggle generator. There are also three suggestions.

Round-1 items

Item Status Evidence at a72bca942f
C1. HOH file cites § 36(f)(2), not (f)(3); the toggle, reform comment and changelog cite (f)(2) only ADDRESSED head_of_household.yaml:118 cites "Cal. Const. art. XIII, § 36(f)(3)" and :120 cites Prop 55 amending § 36(f)(3). in_effect.yaml:15 cites § 36(f)(2)-(3). ca_prop3_reform.py:7-11 names (f)(2) for single and separate (and, through RTC § 17045, joint and surviving spouse) and (f)(3) for HOH. ca-prop3-2030-expiry.fixed.md:1 cites § 36(f)(2)-(3).
C2. Reform hard-codes {6: 0.103, 7: 0.113, 8: 0.123} ADDRESSED TOP_BRACKET_RATES is gone. ca_prop3_reform.py:83 picks the brackets whose enacted rate changes between 2030 and 2031, and :86 copies the 2030 rate, including a rate the user set for 2030. No rate literal remains. The only constants are the sunset dates and the 2100 horizon (:12-14), which come from the measure text.
S1. The range(5) scan misses 2031 and a 2026-only toggle leaks ADDRESSED _active_windows (:24-43) reads in_effect.values_list and clips each true interval to 2031-01-01 through 2100-12-31. create_ca_prop3_reform (:126) creates the reform only when a window exists. Tests: test_ca_prop3_app_activation_from_2031 (a 2031 start gives $101,169.10; no reform gives $88,942.90), and test_ca_prop3_respects_delayed_and_bounded_activation (2026-only gives no reform; a 2033 start; true / false / true). All of them go through Reform.from_dict.
S2. in_effect.yaml lacks the measure text and the Figure 1 anchor ADDRESSED in_effect.yaml:9-16: LAO Figure 1 prop3.pdf#page=2, Prop 3 SEC. 4 on #page=6 and #page=7, and § 36(f)(2)-(3). The round-1 audit confirmed those pages show the strikes.
S3. Joint and QSS files lack RTC § 17045 ADDRESSED joint.yaml:124-125 and surviving_spouse.yaml:124-125.
S4. HOH, MFS and QSS untested in 2031 ADDRESSED Baseline: ca_income_tax_before_credits.yaml:66, 77, 88, plus HOH 2030 at :99. Reform: ca_prop3.yaml:28, 52, 64. The values match the round-1 table exactly.
S5. The changelog is only .fixed.md ADDRESSED changelog.d/ca-prop3-2030-expiry.added.md for the reform and toggle, and .fixed.md for the sunset. Both are top-level.
S6. No AMT test ADDRESSED ca_amt.yaml:56-72 (baseline: AMT $9,057.10, total $98,000) and ca_prop3.yaml:125-141 (Prop 3: AMT $0, total $101,169.10).
Sugg. 1: CPI-independent assertions and arithmetic comments ADDRESSED test_ca_prop3_rates_all_statuses_and_years checks the full ladder for all five statuses in 2030, 2031, 2035 and 2100. A 300-example property checks unchanged thresholds and lower rates, the exact 1% increments and the § 17045 scaling. The YAML header comments give f2030 and f2031 and the formulas.
Sugg. 2: incomes inside a single surcharge bracket ADDRESSED ca_prop3.yaml:77 ($450,000 → $38,061.02) and :89 ($600,000 → $54,432.81, Max's correction). There is also a control below the 10.3% threshold at :101.
Sugg. 3: an MHST case that can fail ADDRESSED The isolated MHST assertion is gone. ca_income_tax_before_refundable_credits.yaml:36 gives the baseline $191,942.90, and ca_prop3.yaml:143 gives Prop 3 $234,169.10.
Sugg. 4: test layout ADDRESSED Moved to contrib/states/ca/prop3/ca_prop3.yaml. The header (:1-14) explains the bypass instance and points to the baseline controls. The redundant toggle inputs are removed.
Sugg. 5: import and registration style ADDRESSED reforms.py:293-295, 568, 719 now sit next to ca_ab2591. states/ca/__init__.py:2 uses from .prop3 import .... prop3/__init__.py exports create_ca_prop3.
Sugg. 6: dated Prop 55 references ADDRESSED All five schedules cite Prop 55 SEC. 4 at text-proposed-laws.pdf#page=16 (:120), with (f)(3) in the HOH file. The RTC § 17041 titles name "(a)(1), (h)" or "(c)(1), (h)" (:122). The stale "Index according to federal tax parameter rules" comment is removed.
Sugg. 7: post-election follow-up ADDRESSED Each schedule has a comment at :73-74: "check the certified result and remove these entries if it passes". The PR body also says the contributed reform will be retired.
Pre-existing: misnamed 2021 test ADDRESSED ca_income_tax_before_credits.yaml:14 is now "Head of household with $1 million in 2021 reaches the 12.3% bracket".
Pre-existing: § 17041(h)(2)(B) nearest-dollar threshold rounding ACKNOWLEDGED-DEFERRED The PR body says it "remains outside this PR". That is reasonable: the effect is under $1 and was never in scope.
Pre-existing: FTB schedule references lack the schedule name and #page=1 NOT ADDRESSED (out of scope) :108-117 in each schedule is unchanged.

Max answered every item in a table in the PR body. There are no inline review threads.

Critical (must fix)

None.

Should address

1. The Hypothesis too_slow fix covers only one of the two properties that draw from the same toggle generator

  • Where: policyengine_us/tests/policy/contrib/states/ca/prop3/test_ca_prop3_reform.py
    • :377-381: the window property now has suppress_health_check=[HealthCheck.too_slow].
    • :420: test_ca_prop3_restores_only_the_enacted_sunset still has only @settings(max_examples=150, deadline=None).
    • :149: test_ca_prop3_preserves_thresholds_lower_rates_and_joint_scaling has the same settings.
    • None of the three sets derandomize=True.
  • Why it matters:
    • Commit 8fb31d7218 exists because the window property failed Hypothesis's input-generation health check on a loaded host. The PR body reports it at 279f541dd1: "The only failure was Hypothesis's input-generation health check on the loaded host".
    • That check times data generation only. Here it can trip on a host stall or a garbage-collection pause in a process that holds several tax-benefit systems, and these properties run after the file has built several.
    • This process does stall. In my local run of the file, the window property spent 249.8 s on 327 examples, although Hypothesis reports a typical example at 1-3 ms (under 1 ms of it generation). With the suppression in place, those stalls could not fail that test. The restoration property happened not to stall this time (30.4 s for 163 examples).
    • The restoration property draws the same UPDATES lists (:364-372) plus up to six EDITS tuples (:408-417). So each example generates more data than the test that already failed, and it is at least as exposed.
    • The file runs in the Contrib (other-shard-1) job on every PR (pytest $(find policyengine_us/tests/policy/contrib -name 'test*.py')). A health-check failure there would fail unrelated PRs.
    • Without derandomize, each CI run also draws new examples, so a failure would not reproduce from the log.
  • Repo pattern: 14 of the other 16 Hypothesis test files suppress too_slow, usually along with data_too_large, and 10 of them also set derandomize=True. For example, tests/core/test_dependent_payroll_taxes_ctc_and_surtax_invariants.py:368-373 notes "derandomize keeps CI runs reproducible", and tests/core/test_override_branches.py:231-235 does the same suppression.
  • Fix: use one settings object for all three properties:
    PROPERTY_SETTINGS = dict(
        deadline=None,
        derandomize=True,
        suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large],
    )
    
    @settings(max_examples=300, **PROPERTY_SETTINGS)   # :149 and :377
    @settings(max_examples=150, **PROPERTY_SETTINGS)   # :420
    Leave derandomize out if you want CI to keep exploring new examples, but at least extend the health-check suppression to :420 and :149. No assertion changes.

New suggestions

  1. rulespec-us#1565 calls its quotation verbatim, but it leaves out § 36(f)(2)(C) and (f)(3)(C) with no ellipsis.
    • The issue's quote runs "(B) ... recomputed ... (D) This paragraph shall become inoperative on December 1, 2031."
    • The skipped clause (C)(ii) says "the modified tax brackets and tax rates established and imposed by this paragraph shall be deemed to be established and imposed under Section 17041". That is the legal hook for the 2031 AMT comparison (RTC § 17062 compares tentative minimum tax with the § 17041 regular tax), which this PR now tests.
    • Fix: add (C)(i)-(ii) to the quotation, or mark the gap with an ellipsis. This is optional polish; the issue is otherwise dispatch-ready (see the axiom section).
  2. Tidy the PR body now that CI has finished.
    • "[full CI] is still running with no failures observed" is stale: all 35 checks passed.
    • The Validation section mixes in host-process details that reviewers can't act on: "nice 0", "macOS timing wrapper", "sandbox-denied sysctl", "recovered checkpoint" and "hub runner". The heading "Methodology choice for Max" reads as an internal note.
    • A short Validation section would be enough: CI green at a72bca942f, plus the local YAML and pytest counts.
  3. Optional: note the new runtime file read in _enacted_schedule.
    • ca_prop3_reform.py:46-50 re-reads each schedule's YAML through file_path when the reform is applied. No other reform does this.
    • It is the right design: it is the only way to tell the statutory sunset from a user edit without importing the baseline system. It is also guarded: test_ca_prop3_enacted_schedules_match_baseline_rates checks that the file and the baseline agree in every year from 2021 to 2101.
    • It does make Prop 3 depend on the YAML being readable at the recorded path, for example in a system unpickled on another machine. A one-line comment saying so would help the next maintainer.

Verification

The law and the year boundary.

  • (f)(2), single and separate, and through § 17045 joint and QSS. Cal. Const. art. XIII, § 36(f)(2): "For any taxable year beginning on or after January 1, 2012, and before January 1, 2031, ... the income tax bracket and the rate of 9.3 percent set forth in paragraph (1) of subdivision (a) of Section 17041 ... shall be modified". The modifications are 10.3% over $250,000, 11.3% over $300,000 and 12.3% over $500,000, indexed under (B).
  • (f)(3), head of household. It has the same cutoff for § 17041(c)(1), with base thresholds of $340,000 / $408,000 / $680,000.
  • The (D) clauses ("inoperative on December 1, 2031") are correctly not used as the switch date. Taxable years are what count.
  • Prop 55 extended the cutoff. Prop 55 (2016) SEC. 4, PDF p.16 moved "before January 1, 2019 2031".
  • The LAO agrees. The 2026 LAO analysis, PDF p.2 says "Prop 55 extended these higher rates until 2030", and Figure 1's columns are "Now Through 2030" and "After 2030, if This Proposal Is ... Approved / Rejected".
  • So TY2030 is the last surcharge year, and the parameters and tests agree.
    • 2030 single $1M is $101,596.07, and 2030 HOH $1.5M is $154,557.25. Both carry the surcharge.
    • All 2031 baseline cases revert.
    • The Prop 3 control at ca_prop3.yaml:113 shows 2030 tax unchanged by the reform.

Prop 3 (November 3, 2026).

  • Operative text. Text of Proposed Law, SEC. 4, PDF p.6 strikes "and before January 1, 2031," from (f)(2). PDF p.7 strikes (f)(2)(D), the same phrase in (f)(3), and (f)(3)(D).
  • No other changes. It changes no rate or threshold, so the higher rates continue in every year from 2031.
  • The reform matches. The bypass instance restores 10.3% / 11.3% / 12.3% on brackets 6-8 of all five schedules from 2031-01-01 through 2100-12-31. test_ca_prop3_rates_all_statuses_and_years checks 2031, 2035 and 2100.

The reform logic, ca_prop3_reform.py.

  • Windows (:24-43).
    • values_list is newest-first, and each true value runs until the day before the next later instant. Windows are clipped to [2031-01-01, 2100-12-31] and dropped if empty.
    • Hand-traced for the app's usual {"2026-01-01.2100-12-31": True}: the values are 2101-01-01 false, 2026-01-01 true and 0000-01-01 false, which gives the single window [2031-01-01, 2100-12-31].
    • A 2026-only toggle gives no window, so create_ca_prop3_reform returns None and the baseline costs nothing.
  • Which brackets (:83). Only brackets whose enacted rate differs between 2030-01-01 and 2031-01-01 qualify. That is indexes 6-8 today, and it follows any future change to the layout.
  • Restoration (:86-95).
    • The value restored is the bracket's 2030 rate in the reformed tree, so a 2030 user edit carries forward. This is what round 1 proposed.
    • Restoration applies only on sub-intervals where the current rate still equals the enacted rate. So explicit user rates in 2031+ survive, as do user edits to non-surcharge brackets, including a 2030-only edit to bracket 1.
    • All reads happen before any write.
    • The restored value differs from the enacted 9.3%, so the second and third passes (CountryTaxBenefitSystem.__init__ re-applies the reform set, and Simulation.__init__ re-runs structural detection, system.py:155-166, 250-254) change nothing.
  • Other reforms. git grep finds no other reform or variable that writes gov.states.ca.tax.income.rates. The rates' formula readers are ca_income_tax_before_credits.py:16 and ca_withheld_income_tax.py:17 (single schedule). AMT and MHST stacking pick up the change through ca_income_tax_before_credits. Registration (reforms.py:568, 719) adds one more None-returning factory to the structural list.
  • Disclosed limit. An explicit future edit equal to 9.3% inside an active window is treated as the sunset and restored. That is inherent to comparing values, and the PR body says so.

The property tests.

  • Settings. deadline=None is set on all three, so per-example timing can't fail them. The health-check coverage is the Should item above.
  • They test something meaningful.
    • The window property compares _active_windows against the toggle's own value at January 1 and July 1 of every year and on both sides of every change and of the horizon.
    • The restoration property compares _restore_pre_sunset_rates against an independent oracle built from the baseline tree, not the enacted file, at both sides of every edit and window edge. It also checks idempotence.
    • Suppressing too_slow drops no examples or assertions.
  • Not skipped in CI.
    • The file imports hypothesis directly, with no importorskip. hypothesis>=6.100.0 is a dev dependency (pyproject.toml:57).
    • The Contrib (other-shard-1) log for run 37606433807 shows plugins: anyio-4.12.1, hypothesis-6.168.3 and 15 passing tests from this file (about 11:30 to 11:35 UTC on 2026-10-07).
    • A local environment without Hypothesis gets a collection error rather than a skip. That is the same as the other directly-importing files, so it is not a finding.

The YAML reforms: key. All 11 contrib cases set reforms: policyengine_us.reforms.states.ca.prop3.ca_prop3 at the case level (ca_prop3.yaml:19, 31, 43, 55, 67, 80, 92, 104, 116, 128, 146), so the structural reform is actually applied. The 2031 expectations differ from the baseline controls, so the cases can fail.

Hand checks. These use f2030 = 193 / 172.797 and f2031 = 196.85 / 172.797 on the FTB 2025 thresholds (Schedule X 72,724 / 371,479 / 445,771 / 742,953; Schedule Z 98,990 / 505,208 / 606,251 / 1,010,417). Tax at the 9.3% threshold is 3,201.97f (Schedule X) and 3,616.45f (Schedule Z).

Case Hand result Expected
Single $1M, 2030 baseline 101,596.071 101,596.07
Single $1M, 2031 baseline 88,942.904 88,942.90
HOH $1.5M, 2031 baseline 133,132.316 133,132.32
HOH $1.5M, 2030 baseline 154,557.250 154,557.25
Single $1M, 2031 Prop 3 101,169.102 101,169.10
Joint / QSS $2M, 2031 Prop 3 (2 × single $1M) 202,338.204 202,338.20
HOH $1.5M, 2031 Prop 3 153,959.947 153,959.95
Single $450k, 2031 Prop 3 38,061.022 38,061.02
Single $600k, 2031 Prop 3 54,432.808 54,432.81
Single $2M, 2031 Prop 3, regular tax + MHST (.01 × 1,000,000) 224,169.102 + 10,000 224,169.10 / 234,169.10
Single $2M, 2031 baseline, regular tax + MHST 181,942.904 + 10,000 191,942.90
AMT, 2031: .07 × 1,400,000 = 98,000 − regular tax baseline 9,057.097; Prop 3 0 9,057.10 / 0

All 17 expectations agree to within half a cent.

Local runs (extracted head tree, local policyengine-core 3.26, one process at a time).

  • policyengine-core test on ca_prop3.yaml, ca_income_tax_before_credits.yaml, ca_amt.yaml and ca_income_tax_before_refundable_credits.yaml: 30 passed (11 + 9 + 6 + 4) in 67.8 s.
  • pytest test_ca_prop3_reform.py, with Hypothesis 6.168.5: 15 passed in 1,054.6 s.
    • The three properties ran 300, 300 and 150 examples with no failures.
    • The window property took 249.8 s, although its typical example takes 1-3 ms (see Should 1).

Partner contract tests. None are affected. All 144 partner YAML files, at this head and on current main, use periods 2024, 2025, 2026 or 2026-01, all before the 2031-01-01 change. The diff touches no partner file.

Axiom line assessment

axiom: CA Constitution XIII §36(f)(2)-(3) https://github.com/TheAxiomFoundation/rulespec-us/issues/1565 queued | CA Prop 3 (2026) §4 https://github.com/TheAxiomFoundation/rulespec-us/issues/1566 queued

Valid. Both issues are OPEN, labelled pe-parity, and were updated on 2026-10-09 against this head (a72bca942f).

  • Need to remove unneeded use of taxsim35_emulation #1565 (the enacted sunset) has everything a dispatch needs:
    • the module path, us-ca/constitution/xiii/36/f.yaml, importing rtc/17041;
    • the official Prop 55 text (PDF p.16) and RTC § 17045, quoted;
    • the required outputs;
    • seven CPI-independent companion cases from the same official texts, which explicitly exclude PolicyEngine's forecast dollar values.
    • It records a corpus-promotion blocker: the pinned rulespec release has no CA constitution scope. That is upstream of this PR. The (C) omission is Suggestion 1.
  • Remove unneeded taxsim35_emulation statements from tests #1566 (the Prop 3 proposal) has the module path (us-ca/policies/proposals/2026-proposition-3/income-tax-sunset.yaml), the verbatim surviving text from PDF pp. 6-7, five rate/date companion cases, and the same proposal-corpus blocker. It correctly treats the measure as conditional until the certified result.
  • Both issues match the PR's current behavior.

CI status

  • All green at a72bca942f: run 37606433807 (2026-10-07) passed 35/35 checks. That covers every Baseline and Contrib shard, Microsimulation, Rest (Python + variables), Household API Partners, Lint, the changelog check, smoke imports on 3.11-3.14 and the bundle and wheel checks.
  • Contrib (other-shard-1) (45m10s) ran test_ca_prop3_reform.py: 75 passed in that pytest stage, including all 15 from this file, which took about 5 minutes. The file's slowest test, the threshold/scaling property, took 52.8 s.
  • That run predates the 82 newer main commits, so it has not tested the merge.

Branch status

Verdict: REQUEST_CHANGES

@DTrim99 DTrim99 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Round 2: all 15 round-1 items are addressed. TY2030 is the last surcharge year under § 36(f)(2)-(3), the parameters and tests agree, the reform restores only the enacted sunset and leaves user edits intact, and the rulespec issues are dispatch-ready. One should item (see the comment above): the Hypothesis too_slow fix covers only the window property. The restore-only property at :420, and the test at :149, draw the same strategies with no derandomize or health-check suppression, so they can hit the same stall on a busy host. Please use shared settings with derandomize=True and suppress too_slow and data_too_large.

This branch has not been deployed

No deployments
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.

2 participants