Repository navigation
Tax Maryland's capital gains surtax on net capital gain as the Internal Revenue Code defines it - #9898
Conversation
Md. Code, Tax-General 10-105(a)(3) taxes an additional 2% of "net capital gain, as defined and determined under the Internal Revenue Code" included in Maryland AGI, less gain from excepted assets; (a)(4) applies it when federal AGI is over $350,000. Technical Bulletin No. 58 gives the IRC 1222(11) definition: net long-term capital gain over net short-term capital loss. The model summed every tax unit member's long- and short-term gains, subtracted the capital loss deduction again, left out capital gain distributions, and tested Maryland AGI against the threshold. - md_net_capital_gain (new, Form 502CG line 1): the head and spouse's max(0, min(Schedule D line 15, line 16)), with distributions reported without Schedule D floored per person. A net short-term gain and qualified dividends are not part of it. - md_capital_gains_surtax_excepted_gain (new input, Form 502CG line 8). - md_capital_gains_surtax: 2% of max(0, line 1 - line 8) (line 9). - md_capital_gains_surtax_applies: federal AGI over the threshold. - Parameter references to the statute, ch. 604 and the 2025 forms. Tests: YAML cases with Schedule D and Form 502CG line comments, including the Technical Bulletin 58 example (line 9 = $168,000); property tests with a differential against the federal net_capital_gain. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9898 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 4 4
Lines 76 58 -18
Branches 2 1 -1
=========================================
- Hits 76 58 -18
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
PR Review: Tax Maryland's capital gains surtax on net capital gain as the Internal Revenue Code defines itPR #9898 (author: MaxGhenis, head SummaryThe PR rebuilds Maryland's 2% capital gains surtax (Form 502 line 21b) on the statutory base.
The law and the code agree:
Two documentation-level items need a fix before approval:
Neither changes a result. Critical (must fix)None. Should address1. Two new variable references point to pages that don't show what the title cites.
2. The test file says the deterministic grid "runs without Hypothesis", but without Hypothesis the whole file is skipped.
Suggestions
Verified correct
Axiom line assessmentValid
CI statusAll green: 37/37 checks pass ( Branch status
Verdict: REQUEST_CHANGES |
DTrim99
left a comment
There was a problem hiding this comment.
The surtax base is right: net capital gain under IRC § 1222(11), the treatment of capital gain distributions and qualified dividends, the strict federal AGI threshold, and line 9 (TB-58 gives 3,360) all check out, local tax is untouched, and rulespec-us#1518 is dispatch-ready. Two should items (see the comment above): S1, two reference anchors don't show what their titles cite (booklet line 21b is on #page=22, and the TB-58 example is on p. 3), so use one reference per page and fix the PR body's anchor too; and S2, the invariants file says the grid runs without Hypothesis, but the module-level importorskip skips the whole file, so split the module or correct the comment and the PR body.
PR Review (round 2): Tax Maryland's capital gains surtax on net capital gain as the Internal Revenue Code defines itPR #9898 (author: MaxGhenis). Head SummaryBoth round-1 should-address findings and all nine suggestions are fixed.
There are no new critical or should-address findings. Three suggestions remain:
CI. 25 of 27 checks pass. The two failures are one ratchet failure on Prior findings
S1 details
I checked the page text for each: Line 20a is on booklet p. 21 and Line 21b on p. 22. TB-58 p. 2 has "the dwelling sold for less than $1,500,000", and the Jane McDonald example is on p. 3. S2 details
Suggestion 4 details
New findingsCritical (must fix)None. Should addressNone. Suggestions1. Merge the per-page reference entries into one entry per document, following the house convention.
2. Update the source-conflict backlog item this PR makes stale.
3. Refresh the PR body's Validation section after the next merge with
VerifiedLaw (re-checked against the sources)
Hand checks of every new or changed expectation
The optional-Hypothesis change
Other checks
Axiom line assessmentValid rulespec-us#1518 meets every requirement:
rulespec-us#1490 (the § 852(b)(3)(B) dependency) is OPEN and labelled CI status25 of 27 checks pass; 2 fail (run 39531581561 at
Branch status
Verdict: APPROVE |
DTrim99
left a comment
There was a problem hiding this comment.
Round 2: all prior findings are fixed. The reference anchors are corrected, and the Hypothesis split means the grid runs without it. The law re-checks against TB-58, Form 502CG and ch. 604 s. 34, and the new witnesses hand-check. The two red checks are only the known FL/SD LIHEAP ratchet failure (no MD file listed; the run predates #10334), so merge main and re-run. Three suggestions, including merging the per-page reference split I asked for in round 1 back into one entry per document, per CLAUDE.md. Full report in the comment above.
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
1 similar comment
|
Picking this up for the US hub. I'll post a hand-off comment here when it's ready. Generated by Claude Code |
…idate The candidate still described the form-based reading the PR replaced. It now records the 1222(11) base the code follows, points at md_net_capital_gain, and the deciding line carries a conflict comment counted in KNOWN_UNMARKED. References that differed only by page are consolidated into one entry per document. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6FjKTA223kkKGWSie6
Main carries the source-conflict ratchet counts for the Florida and South Dakota LIHEAP files whose absence failed this branch's final-head CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LwQq6FjKTA223kkKGWSie6 # Conflicts: # source_conflicts/candidates.yaml
|
Ready for hub review Head: What the statute review asked, and how it is resolvedP2: complete the source-conflict registration (as far as this repository can; the register entry itself is a rulespec-us change).
P3: CI claims. The body's Validation section now reports the final-head result: run 39531581561 at P3: duplicate PDF references. One entry per document:
Main merge (judgment call)The item said to push as a fast-forward. The previous head's CI failed only on the source-conflict ratchet for the Florida and South Dakota LIHEAP files, and the same test failed locally on the branch. Main already carries those files' counts, so I merged main ( Tests (local, one file per process, at
|
| File | Result |
|---|---|
md/tax/income/capital_gains/md_capital_gains_surtax.yaml |
27 passed |
md/tax/income/capital_gains/md_capital_gains_surtax_applies.yaml |
5 passed |
md/tax/income/capital_gains/md_net_capital_gain.yaml |
7 passed |
tests/test_md_capital_gains_surtax_invariants.py |
2 passed |
tests/test_md_capital_gains_surtax_properties.py |
1 passed (25 generated examples) |
tests/code_health/test_source_conflicts.py |
103 passed (102 passed, 1 failed before the merge) |
No tax result changed this round: only comments, references, the backlog entry and the merge.
CI: the Pull request run 41733019138 at 3427c3acd0 completed with 36 of 36 jobs passing.
Not done
- No statute or form text is quoted in the candidate or code: this session's network couldn't reach the Maryland or Comptroller sites. The candidate's readings paraphrase the PR's existing description and the review; unverified here.
- The register entry, the
# Source conflictmarker and the pin refresh wait for a rulespec-us change once this is on main.
Generated by Claude Code
|
Hub review in progress Head under review: So far:
Complete: see the final review comment below. Generated by Claude Code |
|
Ready for hub review The head hasn't moved since the hand-off. This round's code and register changes are sound. The surtax results, the merge, CI and every mutant I ran check out. What blocks is the PR body: it has no FindingsP2: the body has no
|
| Case (2025, single, MD) | Hand value | main | head |
|---|---|---|---|
| W1: wages 400,000, LT 100,000, ST 50,000 | 100,000 × 0.02 = 2,000 | 3,000 | 2,000 |
| W2: wages 450,000, ST 80,000 only | min(0, 80,000) → 0 | 1,600 | 0 |
| W3: head wages 400,000 and LT 10,000; dependent LT 40,000 | 10,000 × 0.02 = 200 | 1,000 | 200 |
| W4: MFJ, head LT 60,000, spouse ST −20,000, wages 500,000 | min(60,000, 40,000) × 0.02 = 800 | 800 | 800 |
| W5: wages 400,000, non-Schedule D distributions 30,000, ST −5,000 | min(30,000, 25,000) × 0.02 = 500 | 0 | 500 |
| W6: 2024 · W7: Virginia | 0 · 0 | 0 · 0 | 0 · 0 |
policyengine-core test witness.yaml -c policyengine_us: head 7 passed; main 4 failed, 3 passed (W1 [3000.] differs from 2000.0, W2 [1600.], W3 [1000.], W5 [0.]). The body's YAML also pins federal AGI rather than Maryland AGI for the threshold.
Replacement: add this line next to the axiom: line:
impact: baseline changes for Maryland filers with federal AGI over $350,000 from 2025: a net short-term gain no longer enlarges the surtax base, dependents' gains no longer count, capital gain distributions reported without Schedule D now count, the threshold tests federal rather than Maryland AGI, and excepted gain is a new input (default 0). Needs a hub microsimulation of md_capital_gains_surtax and md_income_tax for 2025 and 2026, before and after.
P3: the body's "Response to @DTrim99" S1 bullet describes references that no longer exist
- Where: PR body, "Response to @DTrim99", bullet S1: "split booklet references into line 20a at page 21 and line 21b at page 22; separate TB-58 exception and example references by page."
- Failure scenario: commit
22ee7937c7merged those entries back into one per document, as DTrim99's round-2 suggestion 1 asked. The Validation section's References bullet already says so, so the body now contradicts itself. - Check:
git show 22ee7937c7 -- '*md_capital_gains_surtax.py' '*excepted_gain.py' '*surtax_applies.yaml'removes the per-page entries: 1 booklet, 3 TB-58 and 3 ch. 604 entries become 1 each. - Replacement: "S1 — reference anchors: the booklet, TB-58 and ch. 604 references each now open at the first cited page, with their pages in the title (
#page 21-22,#page 1-3,#page 163-164,200). Round 1 split them by page; round 2 merged them back to one entry per document, per DTrim99's round-2 suggestion 1. The PR body's anchors are corrected too."
P3: the candidate paraphrases its sources, and no capture backs TB-58 or the 502CG instructions
- Where:
source_conflicts/candidates.yaml, itemus-md:capital-gains-surtax-short-term-gains(readings,sources). - What the build asked for and didn't get: "Cite both sources with quotes of 25 words or fewer." The readings are paraphrases, which the hand-off admits ("No statute or form text is quoted"). CONTRIBUTING.md step 1 also asks the ruling PR to add a capture under
source_conflicts/sources/when the Axiom corpus lacks a source's text.source_conflicts/sources/us-md/holds onlyresident-booklet-2025-p23.txt. I can't see whether rulespec-us already has TB-58 or Form 502CG in its corpus. - Failure scenario: step 2 in rulespec-us can't pin a capture of TB-58 or the 502CG instructions if the corpus lacks them. That would need another PolicyEngine US PR first.
- A smaller point in the same item: the third source's citation names both the 502CG instructions and resident booklet p. 13, but its
urlpoints only to502cg.pdf#page=2. The booklet page, which carries the Form 1040 line 7a link, has no URL. - Fix: when a session can reach marylandcomptroller.gov, put short quotes in the readings, for example the 502CG instruction "Residents use Line 1c of Form 502" and the booklet's line 1c text. Split the booklet into its own
kind: instructionssource atresident-booklet.pdf#page=13. If the corpus lacks them, add captures of TB-58 p. 1 and 502CG p. 2. This can also land as a follow-up before step 2; it changes no result.
The statute review's items (round 1 at 49558d1f4d)
- P2, register item: answered as far as one PR can. Checked against
candidates.yaml's header andtest_source_conflicts.py:follows: statuteandstate: ruledare valid values. "ruled" means "PolicyEngine has picked", and the code does follow the statute.codelists onlymd_net_capital_gain.py, a tracked file that holds the deciding line.- There are three sources with valid kinds (
statute,guidance,instructions) and https URLs. prslists Tax Maryland's capital gains surtax on net capital gain as the Internal Revenue Code defines it #9898.- The examples recompute by hand. $100,000 LT and $50,000 ST: statute min(100,000, 150,000) × 0.02 = 2,000; form line 7a 150,000 × 0.02 = 3,000. $80,000 ST alone: statute min(0, 80,000) = 0, so 0; form 80,000 × 0.02 = 1,600. The model at main gives exactly the form values (W1, W2 above), and the head gives the statute values.
- The
note's claim thatmd_capital_gains_surtax.yamlpins both witnesses holds: "A net short-term capital gain is not net capital gain" gives 2,000, and "Short-term gains only" and the 2026 statute-first witness give 0. test_source_conflicts.py: 103 passed.
- The deciding line:
md_net_capital_gain.py:70-75sits directly abovereturn max_(0, min_(long_term, combined)). The comment says the 502CG instructions use Form 502 line 1c, the Form 1040 line 7a gain, which also counts a net short-term gain, while § 10-105(a)(3) and TB-58 use the § 1222(11) amount, and it names the candidate. That matches the variable's documentation, the candidate, and the page text DTrim99 quoted in the round-1 review ("Residents use Line 1c of Form 502"; line 1c is "the amount of net capital gain or loss reported as taxable income on Line 7a of the federal Form 1040"). I couldn't fetch those pages myself.KNOWN_UNMARKEDgainsmd_net_capital_gain.py: 1, in sorted order, and the guard passes. No# Source conflictmarker is added, which is correct: CONTRIBUTING.md says the marker waits for the pinned register entry.
- P3, CI claims: answered. The body now gives run 39531581561 at
49558d1f4d(25 passed, 2 failed on the Florida/South Dakota LIHEAP ratchet) and run 41733019138 at the head. - P3, duplicate references: answered, and extended to
md_capital_gains_surtax_excepted_gain.pyandsurtax_applies.yaml. Each changed file now has one entry per document, with an href to the first page and a title ending in a page list (quoted in the YAML).
DTrim99's review
The brief says DTrim99's CHANGES_REQUESTED still stands. It doesn't: DTrim99 APPROVED at 49558d1f4d on 2026-10-21, after round 2 found all 11 round-1 items fixed (S1, S2 and suggestions 1–9). That approval is on an earlier head. Round 2 left three suggestions:
- One entry per document: answered in
22ee7937c7. - Update the backlog item: answered.
codeis nowmd_net_capital_gain.py,followsisstatute, and the note is updated. The rulespec-us entry, marker and pin refresh are post-merge steps. - Refresh the Validation section: answered. The S1 bullet in the response section is stale (P3 above).
Checked and sound
-
Head and merge:
- The PR head is
3427c3acd0, unchanged. git diff 6145ad4513 3427c3acd0 --stat(main parent → head) shows only the PR's 16 files.- Parsing
candidates.yamlat each commit: the Maryland item at the head equals22ee7937c7's;us-md:ccs-copay-flat-scheduleequals main's side (not in the merge base); no other id differs from main's side; ids are sorted (357). git merge-tree --write-tree origin/main HEADis clean against current main, which is 6 commits past the merged parent.
- The PR head is
-
CI: run 41733019138 ("Pull request", head
3427c3acd0) completedsuccess; jobs API: 36 success, none cancelled or skipped. -
Local tests, one file per process:
md_capital_gains_surtax.yaml27 passed;md_capital_gains_surtax_applies.yaml5;md_net_capital_gain.yaml7;test_md_capital_gains_surtax_invariants.py2;test_md_capital_gains_surtax_properties.py1 (25 examples);test_capital_gain_distributions_loss_limit.py9;test_source_conflicts.py103.
-
Mutants (the hand-off lists none; all five are mine). Each ran in its own process, the code was restored after each, and
git statuswas clean afterwards.Mutant Killed by M1: return max_(0, combined)(the form reading)md_net_capital_gain.yaml"Spouses' short-term amounts net to a gain, which adds nothing";md_capital_gains_surtax.yaml7 failures, including "A net short-term capital gain is not net capital gain" and "Short-term gains only"M2: drop the max_(0, …)on excepted gain"A negative excepted gain cannot increase the surtax" M3: >=threshold"Federal AGI equal to the threshold"; "Federal AGI at the threshold is not over it" M4: count dependents' distributions ( filermask always true)md_net_capital_gain.yaml"A dependent's capital gain distributions are on the dependent's own return" (survivesmd_capital_gains_surtax.yaml)M5: threshold on md_agimd_capital_gains_surtax_applies.yaml"Maryland subtractions do not take federal AGI under the threshold" and "Maryland additions do not put federal AGI over the threshold";md_capital_gains_surtax.yaml"Statute-first witness - federal AGI controls…" -
Readers (grep of non-test code):
md_capital_gains_surtaxis read only bymd_income_tax_before_credits.py:38, behindsurtax_applies.md_capital_gains_surtax_appliesis only the surtax'sdefined_for.md_net_capital_gainandmd_capital_gains_surtax_excepted_gainfeed only the surtax.- No other reader exists, so no reader is newly reachable and worse.
-
Rule across entities and years:
- Head and spouse only: the YAML pins
tax_unit_non_dep_addand the per-filer distribution mask, and M4 is killed. MFJ netting is checked by W4. - One threshold for every filing status: HOH is in YAML.
- 2024 gives 0, and 2025, 2026 and 2029 apply (the parameter has no end date).
- Non-Maryland households give 0 (W7).
- Head and spouse only: the YAML pins
-
Scope: no file under
.github/orpolicyengine_us/tests/policy/baseline/partners/. -
Overlap: open PR Leave dependents' capital income out of state lines on the filers' return #9894 touches
md_capital_gains_surtax.pyand its YAML, as DTrim99 noted; whichever lands second should keep this PR's Maryland files. Search hits forcandidates.yaml(Follow state statutes where forms differ: NM medical exemption, VT CDCC, NY/NYC household credit, PR EITC #10300, Follow the statute for the NYC school tax rate reduction, IN supplemental homestead, VA 529 and WA senior/disabled property tax exemption #10330, Add Colorado LEAP regular heating assistance #10609 and others) touch other ids. -
TB-58 arithmetic: (610,000 − 442,000) × 0.02 = 3,360, matching the YAML. The inputs come from the PR; I couldn't fetch TB-58.
What I couldn't check
-
Sources: mgaleg.maryland.gov, www.marylandcomptroller.gov and govinfo.gov all returned a proxy policy denial (CONNECT 403 / DNS refusal) from this session. So I confirmed none of the following against its text:
- § 10-105(a)(3)–(4);
- ch. 604 pp. 163–164 and 200;
- TB-58 pp. 1–3;
- Form 502CG pp. 1–2;
- the resident booklet pp. 13 and 21–22;
- 26 U.S.C. §§ 1222(11) and 852(b)(3)(B).
This includes whether each consolidated reference's page list holds what its title cites. The page assignments agree with DTrim99's quoted page text, but that is their reading, not a fetch of mine.
-
Population impact: I ran no microsimulation. That is the hub's check under the
impact:line above. -
rulespec-us: out of this session's GitHub scope. I didn't check whether its corpus holds TB-58 or Form 502CG text, or the current state of rulespec-us#1518.
Generated by Claude Code
|
US + core hub merge audit, us#9898 at
🤖 Generated with Claude Code |
Summary
From tax year 2025, Maryland charges filers whose federal adjusted gross income exceeds $350,000 an additional 2% of the net capital gain in their Maryland adjusted gross income. This PR computes that gain as the Internal Revenue Code defines it and tests federal AGI against the threshold, as the statute says:
md_capital_gains_surtax_excepted_gain, takes the gain from assets the statute excepts (Form 502CG line 8). It defaults to $0.For example, a filer over the threshold with $100,000 of long-term gain and $50,000 of short-term gain owes a $2,000 surtax, where main charges $3,000.
What the sources say
Statute or form
The statute and the forms part ways only when the filers have a net short-term gain: the § 1222(11) base leaves it out, and the line 7a figure counts it. For $100,000 of long-term and $50,000 of short-term gain, the statute gives a $2,000 surtax and the forms give $3,000. For $80,000 of short-term gain alone, they give $0 and $1,600.
This PR follows the statute, which the bulletin's definition matches.
source_conflicts/candidates.yamlrecords the conflict asus-md:capital-gains-surtax-short-term-gains, and a comment on the deciding line ofmd_net_capital_gain.pynames it. Its register entry in rulespec-us and the# Source conflictmarker come after this PR lands.The model
Each sum runs over the head and spouse, and each person's distributions reported without Schedule D count only when positive.
md_net_capital_gain(new) computes Schedule D line 15 as long-term gain plus those distributions, line 16 as line 15 plus short-term gain, and Form 502CG line 1 as max(0, min(line 15, line 16)).md_capital_gains_surtax_excepted_gain(new input) holds Form 502CG line 8.md_capital_gains_surtax_appliestests whetheradjusted_gross_incomeexceedssurtax_threshold($350,000) for every filing status, from 2025.md_capital_gains_surtaxchargessurtax_rate(2%) on max(0, net capital gain − max(0, excepted gain)), the amount on Form 502CG line 9.md_income_tax_before_creditsadds it, as on main.Main adds every tax unit member's short-term and long-term gains, subtracts
limited_capital_loss, floors the result at zero and tests Maryland AGI. The three parameters keep their values.Worked cases
Each household files in Maryland for 2025. The "Before" column runs this PR's YAML cases on main
ba14b81f7b.Main has no excepted-gain input, so its value for the bulletin's example taxes all $610,000.
Invariants
test_md_capital_gains_surtax_invariants.pychecks these on a grid of households, andtest_md_capital_gains_surtax_properties.pychecks them on 25 households that Hypothesis generates:md_net_capital_gainequals max(0, min(line 15, line 16)) computed from the inputs.md_net_capital_gainequals the federalnet_capital_gain, a separate § 1222(11) path, less qualified dividends, which § 1(h)(11) adds only for § 1(h).md_net_capital_gainnever exceeds the positive part of the filers' Form 1040 line 7a gain, so federal AGI includes all of it.md_income_tax_before_creditsadds the surtax once: neutralizing the surtax lowers that tax by the surtax amount.Tests
3427c3acd0passed all 36 jobs.3427c3acd0with mainba14b81f7b, one process each:taxsim_outputs.yamlpasses 22;test_md_capital_gains_surtax_invariants.pypasses 2,test_md_capital_gains_surtax_properties.py1 andtest_capital_gain_distributions_loss_limit.py9;test_source_conflicts.pypasses 103 andtest_no_group_variables_in_person_adds.py10.remainingshards. In that CI run, the invariants file's two tests took 28.4 s and the properties test 11.8 s, in shards that peaked at 7.4 and 7.2 GiB.Not modeled
Overlap with open PRs
md_capital_gains_surtax.pyand its YAML to leave out dependents' gains, which this PR does inmd_net_capital_gain.py. Whichever lands second keeps this PR's Maryland files.Microsimulation impact
We compared
mainat6145ad4513with3427c3acd0on the default dataset for 2025 and 2026. We estimate that Maryland's capital gains surtax falls by $30.0 million in 2025, from $250.1 million to $220.1 million, and by $31.2 million in 2026, from $259.9 million to $228.7 million. Maryland income tax falls by the same amounts, and federal income tax and benefits don't change.The table applies the PR's changes to main's surtax one at a time, in this order. Each cell gives the change, then the sample tax units it moves and their weight.
In the dataset, every tax unit that pays the surtax on main has a net short-term gain, so each pays less. In 2026 one of them stops paying, because it has no net long-term gain. The units that start paying report distributions without Schedule D, or have federal AGI over $350,000 and Maryland AGI under it.
impact: on the default dataset, Maryland's capital gains surtax falls by $30.0 million in 2025 and $31.2 million in 2026 (54 and 57 tax units; 86,867 and 87,488 weighted), and Maryland income tax falls by the same amounts. Federal income tax and benefits don't change.
axiom: TheAxiomFoundation/rulespec-us#1518 queued for Md. Code, Tax-General § 10-105(a)(3)–(4).
us-md/statutes/gtg/10-105.yamlencodes the 2% rate and the $350,000 federal AGI threshold and defersadditional_capital_gain_tax. Becausemd_capital_gains_surtax_appliestests federal AGI alone, the issue maps its output 2 tomd_capital_gains_surtax_applies & (md_net_capital_gain > 0). rulespec-us already encodes the § 1222(11) base asus:statutes/26/1222#net_capital_gain, and TheAxiomFoundation/rulespec-us#1490 queues § 852(b)(3)(B).🤖 Generated with Claude Code