Skip to content

Count only filers' tips and overtime toward the deductions, and require only the recipient's SSN - #10396

Merged
MaxGhenis merged 4 commits into
mainfrom
fix-tips-overtime-dependents-ssn
Oct 23, 2026
Merged

MaxGhenis merged 4 commits into
mainfrom
fix-tips-overtime-dependents-ssn

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Follows #10393 (merged as 647fec60f9), which fixed separate filers and the phase-out steps in the same two deductions. Both defects here were left out of #10393 to keep it to the findings an outside reviewer reported. #10393's law review flagged the dependents issue as its finding 9, and the SSN issue came from comparing the code with the 2025 Form 1040 instructions. This branch was first stacked on #10393 and is now rebased onto main; its own diff is unchanged.

axiom: us:statutes/26/224 TheAxiomFoundation/rulespec-us#1882 queued; us:statutes/26/225 TheAxiomFoundation/rulespec-us#1882 queued

What this fixes

Two defects in the federal qualified tips and overtime deductions (26 U.S.C. 224 and 225, TY2025-2028):

  1. A dependent's tips and overtime raised the filer's deductions. tip_income_deduction and overtime_income_deduction summed tip_income and fsla_overtime_premium over every tax-unit member.
    • Example: a head-of-household parent with AGI $60,000 and a 17-year-old dependent who earned $6,000 of tips and $1,000 of overtime got a $6,000 tips deduction and a $1,000 overtime deduction.
    • The teen's wages are not in the parent's AGI: irs_gross_income leaves dependents' income out.
    • A dependent's pay is the dependent's own income (26 U.S.C. 73(a) for a child's services) and goes on the dependent's own return.
  2. Joint returns needed both spouses' SSNs. tip_income_deduction_ssn_requirement_met and overtime_income_deduction_ssn_requirement_met returned false for a joint return unless both spouses held a valid SSN, so the couple got neither deduction. The law asks only for the SSN of whoever received the tips or overtime:
    • 224(e)(1) and 225(d)(1): "No deduction shall be allowed under this section unless the taxpayer includes on the return of tax for the taxable year such individual's social security number".
    • 2025 Schedule 1-A, Part II and III cautions: "You and/or your spouse who received qualified tips must have a valid social security number to claim the deduction", and the same for overtime.
    • 2025 Form 1040 instructions (p. 101, p. 106): "If you are married filing a joint return, the spouse who received the qualified tips must have a valid SSN", and likewise for overtime.
    • Proposed Treas. Reg. 1.224-1(e)(2) (REG-110032-25, p. 55): "married taxpayers are required to include only the SSN of the taxpayer who has received the tips to claim the deduction, and an SSN is required of both taxpayers only when both have qualified tips for which the deduction is being claimed."

Statute, form, instructions and proposed regulation agree, so there is no source conflict to register.

The change

  • New tip_income_deduction_eligible_person and overtime_income_deduction_eligible_person (Person). True for a filer who has a valid SSN. A filer is a tax-unit member who is not a dependent; that is the ~is_tax_unit_dependent test irs_gross_income uses. A valid SSN is ssn_card_type in the existing eligible_ssn_card_type parameter: CITIZEN or NON_CITIZEN_VALID_EAD.
  • tip_income_deduction and overtime_income_deduction sum only those people's qualifying amounts. The caps, the whole-$1,000 phase-out steps and the separate-filer rule from Deny tips and overtime deductions to separate filers and phase them out in whole $1,000 steps #10393 apply as before, to that sum.
  • The tax-unit *_ssn_requirement_met variables keep their names and entity and stay the deductions' defined_for gates. They now hold when any filer has a valid SSN; before, a joint return needed both. Setting one to false as an input still zeroes the deduction. Setting it to true no longer counts a person without a valid SSN, because the person-level check applies anyway.
  • Parameter metadata. The eligible_ssn_card_type descriptions now say the rule is per recipient. Their references add 224(e) / 225(d), 24(h)(7) and the instructions pages.

The mask is ~is_tax_unit_dependent rather than is_tax_unit_head_or_spouse, as the round-1 review found. When no adult filer is left, the head-or-spouse inference can pick an input dependent as head and pass over a filer under 18. The YAML and property tests now cover both of those households.

Invariants (all hold for every input; property tests in policyengine_us/tests/core/test_tip_and_overtime_deduction_properties.py)

  • I1. Dependents add nothing. Removing every dependent, with their tips and overtime, leaves both deductions unchanged.
  • I2. No valid SSN, no contribution. Zeroing the tips and overtime of each filer without a valid SSN leaves both deductions unchanged.
  • I3. No amounts, no SSN needed. A spouse with no tips or overtime needs no valid SSN: switching that spouse's card type between valid and invalid leaves both deductions unchanged.
  • I4. Monotone in SSN validity. Giving every head and spouse a valid SSN never lowers either deduction.
  • I5. Schedule 1-A on the counted amounts. Each deduction equals an independent restatement of Schedule 1-A lines 7-13 (tips) and 15-21 (overtime) applied to the counted amounts, and is zero for married filing separately. The seeded draws now vary SSN card types, add dependents (children, and adults input as dependents) and make some heads 17. Deny tips and overtime deductions to separate filers and phase them out in whole $1,000 steps #10393's bounds check runs on these draws. Its $100-step and MAGI-monotonicity tests are unchanged.

I1-I4 compare the model with itself on paired returns, so they don't depend on the restatement in I5. The paired returns mix the seeded draws, returns below both thresholds and caps, and two households where a filer under 18 lives with an adult input dependent. Each property asserts that the draw contains returns where breaking it would change the deduction.

Tests

  • YAML, in the variable-named files:

    • tip_income_deduction_eligible_person.yaml, overtime_income_deduction_eligible_person.yaml (new).
    • tip_income_deduction_ssn_requirement_met.yaml: renamed from tip_income_deduction_ineligible_cardholder_present.yaml, whose first case pinned the both-spouses rule.
    • overtime_income_deduction_ssn_requirement_met.yaml.
    • New cases in tip_income_deduction.yaml and overtime_income_deduction.yaml: the dependent-teen household above; joint returns with an ITIN spouse who has no amounts, has all the amounts, or has some; and the two input-dependent households.
    • Four cases in tip_and_overtime_income_deduction.yaml asserted the old rule and now expect the eligible spouse's amounts.
  • Targeted run at dcf7e5b906: the 34 test files (27 YAML, 7 Python) that rg -l 'tip_income|fsla_overtime_premium|overtime_income_deduction|tip_income_deduction' policyengine_us/tests lists, one process per file under the shared heavy-job lock, 4 workers or fewer.

    • All 27 YAML files pass, 246 cases.
    • Six of the seven Python files pass.
    • test_run_selective_tests.py fails 2 tests locally only because the local virtualenv has no coverage module ("No module named 'coverage'"). Deny tips and overtime deductions to separate filers and phase them out in whole $1,000 steps #10393's own targeted runs, at its commits 660be20f18 and f098447ef8 with the same virtualenv, failed the same 2 tests with the same error. CI installs coverage.
    • The final head bbf9b859e1 changes only the property test. That test passes there (see the none mutant run below).
  • Mutants, each applied to a copy of the tree at bbf9b859e1. Each run covers the six YAML files for the changed variables plus tip_and_overtime_income_deduction.yaml (101 cases), and the property test file (5 tests). All ten are killed, and the none control passes:

    Mutant YAML failures Property tests failing
    base model (Deny tips and overtime deductions to separate filers and phase them out in whole $1,000 steps #10393 formulas) 20 Schedule 1-A match; paired returns
    tips-sum-all / overtime-sum-all (every member counts again) 8 / 8 both
    tips-dependents-count / overtime-dependents-count (SSN check only) 10 / 10 both
    tips-ssn-ignored / overtime-ssn-ignored (filer check only) 10 / 10 both
    tips-both-spouses / overtime-both-spouses (old joint SSN rule) 7 / 7 both
    tips-head-or-spouse / overtime-head-or-spouse (round-1 mask) 4 / 4 both
    none 0 none
  • Python test cost (CLAUDE.md "Test design and CI cost"):

    • Scope. The change adds one test to policyengine_us/tests/core/test_tip_and_overtime_deduction_properties.py and widens the draws of the existing ones. That file runs in the core group of Rest Python shard 2 (REST_PYTHON_SHARD_2_GROUPS in the Makefile). It adds no job, group or worker.

    • Why Python. YAML can't express the new test: it compares the model with itself on 162 paired returns in six variants. It builds one batched simulation on the shared read-only system, like the existing tests.

    • Local measurements (macOS; pytest's own time, which leaves out the import; wrapper wall times include heavy-lock queue waits, so they are not reported):

      Head Tests pytest time Peak RSS
      Deny tips and overtime deductions to separate filers and phase them out in whole $1,000 steps #10393's head 5b3a4cf8d7 (see note) 4 7.41 s 1.106 GB
      dcf7e5b906 5 8.08 s 1.117 GB

      Note: the baseline ran in Deny tips and overtime deductions to separate filers and phase them out in whole $1,000 steps #10393's worktree. That worktree was clean at 5b3a4cf8d7 when this work began, and its 4-test count matches that commit's file; this PR's heads have 5 tests. The runner's summary header recorded this PR's worktree head instead (a runner bug, since fixed). Deny tips and overtime deductions to separate filers and phase them out in whole $1,000 steps #10393's worktree no longer exists, and a host reservation blocked a rerun on main, whose copy of the file is identical.

      The final head bbf9b859e1 only changes the guards' arithmetic over those 162 returns. Its run under the none mutant took 5.10 s with no memory measurement. Another timed run was blocked by a host reservation, so no peak RSS was taken at that head.

    • Linux CI before. Deny tips and overtime deductions to separate filers and phase them out in whole $1,000 steps #10393's run 39876196022: the core group ran 1,784 tests in 52:49 wall time with an 11.54 GB peak (11,268,120 KiB, rest-python-core-resources.txt). The four property tests took 0.28 + 0.26 + 1.82 + 0.86 s (rest-python-core.xml).

    • Linux CI after. This PR's own run will report the same group in its rest-python-test-timings-shard-2 artifact (rest-python-core-resources.txt, rest-python-core.xml) when it completes. The first ready run, 39945560511, tests a merge with main at ae34b9d4a5, three commits behind c5837fb00b; none of them touches tips, overtime, SSNs or tax-unit roles.

Impact

These are real microsimulation runs on the default dataset for 2025 and 2026. They compare main at the merge base 647fec60f9 (which includes #10393) with this PR's head dcf7e5b906. The runs went one at a time under the shared heavy-job lock (hub impact_queue.sh). Sums are weighted; records are tax units for the deductions and income tax, and households for net income. The later head bbf9b859e1 changes only the property test.

Output 2025 change 2026 change 2025 records changed (weighted) 2026 records changed (weighted)
tip_income_deduction -712.21M -743.61M 66 (272,985) 66 (274,934)
overtime_income_deduction +1.42bn +1.47bn 414 (1,011,988) 411 (1,019,206)
state_income_tax +8.97M -2.31M 63 (96,138) 73 (139,523)
income_tax -80.82M -83.96M 380 (1,043,993) 379 (1,060,915)
household_net_income +71.85M +88.63M 381 (1,043,993) 383 (1,061,848)
household_benefits +0 +0 0 (0) 0 (0)

The two fixes move the deductions in opposite directions, and the code fixes the direction of each. Where the old SSN rule held, the new rule can only remove dependents' amounts. Where it failed, the old deduction was zero. Both deductions are non-decreasing in the amount counted.

  • Increases can only come from a joint return on which exactly one spouse has a valid SSN, which now deducts that spouse's amounts instead of nothing.
    • Overtime: +2.48bn on 273 records (489,442 weighted) in 2025.
    • Tips: +50.08M on 3 records (3,958 weighted).
  • Decreases can only come from a return whose dependents had tips or overtime, which no longer count.
    • Overtime: -1.06bn on 141 records (522,545 weighted).
    • Tips: -762.29M on 63 records (269,027 weighted).

Federal income tax falls by $80.8M (2025) and $84.0M (2026), net. State income tax moves through conformity:

Year Changes by state
2025 IA +8.92M; CO +1.65M; OR -0.90M; ID -0.71M; AZ -411
2026 IA +9.30M; MI -5.39M; IN -4.27M; OR -1.09M; ID -0.87M; CO +1,475; AZ -428

An earlier run compared #10393's head 5b3a4cf8d7 with this PR's first head 40c97f8733, before the change from the head-or-spouse mask to the dependent mask. It matched this one to the dollar on every output. The default dataset supplies tax-unit roles but no explicit dependent flags, so on it the two masks agree.

Comparison files: fixes/10396-impact/ (compare.<year>.json, by_state.<year>.json, heads.txt) on the hub host.

Downstream

  • AZ, CO, ID, IN, MI, MT and SC read the federal deductions and pick up the corrected amounts; the impact table shows the state effects.
  • GA: ga_cash_tip_exclusion and ga_qualified_overtime_exclusion compute their own amounts. They still sum dependents' tips and overtime, a sibling issue outside this PR (addressed separately in Leave dependents' tips and overtime out of Georgia's HB463 exclusions #10405).
  • The AL HB527 contrib reform already excludes dependents.
  • Microcosm lists both *_ssn_requirement_met names among its inspected constant-membership formulas (test_support/microcosm_build/us_batched_target_materialization.py). These formulas now aggregate person-level variables instead of comparing card types directly, so its inspection may need the new *_eligible_person names when it next bumps policyengine-us.

🤖 Generated with Claude Code

@MaxGhenis MaxGhenis changed the title Count only tips and overtime received by a head or spouse with a valid SSN Count only filers' tips and overtime toward the deductions, and require only the recipient's SSN Oct 23, 2026
MaxGhenis and others added 3 commits October 22, 2026 23:12
…d SSN

26 U.S.C. 224(e)(1) and 225(d)(1) require "such individual's social
security number", meaning the recipient's. The 2025 Schedule 1-A cautions
and Form 1040 instructions say the spouse who received the tips or overtime
must have a valid SSN, and proposed Treas. Reg. 1.224-1(e)(2) needs both
SSNs only when both spouses claim tips. The model instead denied both
deductions to any joint return with one spouse lacking a valid SSN.

The deductions also summed every tax-unit member's tips and overtime, so a
dependent's amounts raised the filer's deduction although the dependent's
wages are outside the filer's gross income (irs_gross_income; 26 U.S.C.
73(a) for a child's services).

New person-level tip_income_deduction_eligible_person and
overtime_income_deduction_eligible_person mark a head or spouse with a
valid SSN; only their amounts count. The tax-unit SSN variables now hold
when any head or spouse has a valid SSN, and stay the deductions' gates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…th-spouses rule

Round-1 review: is_tax_unit_head_or_spouse can differ from
~is_tax_unit_dependent when dependents are input. When no adult filer is
left, the role inference keeps input dependents as head candidates, so an
input dependent counted, and a filer under 18 with an adult dependent did
not. The eligibility variables now use ~is_tax_unit_dependent, the mask
irs_gross_income uses.

Four tip_and_overtime_income_deduction.yaml cases still asserted that a
joint return with one spouse lacking a valid SSN gets nothing; they now
expect the eligible spouse's amounts. New YAML cases cover the two
input-dependent households, and the property draws include 17-year-old
filers with adult dependents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round-2 review: the paired-return test passed with the role-based mask
restored, because its guard counted returns where the mask makes no
difference (joint returns, or deductions already phased out). The test now
adds returns below both thresholds and caps, plus two minor-filer
households whose only adult is an input dependent. Each guard counts only
returns where breaking its property would change the deduction.

The eligibility documentation no longer equates a filer with the head or
spouse, which a filer under 18 may not be.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis force-pushed the fix-tips-overtime-dependents-ssn branch from 51d7f66 to dcf7e5b Compare October 23, 2026 03:12
Round-3 review nit: the dependent and minor-filer guards counted amounts
of dependents without a valid SSN, which a mask that ignores dependent
status would still exclude, so those returns could not show the
regression. The guards now count only valid-SSN dependents' amounts, and
the minor-filer guard checks the role-based mask's counterfactual directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
MaxGhenis added a commit that referenced this pull request Oct 23, 2026
- The two in_effect parameters cite the enrolled bill once each, with
  both pages in the title, as CLAUDE.md's one-entry-per-source rule asks.
- The year cases move into the variable-named files
  (tip_income_deduction.yaml, overtime_income_deduction.yaml, at the top,
  clear of #10396's appended cases), the list-level cases into the
  existing tip_and_overtime_income_deduction.yaml, and the Arizona cases
  into az_subtractions.yaml beside its variable.
- The property test draws once for its year check, so the switched-on
  reform builds one model: 41.8 s and 3.13 GB peak footprint locally,
  down from 64.5 s and 3.18 GB.
- The changelog says the lists now name both deductions in every year.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@MaxGhenis
MaxGhenis marked this pull request as ready for review October 23, 2026 11:21
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

Hand-off to the US + core hub. Head bbf9b859e117a620b643d856c8a322ba9694775a.

Review. Independent review by GPT-6.1 Sol, four rounds (reports in ~/reviews/tips-overtime-dependents-ssn-2026-10-22/review-r{1,2,3,4}.md):

  • r1: REQUEST-CHANGES at 40c97f87. The dependent mask had to be ~is_tax_unit_dependent, and four integration cases still pinned the old rule. Both fixed.
  • r2: REQUEST-CHANGES at 6b05da1f. The Axiom draft's corpus status was wrong and the paired-test guard was vacuous. Both fixed.
  • r3: APPROVE-WITH-NITS at dcf7e5b9. A guard was imprecise; fixed in bbf9b859, which changes only the property test.
  • r4: APPROVE-WITH-NITS at bbf9b859 (the current head). The nits were PR-body wording only; the body is updated and the head is unchanged.

Passed:

  • Targeted run at dcf7e5b9: 34 files, one process each.
  • Mutants at bbf9b859: all ten are killed, and the none control passes.
  • Impact: hub impact_queue.sh, main 647fec60f9 against dcf7e5b9, 2025-2026. Files are in ~/reviews/us-hub/fixes/10396-impact/, and the numbers are in the body.

Pending:

Depends on: nothing; #10393 is merged.

Interacts with:

Decision: none. This is a bug fix that moves aggregates, which needs no approval under the merge rules.

Follow-ups:

@MaxGhenis
MaxGhenis merged commit 508f382 into main Oct 23, 2026
59 of 61 checks passed
@MaxGhenis

Copy link
Copy Markdown
Contributor Author

US + core hub merge audit, us#10396 at bbf9b859e117a620b643d856c8a322ba9694775a (squash): Count only filers' tips and overtime toward the deductions, and require only the recipient's SSN.

@MaxGhenis
MaxGhenis deleted the fix-tips-overtime-dependents-ssn branch October 23, 2026 19:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant