Repository navigation
Cap the regular tax at the tax on all taxable income (26 U.S.C. 1(h)(1)) and use it on Form 6251 line 10 - #9852
Merged
Merged
Conversation
…rm 6251 line 10 26 U.S.C. 1(h)(1) says the tax "shall not exceed" the sum of the subparagraph (A) to (F) amounts, and Schedule D Tax Worksheet line 47 takes the smaller of line 45 and line 46 (the tax on all of line 1). capital_gains_tax now stops at tax_on_taxable_income_at_main_rates (new, line 46) less income_tax_main_rates. The cap binds where gain taxed at 15 percent falls in the 12 percent bracket: up to $3.75 single and $7.50 joint in 2025, and $28.50 and $57 in 2026. The Biden and Harris capital gains reforms apply the same limit, and the additional_tax_bracket reform figures line 46 on its own schedule. regular_tax_before_credits was a second Schedule D Tax Worksheet (2017 numbering) whose result was min(worksheet tax including the gains, income_tax_main_rates excluding them). Form 6251 line 10 then added capital_gains_tax. Whenever the worksheet path fell below the main-rates path, line 10 understated the regular tax: before #9781 that happened for 18 dataset tax units in 2025 ($328M weighted), because dwks10 ignored the Form 4952 line 4g election and because the worksheet took line 28 as min(line 21, line 22) instead of their sum. regular_tax_before_credits is now income_tax_main_rates plus capital_gains_tax, the regular tax income_tax_before_credits charges, and Form 6251 line 10 uses it alone. Tests: real-unit regression cases for tax units 1101624, 1004930, 1156248 and 1032460; line 45/46 boundary cases for 2025 and 2026 (single, joint, head of household, Form 2555); the existing worksheet property tests now compare with line 47 and Foreign Earned Income Tax Worksheet line 25 exactly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Section 3 of Rev. Proc. 2025-32 modifies 2025 items (only .01, the standard deduction, and .02, section 179); the 2026 adjusted items are section 4. The 2026 AMT breakpoints are section 4.10 (PDF page 16) and the 2026 additional standard deduction for the aged or blind is section 4.14(3) (PDF page 18). Also give the dependent standard deduction citation its section number (4.14(2)). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…47 limit test_regular_tax_before_credits.py transcribes the 2025 Schedule D Tax Worksheet, lines 1 to 47, with every skip instruction, the Form 4952 election, capital gain distributions, short-term losses and Schedule D lines 18 and 19. The regular rates come from the 2025 Tax Computation Worksheet rows. 2026 runs on Rev. Proc. 2025-32's tables and capital gains amounts. The model matches lines 10, 13, 14, 21, 44, 46 and 47 for a grid, households at the 0 percent amount, and Hypothesis draws. Properties, 2018 to 2035: - 0 <= regular tax <= line 46; - 0 <= capital_gains_tax <= line 46 - income_tax_main_rates; - regular_tax_before_credits = main rates + capital gains tax; - income_tax_before_credits = regular + AMT; - the AMT is line 9 less line 10; - without net capital gain, the regular tax is line 46; - the worksheet lines (dwks10, dwks13, line 18 <= 21 <= 14); - the tax is monotone in wages, gains and dividends; - results are independent of batch order. The limit binds at the top of the 12 percent bracket in every year and filing status, with the published band widths for 2018 to 2026. The monotonicity check sets aside households with capital gain distributions and a Schedule D net loss, a known defect it found. Gross income adds non_sch_d_capital_gains outside the loss limit, while net_capital_gain nets them (a 2018 single filer's tax falls $0.12 with $1 more gain). No dataset tax unit has both. It is left for a follow-up. Also adds 2026 joint, separate and non-binding line 45/46 YAML cases, from the same Rev. Proc. 2025-32 figures as the Axiom companion tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #9831 (amounts_in_brackets, the shared bracket walk that tax_at_main_rates and amount_taxed_below_rate use) and #9788 (capital gain distributions netted inside the capital loss limit). No textual conflicts: the additional_tax_bracket reform's tax_on_taxable_income_at_main_rates override calls tax_at_main_rates, so it takes #9831's walk. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s netting - test_additional_tax_bracket_unset_bracket.py (#9831) listed regular_tax_before_credits among the variables the reform replaces. On this branch the reform replaces tax_on_taxable_income_at_main_rates instead, and regular_tax_before_credits is income_tax_main_rates plus capital_gains_tax. The default-system leak check now covers the three replaced variables, and the finite and monotone checks cover the reform's three taxes: income_tax_main_rates, tax_on_taxable_income_at_main_rates and regular_tax_before_credits. - test_regular_tax_before_credits.py set aside households with capital gain distributions and a Schedule D net loss in the monotonicity property. #9788 nets the distributions inside the capital loss limit in AGI, so the property now holds for every household. The exclusion is gone, and the minimized counterexample (2018 single, $50,000 of distributions, a $40,273 short-term loss, $1 more long-term gain) is an explicit example; it failed before #9788 (the regular tax fell by $0.12) and passes now. Both files pass after the merge: 9 and 6 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… capital gains tax PolicyEngine/salt-amt-calculator's API reads regular_tax_before_credits as its regular tax, so the meaning change belongs in the release note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unts Three links pointed into the wrong section of Rev. Proc. 2025-32: - the capital gains rate thresholds linked page 12, which is section 4.02 (kiddie tax). Section 4.03 and its 2026 amounts (49,450, 98,900, 66,200, 545,500, 613,700, 579,600 and 306,850) are on page 13; - the child tax credit amount ($2,200) and refundable maximum ($1,700) linked page 13, which holds sections 4.03 and 4.04. Section 4.05 and its amounts are on page 14. The other parameter links to the Rev. Proc. land on the page that has the 2026 amounts or on the page where their section starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #9841 (28 percent rate gain and unrecaptured section 1250 gain netted against losses), #9801, #9790, #9779, #9768, #9741 and #9630. Only test_section_911_tax_stacking.py changed on both sides, in different functions, and git merged it without conflict. #9841 passes Schedule D lines 18 and 19 entered already netted through unchanged, and this branch's capital_gains_tax, reforms and tests read the section 911 amounts and Schedule D inputs that #9841 keeps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Merged by the US + core hub on its merge gates. Audit:
|
This was referenced Oct 6, 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-ups 1, 2 and 5 from #9792.
Summary
capital_gains_taxnow stops attax_on_taxable_income_at_main_rates(new, line 46) lessincome_tax_main_rates. The Biden and Harris capital gains reforms apply the same limit, andadditional_tax_bracketfigures line 46 on its own schedule.regular_tax_before_creditswas a second Schedule D Tax Worksheet. It returned the smaller of its own total, which included the gains, andincome_tax_main_rates, which leaves them out.alternative_minimum_taxthen addedcapital_gains_taxon top.regular_tax_before_creditsis nowincome_tax_main_rates + capital_gains_tax, the same regular taxincome_tax_before_creditsadds, and line 10 uses it alone. Its meaning changes: it now includes the capital gains tax (see "Outside readers" below).amt_tax_including_cg.yaml(3.10 → 4.10), the Maine aged-or-blind parameter (3.14(3) → 4.14(3)) and a test comment (".14(2)" → 4.14(2)). The repo's eleven other citations of the Rev. Proc. by section or name are right: 4.01 three times, 4.14(2) four times, 4.31 twice, and two without a section. Of the 22 parameter links to its PDF, three pointed into the wrong section and now point at the pages with their 2026 amounts. The capital gains rate thresholds moved from page 12 (section 4.02) to page 13 (section 4.03). The child tax credit amount and refundable maximum moved from page 13 to page 14 (section 4.05). The other links land on the page with the amounts or on the page where their section starts.axiom: TheAxiomFoundation/rulespec-us#1501 queued
Law and worksheet lines
26 U.S.C. 1(h)(1): "If a taxpayer has a net capital gain for any taxable year, the tax imposed by this section for such taxable year shall not exceed the sum of— (A) a tax computed at the rates and in the same manner as if this subsection had not been enacted on the greater of— (i) taxable income reduced by the net capital gain; or (ii) the lesser of— (I) the amount of taxable income taxed at a rate below 25 percent; or (II) taxable income reduced by the adjusted net capital gain; (B) 0 percent ... (F) 28 percent of the amount of taxable income in excess of the sum of the amounts on which tax is determined under the preceding subparagraphs".
2025 Schedule D Tax Worksheet, lines 44 to 47:
The Qualified Dividends and Capital Gain Tax Worksheet does the same on lines 23 to 25. 2025 Form 6251 line 10: "Add Form 1040 or 1040-SR, line 16 (minus any tax from Form 4972), and Schedule 2 (Form 1040), line 1z. Subtract from the result Schedule 3 (Form 1040), line 1 ...". Form 1040 line 16 is the worksheet's line 47.
Worksheet numbering. I compared the worksheet text for every year from 2017 to 2025:
The old
regular_tax_before_creditsused the 2017/2018 numbering.Where line 45 exceeds line 46. It happens only where gain taxed at 15 percent sits in the 12 percent bracket. The 0 percent rate amount ends below the top of that bracket by $100 to $250 in 2018 to 2025 (up to $3.75 single and $7.50 joint). Since P.L. 119-21 it is $950, $1,900 and $1,250 in 2026 (Rev. Proc. 2025-32 sections 4.01 and 4.03), worth up to $28.50 single and separate, $57 joint and $37.50 head of household. The 25 and 28 percent amounts are always above the 24 percent bracket top, where the regular rate is at least 32 percent, so line 45 never exceeds line 46 because of them.
Traced cause of follow-up 1
#9792's run found
regular_tax_before_creditsbelowincome_tax_main_ratesfor 18 tax units in 2025 ($328M weighted, up to $19,123). The run used #9780's head, before #9781. All 18 units elect under Form 4952 line 4g (investment_income_elected_form_4952, from the dataset). Two errors stacked:dwks10ignored the election, whilenet_capital_gainapplied it.dwks10readinvestment_income_form_4952, an input no dataset fills. So worksheet line 10 was too large and line 21 (dwks19) too small. For unit 1101624, line 10 was 222,465 against a net capital gain of 80,593. Apply the Form 4952 election in Schedule D Tax Worksheet lines 3-10 #9781 (2137342) fixed this. On main,dwks10equalsnet_capital_gainfor every tax unit, to float32 rounding (at most $0.25). This PR ran main at ea79c39 for 2025 and 2026, and at 47ab53e for 2025. In those runs, no tax unit hasregular_tax_before_creditsbelowincome_tax_main_ratesby more than half a cent. The hub's run on 3f15391 found the same. The duplicate worksheet still fell belowincome_tax_main_ratesby float32 rounding, at most $0.002. That happened for 789 tax units in 2025 and 701 in 2026, and for 790 in 2025 on 47ab53e.min_(line 21, line 22). Every worksheet adds them (2025 line 28 = lines 21 + 22; 2017 and 2018 line 26 = lines 19 + 20). Becausemin_took line 22 instead of adding line 21, line 29 was too large, so gain that belonged at 20 percent was taxed at 15. For unit 1101624 that was all of line 23: $11,123 of the $19,123 gap (5% of 222,465). The other $8,000 came from cause 1.Form 6251 line 10 used
min(duplicate worksheet tax, income_tax_main_rates) + capital_gains_tax, so any shortfall in the duplicate went straight to line 10. Error 2 never showed on main, because the duplicate's total, gains included, stayed aboveincome_tax_main_rates. The duplicate had other latent errors too:income_tax_main_rates, not the tax on line 1;The fix removes the duplicate. Line 10 is now built from the same variables as
income_tax_before_credits, so a difference between two routes can no longer change it. The worksheet itself is now transcribed in the tests (see below), where a disagreement fails CI instead of moving the AMT. None of the 18 units owed AMT in either run: line 9 stayed well below line 10.Invariants (property tests,
test_regular_tax_before_credits.py)These hold for every household, filing status and year. The tests check them in three ways:
The invariants:
0 <= regular_tax_before_credits <= tax_on_taxable_income_at_main_rates("shall not exceed").0 <= capital_gains_tax <= line 46 - income_tax_main_rates, soincome_tax_main_rates <= regular_tax_before_credits.regular_tax_before_credits = income_tax_main_rates + capital_gains_tax;income_tax_before_credits = regular_tax_before_credits + alternative_minimum_tax;dwks10is the net capital gain anddwks13the adjusted net capital gain;income_tax_main_ratestaxes line 21.The tests also cover every year from 2018 to 2035 and every filing status. Taxable income is set at the top of the 12 percent bracket, with dividends from the 0 percent amount up. In each case the limit binds and the gains tax is 12 percent of the dividends. The band is $100 to $250 wide in 2018 to 2025 and $950 to $1,900 in 2026.
Differential. Lines 1 to 47 of the 2025 Schedule D Tax Worksheet are transcribed line by line. The transcription includes:
The regular rates come from the 2025 Tax Computation Worksheet's rows, also below $100,000, where the form uses the Tax Table, which rounds within $50 rows. The model does the same. 2026 runs on Rev. Proc. 2025-32's tables (sections 4.01 and 4.03), and a test checks the rows against the printed subtraction amounts and bases. The model matches the transcription's lines 10, 13, 14, 21, 44, 46 and 47 for:
The existing differential tests changed in two ways:
test_form_6251_part_iii.pynow compares the regular tax with Schedule D Tax Worksheet line 47.test_section_911_tax_stacking.pyandtest_form_4952_section_911_interaction.pycompare it with Foreign Earned Income Tax Worksheet line 6, figured from line 25 of the Qualified Dividends and Capital Gain Tax Worksheet.Both match to single-precision tolerance. Before, they matched line 45, or line 6 figured from line 23. A separate check allowed the capped figure to be up to $7.50 lower (in the Form 4952 test, up to the rate overlap).
A counterexample the properties found, now fixed on main by #9788. Gross income used to add
non_sch_d_capital_gainsoutside the Schedule D loss limit, whilenet_capital_gain(since #9781) nets the same distributions on Schedule D line 13. With a Schedule D net loss, more long-term gain then raised net capital gain but not taxable income. A 2018 single filer with $50,000 of distributions, a $40,273 short-term loss and $1 of long-term gain paid $0.12 less. Before #9788 merged, property 6 set such households aside. #9788 nets the distributions inside the capital loss limit, so after merging main the property holds for every household. The minimized case is now an explicit Hypothesis example. It failed before #9788 and passes now.Merged main
The branch merges main twice, with merge commits: at ea79c39 (3ec4485) and at 47ab53e (ede9aad).
amounts_in_bracketsis the shared bracket walk. Theadditional_tax_bracketreform's line 46 override callstax_at_main_rates, so it uses that walk too. Give the additional_tax_bracket reform's unset bracket zero tax instead of NaN #9831's reform test listedregular_tax_before_creditsamong the variables the reform replaces. The reform now replacestax_on_taxable_income_at_main_ratesinstead, so the test's default-system leak check covers the three replaced variables,income_tax_main_rates,tax_on_taxable_income_at_main_ratesandtaxable_income_taxed_below_25_percent. Its finite and monotone checks cover the reform's three taxes:income_tax_main_rates,tax_on_taxable_income_at_main_ratesandregular_tax_before_credits.test_section_911_tax_stacking.pychanged on both sides, in different functions, and git merged it without conflict. Net losses against 28 percent rate gain and unrecaptured section 1250 gain (26 U.S.C. 1(h)(4), 1(h)(6)) #9841 passes Schedule D lines 18 and 19 entered already netted through unchanged, so the transcription, which takes them as entered, still applies. This branch'scapital_gains_taxand reforms read the section 911 amounts that Net losses against 28 percent rate gain and unrecaptured section 1250 gain (26 U.S.C. 1(h)(4), 1(h)(6)) #9841 updated.Tests, before and after
The new cases are in
alternative_minimum_tax/regular_tax_before_credits.yamlandbefore_credits/tax_on_taxable_income_at_main_rates.yaml. The copies run on main and before #9781 leave outtax_on_taxable_income_at_main_rates, which does not exist there.regular_tax_before_credits, from the meaning change. 1004930 and 1032460 have no capital gains tax and pass.dwks10ornet_capital_gain(the Form 4952 election defects #9781 fixed)capital_gains_taxby $3.75, $7.50, $3.75, $28.50, $57, $28.50 and $37.50regular_tax_before_credits(meaning change only)Main: 12 failed, 2 passed. Before #9781: 14 failed. The failure lines on main are identical at ea79c39 and at the original base a2f0fd4. A third run, at 47ab53e, got 3 seconds of CPU in 9.5 minutes on the loaded host and was stopped.
Run on the branch after the second merge (ede9aad), one process at a time:
capital_gainsandbefore_credits: 91 passed, including Net losses against 28 percent rate gain and unrecaptured section 1250 gain (26 U.S.C. 1(h)(4), 1(h)(6)) #9841's 30 new cases;alternative_minimum_taxandforeign_earned_income_exclusion: 145 passed;additional_tax_bracket,harris/capital_gains,biden/budget_2025,crfb/agi_surtax.yaml,congress/watca.yaml) and Net capital gain distributions inside the capital loss limit in AGI #9788'sschedule_d_capital_gain_distributions.yaml: 88 passed;test_regular_tax_before_credits.py,test_form_6251_part_iii.py,test_section_911_tax_stacking.py,test_form_4952_section_911_interaction.pyandtest_negative_main_rates_inverted_brackets.py: 37 passed;test_capital_gain_rate_group_netting.pyand Net capital gain distributions inside the capital loss limit in AGI #9788'stest_capital_gain_distributions_loss_limit.py: 12 passed;test_additional_tax_bracket_unset_bracket.py: 9 passed.The same suites passed after the first merge, and the YAML folders passed before either merge (275 tests).
Impact (default dataset)
Four full microsimulations were run: main at ea79c39 and this branch's change on it (d5f4e88), for 2025 and 2026. Each used
populace_us_2024(populace-us-2024-spm-20260909) and policyengine-core 3.32.15, with 79,729 tax units.capital_gains_taxfalls by more than half a cent)income_tax, weighted changehousehold_net_income, weighted changealternative_minimum_taxstate_income_taxEvery tax unit where the limit binds has taxable income near the top of the 12 percent bracket. In 2026, joint units are at $99,095 to $100,906, around the $100,800 top and above the $98,900 zero rate amount. Single units are at $49,576 to $50,480, and heads of household at $66,904 to $67,417. In 2025, the two units are at $96,757 (joint) and $48,439 (single). In 2025 one more tax unit's
capital_gains_taxfalls, and in 2026 five more, each by under $0.001 (float32). In both years,income_taxis bit-for-bit unchanged in every tax unit whosecapital_gains_taxis unchanged. No tax unit'scapital_gains_taxrose.regular_tax_before_creditschanges for 13,275 tax units in 2025 and 13,372 in 2026, by +$279.6 billion and +$294.0 billion weighted. That is the meaning change: it now includes the capital gains tax. Apart from float32 rounding, Form 6251 line 10 changes only where the limit binds, and by the same amount as the regular tax.These invariants hold on every tax unit of the branch, in both years, to 5 cents plus 5e-7 of taxable income (the no-gain identity bit for bit):
0 <= regular_tax_before_credits <= line 46;regular_tax_before_credits = income_tax_main_rates + capital_gains_tax;0 <= capital_gains_tax <= line 46 - income_tax_main_rates;income_tax_before_credits = regular_tax_before_credits + alternative_minimum_tax;dwks10 = net_capital_gain;2025 again after the second merge. Main at 47ab53e against the branch at ede9aad gives the same result. The limit binds for the same 2 tax units (combined weight 0.7),
income_taxfalls by $0.06 weighted, the AMT is unchanged in every tax unit, and the invariants hold.regular_tax_before_creditschanges for 13,278 tax units (+$279.6 billion). The matching 2026 run of the branch hit this build's 10-minute cap for a single run, after it had computedincome_tax. Retries could not get the shared microsimulation lock, and the host's load average later passed 100. So the 2026 figures above are from ea79c39. #9841 passes the dataset's Schedule D lines 18 and 19 through unchanged.Outside readers of
regular_tax_before_creditsGitHub code search across the PolicyEngine and TheAxiomFoundation organizations (2026-10-06) finds one program outside this repo that reads the variable from policyengine-us. PolicyEngine/salt-amt-calculator's API (
api/salt_amt_api/simulation/calculation.py) reportsregular_tax_before_creditsfor 2026 as its "regular tax". It compares that withamt_base_tax(gap = max(regular_tax - amt, 0),amt_binds = amt > regular_tax). Its Modal image installspolicyengine-us>=1.0.0, unpinned (api/modal_app.py), so its next image build picks up this change.api/uv.lock, which pins 1.485.0, is not used there. With this change, its regular tax for a household with long-term gains or qualified dividends includes the capital gains tax, as Form 6251 line 10 does. Itsgap(max(regular_tax - amt, 0)) can grow by up tocapital_gains_tax, andamt_bindscan turn false. Its reported regular tax then matches Form 6251 line 10. The other matches are:regular_tax_before_creditsin rulespec-us and the tools that read it. That variable is the subject of rulespec-us#1501.No partner contract test under
tests/policy/baseline/partnersis touched.Not in this PR
additional_tax_bracketNaN with default 2026 parameters) was fixed by Give the additional_tax_bracket reform's unset bracket zero tax instead of NaN #9831, merged here.🤖 Generated with Claude Code