Skip to content

Run the S-corp end-to-end workflow on the dashboard's pinned policyengine-us as well as the latest release - #1440

Draft
MaxGhenis wants to merge 6 commits into
mainfrom
scorp-e2e-dashboard-pin
Draft

MaxGhenis wants to merge 6 commits into
mainfrom
scorp-e2e-dashboard-pin

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

.github/workflows/scorp-e2e.yml installed the dashboard requirements and then upgraded policyengine-us to 2.10.1 or later in both jobs. #1199 added that upgrade because passive mode was unsafe on older releases. #1411 backported the EITC investment-income fix (pe-us#9572) to every older release, so the upgrade is no longer needed. With it in place, the workflow never ran the configuration the dashboard refresh uses: policyengine-us 2.6.17 plus that backport.

Both jobs now run on a two-release matrix:

  • dashboard-pin: the locked dashboard refresh environment (scripts/dashboard-refresh-requirements.txt, policyengine-us 2.6.17) with the refresh's 5 GiB worker memory limit. On the pin, the interface job installs its test-only packages with the locked versions as constraints. The new scripts/check_pinned_environment.py then fails the job if any of the 86 pinned packages is installed at another version, and pip check runs as it does in the refresh.
  • latest: the same requirements with policyengine-us upgraded to the newest release, as before. Its alignment workers get an 8 GiB memory limit (see below).

The PR also:

  • Adds end-to-end case 6 to tests/test_scorp_e2e.py (wages 8,000, interest 12,500, S-corp −6,000, TY2025) and compares v25. Before this, no end-to-end case depended on the backport, so the pinned leg would have passed even if the backport were skipped. The expectation comes from the bundled TAXSIM binary, which gives no EITC.
  • Records the policyengine-us and policyengine-core versions and the memory limit in each alignment report.
  • Also triggers the workflow on changes to scorp.py, scorp_reform.py, scripts/refresh_dashboard.py, the new check script, the pinned requirements and variable_mappings.yaml. Backport the EITC investment-income fix to older policyengine-us and default scorp to passive everywhere #1411 changed the first two, and the workflow did not run on it. Runner, CLI and API changes stay covered on every PR by the main CI's scorp-validation job, which runs on the pin. The docs say so.
  • Adds --max-memory-gb to scripts/validate_scorp_alignment.py, defaulting to the refresh's 5.
  • Updates docs/scorp-validation.md to say which policyengine-us versions are covered, and how.

CI evidence

Run 38193095171 at 51ff5d3 (before the last round of fixes):

job dashboard-pin (2.6.17) latest (2.39.1)
interfaces pass pass
alignment 2021 pass (peak 4,765 MiB) fail: worker exceeded the 5 GiB limit on its first batch
alignment 2022 pass (3,230 MiB) pass (3,860 MiB)
alignment 2023 pass (3,247 MiB) pass (3,878 MiB)
alignment 2024 pass (3,242 MiB) pass (3,907 MiB)
alignment 2025 pass (1,661 MiB) pass (1,163 MiB)

The 2021 latest failure comes from policyengine-us using more memory, not from this PR. The same 2021 batch peaked at 4,280 MiB on 2.17.0 in the September 29 run, and 2022–2024 grew by about 1.1 GiB between 2.17.0 and 2.39.1. Locally on macOS, the first 2021 batch peaks at 4,895 MiB on 2.6.17 and 5,599 MiB on 2.40.1. Hosted Linux runners have 16 GB, so the latest leg now gets 8 GiB. The pin keeps the refresh's 5 GiB. Its 2021 workers have the least headroom: 4,765 of 5,120 MiB.

That run's test (macos-latest, 3.11) failure is not from this PR. It is the wall-clock flake in #1429 plus a child-process segfault in tests/test_deterministic_ordering.py, which is being looked at separately.

Local evidence (macOS, Python 3.11, bundled TAXSIM)

SCORP_E2E=1 pytest tests/test_scorp_e2e.py:

environment result
policyengine-us 2.6.17 (dashboard requirements) 6 passed
policyengine-us 2.38.19 6 passed
2.6.17 with the backport disabled (EITC_BASKETS_MIN_PE_VERSION = (0, 0, 0)) 3 failed: test_real_cli[passive], test_stdin_default_is_passive, test_real_http[False-passive], each fiitax −352.18 vs TAXSIM 0 on case 6

Without the backport, case 6's $352.18 EITC follows the statute once the S-corp loss nets investment income down to $6,500:

26 USC 32(a)(2) (LII): "The amount of the credit allowable to a taxpayer under paragraph (1) for any taxable year shall not exceed the excess (if any) of— (A) the credit percentage of the earned income amount, over (B) the phaseout percentage of so much of the adjusted gross income (or, if greater, the earned income) of the taxpayer for the taxable year as exceeds the phaseout amount."

Rev. Proc. 2024-40, section 2.06 (PDF pages 8–9), "None" column: Earned Income Amount $8,490; Maximum Amount of Credit $649; Threshold Phaseout Amount (all other filing statuses) $10,620. Section 2.06(2): investment income limit $11,950.

The credit is min(7.65% × 8,000 = 612, 649 − 7.65% × (14,500 − 10,620) = 352.18) = 352.18. PE 2.6.17's eitc_phased_in (612), eitc_reduction (296.82) and eitc (352.18) match. The first reviewer recomputed it as $315.18, and the second review accepted this correction.

check_pinned_environment.py: 14 tests pass on Python 3.11 and 3.10, including Hypothesis round-trip and mismatch properties. It passes on the pinned environment after the constrained test-only installs (86 pins), and lists 19 differing packages on the latest-release environment.

Reviews

  • Round 1 (7d348f0): APPROVE, with six non-blocking findings. All are addressed, except the $352.18 point, which is refuted above.
  • Round 2 (51ff5d3): APPROVE, with eight non-blocking findings. All are addressed in d54a9f2.
  • Round 3 (d54a9f2): pending.

Invariants

  • On the pin, every package in scripts/dashboard-refresh-requirements.txt is installed at its pinned version, checked after every install step.
  • pins() round-trips: any set of exact pins written in uv's format (markers, hash continuations, comments) parses back to the same mapping, and pins for another interpreter are skipped (property test).
  • mismatches() flags exactly the packages that are changed or missing (property test).

axiom: n/a: CI, test and documentation only, no policy change.

🤖 Generated with Claude Code

…gine-us

scorp-e2e.yml upgraded policyengine-us to 2.10.1+ on top of the dashboard
requirements in both jobs, because passive mode was unsafe on older releases
when #1199 added the workflow. #1411 backported the EITC investment-income
fix (pe-us#9572) to every older release, so the override is no longer needed,
and with it the workflow never ran the configuration the dashboard refresh
uses: policyengine-us 2.6.17 plus that backport.

Run both jobs on a two-release matrix instead: the dashboard environment as
pinned, and the latest release. The pinned leg fails if the installed
policyengine-us is not the pinned one. Each alignment report now records the
policyengine-us and policyengine-core versions, and the workflow also runs
when the S-corp classification, its reform or the pinned requirements change.

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

vercel Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
policyengine-taxsim Ready Ready Preview Oct 12, 2026 3:08pm UTC

Request Review

…backport

The interface tests had no case whose result depends on the EITC
investment-income backport, so the dashboard-pin leg would pass even if
the backport were skipped. Case 6 (wages 8,000, interest 12,500, S-corp
-6,000) gets no EITC from TAXSIM; policyengine-us 2.6.17 without the
backport nets the passive loss against the interest and gives 352.18.
Compare v25 as well, so the EITC difference is reported directly.

Checked locally: 6 of 6 pass on 2.6.17 and on 2.38.19; with the backport
disabled on 2.6.17, the three passive-mode tests fail with fiitax -352.18
against TAXSIM's 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of 7d348f0 (GPT-6.1 Sol, APPROVE with non-blocking findings):

- The pin check only compared policyengine-us, and the interface job
  installs test packages after the locked set. Add
  scripts/check_pinned_environment.py, which fails unless every
  requirements pin is installed at its version (it tolerates markers and
  hash continuations), and run it plus pip check, as the dashboard refresh
  does. Locally it passes on the pin with the test packages and lists 19
  differing packages on the latest-release environment.
- Run the workflow when scripts/refresh_dashboard.py (imported by the
  alignment script) or the new check changes. Runner, CLI, API and mapping
  changes stay covered per PR by the scorp-validation unit job, as the
  docs now say.
- Fix the max-parallel comment: the cap is four jobs in total, not two
  per release.
- Docs: the interface assertions now include the EITC; explain case 6's
  352.18 (26 USC 32(a)(2) limitation, Rev. Proc. 2024-40 section 3.06),
  which the review had recomputed without the limitation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
CI on 51ff5d3: every dashboard-pin job passed (alignment 2021-2025 and
interfaces), and pip check passed on both releases. alignment (2021,
latest) failed: the first 500 households of 2021 exceeded the 5 GiB
worker limit on policyengine-us 2.39.1. The same batch peaked at
4,280 MiB on 2.17.0 (September 29 run) and 4,765 MiB on the pin today,
and measures 5,599 MiB on 2.40.1 locally. Give the latest leg an 8 GiB
limit through a new --max-memory-gb option (default 5, the refresh's
setting); hosted Linux runners have 16 GB. The pin keeps 5 GiB.

Second review (APPROVE, non-blocking):
- Install the interface job's test-only packages under the locked
  versions as constraints on the pin, so a new modal or fastapi release
  cannot move a locked package.
- check_pinned_environment.py now parses lines with packaging, evaluates
  environment markers, compares versions under PEP 440 and raises on any
  requirement line that is not an exact pin. Add a round-trip property
  test for the parser and marker, rejection and normalization tests.
- Run the per-package check before pip check, and run pip check even when
  the per-package check fails, so both diagnostics appear.
- Trigger on variable_mappings.yaml; correct the docs on which refresh
  settings the pin matches.

The macOS 3.11 job failed on the known wall-clock flake (#1429) and a
child-process segfault in the hash-seed test, which this PR does not
touch; the segfault is split out as a separate task.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Third review (GPT-6.1 Sol, APPROVE at d54a9f2, non-blocking):
- check_pinned_environment.py skipped every indented line and every
  "--" option, so an indented requirement or "--requirement" include went
  unchecked, and it rejected valid same-line hashes, tab-separated
  comments and "-i". It now strips comments and hash options, skips only
  options that choose a package source, checks indented requirements,
  and raises on includes, constraints and editables. Tests cover each.
- Print the installed policyengine-us and policyengine-core with pip list,
  so a missing distribution cannot stop pip check from running.
- The EITC amounts are in Rev. Proc. 2024-40 section 2.06 (SECTION 2,
  "2025 ADJUSTED ITEMS"), not 3.06.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fourth review (GPT-6.1 Sol, APPROVE at 01fc36e, non-blocking): the hash
regex could rewrite a package name containing "--hash" or a quoted marker
string, silently dropping a pin, and a source option followed by -r on the
same line was skipped.

pins() now joins continuation lines, removes comments by pip's rule, and
takes a line's requirement as its tokens up to the first one starting with
"-", as pip's break_args_options does. A requirement may carry only --hash
options; a line without one may carry only source options. Any other
option, wherever it appears, raises. Tests cover both review cases and
-r/-e/--global-option after other tokens.

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

This branch was successfully deployed

1 active deployment
Preview — 60ab216e Deployed Oct 12, 2026 by vercel[bot]
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