Skip to content

Fix pre-2018 AMT child exemption and income base - #10308

Merged
MaxGhenis merged 7 commits into
mainfrom
amt-child-pre-2018
Nov 5, 2026
Merged

MaxGhenis merged 7 commits into
mainfrom
amt-child-pre-2018

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Correct the pre-2018 AMT child exemption and income base identified in Yale/PSL unit U4. A child with $50,000 AMTI and a $7,500 exemption should have a $42,500 AMT base, and a 2017 joint return by young spouses should get the ordinary joint exemption.

Plan:

  • Install U4's two historical regressions and keep its three passing controls.
  • Apply the limited exemption to AMTI in every year, and keep the historical child cap in amt_exemption (§55(b)(1)(A)(ii), §59(j)).
  • Exclude joint returns from amt_kiddie_tax_applies (§1(g)(2)(C)).
  • Review follow-up: apply the §1(g)(2)(A)(ii)(II) support test to an 18-year-old or a full-time student under 24, using care_and_support_costs (new parameters gov.irs.income.amt.exemption.child.support_test.age_threshold and .rate). The model treats zero support as unreported and the test as met, so a student or 18-year-old with earnings keeps main's cap until someone enters support.
  • Review follow-up: judge the child rule by the filer alone, by dropping the spouse-age clause; a joint return still excludes the unit.
  • Review follow-up: count only the head's earnings in the §59(j) cap (§59(j)(1)(A)), and pin it with a separate filer whose spouse has earnings.
  • Review follow-up: document that the model has no input for a living parent (§1(g)(2)(B)) and assumes one.
  • Review follow-up: add 2017 cases for an itemizing child and a pair either side of the §59(j) cap, plus support-test boundary cases.
  • Review follow-up: replace the property test, which restated the old formulas, with one that checks legal properties (below).
  • Impact analysis (hub).

Sources:

Tests:

  • test_child_amt_exemption_properties.py (renamed from test_post_2017_child_amt_invariance.py) checks, across generated households in 2015, 2017, 2018, 2025 and 2026, that:
    • joint returns are never covered;
    • before 2018, a covered child's exemption is the identical adult's exemption, capped at earned income plus the child amount;
    • from 2018, a child's exemption equals the identical adult's.
  • It builds one simulation per example and no reformed system. Local cost (4 vCPU Linux VM, not CI): the old test took 19.7 s call plus 10.7 s setup, with a 2.0 GB peak RSS for the pytest process. The new one takes 4.2 s call with no fixture setup, and 1.0 GB peak RSS.
  • YAML: amt_kiddie_tax_applies.yaml holds 16 cases, amt_income_less_exemptions.yaml 14 and amt_exemption.yaml 18.
  • CI: the latest Pull request run, at 78a02e1b52 (https://github.com/PolicyEngine/policyengine-us/actions/runs/41971589911), concluded success: all 36 jobs passed.

Known gaps this PR doesn't change (pre-2018 AMTI, now inherited by child filers): the line 6 overall-limit (Pease) adjustment, line 7 (tax refunds), line 4 (home mortgage interest), and the missing pre-2018 standard deduction values. #10099 covers lines 6 and 7.

impact: none from 2018 on. In those years amt_exemption returns before it reads amt_kiddie_tax_applies, nothing else reads that variable at this head, and amt_income_less_exemptions already used amt_income. For 2013-2017 the PR changes AMT for child filers in household calculations. The default dataset holds 2024 records and no 2013-2017 data, so we report no aggregate for those years.

axiom: us:statutes/26/59#section_59_j_kiddie_exemption_limit_applies encoded-correct for 2018 onward (false from 2018-01-01; companion tests us/statutes/26/59.test.yaml); TheAxiomFoundation/rulespec-us#1857 queued for TY2013-2017 (§59(j), §1(g)(2) including the joint-return exclusion, and the §55(b)(1)(A)(ii) AMTI taxable excess). It waits on three things: axiom-corpus ingestion of the TY2013-2017 statute text and the Form 6251 instructions (TheAxiomFoundation/axiom-corpus#915), a re-pin to a release that carries the 2013-2016 revenue procedures, and approval of the billed encoder run.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis marked this pull request as ready for review October 21, 2026 00:16
@MaxGhenis MaxGhenis closed this Oct 21, 2026
@MaxGhenis MaxGhenis reopened this Oct 21, 2026
@DTrim99

DTrim99 commented Oct 21, 2026

Copy link
Copy Markdown
Collaborator

PR Review: Fix pre-2018 AMT child exemption and income base

PR #10308 (author: MaxGhenis), head e7b010e809. This is a first review. The PR makes two model changes:

  • The income base. Before 2018, amt_income_less_exemptions gave a child subject to the kiddie tax regular taxable income less the AMT exemption. It now uses AMTI less the exemption in every year. The §59(j) cap stays in amt_exemption.
  • Joint returns. amt_kiddie_tax_applies now returns false for joint returns, under §1(g)(2)(C).
  • Tests. It adds 3 YAML cases to amt_exemption.yaml and 2 to amt_income_less_exemptions.yaml. It flips 4 expectations to the joint-return rule, adds 2 cases and filing statuses to amt_kiddie_tax_applies.yaml, and adds a Hypothesis property test plus a changelog fragment.

The PR has no comments or reviews yet. It touches no partner test file.

Path prefixes:

  • V/ = policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/
  • T/ = policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/

Sources read for this review (all fetched 2026-10-21):

Summary

What is right.

  • The law: the cap limits only the exemption.
    • §59(j)(1) (2016 ed.): "In the case of a child to whom section 1(g) applies, the exemption amount for purposes of section 55 shall not exceed the sum of— (A) such child's earned income (as defined in section 911(d)(2)) for the taxable year, plus (B) $5,000", indexed under (j)(2).
    • The base is still AMTI. §55(b)(1)(A)(ii) defines the taxable excess as "so much of the alternative minimum taxable income for the taxable year as exceeds the exemption amount".
    • The 2017 form does the same. Form 6251 line 30 is "Subtract line 29 from line 28" (line 28 is AMTI). Exemption Worksheet line 2 reads "Enter your alternative minimum taxable income (AMTI) from Form 6251, line 28". Lines 7-10 then cap the exemption at "$7,500" plus earned income: "Enter the smaller of line 6 or line 9" (file page 10).
    • The old code had no basis. No pre-2018 form or instruction swaps regular taxable income in for a child. The PR's new formula, max_(0, amt_income - amt_exemption), is §55(b)(1)(A)(ii).
  • Joint returns.
    • §1(g)(2) applies only if "(C) such child does not file a joint return for the taxable year".
    • The 2017 instructions (file page 11) say: "Exception. If you filed a joint return for 2017 or neither of your parents was alive at the end of 2017, don't complete lines 7 through 10 of the Exemption Worksheet." The 2015 and 2016 instructions say the same.
    • The new & ~joint is right. Before, two young spouses filing jointly got the child cap.
  • 2018 and later are unchanged. The cap is off from 2018, and the statute keeps it off in 2026:
    • The current statute. Current §55(d)(4)(A) reads: "In the case of any taxable year beginning after December 31, 2017 — ... (iii) subsection (j) of section 59 shall not apply."
    • Who turned it off. The SECURE Act (Pub. L. 116-94, §501(b)) added clause (iii), "applicable to taxable years beginning after Dec. 31, 2017". TCJA itself didn't repeal §59(j). The original 2018 Form 6251 still applied the cap, and the Rev. February 2020 instructions (file page 10) say the SECURE Act "retroactively suspended the special exemption limit".
    • OBBBA. §70107(a)(1) "struck out ', and before January 1, 2026' after 'December 31, 2017'", effective for years after 2025. So §59(j) stays off in 2026 and later. child/in_effect.yaml (false from 2018, with no 2026 entry) matches.
    • The code. In those years amt_exemption returns before it reads amt_kiddie_tax_applies. amt_income_less_exemptions already used AMTI there. No state reads amt_kiddie_tax_applies (Colorado reads amt_income_less_exemptions, which doesn't change after 2017). So 2018+ outputs are unchanged, as the 2018, 2021, 2022, 2025 and 2026 cases confirm.
  • The pre-2018 income base (Form 6251 lines 1-7, 2015-2017). The PR doesn't change amt_income, but child filers now go through it. Its add-backs check out:
    • Standard deduction and exemptions. Line 1 is Form 1040 line 41 for itemizers and line 38 (AGI) otherwise; neither subtracts personal exemptions. amt_income gives taxable_income + exemptions (or agi - deductions at zero taxable income), plus the standard deduction for non-itemizers.
    • Medical. The 2015-2016 line 2 adds back 2.5% of AGI only "If you or your spouse was 65 or older". The 2017 line 2 is "Reserved for future use". The model's medical floor is 10% for everyone in 2013-2016 and 7.5% from 2017, so it needs no AMT add-back.
    • Taxes and miscellaneous deductions. Line 3 ("Taxes from Schedule A, line 9") and line 5 (Schedule A line 27, after the 2% floor) match salt_deduction and misc_deduction in itemized_deductions_add_back.yaml.
    • Lines 4, 6 and 7 are pre-existing gaps (see G3).
  • Tests. I hand-checked 11 cases (about 40 expectations), listed under Verified correct. CI passed every changed file.
  • Batch checks. The source-conflict ratchet passes on the merged tree, in CI and statically. The PR has no state-gated key and changes no income-source list.

There are no critical findings and three should-address items:

  • S1. Invalid Axiom line. axiom: pending: hub files parity is not an allowed form, as on Fix the 2021 CTC split and surviving-spouse phaseout threshold #10306. rulespec-us encodes only the post-2017 off switch, not the pre-2018 cap or §1(g)(2)(C).
  • S2. Missing coverage for the newly reached branches. Child filers now get every AMTI add-back, but the tests cover only the non-itemizer path. No pre-2018 case has a child whose earned income lifts the cap above the ordinary exemption.
  • S3. The Python property test. It keeps copies of the old formulas and builds a second reformed system. Its JOINT-only assertion locks in the rest of the old predicate. The PR body doesn't report the CI cost that CLAUDE.md requires: about 22 s, and a larger peak in the Rest Python policy group.

There are also four suggestions: the stale PR body, gaps left in the child test, pre-existing pre-2018 AMTI gaps, and references for the pre-2019 child amounts.

Status.

Critical (must fix)

None.

Should address

S1. The Axiom line axiom: pending: hub files parity is not a valid form

  • Where: the last line of the PR body.
  • The rule (CLAUDE.md, "Axiom Parity"): a PR that adds, updates or fixes policy needs one of encoded-correct, <rulespec PR> encoded, <rulespec issue> queued or n/a: <reason>. pending is not one of them. We flagged the same form on Fix the 2021 CTC split and surviving-spouse phaseout threshold #10306.
  • Why n/a doesn't fit. §59(j)(1), §1(g)(2)(C) and §55(b)(1)(A)(ii) are statutory, and the PR changes 2013-2017 AMT for child filers and young joint filers.
  • What rulespec-us has (main 5796861e58):
    • us/statutes/26/59.yaml has section_59_j_kiddie_exemption_limit_applies, with a single version from 2018-01-01 whose formula is false. That matches the model from 2018, so encoded-correct is available for the switch.
    • No pre-2018 version. There is no version before 2018: no cap of earned income plus the indexed amount, and no §1(g)(2) applicability rule. us/statutes/26/1/ has only h and j.
    • 55.yaml is "Bounded tax-year-2026": every rule, including amt_income_less_exemptions, starts 2026-01-01.
    • Issues. Searches for "59(j)", "kiddie", "1(g)", "Form 6251" and "alternative minimum" found California integration test #1424 (§1(g)(7) parent's election) and Add South Dakota Medicaid expansion #1487 (§55(b)(3)). Neither covers these provisions.
  • Severity: should. An invalid Axiom line is a merge blocker, not a model error.
  • Fix: file an open, pe-parity-labelled, dispatch-ready issue in TheAxiomFoundation/rulespec-us, modelled on Add AMT parameters for 2020-2023 #1467. Then change the line to axiom: us:statutes/26/59#section_59_j_kiddie_exemption_limit_applies encoded-correct; TheAxiomFoundation/rulespec-us#<n> queued. Alternatively, run the signed encoder and cite the PR as encoded. The issue needs:
    • Target module: a pre-2018 version (taxable years 2013-2017) of the child limit in us/statutes/26/59.yaml. It also needs a §1(g)(2) applicability rule (a new us/statutes/26/1/g.yaml, or inputs) and the taxable excess for those years.
    • Corpus citations: us/statute/26/59/j, us/statute/26/1/g/2, us/statute/26/55/b/1 and us/statute/26/55/d/4.
    • Verbatim law: §59(j)(1)-(2) and §1(g)(2)(A)-(C) (2016 edition), §55(b)(1)(A)(ii), and §55(d)(4)(A)(iii) for the cut-off. All are quoted under Summary.
    • Required outputs: whether §1(g) applies to the filer, the §59(j) cap, the exemption after the cap, and the taxable excess.
    • Companion tests from the 2017 Form 6251, its Exemption Worksheet and Rev. Proc. 2016-55 §3.10-3.11, not from PolicyEngine output. These are the same sources as the PR's YAML. All are single filers aged 17 unless noted:
      • AMTI $50,000, earned income $0. Worksheet line 6 is $54,300, line 9 is $7,500 and line 10 is $7,500. Form line 30 is $42,500 and line 31 is $11,050.
      • AMTI $200,000, earned income $10,000. Line 4 is $79,300, line 5 is $19,825, line 6 is $34,475, line 9 is $17,500 and line 10 is $17,500. Line 30 is $182,500.
      • Joint, spouses 18 and 17, AMTI $150,000, earned income $29,000. The Exception skips lines 7-10, so line 29 is $84,500 and line 30 is $65,500.
      • Separate return, age 18, no earned income: the limit applies.
    • Why queued: the blocker. The likely one is scope, since rulespec-us individual-tax modules are bounded to 2026. Approval for a billed run is another.

S2. Missing coverage for the branches child filers now reach

  • Where: T/income/amt_income_less_exemptions.yaml:108 and T/exemption/amt_exemption.yaml:70.

  • Child filers now reach every AMTI add-back. Before the PR, a pre-2018 child's base skipped amt_income. It now includes all of its add-backs:

    • the standard deduction and personal exemptions (Form 6251 line 1);
    • taxes (line 3) and miscellaneous deductions after the 2% floor (line 5) for itemizers;
    • the separate-return addition.
  • What the new cases cover. The new 2017 child case covers only the non-itemizer path (standard deduction 6,350 and exemption 4,050). No pre-2018 case has an itemizing child, so the line 3 and line 5 add-backs never reach a child's base in a test.

  • No case on the other side of the cap. The pre-2018 cases have earned income of $0 or $10,000, so the cap always binds. None has a child whose earned income plus the indexed amount exceeds the phased-out exemption, so the ordinary exemption never binds for a child.

  • Severity: should (missing edge tests for changed logic).

  • Fix: add these 2017 cases (TX, single, age 17, not claimed elsewhere, with upstream inputs supplied as in the PR's own 2017 cases). I computed the values from the 2017 Form 6251, the Exemption Worksheet and Rev. Proc. 2016-55. The last column shows what the old code gave.

    Case Taxable income AMTI amt_exemption amt_income_less_exemptions amt_base_tax Old base
    Itemizer: interest 60,000; SALT 6,000; misc 1,500 (2,700 of expenses less 2% × 60,000); charity 1,000; exemption 4,050 47,450 59,000 7,500 51,500 13,390 39,950
    Wages 46,000 + interest 30,000; standard deduction 6,350; exemption 4,050 65,600 76,000 53,500 (cap binds) 22,500 5,850 12,100
    Wages 47,000 + interest 30,000; same deductions 66,600 77,000 54,300 (cap 54,500 does not bind) 22,700 5,902 12,300
    • The itemizer row. Line 1 = 60,000 − 8,500 = 51,500. Add line 3 (6,000) and line 5 (1,500) to get 59,000. The charitable deduction stays deducted. AGI is below the $261,500 Pease threshold.
    • The wage rows sit on either side of the boundary at earned income of $46,800, where 46,800 + 7,500 = 54,300.

S3. The Python property test keeps old formulas, locks in the old predicate and has no cost report

  • Where: T/test_post_2017_child_amt_invariance.py.
  • What it does. Lines 36-74 restate both old formulas as a Reform. Lines 77-79 build that reformed system in a module fixture. The test then compares the two systems over 2018-2030 for about 365 tax units in each of 3 Hypothesis examples.
  • It locks in the old predicate. Line 216 asserts that amt_kiddie_tax_applies changes only for JOINT rows. Line 214 asserts that it changes somewhere in every year.
    • Why it matters. The predicate still omits the §1(g)(2)(A)(ii)(II) support test for 18-year-olds and students (G2). A later legal fix there would change only pre-2018 results, yet it would fail this test.
    • What it really checks. The test pins the variable to the legacy formula, which is more than its docstring ("post-2017 AMT unchanged") says.
  • CLAUDE.md ("Test design and CI cost"):
    • Duplicated formula. "Do not duplicate a policy formula in Python merely to test it against itself." After merge, the reform is a frozen copy of code the PR deletes.
    • YAML can do this. The post-2017 claim is already covered in YAML: the 2018 transition case, the 2021 and 2022 controls, and the 2025 and 2026 cases.
    • No measurement. "Measure simulation-heavy additions" asks for elapsed time, including setup, and peak memory for tests that build models. The PR body gives neither.
  • The cost, as measured in CI (Linux; not a controlled before/after):
    • Quick Feedback: 11.70 s call and 8.36 s setup.
    • Rest Python policy group: 17.94 s call and 3.99 s setup, the eighth-slowest of 981 tests.
    • Memory. That group peaked at 11,954,096 KiB (11.4 GiB). Two PR runs on the same main base peaked at 11,590,704 and 11,608,628 KiB, so about 340-355 MiB more here. The Makefile's stated goal for these groups is about 10 GB per process, on a 16 GB runner.
  • Severity: should (a code-pattern violation of the repository's test rules).
  • Fix: delete the file, since the YAML controls carry the post-2017 claim. If you keep it, drop the assertions on lines 214-216, say in the PR body which failure only this test catches, and give the CI cost above.

Suggestions

G1. The PR body is stale

  • Draft language. It opens with "WIP:" although the PR is not a draft.
  • Stale validation section. It says folder validation is incomplete: qualified_dividend_netting.yaml was stopped and regular_tax_before_credits.yaml wasn't started. The Baseline (irs) suite has since passed in CI, including both files.
  • Hub analysis is still listed as pending.
  • Fix (optional): replace the validation section with the CI result, and fix the Axiom line (S1).

G2. Gaps left in the child test, all pre-existing

  • The support test. §1(g)(2)(A)(ii)(II) applies the cap to an 18-year-old, or a full-time student aged 19-23, only if "earned income ... does not exceed one-half of the amount of the individual's support". The 2017 instructions list this as conditions 2 and 3. amt_kiddie_tax_applies doesn't model it, so the 2017 test "A young separate filer remains subject to the child limitation" (T/kiddie_tax/amt_kiddie_tax_applies.yaml:110) holds only because the filer has no earnings.
  • The spouse-age clause is now redundant. After the joint exclusion, no_or_young_spouse (V/kiddie_tax/amt_kiddie_tax_applies.py:49) matters only for a non-joint unit that contains a spouse. That happens only with a hand-set filing_status. In that case §1(g)(2) looks at the filer alone, so the clause would wrongly drop the cap.
  • The living-parent condition (§1(g)(2)(B)) is assumed, as the test comment notes.
  • Fix (optional):

G3. Pre-existing pre-2018 AMTI gaps that child filers now inherit

These predate the PR, which doesn't claim them. Because children now go through amt_income, they apply to child filers too.

  • Line 6, the overall limit (Pease). The 2017 instructions say: if Form 1040 line 38 is over $261,500 ($313,800 joint), "enter the amount from line 9 of the Itemized Deductions Worksheet ... Enter it as a negative amount". amt_income adds back SALT and miscellaneous deductions in full but never subtracts the §68 reduction (itemized_taxable_income_deductions_reduction, active 2013-2017). So it overstates 2013-2017 AMTI for high-income itemizers.
  • Line 7 (tax refunds) isn't modelled either. Draft Leave state tax refunds and the section 68 reduction out of federal AMTI, and fix Colorado AMTI to match #10099 (same author) addresses lines 6 and 7.
  • Line 4 (home mortgage interest adjustment) isn't modelled.
  • The standard deduction (gov.irs.deductions.standard.amount) has no values before 2018, so 2017 backdates $12,000/$24,000. The PR's tests work around this by supplying 2017 amounts. AMTI for non-itemizers is AGI either way, but end-to-end 2017 regular tax, and so AMT, is still off.
  • Fix (optional): mention these in the PR body, and open an issue for the standard deduction and line 4 if none exists.

G4. References for the pre-2019 child amounts

  • The gap. policyengine_us/parameters/gov/irs/income/amt/exemption/child/amount.yaml has references only from 2019. The PR's fix now depends on its 2013-2018 values ($7,400 for 2015-2016, $7,500 for 2017).
  • The values are right. Rev. Proc. 2016-55 §3.11 (file page 14) gives "$7,500" for 2017. The 2015 and 2016 Exemption Worksheets, line 7, give "$7,400".
  • Fix (optional): add those Rev. Procs. as references.

Verified correct

  • Hand-checks (11 cases, about 40 expectations). Each is TX.
    1. T/exemption/amt_exemption.yaml:203, 2017 joint, spouses 18 and 17, wages 29,000, interest 121,000.
      • Taxable income = 150,000 − 12,700 − 2 × 4,050 = 129,200. Rev. Proc. 2016-55 gives the $12,700 standard deduction (file page 15) and the $4,050 exemption (file page 18).
      • AMTI = 129,200 + 8,100 + 12,700 = 150,000. That is below the $160,900 joint phase-out start, so the exemption is $84,500 (§3.10, file page 13). The Exception applies, so amt_kiddie_tax_applies is false.
      • Base = 65,500 and the tentative tax is 26% × 65,500 = 17,030.
      • Regular tax = 10,452.50 + 25% × 53,300 = 23,777.50 (Table 1, file page 7), so AMT is 0.
      • Old code: cap 29,000 + 7,500 = 36,500, base 129,200 − 36,500 = 92,700, tax 24,102, AMT 324.50.
    2. T/income/amt_income_less_exemptions.yaml:108, 2017 single, age 17, interest 50,000.
      • Taxable income = 50,000 − 6,350 − 4,050 = 39,600. AMTI = 50,000.
      • Exemption = min(54,300, 0 + 7,500) = 7,500 (earned income below the limit). The base is 42,500 and the tax 11,050.
      • Old code: base 32,100.
    3. Same file, line 47, 2017, AMTI 200,000, earnings 10,000.
      • Exemption. The phase-out is 25% × 79,300 = 19,825, leaving 34,475, and the cap is 17,500. So the exemption is 17,500, the base 182,500 and the old base 172,500.
      • T/exemption/amt_exemption.yaml:70 (17,500) is unchanged.
    4. Same file, line 146, 2025 single, age 17, interest 200,000.
      • Taxable income = 200,000 − 15,750 = 184,250. AMTI is 200,000 and the exemption 88,100 (no cap after 2017).
      • The base is 111,900 and the tax 29,094.
    5. T/exemption/amt_exemption.yaml:252, 2021 joint, wages 29,000.
      • Taxable income is 29,000 − 25,100 = 3,900, and AMTI is 29,000.
      • The exemption is 114,600, so the base and AMT are 0.
    6. Same file, line 284 (2022). Taxable income is 29,000 − 25,900 = 3,100, the exemption 118,100, and the base 0.
    7. Same file, lines 134 and 170, joint with both spouses 18. amt_kiddie_tax_applies is false. The exemption is 137,000 in 2025 and 140,200 in 2026, unchanged from before.
    8. T/income/amt_income_less_exemptions.yaml:88, 2025 joint with both spouses 18, AMTI 100,000. The predicate is false and the base is 0.
    9. T/kiddie_tax/amt_kiddie_tax_applies.yaml:
      • Line 110 (2017, separate return, age 18): true. §1(g)(2)(C) excludes only joint returns.
      • Line 118 (2017, joint, students aged 22 and 23): false. The old code gave true, because both are under 24.
      • Line 18 (2022, joint, both 18): false.
    10. 2017 against 2018.
      • T/exemption/amt_exemption.yaml:86 (2018, single, age 18, no earnings): 70,300 with no cap, because in_effect is false from 2018.
      • The 2017 cap. For 2017 the cap uses the $7,500 amount.
    11. The indexed amount. child/amount.yaml matches the IRS:
      • 2015 and 2016: $7,400 (worksheet line 7, file page 10 of each year's instructions).
      • 2017: $7,500 (Rev. Proc. 2016-55 §3.11).
  • The 2015-2017 form.
    • The exemption amounts and phase-out starts in amount.yaml and phase_out/start.yaml match the line 29 charts. In 2017, for example, they are $54,300/$120,700, $84,500/$160,900 and $42,250/$80,450.
    • The 26% bracket ends at $187,800 in 2017, which matches line 31.
  • Callers at the head:
    • amt_kiddie_tax_applies is read only by amt_exemption, and only while child.in_effect is true (before 2018).
    • amt_income_less_exemptions feeds the federal AMT, the §911 variables and co_alternative_minimum_taxable_income. Those change only before 2018. The Colorado AMT rate starts in 2021.
    • California. ca_amt_exemption reads only child.amount and its own child test, so it is unaffected.
  • References.
    • The 2017 instructions. File page 10 holds the Exemption Worksheet (amt_income_less_exemptions.py), and file page 11 holds the joint-return Exception (amt_kiddie_tax_applies.py).
    • Rev. Proc. 2016-55. The test comments cite pages 7, 15 and 18, which show the rate table, the standard deduction and the exemption.
    • §56(b)(1)(D), cited in a test comment, is the standard-deduction and exemption disallowance.
  • Changelog. changelog.d/amt-child-pre-2018.fixed.md is a top-level fragment, and fixed is the right type.
  • Batch lessons:

Axiom line assessment

Invalid: axiom: pending: hub files parity (S1).

  • Recommended form: axiom: us:statutes/26/59#section_59_j_kiddie_exemption_limit_applies encoded-correct; TheAxiomFoundation/rulespec-us#<n> queued. The queued issue is a new open, pe-parity-labelled, dispatch-ready issue for the pre-2018 §59(j)(1) cap, the §1(g)(2) applicability (including (C)) and the taxable excess. The required contents are listed under S1. Alternatively, cite an encoder PR as encoded.
  • Not n/a. These are statutory provisions, and the PR changes 2013-2017 AMT results.
  • encoded-correct covers only the post-2017 switch. rulespec-us main (5796861e58) encodes the switch as false from 2018-01-01, which matches the model. It has no pre-2018 version, and 55.yaml covers only 2026.

CI status

27 pass, 3 skipping, 0 pending, 0 failed. I re-checked gh pr checks 10308 at the end of this review, and the head is still e7b010e809.

  • The green run. Run 39608011006 (pull_request, created 2026-10-21 13:26 UTC) tested merge commit 70ec14b2. That merges the head into fec98b87c8 (Count the FL and SD LIHEAP comments in the source-conflict ratchet baseline #10334), so CI's tree contains the Tie source-conflict markers to the register shared with Axiom #10287 ratchet and the FL/SD LIHEAP baseline fix. The known main-caused ratchet failure therefore doesn't apply.
  • The skipped checks are the umbrella jobs of the superseded run 39507938034 (2026-10-20 22:25 UTC).
  • Where the changed tests ran.
    • Quick Feedback selected and passed: amt_exemption.yaml (17), amt_income_less_exemptions.yaml (10), amt_kiddie_tax_applies.yaml (11) and the property test (1).
    • Full suites. The Baseline (irs) suite, the Rest Python policy group (which reran the property test), Microsimulation and Household API Partners all passed.
  • Locally: I ran no tests or model probes. CI is green at the current head. Every expectation and the S2 values were checked by hand from the code, the parameters and the IRS sources.

Branch status

0 critical, 3 should and 4 suggestions are open at this head.

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.

Both changes are correct. s.59(j) caps only the exemption, joint returns are excluded under s.1(g)(2)(C), s.59(j) stays off from 2018 (SECURE Act, and OBBBA s.70107 struck the 2026 end date), and the pre-2018 add-backs match Form 6251. Three should-address items: (1) the 'pending: hub files parity' axiom line is invalid (cite 59(j) encoded-correct, plus a queued issue for the pre-2018 cap and 1(g)(2)); (2) add cases for an itemizing 2017 child and a pair either side of the cap; (3) the Python property test restates the old formulas and locks in the old predicate, with no cost report. Four suggestions. Full report in the comment above.

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub. I'll post a hand-off comment here when it's ready.


Generated by Claude Code

Review follow-up. Section 1(g)(2)(A)(ii) covers an 18-year-old, or a
full-time student under 24, only when earned income is at most half of
support, so the predicate now reads care_and_support_costs. Section
1(g)(2) looks at the filer alone, so the spouse-age clause is dropped;
a joint return still excludes the unit under 1(g)(2)(C). The model has
no input for a living parent (1(g)(2)(B)), which the variable now
documents.

Adds cases for an itemizing 2017 child, a pair either side of the
59(j) cap, and the support-test boundary, plus references for the
2015-2017 child amounts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6JJsMauziPgWAqgG3k
The property test restated the pre-fix formulas as a reform and pinned
the predicate to them. It now checks what follows from the law: the
59(j) limit bounds a covered child's exemption before 2018, joint
returns are never covered under 1(g)(2)(C), and from 2018 a child's
exemption equals an identical adult's under 55(d)(4)(A)(iii). It builds
one simulation per example and no reformed system, and the file is
renamed to match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwQq6JJsMauziPgWAqgG3k
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Picking this up for the US hub, continuing an earlier session's work on DTrim99's review (that session pushed no commits here). I'll post a hand-off comment here when it's ready.


Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review

Head: 65b6b346315bb6f9c4bbe2d7e95cd4ad9c4c6fb6

This continues an earlier session's work. That session pushed no commits here, so there was nothing of its to keep or fix. Since then nobody but me has pushed or commented here after 2026-11-05T00:29Z. The head before this push was e7b010e809, as the brief named.

Path prefixes:

  • V/ = policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/
  • T/ = policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/

Commits

  • ec2b5354fb: merge of origin/main (0315452), clean.
  • 5f6f299915: the §1(g)(2) fixes, the new cases, and references for the 2015-2017 child amounts.
  • 65b6b34631: the property test rewrite, renamed to T/test_child_amt_exemption_properties.py.

What the item asked for

2. Cases (all 2017, TX, single, not claimed elsewhere, with 2017 upstream return amounts supplied as in the PR's existing cases; Rev. Proc. 2016-55 single exemption 54,300, phase-out start 120,700, child amount 7,500, 26% bracket):

  • Itemizing child (T/income/amt_income_less_exemptions.yaml:179). Age 17, interest 60,000; SALT 6,000, misc 1,500 (2,700 less 2% × 60,000), charity 1,000; exemption 4,050.
    • Taxable income = 60,000 − 8,500 − 4,050 = 47,450.
    • Form 6251 line 1 = 47,450 + 4,050 = 51,500. Adding line 3 (6,000) and line 5 (1,500) gives AMTI = 59,000.
    • Exemption = min(54,300, 0 + 7,500) = 7,500. Base = 51,500. Tax = 26% × 51,500 = 13,390.
    • The old base would have been 47,450 − 7,500 = 39,950.
  • Either side of the §59(j) cap (the hub's companion rows 7 and 8, which I checked). The cap equals 54,300 at earned income 46,800.
    • :219: wages 46,000 + interest 30,000. Taxable income 65,600; AMTI 76,000; exemption min(54,300, 53,500) = 53,500; base 22,500; tax 5,850.
    • :255: wages 47,000 + interest 30,000. Taxable income 66,600; AMTI 77,000; exemption min(54,300, 54,500) = 54,300; base 22,700; tax 5,902.
    • Both match the hub's rows ($53,500 against $54,300; $22,500 against $22,700).

3. Property test (T/test_child_amt_exemption_properties.py). It no longer restates the old formulas or builds a reformed system. For each generated household it adds an otherwise identical 40-year-old twin and checks, in 2015, 2017, 2018, 2025 and 2026:

  • :114: a joint return is never covered (§1(g)(2)(C)), in every year;
  • :116: a 40-year-old is never covered;
  • the exemption never exceeds the statutory amount for the filing status;
  • :127: before 2018, a covered child's exemption equals the twin's capped at earned income plus the child amount (§59(j)(1)), and the cap binds in at least one fixed case;
  • :136: from 2018, the child's exemption equals the twin's (§55(d)(4)(A)(iii)).

Five fixed cases (:29) keep each property non-vacuous. Hypothesis runs 5 derandomized examples of up to 20 extra households each.

Cost, measured locally on this 4 vCPU Linux VM (not CI), one pytest process each:

call setup pytest total peak RSS
Old test 19.7 s 10.7 s 38.5 s 2,054,980 KiB
New test 4.2 s none 8.7 s 1,027,668 KiB

Peak RSS is the whole pytest process, including importing the model. The new file is in the same Rest Python policy group as the old one, and test_rest_python_groups.py passes (62).

Hub parity inputs: the three §1(g)(2) departures.

  • (a) Support test: fixed. V/kiddie_tax/amt_kiddie_tax_applies.py:50.
    • A filer under 18 is covered with no support test.
    • At 18, or as a full-time student under 24 (the §152(c)(3) age test), the filer is covered only if the head's adjusted_earnings is at most half of the head's care_and_support_costs.
    • New parameters: gov.irs.income.amt.exemption.child.support_test.age_threshold (18) and .rate (0.5).
    • Cases at T/kiddie_tax/amt_kiddie_tax_applies.yaml:145-213: equal to half (true), 1 above half (false), a student aged 22 (true), a 17-year-old with earnings (true), and no reported support (false). T/income/amt_income_less_exemptions.yaml:293 carries the failed test through to the exemption: 54,300 instead of a 17,500 cap.
  • (b) Living parent: not supported, documented. The model has no input for whether a parent is alive at year end. The variable's documentation and :55 say so. Fixing it needs a new person or tax-unit input, which I didn't add.
  • (c) Spouse-age clause: dropped. The predicate reads only the head's facts (:31); a joint return still excludes the unit (:59). The old 2022 case "Kiddie tax does not apply when spouse is 19+" is now T/kiddie_tax/amt_kiddie_tax_applies.yaml:12: a 2017 separate filer aged 18 with a 30-year-old spouse is covered.

I didn't see the hub draft's rows 9 and 10, so the (a) cases are my own, hand-computed above.

Suggestions

  • G1 (PR body): taken. Dropped "WIP", replaced the stale validation section, and ticked the finished plan items. I left the axiom: line as the hub wrote it.
  • G2 (child-test gaps): taken. These are the hub's (a), (b) and (c) above. I didn't link Unify AMT "kiddie tax" child definition with EITC one #3908 or Refactor the Kiddie Tax logic and separate it from AMT #5087, because I didn't read them.
  • G3 (pre-2018 AMTI gaps): listed in the PR body only. They predate the PR, and this brief rules out filing issues.
  • G4 (references): taken. policyengine_us/parameters/gov/irs/income/amt/exemption/child/amount.yaml:22-27 adds Rev. Proc. 2016-55 (file page 14) for 2017 and the 2015 and 2016 Form 6251 instructions (file page 10). The page numbers are the review's; I couldn't reach irs.gov to check them.

Tests (one file per process)

  • T/kiddie_tax/amt_kiddie_tax_applies.yaml: 16 passed.
  • T/income/amt_income_less_exemptions.yaml: 14 passed.
  • T/exemption/amt_exemption.yaml: 17 passed.
  • T/test_child_amt_exemption_properties.py: 1 passed.
  • policyengine_us/tests/code_health/test_source_conflicts.py: 103 passed.
  • Other code-health checks: test_rest_python_groups.py 62, test_parameter_yaml_keys.py 34, test_reference_urls.py 32, test_no_concatenated_reference_urls.py 48, test_known_dead_reference_urls.py 40, test_selective_test_batches.py 18 and test_no_builtin_sum_over_entity_calls.py 1. All passed.

Mutants (each in its own process, code restored after each)

Mutant Killed by
Drop & ~joint predicate YAML (3 failures); property test
Drop the support test predicate YAML (2); amt_income_less_exemptions.yaml (1). The property test doesn't check it.
Restore a spouse-age clause predicate YAML (1)
Apply the child cap after 2017 (in_effect ignored) property test
Restore the old regular-taxable-income base before 2018 amt_income_less_exemptions.yaml (5)

The first version of the property test let the joint mutant survive: its fixed joint case was an 18-year-old with earnings and no support, so the support test already excluded it. I changed that case's head to age 17. One mutant run of the property test hung under Hypothesis and was killed; the rerun with a timeout completed. While I was killing it, a backup copy overwrote amt_exemption.py locally; I restored it and amt_income_less_exemptions.py from git before committing, so neither file differs from the merge.

CI

Pull request run 41948903573 at 65b6b34631: completed, success, with all 36 jobs passing. That includes Quick Feedback, Baseline (irs), the Rest Python shards, Microsimulation and Household API Partners. No partner test changed.

Judgment calls

  • Support when unreported. care_and_support_costs defaults to 0. Read literally, an 18-year-old or student with any earned income then fails the test (earnings above half of 0), and the cap doesn't apply. Leaving support at 0 means "treat as self-supporting". The alternative, treating 0 as unknown and assuming the test is met, would keep the old behavior but can't tell a real 0 from a missing value. This affects only 2013-2017, and the default data has no records for those years.
  • Whose income and support. The predicate reads the head's own adjusted_earnings (the §911(d)(2) measure amt_exemption already uses) and care_and_support_costs. A joint return is excluded anyway, so the head is the only filer the predicate ever covers.
  • Statute text. govinfo.gov and irs.gov were unreachable from this VM. The support-test words in the YAML comment are DTrim99's quotation of §1(g)(2)(A)(ii)(II). The age split (under 18 unconditionally; otherwise the §152(c)(3) test plus support) follows the 2017 instruction conditions as the review describes them, and is unverified by me against the 2016 text.
  • The renamed test file. Its old name described the replaced test. Nothing in the Makefile or workflows names the file.

Follow-up issues I'd propose

  • "Model the §1(g)(2)(B) living-parent condition for the pre-2018 AMT child limit." Add an input for whether either parent is alive at year end and read it in amt_kiddie_tax_applies. Without it, a child whose parents have both died still gets the 2013-2017 cap.
  • "Pre-2018 AMTI: Pease (line 6), refunds (line 7), mortgage interest (line 4) and the 2013-2017 standard deduction." These gaps now reach child filers. Leave state tax refunds and the section 68 reduction out of federal AMTI, and fix Colorado AMTI to match #10099 covers lines 6 and 7; the other two have no PR.

Reply to DTrim99

Thanks for the review. Item by item:

  • S1 (Axiom line). The hub handles this line. It now cites rulespec-us#1857 as queued; I didn't change it.
  • S2 (coverage).
    • Added your three 2017 rows, with the values you gave: the itemizer (T/income/amt_income_less_exemptions.yaml:179), the binding cap (:219) and the non-binding cap (:255).
    • They pass, and the old-base mutant fails all three.
  • S3 (property test).
    • Rewritten as T/test_child_amt_exemption_properties.py. It drops the reform and the JOINT-only assertion, and checks the §1(g)(2)(C), §59(j)(1) and §55(d)(4)(A)(iii) properties against an identical adult twin.
    • Local cost: 4.2 s call with no setup and 1.0 GB peak, against 19.7 s + 10.7 s and 2.0 GB for the old test.
    • I kept a Python test because the twin comparison checks every filing status and many incomes in one simulation, which YAML cases would have to enumerate one at a time.
  • G1: done (PR body).
  • G2:
    • The support test is now modeled from care_and_support_costs (V/kiddie_tax/amt_kiddie_tax_applies.py:50).
    • The spouse clause is dropped.
    • The living-parent assumption is documented in the variable.
  • G3: noted in the PR body; no code change.
  • G4: references added in child/amount.yaml.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Hub review in progress

Reviewing head 65b6b34631bfead5ba20296778b6b0770371a9fa (unchanged since the hand-off), as a delta from e7b010e809. The findings below are provisional.

  • Main reproduces the defect: the head's three YAML files fail 15 of 47 cases against main's code (3 in amt_exemption.yaml, 7 in amt_income_less_exemptions.yaml, 5 in amt_kiddie_tax_applies.yaml).
  • The changed files pass one per process at the head: 16, 14 and 17 YAML cases, the property test (1), and test_source_conflicts.py (103).
  • CI run 41948903573 at the head: 36 of 36 jobs succeeded. git merge-tree against main (0315452583, unchanged) is clean.
  • Provisional P2: an unreported support amount (care_and_support_costs is 0) now treats any 18-year-old or student under 24 who has earnings as self-supporting. For example, a 2017 full-time student aged 20 with $5,000 of wages and $60,000 AMTI gets an exemption of 54,300 at the head, against 12,500 on main.
  • Provisional P3: amt_exemption still adds a spouse's earnings to the §59(j) cap for a separate filer that the predicate now covers.
  • Provisional P3: the hand-off's full head SHA (65b6b346315bb6f9…) isn't an object in the repository.
  • Mutants are still running.

Generated by Claude Code

@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Ready for hub review
VERDICT: REQUEST_CHANGES
65b6b34
METHODOLOGY_CHOICE: yes

This is a round 2 delta review: the fix session's changes from e7b010e809 (the head DTrim99 reviewed) to 65b6b34631. The head hasn't moved since the hand-off. The commits since e7b010e809 are:

  • ec2b5354fb, which merges main at 0315452583;
  • 5f6f299915, the §1(g)(2) changes, the cases and the references;
  • 65b6b34631, the property test rewrite.

Path prefixes:

  • V/ = policyengine_us/variables/gov/irs/tax/federal_income/alternative_minimum_tax/
  • T/ = policyengine_us/tests/policy/baseline/gov/irs/tax/federal_income/alternative_minimum_tax/

Sources. This VM couldn't reach govinfo.gov, irs.gov, uscode.house.gov, ecfr.gov or law.cornell.edu: the proxy refused each one, through both curl and the fetch tool. I quote no law from memory. On the statute I work from the hub's reading of the 2016 edition of §1(g)(2) and §59(j), taken on 2026-11-05 and given in the brief. On the 2017 Form 6251 and Rev. Proc. 2016-55 I work from DTrim99's quotations in his review. I couldn't reach any of these sources myself (see "What I couldn't check").

Summary. The base fix and the joint-return exclusion hold.

  • At main, the head's three YAML files fail 15 of 47 cases, so the defect reproduces.
  • Every new 2017 case matches my hand computation.
  • CI is green, and the head merges cleanly.

I ask for one change: the PR's reading of an unreported support amount (P2-1). For a common household, a full-time student aged 19 to 23 (or an 18-year-old) with a part-time job, it makes the 2013–2017 result worse than main. The remaining findings are smaller.

Findings

P2-1. An unreported support amount now removes the §59(j) cap for every 18-year-old or student under 24 with earnings

Where: V/kiddie_tax/amt_kiddie_tax_applies.py:49-50.

support = tax_unit.sum(person("care_and_support_costs", period) * is_head)
meets_support_test = earned_income <= support_test.rate * support

What the law asks. In the brief's reading, §1(g)(2)(A)(ii)(II) covers an older child whose earned income "does not exceed one-half of the amount of the individual's support". That support is the child's total support from every source, so it is never 0 for a living person.

What the code does.

  • care_and_support_costs defaults to 0. The model's only other readers of it are the AZ and ID senior support ratios (via share_of_care_and_support_costs_paid_by_tax_filer), and no federal rule asks a user for it for a child.
  • At the default, any positive earnings fail the test, so the child gets the full exemption.
  • For the typical filer in this group, a full-time student whose parents pay most of their support, the law applies the cap. main applied it; the head drops it.

Failure scenario. 2017, single, a full-time college student aged 20, $5,000 of wages, AMTI set to $60,000, support not entered. I ran the same script against each tree, setting PYTHONPATH so that each run imports its own tree's package:

amt_kiddie_tax_applies amt_exemption amt_income_less_exemptions
main 1 12,500 0 (old taxable-income base)
head 0 54,300 5,700
head, support 30,000 1 12,500 47,500
head with the fix below 1 12,500 47,500

With support entered, the head computes the law's base of 47,500 (60,000 − 12,500). With support left blank it gives 5,700. An 18-year-old with $1 of wages behaves the same way: the head gives 54,300 and the fix gives 7,501.

Why I'm asking for a change. The session flagged this as a judgment call, and the law leaves the missing fact open, so METHODOLOGY_CHOICE is yes. I disagree with the reading it chose:

  • A zero is impossible as a fact, so it can only mean "not entered".
  • Treating "not entered" as fully self-supporting flips the common case, and it does so silently.
  • Treating 0 as unreported keeps main's result unless the user enters support. It still applies the support test whenever support is given.

The population impact should be nil, since the PR body says the default data has no pre-2018 records. The change matters for 2013–2017 household calculations.

Fix. Replace V/kiddie_tax/amt_kiddie_tax_applies.py:50 with:

        # Support is never zero for a living person, so a zero amount means
        # none was reported. Treat the test as met unless support is given.
        support_reported = support > 0
        meets_support_test = ~support_reported | (
            earned_income <= support_test.rate * support
        )

Three companion changes go with it:

  • Documentation. In the variable's documentation, change "(care_and_support_costs, which defaults to zero)" to "(care_and_support_costs; when none is reported the test is treated as met)".
  • The last case. In T/kiddie_tax/amt_kiddie_tax_applies.yaml (the case at line 213), rename it to "An 18-year-old with earnings and no reported support is covered", expect true, and change the comment to # care_and_support_costs is 0, so no support was reported: the test is treated as met.
  • The property test. In T/test_child_amt_exemption_properties.py:43, change the fourth fixed case's support=0 to support=15_000, so that it stays on the failing side (10,000 > 7,500), as the comment at line 27 intends.

I applied the code change on a scratch copy. Only the "no reported support" case fails (1 failed, 15 passed). amt_income_less_exemptions.yaml (14) and amt_exemption.yaml (17) pass, and so does the property test (1).

P3-1. The §59(j) cap still counts a spouse's earnings, although the predicate now judges the filer alone

Where: V/exemption/amt_exemption.py:58. The PR doesn't change this file, but dropping the spouse-age clause makes this branch reachable.

adj_earnings = tax_unit_non_dep_sum("adjusted_earnings", tax_unit, period)

What goes wrong. §59(j)(1)(A) uses "such child's earned income". With the spouse clause gone, a non-joint unit that contains a spouse is now covered. That happens only with a hand-set SEPARATE status. In such a unit the cap adds the spouse's earnings.

Failure scenario. 2017, SEPARATE. The head is 18, with $10,000 of wages and support of $20,000. The spouse is 30, with $40,000 of wages. AMTI is $60,000.

  • By hand: the 2017 separate-filer exemption is 42,250, and the phase-out starts at 80,450, so there is no phase-out. The cap is 10,000 + 7,500 = 17,500, so the exemption is 17,500 and the base is 42,500.
  • At the head, the predicate gives 1 and the exemption 42,250 (the cap is 57,500), so the base is 17,750.
  • With the fix below, the exemption is 17,500 and the base 42,500.
  • main gives 42,250 too, because the old spouse clause dropped the cap. So this case is incomplete rather than a regression.

The same gap leaves a mutant alive. Mutant M8, in which the support test sums every member's earnings instead of only the head's, survives all four changed test files. No case builds a real spouse with earnings.

Fix. Replace V/exemption/amt_exemption.py:58 with:

        # IRC 59(j)(1)(A): the child's own earned income. A joint return is
        # never covered, so this is the head's.
        person = tax_unit.members
        adj_earnings = tax_unit.sum(
            person("adjusted_earnings", period) * person("is_tax_unit_head", period)
        )

Then add the case above to T/exemption/amt_exemption.yaml. Give the head and spouse explicit is_tax_unit_head and is_tax_unit_spouse flags, filing_status: SEPARATE and amt_income: 60_000, and expect amt_kiddie_tax_applies: true, amt_exemption: 17_500 and amt_income_less_exemptions: 42_500. With the fix that case gives 17,500. With the fix plus M8 it gives 42,250, so the case kills M8.

P3-2. The hand-off gives a head SHA that doesn't exist

The hand-off's "Head:" line reads 65b6b346315bb6f9c4bbe2d7e95cd4ad9c4c6fb6. git cat-file -t on it returns "could not get object info". The real head is 65b6b34631bfead5ba20296778b6b0770371a9fa. The two share only the first 10 characters. A gate that checks the hand-off's SHA against the PR head will fail. Use the SHA in this review.

P3-3. The PR body has no impact: line

The plan keeps an unticked "Impact analysis (hub)" item, but there is no impact: line. The hub owns that line; I'm noting it only because the gate requires one.

Can baseline outputs move? Only for 2013–2017:

  • amt_exemption reads amt_kiddie_tax_applies only while child.in_effect is true.
  • The removed else branch of amt_income_less_exemptions already used amt_income.
  • So 2018 and later are unchanged. Mutant M4 confirms this: applying the cap after 2017 fails the 2018, 2025 and 2026 cases.

If any pre-2018 dataset is in use, the population check is a 2017 comparison of amt_income_less_exemptions and alternative_minimum_tax for tax units whose head is under 24.

P3-4. A 2022 test name describes the rule the PR removed

Where: T/kiddie_tax/amt_kiddie_tax_applies.yaml:95, "Kiddie tax does not apply when student spouse is 24+".

That unit has two members and no filing status, so the model gives it JOINT. A probe returned ['JOINT'] [False]. The predicate no longer reads the spouse's age, so the case is false only because of the joint exclusion.

Fix: rename it to "Kiddie tax does not apply to a joint return with a student spouse aged 24", and set filing_status: JOINT on the unit so that the reason is explicit.

Checked and sound

  • The defect reproduces at main. I ran the head's three YAML files against main's code. amt_exemption.yaml has 3 failed and 14 passed; amt_income_less_exemptions.yaml 7 failed and 7 passed; amt_kiddie_tax_applies.yaml 5 failed and 11 passed.

  • Changed tests at the head, one file per process:

    File Result
    amt_kiddie_tax_applies.yaml 16 passed
    amt_income_less_exemptions.yaml 14 passed
    amt_exemption.yaml 17 passed
    test_child_amt_exemption_properties.py 1 passed
    test_source_conflicts.py 103 passed

    The property test needs the dev extra (hypothesis), which pyproject.toml:57 declares.

  • Hand computations, done before I read the YAML. All use 2017 single values: exemption 54,300, phase-out from 120,700, child amount 7,500, standard deduction 6,350, personal exemption 4,050, 26% rate. All match the YAML.

    • Itemizer (T/income/amt_income_less_exemptions.yaml:179). AGI 60,000. Itemized deductions are 6,000 + (2,700 − 2% × 60,000) + 1,000 = 8,500, so taxable income is 47,450. Line 1 is 51,500, and AMTI is 51,500 + 6,000 + 1,500 = 59,000. The exemption is min(54,300, 7,500) = 7,500, the base 51,500 and the tax 13,390.
    • The cap binds (:219). Taxable income 65,600, AMTI 76,000, exemption min(54,300, 53,500) = 53,500, base 22,500, tax 5,850.
    • The cap doesn't bind (:255). Taxable income 66,600, AMTI 77,000, exemption min(54,300, 54,500) = 54,300, base 22,700, tax 5,902. The two cases straddle earned income of 46,800, and they match the hub's companion rows 7 and 8.
    • Fails the support test (:293). 10,000 > 0.5 × 15,000, so the predicate is false. The exemption is 54,300 (it would be 17,500 under the cap), and the base is 60,000 − 54,300 = 5,700.
    • The five predicate cases (T/kiddie_tax/amt_kiddie_tax_applies.yaml:145-213).
      • 10,000 ≤ 10,000 is covered (the equal case is right: "does not exceed").
      • 10,000 > 9,999 is not covered.
      • A student aged 22 with 5,000 ≤ 6,000 is covered.
      • A 17-year-old is covered with no support test.
      • 18 with no support is not covered under the PR's reading; see P2-1.
  • The age split (V/kiddie_tax/amt_kiddie_tax_applies.py:36-53) matches the brief's reading of §1(g)(2)(A):

    • under 18 is covered with no condition;
    • otherwise the filer needs the §152(c)(3) age test (under 19, or under 24 as a full-time student) and the support test.
    • The code doesn't use §152(c)(3)(B), the disability exception, which matches "without regard to subparagraph (B)" as the brief summarises it.
    • Earned income is adjusted_earnings, the same proxy for the §911(d)(2) measure that amt_exemption already uses for the cap.
  • The spouse-age clause. Its removal is consistent with §1(g)(2)(C) as the brief reads it: only a joint return excludes a married child.

    • The replacement 2017 separate-filer case (:12) uses only the tax-unit inputs age_head and age_spouse, and builds no spouse.
    • It kills mutant M3 (restore the clause). See P3-1 for a stronger case with a real spouse.
  • Living parent (§1(g)(2)(B)). The documentation and the comment at :55 say the model assumes a living parent.

    • A person-level is_deceased input exists, but nothing links a filer to their parents, so the gap is real.
    • "No input" is loose wording: "no input links the filer to a parent" would be exact.
  • The property test.

    • It no longer restates formulas or builds a reform. It sets amt_income and adjusted_earnings as inputs, so it tests amt_exemption and the predicate, not the AMTI base; YAML covers the base.
    • Its covered set comes from the model's own predicate. It therefore checks the exemption against the predicate and the joint and adult exclusions, not the support or age tests. Mutants M2, M6, M7 and M8 survive it, as the hand-off says for M2.
    • Hypothesis draws ages 14–30, which reach the 18, 19 and 24 boundaries.
  • Cost, measured locally on a 4 vCPU Linux VM (not CI).

    • New test: 3.21 s call, no setup, 10.56 s in pytest, about 1.0 GB peak RSS for the pytest process (1,029,980 KiB).
    • Old test at e7b010e809: 16.93 s call, 9.00 s setup, about 2.0 GB peak (1,999,148 KiB).
    • In CI run 41948903573 the new test passed in a Rest Python shard with a 1.25 s call, and Quick Feedback selected and ran it.
  • Mutants. Each ran in its own process on a scratch worktree, which I restored after each one; git status was clean every time. The first five are the hand-off's.

    Mutant Predicate YAML Base YAML Exemption YAML Property test
    M1: drop & ~joint 3 fail 1 fail 3 fail fails at :114
    M2: drop the support test 2 fail 1 fail pass pass
    M3: restore the spouse-age clause 1 fail pass pass pass
    M4: cap after 2017 pass 4 fail 5 fail fails at :136
    M5: old taxable-income base before 2018 pass 5 fail pass pass
    M6 (mine): strict < in the support test 6 fail 1 fail 3 fail pass
    M7 (mine): 18-year-olds skip the support test 2 fail 1 fail pass pass
    M8 (mine): support test sums all members' earnings pass pass pass pass (survives; P3-1)
    M9 (mine): predicate always false 8 fail 5 fail 3 fail fails at :124
    M10 (mine): child amount doubled in the cap pass 4 fail 1 fail fails at :125
    M11 (mine): exemption +1 above the statutory amount pass 8 fail 16 fail fails at :118
    M12 (mine): no age test 5 fail 1 fail pass fails at :116
    M13 (mine): cap drops earned income pass 3 fail 1 fail fails at :127
    • The hand-off's kill counts for M2, M3 and M5 match exactly.
    • M1 and M4 are also killed by YAML files the hand-off didn't list.
    • Each property assertion (lines 114, 116, 118, 124, 125, 127 and 136) fails under at least one mutant. Line 133 ("the limit binds for at least one fixed case") is a guard against a vacuous pass and fires only once the earlier assertions hold.
  • Readers.

    • amt_kiddie_tax_applies is read only by amt_exemption, before 2018.
    • amt_income_less_exemptions is read by alternative_minimum_tax, amt_tax_including_cg, the two §911 variables and co_alternative_minimum_taxable_income. All of them change only before 2018. CO's AMT reads it, and DTrim99 found that CO's rate starts in 2021.
    • The new support_test parameters are read only by the predicate.
    • child.amount (references only) is read by amt_exemption. ca_amt_exemption reads California's own amount.
  • Parameters.

    • support_test/age_threshold.yaml (18) and rate.yaml (0.5) have a description, a label, a unit and a single 2013 value.
    • Each has one reference per document. The hrefs use #page= only, and the titles carry no page.
  • References in child/amount.yaml:22-27. The format follows the repository rule, and the pages match the pages DTrim99 cited (Rev. Proc. 2016-55 file page 14; 2015 and 2016 Form 6251 instructions file page 10). I couldn't open the PDFs to confirm them.

  • The hand-off's reply to DTrim99.

    • S2: the three rows are added with his values. The old-base mutant (M5) fails 5 cases in that file, which covers the 3 new rows plus the 2 earlier pre-2018 child cases.
    • S3: the property test is rewritten and the cost is reported. I confirmed both.
    • G1, G2 and G4 are done as described.
    • G3 is listed in the body only.
    • The axiom: line is the hub's, and I left it alone.
  • The body's claims. Its test description, the 16/14/17 counts, "one simulation per example and no reformed system", and the cost direction all match what I ran.

  • The changelog changelog.d/amt-child-pre-2018.fixed.md is a top-level fixed fragment.

  • Main and CI.

    • origin/main is still 0315452583, and the head is 0 commits behind it.
    • git merge-tree --write-tree --name-only origin/main 65b6b34631 exits 0 with tree 0ed73f93d1 and lists no conflicts.
    • Pull request run 41948903573 at the head completed with success, 36 of 36 jobs succeeding. That includes Quick Feedback, Baseline (irs), all nine Rest Python shards, Microsimulation and Household API Partners.
  • Scope.

What I couldn't check

  • The law and the IRS sources. I couldn't read §1(g)(2), §59(j) or §152(c)(3) (2016 edition), the 2015–2017 Form 6251 instructions, or Rev. Proc. 2016-55. The proxy refused govinfo.gov, irs.gov, uscode.house.gov, ecfr.gov and law.cornell.edu. Where the review relies on them, it relies on the brief's reading and DTrim99's quotations.
  • The new parameters' references. I couldn't confirm the page numbers in child/amount.yaml, or the file page (10 or 11) of the 2017 instructions that support_test/*.yaml cites for the "Certain Children Under Age 24" conditions.
  • Population impact. I couldn't check whether any pre-2018 dataset exists. I ran no microsimulation.
  • The old test's CI memory. I didn't re-measure the old property test's memory in CI. The hand-off's local figures are consistent with my local rerun.

Generated by Claude Code

…ild's own earnings

`care_and_support_costs` defaults to 0, and every living person has some
support, so zero means nobody reported it. The predicate now treats the
IRC 1(g)(2)(A)(ii)(II) test as met in that case, which keeps main's cap for a
student or 18-year-old with earnings. Supplied support still decides the test.

The IRC 59(j) cap now reads the head's earnings alone, as IRC 59(j)(1)(A) says.
A new case with a real spouse pins it. The joint-return case now sets its filing
status and says why it fails.

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

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Here's where each item landed:

  • S1 (Axiom line): the line cites Federal AMT: the TY2013-2017 child exemption limit, its joint-return exclusion and the AMTI taxable excess (26 USC 59(j), 1(g)(2), 55(b)(1)(A)(ii)) (PE-US #10308) TheAxiomFoundation/rulespec-us#1857 as queued.
  • S2 (coverage): we added your three 2017 rows to amt_income_less_exemptions.yaml, with the values you gave: the itemizer, the binding cap and the non-binding cap. They pass, and the old-base mutant fails all three.
  • S3 (property test): test_child_amt_exemption_properties.py replaces it. It drops the reform and the JOINT-only assertion, and checks the §1(g)(2)(C), §59(j)(1) and §55(d)(4)(A)(iii) properties against an identical adult twin. Local cost: 4.2 s call with no setup and 1.0 GB peak, against 19.7 s + 10.7 s and 2.0 GB for the old test. We kept a Python test because the twin comparison checks every filing status and many incomes in one simulation, which YAML cases would have to enumerate one at a time.
  • G1: done in the PR body.
  • G2: the predicate reads the support test from care_and_support_costs and treats zero as unreported. The spouse clause is gone, and the variable documents the living-parent assumption.
  • G3: noted in the PR body, with no code change.
  • G4: references added in child/amount.yaml.

🤖 Generated with Claude Code

@MaxGhenis
MaxGhenis dismissed DTrim99’s stale review November 5, 2026 06:23

Answered in #10308 (comment): S1 cites rulespec-us#1857, S2 adds the three 2017 rows, S3 replaces the property test, and G1 to G4 follow the mapping there.

@MaxGhenis
MaxGhenis merged commit c81c3f7 into main Nov 5, 2026
36 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10308 at 78a02e1b52c073f29265dd720456f48cae83c6c6 (squash): Correct the pre-2018 AMT child exemption and income base.

  • Review: Round 2 requested changes at 65b6b34631, with METHODOLOGY_CHOICE yes. 78a02e1b52 answers each finding:
    • P2-1: amt_kiddie_tax_applies treats zero support as unreported and the support test as met. For a 2017 single student aged 20 with $5,000 of wages, AMTI of $60,000 and no reported support, the exemption goes back to 12,500 and the base to 47,500. The reviewer ran this change on a scratch copy before the push.
    • P3-1: the §59(j) cap reads only the head's earnings, and a separate filer whose spouse has earnings pins it.
    • P3-2: the hand-off's head SHA named no real commit, and this audit pins the real one.
    • P3-3: the body has an impact: line.
    • P3-4: the 2022 test name now describes the current rule.
  • DTrim99's review: answered in this comment and dismissed with a mapping note.
  • Gates checked live at merge:
    • gh pr checks exits 0;
    • the latest Pull request run at the head concluded success (41971589911, 36 of 36 jobs);
    • MERGEABLE;
    • not a draft;
    • no reviewer's latest review requests changes;
    • the semantic-overlap guard;
    • head pinned.
  • Impact: none from 2018 on, by a code read: amt_exemption returns before it reads amt_kiddie_tax_applies, nothing else reads that variable, and amt_income_less_exemptions already used amt_income. The default dataset holds 2024 records, so no aggregate covers 2013-2017.
  • Axiom: Federal AMT: the TY2013-2017 child exemption limit, its joint-return exclusion and the AMTI taxable excess (26 USC 59(j), 1(g)(2), 55(b)(1)(A)(ii)) (PE-US #10308) TheAxiomFoundation/rulespec-us#1857 queued for TY2013-2017.

🤖 Generated with Claude Code

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.

3 participants