Skip to content

Fix statutory pension annual allowance taper income tests - #2441

Merged
MaxGhenis merged 15 commits into
mainfrom
fix/2244-aa-taper-income
Oct 17, 2026
Merged

MaxGhenis merged 15 commits into
mainfrom
fix/2244-aa-taper-income

Conversation

@MaxGhenis

@MaxGhenis MaxGhenis commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #2244

The annual allowance taper now applies both statutory income tests, counts employer pension provision and supplied statutory DB input in adjusted income, and rounds its reduction down to whole pounds. The taper starts in 2016–17. Main 1bdc7a0ab has been merged in 9a146999c, including #2440, #2089, #2061 and #2423. Current head is 39f5fa85c2e3ed365dc788ad2e9f6355a2c3dd5e. This draft retains the reviewed round-5 corrections and restricts the basic pension-relief uplift to relief at source in the rebuilt trading-loss cap.

Statutory income tests

FA 2004 s. 228ZA defines adjusted income in (4), threshold income in (5), relevant sacrifice arrangements made on or after 9 July 2015 in (6), and whole-pound rounding in (2). Both income limits must be exceeded. The threshold-income limit is the adjusted-income limit less the standard allowance, including under reforms.

Tax years Adjusted-income limit Threshold-income limit Minimum allowance
2016–17 to 2019–20 £150,000 £110,000 £10,000
2020–21 to 2022–23 £240,000 £200,000 £4,000
From 2023–24 £260,000 £200,000 £10,000

References: F(No. 2)A 2015 Sch. 4 para. 10, FA 2020 s. 22, and F(No. 2)A 2023 ss. 20 and 22. For 2016–19 the multiplier is (standard − minimum) / £60,000; from 2020 it is one half. Parameters retain statutory 6 April dates; formula years use the model's calendar-year proxy.

The model's net_income precedes pension relief, which is deducted separately through allowances. Both tests use annual_allowance_net_income in policyengine_uk/utils/pensions.py. Adjusted income adds total pension input and subtracts all actual own member contributions, including unrelievable post-75 payments. Threshold income deducts actual relievable member contributions and adds full relevant salary sacrifice. Both exclude taxable pension death benefits supplied separately. The shared pension input feeds taper and charge and accepts statutory DB accrual; its DC default uses actual contributions. Gross Gift Aid does not reduce either taper income. Durable conventions are in docs/engineering/pension-annual-allowance.md.

Basic pension relief in the rebuilt loss cap

The rebuilt ITA 2007 s.24A cap now reuses pension_contributions_relief_amount. FA 2004 s.190(3) and s.191(7) reserve the basic £3,600 uplift above earnings to RAS; HMRC PTM044100 confirms this distinction. Pure net pay receives relief up to earnings, mixed payments receive only the RAS portion of the uplift, and member relief stops at age 75.

For age 45, £2,000 cash pay, £3,600 gross net pay, no RAS, £250,000 dividends, £200,000 current trading loss, £100,000 employer contributions and £12,500 sacrifice, only £2,000 net pay is relievable. ATI is £250,000, the loss cap £62,500 and post-relief statutory net income £187,500. Threshold income is £200,000, adjusted income £302,000, AA £60,000 and charge £25,245. The reviewed head instead gave £200,400 / £302,400 / £38,800 / £34,785. Equality at the threshold prevents tapering.

The regression covers 2026, 2029 and 2030, each at the £2,000 NICs cap and uncapped, plus the £2,000-payment control: six failing worlds at b6e819662 become green; all six controls already passed and remain green. The control retains the same statutory incomes/allowance and a £24,525 charge on its smaller pension input. This correction also applies to qualifying pre-2029 calculator inputs. The related ordinary adjusted_total_income loss-cap error is deliberately outside this PR; a standalone MOCK issue reproducer is ready for the hub to file. #2454's existing boundary is checked separately below.

Round 5 corrections

Supplied ANI remains authoritative. With #2440 merged, supplied adjusted_net_income already excludes synthetic NICs-returned pay. Its annual-allowance base now restores gross Gift Aid and pension relief recomputed on actual contributions and earnings, without applying pay removal or trading-loss reconstruction to that supplied income. The age-45 2030 case with £2,000 cash pay, £201,000 sacrifice, £100,000 employer contributions and supplied ANI £150,000 has threshold income £351,000 and AA £10,000 at either NICs cap. At the reviewed unmerged head the capped result was £201,000 / £39,500; on the #2440 merge the age-80 and age-45 loss variants still leaked. The cap property covers supplied ANI at ages 45 and 80, with and without trading losses, in separate simulations because ANI is a partial-input override.

Net-pay contributions precede the income limit on trading-loss relief. FA 2004 s. 193(2) deducts net-pay contributions from employment income itself; only the s. 193(4)/(6) excess is a Step 2 deduction. ITA 2007 s. 25(4) restricts Step 2 relief to sufficient income. Annual-allowance construction now limits current-year trading-loss relief to actual income after relievable net-pay contributions, applying the age and earnings relief limits. This applies to every person, including those with no NICs-returned amount.

The corrected age-45 fixture, with £2,000 pay, £201,000 sacrifice, £100,000 employer contributions, £600 net pay and £10,000 loss, has threshold income £201,000, adjusted income £301,600 and AA £39,200 in both 2029 and 2030. The 2026 gate-crossing witness with £200,500 sacrifice and £1,000 net pay has threshold £200,500, adjusted income £301,500 and AA £39,250, replacing the erroneous £60,000 allowance. This changes pre-2029 behavior for the narrow group whose trading-loss relief is income-limited and who make relievable net-pay contributions. The earlier claim that every pre-2029 output is unchanged no longer applies to this correction. Ordinary income-tax formulas remain unchanged; the separate NICs interaction is tracked in #2454.

Against the same-main merged pre-fix tree, the seeded 240-person MOCK population has 14 people with higher threshold and adjusted income in every 2016–2030 year (10 default DC, four supplied DB). Pre-2029 increases range from £600 to £32,754. One DC person crosses threshold income £110,000 → £120,000 in 2016–19: AA falls £40,000 → £10,000 and the charge rises £13,500 each year. This population has no allowance/charge change in 2020–30; the separate 2026 fixture above demonstrates a later gate crossing. Returned pay, raw net income, raw trading-loss relief and pension input arrays are byte-identical. All eight same-main cap/year/scheme ordinary-tax arrays are also byte-identical (960 stored person-world values). These comparisons isolate round 5 from main integration.

#2440 integration. #2440 merged as df61251c6 and is included here. The agreed fixture “ANI pension deduction does not feed back into annual allowance taper or charge” now expects threshold income £330,000, adjusted income £370,000, AA £10,000 and charge £13,500 (£30,000 excess × 45%). Both explanatory comments are retained. Fresh merged-head validation is reported below; round-4 CI predates #2440.

Retained corrections and verified evidence

The earlier helper correction reconstructs trading-loss relief from actual earnings and contributions, rebuilding the ITA 2007 s. 24A cap and s. 64 income limit, including the add-back for brought-forward trading losses. It avoids retaining relief computed on synthetic pay. The no-net-pay age-45 witness (£2,000 pay, £201,000 sacrifice, £100,000 employer contributions, £10,000 loss) remains threshold £201,000 and AA £39,500 in 2029 and 2030. Default-DC and supplied-DB people remain in separate simulations with their input paths asserted.

The round-4 review at 998f3c5b6 confirmed all round-3 witnesses give identical annual-allowance incomes, pension input and allowance at both caps; all four homogeneous DC/DB loss-property nodes pass; and a 240-person loss-rich population has zero cap mismatches, against 32 at round 3. Its 2016–2028 outputs were byte-identical to round 3. That historical pre-2029 result does not cover the new net-pay loss-limit correction.

Earlier statutory witnesses remain:

  • Age 80, £160,000 pay, £30,000 sacrifice and £100,000 employer: threshold £190,000, input £130,000, adjusted income £290,000, AA £60,000, charge £31,500, with employer-cost haircut held at zero.
  • Age 45, £2,000 pay, £201,000 sacrifice, £100,000 employer and £3,600 gross RAS: threshold £199,400, input £304,600, adjusted income £303,000, AA £60,000. The basic RAS amount is preserved under ss. 190–191.
  • The original 2029 regression: £175,000 pay, £30,000 sacrifice and £100,000 employer gives threshold £204,720, adjusted income £304,720, AA £37,640 and charge £41,562, including the inherited £280 wage reduction.
  • Age 80, £210,000 private pension income, £50,000 member payments and £80,000 employer: input £80,000, adjusted income £240,000, AA £60,000, charge £9,000. FA 2004 ss. 233(1)(a) and 188(3)(a) exclude post-75 member payments from default DC input while employer payments remain. The age-45 mutation witness instead has relief £3,600, threshold £206,400, adjusted income £290,000, AA £45,000.

Age is read on 6 October. Payments in the year of a 75th birthday are treated all-or-none according to that date, without splitting at the birthday. Relievable pre-75 RAS payments cancel from adjusted income; unrelievable payments need not.

Cap invariance covers threshold income, adjusted income, pension input and allowance. Charge comparisons hold ordinary income-tax inputs fixed. The original 300-person check had a £3,242.83 charge difference through ANI and the personal-allowance interaction despite invariant taper accounting. #2454 tracks this separate ordinary-tax defect: the reviewed post-75 ordinary-tax increases were £12,600 and £16,371, with a £1,885.50 charge change in the latter witness; the basic-RAS witness had a £900 charge change through ordinary-tax inputs. No unrestricted charge-invariance claim is made.

The basic-RAS £900 charge difference is a historical pre-#2440 result: merging #2440 removes that charge and personal-allowance difference (the capped-minus-uncapped ANI difference is now £1,600). Both post-75 ordinary-tax differences remain. Across all six NICs boundary witnesses, all 252 recorded variable cells are identical between the same-main merged pre-fix tree and this final tree, so round 5 does not expand that boundary.

Validation

Validation at 39f5fa85c2e3ed365dc788ad2e9f6355a2c3dd5e:

  • New regression/control matrix: 12 passed after the fix; exact b6e819662 production gave 6 assertion failures and 6 passing controls. The failing cases all reported threshold £200,400 instead of £200,000.
  • Pensions and income-tax YAML suites: 403 passed; six Python files covering taper, properties, statutory edges, supplied ANI, Gift Aid and Scottish rates: 100 passed (2,151.85s), with -n 3 and numerical thread limits of one.
  • Unchanged reviewer diagnostics: all nine requested scripts passed (10 executions including the NICs-only salary sacrifice cap changes ordinary income tax after age 75 #2454 baseline): 76 cap comparisons and 22 oracle cases agree; 240-person gate checks have zero mismatches in each year; four supplied-ANI pairs, the 16-row cap matrix, basic RAS and 300-person/age-boundary checks pass. The adversarial matrix has 6/6 corrected net-pay/loss worlds and 48/48 supplied-ANI worlds passing. The NICs-only salary sacrifice cap changes ordinary income tax after age 75 #2454 reproducer has 252/252 cells unchanged between b6e819662 and this head.
  • Exact shared-helper probe: 8/8 vectors pass, including net pay versus RAS, mixed payments, basic relief at zero earnings, age 74/75 and post-75 payments.
  • make format and lint passed; uvx ruff format --check . passed for 1,748 files. One pre-existing anyio assertion-rewrite warning remains in the YAML harness.
  • Fresh GitHub CI has been triggered for this pushed head and is pending at handoff; live checks. Previous head b6e819662 had 16 successful checks; that result is historical.

Foreground suite commands (the local harness only tunes GC and uses independent parsed-system clones):

UV_LINK_MODE=clone uv sync --frozen --extra dev
uv run --frozen --no-sync policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc/pensions policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax -c policyengine_uk
uv run --frozen --no-sync pytest -n 3 -o usefixtures=cloned_uk_tax_benefit_system policyengine_uk/tests/test_pension_annual_allowance_taper.py policyengine_uk/tests/test_pension_annual_allowance_properties.py policyengine_uk/tests/test_statutory_pension_edges.py policyengine_uk/tests/test_adjusted_net_income_pension_relief.py policyengine_uk/tests/test_gift_aid.py policyengine_uk/tests/test_scottish_income_tax_rates.py -q
make format
uvx ruff format --check .

The requested local suites ran with MOCK and offline data access. The unchanged review scripts were SHA256-verified, run serially in the foreground, and their model-source imports checked. No full repository suite or documentation build was run locally: the requested paths cover this helper correction and the engineering documentation is standalone Markdown. No new independent review or merge was performed; the hub dispatches the review at this head.

Impact and interpretation

Round 6: unchanged from round 5, confirmed by an input proof rather than a new microsimulation. Certified 1.58.1 bytes and SHA256 were rechecked. Its 113,589-person table has neither trading-loss nor supplied-ANI input; trading loss has no formula and defaults to zero. The sole changed production path is relieved_contributions → adjusted_total → loss_cap → actual_loss_relief, and zero loss makes the final deduction identically zero under either cap calculation. The saved R5 aggregate/source provenance (666 source hashes plus 113 diagnostic hashes) was verified against the original commits. Thus 2026 and 2030 AA charge, income tax, chargeable excess and weighted/unweighted excess counts have exactly zero R6-minus-R5 change. The table below retains the R5 measurements and their original baseline. No fresh fiscal replay is claimed.

Three sequential foreground microsimulations completed on the certified uk-data 1.58.1 enhanced FRS 2024–25: current main 1bdc7a0ab269be750f8b6f57bd0bdb3a09b26517, the same-main merged pre-fix tree 9a146999c73f83be8cb11d03ad0cdede016c6ba6, and final b6e81966284d4f4d9ff4236dbb3460427e86024a. The artifact is 128,409,762 bytes, SHA256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784, with 113,589 person records. Source hashes were verified against each full commit, including 107 pension/income-tax source files for main and 113 for each PR tree.

The table measures the entire PR against current main, including corrections completed before round 5. Monetary figures are pounds rounded to pennies; weighted counts are rounded to one decimal, with totals and deltas rounded independently.

Year Metric Current main Final PR Final minus main
2026 AA charge (£) 3,855,119,004.01 4,685,645,115.99 +830,526,111.98
2026 Income tax including AA (£) 317,275,227,310.92 318,105,753,230.49 +830,525,919.57
2026 Excess people (weighted) 283,125.1 287,095.7 +3,970.5
2026 Excess survey records 320 340 +20
2030 AA charge (£) 5,293,751,706.63 6,202,906,708.06 +909,155,001.43
2030 Income tax including AA (£) 382,859,412,487.94 383,768,567,410.40 +909,154,922.46
2030 Excess people (weighted) 304,933.6 311,044.8 +6,111.2
2030 Excess survey records 381 396 +15

The round-5 corrections alone, measured as final minus the same-main merged pre-fix tree, give exactly £0 change in AA charge, income tax and chargeable amount, and zero change in weighted excess people or survey records in both 2026 and 2030, without a zero tolerance. This does not negate the statutory witness changes: the 2026 gate-crossing and supplied-ANI cases exercise inputs the certified population does not necessarily contain.

For each of 2024, 2025, 2026 and 2027, the final microsimulation's aggregate-only diagnostic found zero supplied-ANI records and zero people with both relievable net-pay contributions and a trading loss. No person can enter finding 3's changed loss-limit path in those years, so its annual-allowance base, allowance and chargeable excess are unchanged under both current and reversed-SB2023 AA parameters. This is a zero-candidate proof using model inputs, not a new four-year fiscal replay. The historical SB2023 AA-only figure therefore remains £11,504,572,991.14, with the historical £1,391,235,559.17 reduction against its original main baseline. No new current-main four-year cost estimate is claimed. The correction still changes pre-2029 outputs for qualifying calculator inputs, as the 2026 regression demonstrates.

From 2029, the code removes NICs-only returned pay and reconstructs dependent relief for the AA income tests, while the supplied-ANI path restores only actual relief. The cap properties and unchanged review witnesses test the resulting income/input/allowance invariance. The measured post-cap year here is 2030; no 2029 aggregate fiscal total was measured, and the 2029 behavior is established by the code and statutory/property tests.

Code and tests were pushed before these runs. One microsimulation ran at a time in the foreground, numerical thread caps were all four, Hugging Face was offline, and only aggregates were saved. Recorded pre-run/year guards had compressor 7.563–7.574 GB and free disk 99.414–100.126 GB, satisfying the limits of below 30 GB and above 45 GB. All worlds used policyengine-core 3.33.2, numpy 2.5.4 and pandas 3.0.6. The round-5 pension-helper SHA256 was b35d32a7975bad8e95ef4db69ebf95dd3b7b8d8a883aadc78cc4c91bfd3145f0; the aggregate wrapper SHA256 was fef8712a422cdd93df851625559dda0e8888e5ed7ae811645bd39b38e1eb1c64.

Round 4 did not produce fresh impact totals: its guard stopped before model loading at 44.036 GB free, and space then fell to about 42.266 GB. Historical figures below remain identified with their original baselines.

Historical round-3 impact

The following measurements predate round 4 and must not be read as fresh current-main or round-4 results. Round-3 paired runs used then-main a94c65df96691eeffa5935f4797735e245439999, round-2 6fbf3e07e7f2f08e3eebaaeff3d33787f4844f98, fix-1-only c38168a3f46016224e2eb11bbe46b46f941d77c4, and round-3 final ae2fe59e033699b3e042b2b2dfdacacbe52b4495. Fix 1 was isolated by fix-1-only minus round 2; fix 2 by final minus fix-1-only.

Year Metric Main Final Change
2026 AA charge (£bn) 3.857017521 4.687543633 +0.830526112
2026 Income tax including AA (£bn) 317.736587244 318.567113164 +0.830525920
2026 Excess people (weighted) 283,125.1 287,095.7 +3,970.5
2026 Excess survey records 320 340 +20
2030 AA charge (£bn) 5.335129850 6.244284851 +0.909155001
2030 Income tax including AA (£bn) 384.402877126 385.312032049 +0.909154922
2030 Excess people (weighted) 304,933.6 311,044.8 +6,111.2
2030 Excess survey records 381 396 +15

Totals and differences are rounded independently; raw aggregates are preserved in the evidence.

Year Fix AA charge change (£) Income tax change (£) Excess people / records change
2026 NICs reclassification 0.00 0.00 0.0 / 0
2026 Post-75 DC input 0.00 0.00 0.0 / 0
2030 NICs reclassification 0.00 0.00 0.0 / 0
2030 Post-75 DC input -2.65 -3.74 0.0 / 0

Both historical tables report AA charge, income tax including that charge, and people with a positive chargeable excess; counts include weighted people and unweighted survey records. Round-3 fix 1 changed none of these aggregates. Its pre-existing DC-default correction had zero measured effect in 2026 and lowered 2030 AA charge by £2.65 and income tax by £3.74; excess counts were unchanged in both years. These figures are pounds, not millions of pounds. The exact post-75 charge residue was −£2.6527891159057617; its income-tax residue was −£3.7371826171875.

The original paired 2026 taper correction raised AA revenue by £830.526112 million. That increase came from counting employer contributions and salary sacrifice in adjusted income under s. 228ZA(4); the threshold gate and rounding can only reduce the taper. Its positive-excess tail was 320 → 340 survey records. The round-3 corrections' separate effects are reported above.

policyengine-uk-data#606 tracks employer contributions imputed as 3 × employee contributions in frs.py. The original statutory correction moved AA charges further from HMRC statistics: its previously measured 2024 charge rose from £2.699 billion to £3.513 billion. HMRC's provisional 2024–25 Table 6 estimates £0.3 billion of AA charge, rounded to £0.1 billion; Table 7 reports 30,440 people declaring excess contributions through Self Assessment. The round-3 final 2024 current-law model AA charge was £3,513,221,874.7659545 (£3.513221875 billion), with 267,235.3 weighted people and 276 survey records with chargeable excess. That matched-year model count is 8.779 times HMRC's 30,440; the model remains conditional on imputed contributions and its DB/carry-forward defaults. HMRC's charge estimate models marginal tax on SA breaches and excludes AfT-only cases; reporting lags and the public-service pensions remedy also limit comparison (methodology).

The SB2023 AA-only replay previously moved closer to the OBR comparison: a £1.382156011 billion correction reduced the modelled four-year cost from £12.895808550 billion to £11.513652540 billion. The hub's tax comparator is £1.551794599 billion, with different scope from the net Budget scorecard. The round-3 replay lowered the four-year AA-only package cost by £9,079,548.52, from £11,513,652,539.66 to £11,504,572,991.14 (£11.504572991 billion). Against the unchanged historical main baseline, the round-3 full taper correction was £1,391,235,559.17 (£1.391235559 billion). The change occurs in the lower-AA counterfactual in 2026 and 2027: income tax falls £4,395,220.86 and £4,684,327.66 respectively; current-law 2024–27 aggregates are unchanged. Cost is the negative of current-law minus reversal income tax plus NI. The replay changes only AA, its floor and adjusted-income limit; MPAA/LTA remain. The original unrounded main baseline was £12,895,808,550.316437. These estimates do not validate the imputed pension tail.

The round-3 runs used the read-only certified uk-data 1.58.1 enhanced FRS 2024–25: 128,409,762 bytes, SHA256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784, 113,589 people. Hashes were checked from bytes and model sources verified against their commits. Each process used one Microsimulation in the foreground, sequentially, with five numerical thread caps at four and Hugging Face offline. Only aggregates were saved. The six canonical foreground processes used numpy 2.5.4, pandas 3.0.6, policyengine-core 3.33.2. Pre-run compressor values were main 18.111004672 GB, round2 12.261343232 GB, fix1 12.026724352 GB, final 11.920375808 GB, sb-round2 11.830099968 GB, sb-final 11.486035968 GB, each below 30 GB. The five thread caps (MKL_NUM_THREADS, NUMEXPR_NUM_THREADS, OMP_NUM_THREADS, OPENBLAS_NUM_THREADS, VECLIB_MAXIMUM_THREADS) were all four. The diagnostic main-initial files are excluded from these canonical results.

Scope and remaining limitations

The relief inventory covers current-year general-income trading losses and the separately modeled pension deduction. Brought-forward trading losses, capital allowances and trading allowance depend on profits; employment/property expenses remain confined to source income; property finance-cost relief is a downstream reducer. Charitable gifts of shares and land and other_deductions remain in allowances, although ITA 2007 s. 24(1)(a) places charitable investment-gift relief at Step 2, leaving a disclosed gap shared with ANI. The survey input defaults to zero.

Sacrifice dates and taxable death benefits are unobserved; their inputs default to zero. DB accrual remains a cash proxy unless statutory pension input is supplied; carry-forward history defaults to zero. The 2015 parameters read back an unused £150,000 taper, but no taper applies before 2016–17. The 2016–19 multiplier reads minimum for the statute's literal £10,000, differing only under a reform of that historical minimum. Overseas/anti-avoidance adjustments, contribution-imputation repair, MPAA/LTA changes, new behavioral assumptions and the wider NICs income-tax defect are outside this PR. The paired-model estimates remain conditional on the pension-data and valuation defaults.

axiom: needed — s.228ZA's RuleSpec module defers the income/taper computations and its companion tests are empty; its parameters contain only current values dated 0001-01-01. The signed encoder is unavailable. Hub follow-up must encode computation, dates and same-case tests, including the s.188/s.233 post-75 boundary, s.193/s.25 loss ordering and s.190(3)/s.191(7) RAS-only basic uplift.

Reproducer: policyengine_uk/tests/test_statutory_pension_edges.py::test_basic_ras_uplift_does_not_relieve_net_pay_in_aa_loss_cap, policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_annual_allowance_trading_losses.yaml::Net-pay relief limits the loss before the threshold gate in 2026, policyengine_uk/tests/test_statutory_pension_edges.py::test_nics_cap_preserves_aa_inputs_and_charge_with_fixed_ordinary_tax, policyengine_uk/tests/test_statutory_pension_edges.py::test_trade_losses_do_not_leak_nics_reclassification_into_aa, policyengine_uk/tests/test_statutory_pension_edges.py::test_post75_dc_member_contributions_are_excluded_from_input_amount

@MaxGhenis

MaxGhenis commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Hand-off for hub review at 878ca0ca2628b7fa7ace77645074d2f64c876b4d, stacked on #2240 head 2154a3158b7ab03937a00186444a8836256428aa. No self-review or merge performed.

Validation: 385 pensions/income-tax YAML cases passed; 42 Python taper/charge/Gift Aid/Scottish-rate cases passed (exit zero). New regressions fail on the original base (34 YAML and 17 Python). Formatting, lint, committed-head fix evidence and bundle verification passed. Full repository suite and documentation build were not run.

Impact, same certified cached uk-data 1.58.1 and locked dependencies: 2026 AA charge +£830.526112m; total income tax +£830.525920m. Four-year SB2023 AA-only cost falls £1.382156011bn to £11.513652540bn. 2024 modeled excess persons: 267,235 versus HMRC 30,440. Source hashes verified; sequential foreground simulations, four threads, aggregates only.

Open items: independent hub review; Axiom s.228ZA computations/history/companion tests need signed encoding. The modeled excess tail still depends on cash-based DB inputs, carry-forward and sacrifice-date assumptions. Those limitations and the separate #2243 ANI scope are documented in the PR.

The sandbox blocked shared Git metadata writes. Work was pushed to the requested feature branch and recovery evidence supplied to the hub. Caller metadata/history were untouched. The hub received handoff, commands and aggregate JSON.

@MaxGhenis

MaxGhenis commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Addressed the review at 878ca0ca2 in 6fbf3e07e7f2f08e3eebaaeff3d33787f4844f98, after clean main merge 6fbf3e07e (main a94c65df9). The PR remains a draft for independent hub review.

  1. Full salary-sacrifice add-back: threshold income uses pension_contributions_via_salary_sacrifice_from_pay. Audited every adjusted-sacrifice use in pensions: PIA's remaining capped term balances the excess already included in employee contributions, so changing that term would double-count. Corrected all three texts. The 2029 case gives threshold £204,720, AA £37,640 and charge £41,562. One YAML plus all six cap-property nodes fail on assertions at the reviewed head and pass here.

  2. Impact explanation: the body attributes the entire net 2026 +£830.5m to employer contributions/salary sacrifice in adjusted income, names uk-data#606 and the 3× imputation, and states the 320→340-record tail. It explains the wider HMRC gap and closer SB2023/OBR replay. HMRC's provisional 2024–25 Tables 6 and 7 were read directly: £0.3bn AA charge and 30,440 people, with reporting-scope caveats and source links.

  3. Historical round-2 Deduct pension relief from adjusted net income #2440 status: then open at e4e632381; it has since merged as df61251c6, and round 5 integrates main 1bdc7a0ab with the agreed fixture. git merge-tree --write-tree is clean at this head (tree 32f9123877e9da5ae35e1db5f8e292f1a85c8cc1). Keep Limit pension relief by earnings and charge the annual allowance #2240's uncapped earnings/basic-amount relief and Deduct pension relief from adjusted net income #2440's supplied-ANI restoration. The PR body contains the exact fixture update AA £20,000→£10,000 / charge £9,000→£13,500 and its checked arithmetic; the hub lands Deduct pension relief from adjusted net income #2440 first and requests the final merge round.
    A broader low-pay cap draw exposed Deduct pension relief from adjusted net income #2440's ANI/PA interaction. The cap property now uses fully relieved own contributions and £160k–£400k pay to keep the personal allowance stable; the £175k witness remains. The observed counterexample is checked separately on the clean combined Deduct pension relief from adjusted net income #2440 tree: cap and no-cap both give ANI £103,565, PA £10,788 and AA charge £16,401.20.

  4. Nits: (a) reduction_rate starts in 2020 with the applicable citation; the historical test asserts the multiplier actually used. (b) The new unrelieved age-80 input-built example kills the surviving relief/contributions mutation. (c) R2/R4/R5 use pre-existing variables and fail on assertions at 2154a3158, then pass here. (d) Shares/land Step-2 relief remains in allowances, the same ANI gap. (e) The 2015 parameter read-back is unused. (f) The 2016 multiplier reads minimum for the literal £10,000. The last three are disclosed in the body as requested.

Validation commands and counts:

uv run --frozen --no-sync policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc/pensions policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax -c policyengine_uk
# 390 passed
uv run --frozen --no-sync pytest -n 4 -o usefixtures=cloned_uk_tax_benefit_system policyengine_uk/tests/test_pension_annual_allowance_taper.py policyengine_uk/tests/test_pension_annual_allowance_properties.py policyengine_uk/tests/test_gift_aid.py policyengine_uk/tests/test_scottish_income_tax_rates.py -q
# 48 passed
make format
uvx ruff format --check .

Assertion-red evidence: historical 3; reviewed-head 2029 YAML 1 plus properties 6; relief/contributions mutation 1. The hub received the isolated diagnostic commands and complete logs. The final cap-property domain was also rerun at the reviewed head. Format/lint, diff checks, committed-head fix evidence and bundle verification pass. Full repository tests and docs build were not run.

Impact: certified uk-data 1.58.1 SHA verified before each of three serial foreground aggregate-only microsimulations; four numerical threads; pre-run compressors 13.529/13.218/12.909GB. R2 changes neither 2026 aggregate; it adds £83.042990m AA charge in 2030. Full PR versus then-main: AA +£830.526112m in 2026 and +£909.155004m in 2030. The pre-2029 four-year SB2023 correction stays £1.382156011bn; it was not rerun because behavior in those years is unchanged.

Final-head provenance: final head 6fbf3e07e7f2f08e3eebaaeff3d33787f4844f98 is a test-only descendant of the actually measured head 22794344eb1d365ee306ebac4f77201ed88ca062. Later changes scope the cap property to fully relieved contributions at high pay and merge then-main a94c65df96691eeffa5935f4797735e245439999. Since measured main faf502d9b26993326a68e4f97f716ef9c829c1a2, then-main changes only comments in policyengine_uk/tests/test_legacy_award_readers_properties.py; its AST and non-comment tokens are identical. All non-test files and every recorded impact source hash match their respective measured commits. Neither the final head nor then-latest main was measured again: runtime equivalence was checked from source hashes, and actual measured code SHAs were supplied to the hub.

Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_annual_allowance.yaml::The 2029 NICs cap does not limit the salary sacrifice threshold income addback, policyengine_uk/tests/test_pension_annual_allowance_taper.py::test_nics_salary_sacrifice_cap_preserves_taper_and_charge

The hub received handoff, recovery and aggregate evidence. Caller Git metadata was untouched. Open items: independent hub review, the post-#2440 merge round and Axiom signed encoding/companion tests. No self-review or PR merge performed.

@MaxGhenis

MaxGhenis commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Addressed both findings from the review of 6fbf3e07e in ae2fe59e033699b3e042b2b2dfdacacbe52b4495, through separate commits c38168a3f and ae2fe59e0. The PR remains a draft for the hub's independent review; no PR merge or self-review performed.

  1. NICs reclassification: remove the synthetic returned pay and recompute relief using actual earnings/member contributions. Adjusted income removes the synthetic pay/contribution pair. Full employer sacrifice remains in the pension input and threshold addback. The age-80 witness now has threshold £190,000 / AA £60,000; the basic-RAS witness has threshold £199,400 / AA £60,000 in 2029 and 2030. Coverage includes the review's five regressions, 16 DB/DC × net-pay/RAS × age 45/80/year cases and a broad Hypothesis domain with post-75/low-earnings inputs. Charge properties hold ordinary tax inputs fixed. The old seeded 300 charge difference was £3,242.83 through ANI/PA, so unrestricted charge invariance is explicitly withdrawn.
  2. Post-75 DC inputs: actual member payments from age 75 are excluded under ss. 188/233; employer provision and full sacrifice remain. Section 228ZA(4)(d)(ii) still subtracts all actual own payments from adjusted income. The corrected age 80 fixture gives input £80,000 / adjusted income £240,000 / AA £60,000 / charge £9,000. The mutation witness moved to age 45: relief £3,600 / threshold £206,400 / adjusted £290,000 / AA £45,000. The wrong all-contributions deduction is killed by one expected assertion failure (£160,000 versus £206,400). This corrects a pre-existing DC default; its measured effect is reported below.

Validation:

uv run --frozen --no-sync policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc/pensions policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax -c policyengine_uk
# 392 passed (exit 0)
uv run --frozen --no-sync pytest -n 4 -o usefixtures=cloned_uk_tax_benefit_system policyengine_uk/tests/test_pension_annual_allowance_taper.py policyengine_uk/tests/test_pension_annual_allowance_properties.py policyengine_uk/tests/test_statutory_pension_edges.py policyengine_uk/tests/test_gift_aid.py policyengine_uk/tests/test_scottish_income_tax_rates.py -q
# 67 passed
# Isolated relief-versus-all-contributions mutation
# exactly 1 expected assertion failure; wrapper exit 0
make format
uvx ruff format --check .
# passed

The exact five review regressions fail at 6fbf3e07e (5 assertion failures) and pass within the final suite. The focused P1 run separately passed 6 tests. Committed-head fix evidence and recovery bundle verification pass. All work stayed in the assigned workspace. Tests ran in the foreground with at most 4 workers; numerical workers had 1 thread, microsimulations 4. The existing YAML anyio rewrite warning remains. Full repository tests/documentation build were not run.

Impact: Fresh paired 2026/2030 runs use certified uk-data 1.58.1 (SHA verified before every process), then-main a94c65d, round 2 6fbf3e0, P1 c38168a and final ae2fe59. P1 moves neither metric/count in either year; P2 moves neither metric in 2026, and lowers 2030 AA charge £2.65 / income tax £3.74 with unchanged excess counts. Full PR versus main: AA charge +£830.526112m in 2026 and +£909.155001m in 2030; income tax +£830.525920m / +£909.154922m. Final excess people are 287,095.7 (340 records) / 311,044.8 (396 records). The pre-2028 reversal does move: the fresh four-year package cost falls £9.079549m to £11.504572991bn, so the full taper correction rises to £1.391235559bn. The body retains the #606 imputation/HMRC disclosure. Six serial foreground processes used four threads and pre-run compressors 18.111/12.261/12.027/11.920/11.830/11.486 GB; outputs are aggregates only.

Historical round-3 coordination: #2440 was then open at 7f1b608f2. It has since merged as df61251c6; round 5 merges main 1bdc7a0ab and applies the agreed fixture resolution. The textual merge is clean (f72cddd5f34f6eb400788075fcc0d44f52e26c43) and the review's two-comment semantic patch applies. The body retains the required AA £10,000 / charge £13,500 fixture resolution for the #2440-first landing order. Both PRs' tests must run on that actual merged tree. Axiom signed encoding remains a hub follow-up.

The separate ordinary-tax defect is tracked in #2454, whose issue contains the runnable MOCK reproducer: the post-75 £160k witness gains £12,600 ordinary tax and the £100k PA witness £16,371 in each year.

Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_annual_allowance.yaml::The 2029 NICs cap does not limit the salary sacrifice threshold income addback, policyengine_uk/tests/test_statutory_pension_edges.py::test_post75_dc_member_contributions_are_excluded_from_input_amount

The hub received the handoff and aggregate evidence; caller metadata was untouched.

Add the homogeneous loss property and assert default DC input paths. Clarify RAS and the 6 October age approximation.
@MaxGhenis

MaxGhenis commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator Author

Addressed the round 3 review at ae2fe59e0 in 998f3c5b64019a4748ab0e20d26b306ab7fd5d26. The PR remains a draft for independent hub review; no PR merge or self-review performed.

  1. P1 — actual income and Step 2 relief: both AA income tests use annual_allowance_net_income. It removes the synthetic taxable earnings before reconstructing adjusted total income, the s.24A relief cap and the s.64 deduction limited to available actual income. Threshold income also recomputes pension relief on actual contributions/earnings. Ordinary income-tax formulas are unchanged. The low-pay £10,000 loss witness now gives threshold £201,000 / adjusted £301,000 / AA £39,500 in 2029 and 2030. The age-80 £250,000-pay case gives AA £31,250; the £230,000-pay gate-crossing case gives threshold £202,500 / AA £38,750 / charge £41,062.50. Low-pay £600 member cases cover net pay and RAS before and after 75.
  2. P2 — property input paths: default DC and supplied DB populations are separate. Tests assert each holder's supplied input periods before calculation; fixed DC witnesses assert default input amounts £130,000 and £304,600. The loss-inclusive Hypothesis property uses homogeneous DC/DB simulations across both years, net pay/RAS, low earnings and ages 45/74/75/80. The original reviewer property is rerun unchanged.
  3. P3 — documentation and links: the body and round 3 reply link #2454; the old reply's Reproducer is corrected to test_post75_dc_member_contributions_are_excluded_from_input_amount. The engineering doc qualifies the RAS statement to relievable pre-75 payments and documents the 6 October age / all-or-none birthday-year approximation.

Round 4 validation: 394 pensions/income-tax YAML cases and 71 Python cases passed, exit 0. Python used three workers; numerical workers used one thread. The unchanged review originals also pass: cap matrix 16 rows, basic RAS, 300/300 comparisons per year, age fixtures, 12 loss witnesses, all four homogeneous DC/DB loss-property nodes, and six #2454 boundary rows. The standalone gate-crossing age-80 charge is £41,062.50 at both caps. The six ordinary-tax/ANI/PA/reclassification rows match round 3 exactly; #2454 does not grow.

The hub received the commands and complete logs. The CLI/pytest harnesses tune garbage collection and retain independent model clones; no calculated values are stubbed. The commands invoke the requested policyengine-core YAML suites, the five pension/property/Gift Aid/Scottish-rate Python files (-n 3), and unchanged reviewer scripts. make format, uvx ruff format --check . (1,735 files), diff checks and committed-head fix-evidence validation passed. The YAML run retains the existing anyio rewrite warning. Full repository tests and the documentation build were not run; the requested focused suites cover this change and the engineering doc is standalone Markdown.

Historical round-4 2026/2030 impacts were blocked by host disk capacity. The required guard stopped before model loading at 44.036GB free; host activity then reduced available space to about 42.266GB. Own temporary fetch packs, duplicate snapshot trees and download/tool caches were removed. No new Microsimulation has run, and the 45GB free-disk requirement has not been relaxed. The fix and all tests were pushed first. The certified uk-data 1.58.1 file remains unchanged (128,409,762 bytes; SHA256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784). Then-main was a1b210fd6; round-4 code was 998f3c5b64019a4748ab0e20d26b306ab7fd5d26. Historical round 3 figures below are not fresh round 4 measurements. In particular, the prior P2 move was −£2.6527891159057617, in pounds. The round-4 loss correction was gated by the NICs return from 2029 onward. Round 5 also corrects net-pay/loss ordering before 2029 and measures it against the same-main pre-fix baseline.

Round-4 then-main a1b210fd6c1426891abff42579fed7d6b73c54c6 included #2436. #2440 has since merged as df61251c6; round 5 merges current main 1bdc7a0ab and applies the agreed AA £10,000 / charge £13,500 fixture update with both comments. Axiom signed encoding and the disclosed contribution-data/DB/carry-forward limitations remain hub follow-ups. No unrestricted charge invariance is claimed; general charge comparisons hold ordinary tax inputs fixed. The separate ordinary-tax defect is tracked in #2454.

Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_annual_allowance_trading_losses.yaml::Trading loss limits for AA use actual income in 2029, policyengine_uk/tests/test_statutory_pension_edges.py::test_trade_losses_do_not_leak_nics_reclassification_into_aa, policyengine_uk/tests/test_statutory_pension_edges.py::test_post75_dc_member_contributions_are_excluded_from_input_amount

The hub received handoff, recovery and aggregate evidence. All work stayed in the assigned workspace; caller Git metadata was untouched.

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Addressed the round-4 review at pushed head b6e81966284d4f4d9ff4236dbb3460427e86024a. Main 1bdc7a0ab, including #2440, #2089, #2061 and #2423, is merged in 9a146999c. The first checked push succeeded; the PR remains a draft.

  1. Merge Deduct pension relief from adjusted net income #2440 and resolve its fixture. The actual merge is included, and the agreed two-comment fixture resolution now expects threshold income £330,000, adjusted income £370,000, AA £10,000 and charge £13,500 (£30,000 × 45%). The merged pensions/income-tax YAML suites pass 403 cases, including this fixture. Merged-head CI: 15/15 checks passed at this head, including all four Python shards, full YAML, lint, fix evidence, documentation and Python 3.11–3.14 smoke imports (CI run)..

  2. Supplied ANI no longer receives pay/loss reconstruction. The supplied-input path builds the annual-allowance base from supplied ANI plus gross Gift Aid plus pension relief recomputed on actual contributions and earnings. It does not subtract returned pay or swap trading-loss relief. The cap property now includes supplied-ANI people aged 45 and 80, with and without losses, each in a separate simulation because ANI is a partial-input override. The age-45 2030 witness (£2,000 pay, £201,000 sacrifice, £100,000 employer, supplied ANI £150,000) has threshold £351,000 and AA £10,000 at both caps. The £201,000 / £39,500 leak was at the reviewed unmerged head; after merging Deduct pension relief from adjusted net income #2440, age-80 and age-45 loss variants still required this correction. The helper comment now describes the distinct input path.

  3. Loss relief is limited after relievable net pay in every year. The annual-allowance helper deducts relievable net-pay contributions from the income available for current-year loss relief before applying the s.25(4) limit, including people with no returned pay. This follows FA 2004 s.193(2); only its (4)/(6) excess is a Step 2 deduction. The corrected £600-net-pay witness has threshold £201,000, adjusted income £301,600 and AA £39,200 in 2029/2030. The new 2026 £200,500-sacrifice/£1,000-net-pay witness has threshold £200,500, adjusted income £301,500 and AA £39,250, replacing £60,000. This changes pre-2029 behavior for the narrow income-limited-loss/net-pay group; the durable documentation and PR body now state that scope. Ordinary income-tax formulas remain unchanged; the separate NICs interaction is tracked in #2454.

Against the same-main merged pre-fix tree, the seeded 240-person MOCK population has 14 people with higher threshold and adjusted income in every 2016–2030 year (10 default DC, four supplied DB). Pre-2029 increases range from £600 to £32,754. One DC person crosses threshold income £110,000 → £120,000 in 2016–19: AA falls £40,000 → £10,000 and the charge rises £13,500 each year. This population has no allowance/charge change in 2020–30; the separate 2026 fixture above demonstrates a later gate crossing. Returned pay, raw net income, raw trading-loss relief and pension input arrays are byte-identical. All eight same-main cap/year/scheme ordinary-tax arrays are also byte-identical (960 stored person-world values). These comparisons isolate round 5 from main integration.

  1. PR text corrected. Removed references to evidence files absent from the repository, replaced stale main and pending-Deduct pension relief from adjusted net income #2440 statements, and retained historical results with their original heads and units. Round-4's verified 240-person result remains zero cap mismatches, versus 32 at round 3; all four loss-property nodes passed, and 2016–2028 was byte-identical at that historical revision. The new pre-2029 correction is explicitly distinguished. The prior post-75 2030 charge move remains −£2.6527891159057617, in pounds.

The basic-RAS £900 charge difference is a historical pre-#2440 result: merging #2440 removes that charge and personal-allowance difference (the capped-minus-uncapped ANI difference is now £1,600). Both post-75 ordinary-tax differences remain. Across all six NICs boundary witnesses, all 252 recorded variable cells are identical between the same-main merged pre-fix tree and this final tree, so round 5 does not expand that boundary.

Fresh validation:

  • 403 YAML cases passed, exit 0; one existing anyio assertion-rewrite warning.
  • Unchanged break_reconstruction.py: 76 cap comparisons, zero leaks, all independent oracle comparisons agree, exit 0.
  • Unchanged 16-row cap matrix: zero differences in threshold income, adjusted income, pension input, allowance and stable-band charge.
  • Six-file Python suite, including Gift Aid, Scottish rates and Deduct pension relief from adjusted net income #2440 ANI tests: 88 passed at -n 3, exit 0, in 3,336.44 seconds.
  • Unchanged original scripts and current-head mutation adaptation: All requested unchanged reviewer scripts completed successfully: the 300-person checks in both years, 12 loss witnesses, four DC/DB loss-property nodes, 3,888-case relief oracle, supplied-ANI witnesses and 240-person loss-rich cap checks (zero mismatches). All 35 original files were byte-verified unchanged. The unchanged historical mutation replay retains its three kills and equivalent M4 at 998f3c5b6; the adaptation to this committed head has a green unmodified baseline and all four faults killed by assertion failures, with verified source imports and no import errors. The complete result summary has 20 evidence groups passed, zero pending and zero failed.
  • make format passed formatting/lint; uvx ruff format --check . passed 1,748 files.

Portable equivalents of the actual foreground suite commands:

uv run --frozen --no-sync policyengine-core test policyengine_uk/tests/policy/baseline/gov/hmrc/pensions policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax -c policyengine_uk
uv run --frozen --no-sync pytest -n 3 -o usefixtures=cloned_uk_tax_benefit_system policyengine_uk/tests/test_pension_annual_allowance_taper.py policyengine_uk/tests/test_pension_annual_allowance_properties.py policyengine_uk/tests/test_statutory_pension_edges.py policyengine_uk/tests/test_adjusted_net_income_pension_relief.py policyengine_uk/tests/test_gift_aid.py policyengine_uk/tests/test_scottish_income_tax_rates.py -q
make format
uvx ruff format --check .

The actual harnesses tune garbage collection and reuse independent parsed-model clones; they do not stub calculated values. Numerical workers use one thread, Python uses three workers, and unchanged reviewer scripts run serially. Original script bytes and source import paths are verified. The full repository suite and documentation build were not run locally; GitHub CI documentation and full YAML checks have passed, with remaining merged-head CI status reported above. These diagnostics replay existing evidence and do not constitute an independent review.

Impact:

Three sequential foreground microsimulations completed on the certified uk-data 1.58.1 enhanced FRS 2024–25: current main 1bdc7a0ab269be750f8b6f57bd0bdb3a09b26517, the same-main merged pre-fix tree 9a146999c73f83be8cb11d03ad0cdede016c6ba6, and final b6e81966284d4f4d9ff4236dbb3460427e86024a. The artifact is 128,409,762 bytes, SHA256 056e80fd8e9151d78f432a25cde863340baee81304c6216a37fb575ce8563784, with 113,589 person records. Source hashes were verified against each full commit, including 107 pension/income-tax source files for main and 113 for each PR tree.

The table measures the entire PR against current main, including corrections completed before round 5. Monetary figures are pounds rounded to pennies; weighted counts are rounded to one decimal, with totals and deltas rounded independently.

Year Metric Current main Final PR Final minus main
2026 AA charge (£) 3,855,119,004.01 4,685,645,115.99 +830,526,111.98
2026 Income tax including AA (£) 317,275,227,310.92 318,105,753,230.49 +830,525,919.57
2026 Excess people (weighted) 283,125.1 287,095.7 +3,970.5
2026 Excess survey records 320 340 +20
2030 AA charge (£) 5,293,751,706.63 6,202,906,708.06 +909,155,001.43
2030 Income tax including AA (£) 382,859,412,487.94 383,768,567,410.40 +909,154,922.46
2030 Excess people (weighted) 304,933.6 311,044.8 +6,111.2
2030 Excess survey records 381 396 +15

The round-5 corrections alone, measured as final minus the same-main merged pre-fix tree, give exactly £0 change in AA charge, income tax and chargeable amount, and zero change in weighted excess people or survey records in both 2026 and 2030, without a zero tolerance. This does not negate the statutory witness changes: the 2026 gate-crossing and supplied-ANI cases exercise inputs the certified population does not necessarily contain.

For each of 2024, 2025, 2026 and 2027, the final microsimulation's aggregate-only diagnostic found zero supplied-ANI records and zero people with both relievable net-pay contributions and a trading loss. No person can enter finding 3's changed loss-limit path in those years, so its annual-allowance base, allowance and chargeable excess are unchanged under both current and reversed-SB2023 AA parameters. This is a zero-candidate proof using model inputs, not a new four-year fiscal replay. The historical SB2023 AA-only figure therefore remains £11,504,572,991.14, with the historical £1,391,235,559.17 reduction against its original main baseline. No new current-main four-year cost estimate is claimed. The correction still changes pre-2029 outputs for qualifying calculator inputs, as the 2026 regression demonstrates.

From 2029, the code removes NICs-only returned pay and reconstructs dependent relief for the AA income tests, while the supplied-ANI path restores only actual relief. The cap properties and unchanged review witnesses test the resulting income/input/allowance invariance. The measured post-cap year here is 2030; no 2029 aggregate fiscal total was measured, and the 2029 behavior is established by the code and statutory/property tests.

Code and tests were pushed before these runs. One microsimulation ran at a time in the foreground, numerical thread caps were all four, Hugging Face was offline, and only aggregates were saved. Recorded pre-run/year guards had compressor 7.563–7.574 GB and free disk 99.414–100.126 GB, satisfying the limits of below 30 GB and above 45 GB. All worlds used policyengine-core 3.33.2, numpy 2.5.4 and pandas 3.0.6. The final pension-helper SHA256 is b35d32a7975bad8e95ef4db69ebf95dd3b7b8d8a883aadc78cc4c91bfd3145f0; the aggregate wrapper SHA256 was fef8712a422cdd93df851625559dda0e8888e5ed7ae811645bd39b38e1eb1c64.

Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/pension_annual_allowance_trading_losses.yaml::Net-pay relief limits the loss before the threshold gate in 2026, policyengine_uk/tests/test_statutory_pension_edges.py::test_nics_cap_preserves_aa_inputs_and_charge_with_fixed_ordinary_tax, policyengine_uk/tests/test_statutory_pension_edges.py::test_trade_losses_do_not_leak_nics_reclassification_into_aa, policyengine_uk/tests/test_statutory_pension_edges.py::test_post75_dc_member_contributions_are_excluded_from_input_amount

@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Implemented the round-5 P2 in 39f5fa85c2e3ed365dc788ad2e9f6355a2c3dd5e (direct child of b6e819662, pushed once successfully; PR remains draft).

The supplied isolated patch is applied unchanged: the rebuilt s.24A cap now calls the existing RAS-aware pension_contributions_relief_amount. It limits pure net pay to earnings, permits the basic uplift only for RAS (including mixed payments), and gives no member relief from age 75. The £3,600 net-pay witness now gives threshold £200,000, adjusted £302,000, AA £60,000 and charge £25,245.

Validation:

  • New regression at 2026/2029/2030 × £2,000/uncapped: six assertion failures at b6e8196 → six passes. All six £2,000-payment controls already passed and remain green. Combined focused run 12 passed; eight helper vectors also pass.
  • policyengine-core test pensions + income-tax YAML: 403 passed. Six requested Python files via pytest -n 3: 100 passed (2,151.85s), including Gift Aid/Scottish rates. All foreground; numerical threads one.
  • Unchanged extra_adversarial.py: six net-pay/loss and 48 supplied-ANI worlds pass. Unchanged break_reconstruction.py, gate_invariance.py, confirm_findings.py, supplied_ani.py, cap-cases.py, basic-ras-cap.py and extra-checks.py pass. Originals were SHA256-verified before and after execution.
  • NICs-only salary sacrifice cap changes ordinary income tax after age 75 #2454 reproducer, old versus new: all 252 cells identical, zero growth. make format/lint and uvx ruff format --check . pass (1,748 files).

Impact method: certified-input proof; no new microsimulation. Rechecked 1.58.1's actual SHA256, absent trading-loss/ANI inputs, zero loss default and the exact changed path. Zero loss makes the changed cap irrelevant. R6 minus R5 is exactly zero in 2026/2030 AA charge, income tax and excess counts; R5 provenance was revalidated. The retained PR-versus-main AA-charge deltas remain +£830,526,111.98 and +£909,155,001.43. Pre-2029 calculator scope remains disclosed.

The separate ordinary-ATI formula is unchanged. Its standalone MOCK issue draft is ready for the hub to file (ATI £248,400 versus £250,000; cap £62,100 versus £62,500), as requested. The PR body is updated. Fresh CI is pending; hub independent review and signed Axiom encoding remain open. Handoff/proof logs and a verified recovery bundle are retained in the workspace job folder; Hypothesis storage is removed and no foreground test/model process remains. No self-review or merge performed.

Reproducer: policyengine_uk/tests/test_statutory_pension_edges.py::test_basic_ras_uplift_does_not_relieve_net_pay_in_aa_loss_cap

Merged via the queue into main with commit 8bd87de Oct 17, 2026
17 checks passed
@MaxGhenis
MaxGhenis deleted the fix/2244-aa-taper-income branch October 17, 2026 21:24
@MaxGhenis

Copy link
Copy Markdown
Collaborator Author

Landed by the UK hub through the merge queue, pinned to head 39f5fa85c2e3ed365dc788ad2e9f6355a2c3dd5e.

  • Gates at this head:
    • gh pr checks exits 0 (16 of 16).
    • Mergeable and CLEAN; the hub marked it ready.
    • No CHANGES_REQUESTED review.
  • Reviews: the hub's independent GPT-6.1 Sol reviews, each executed.
    • Round 1, REQUEST_CHANGES at 878ca0ca2: threshold income added back only the NICs-capped part of salary sacrifice, but s. 228ZA(5)(b) adds back all of it.
    • Round 2, REQUEST_CHANGES at 6fbf3e07e: the 2029 NICs-cap reclassification still moved threshold income after age 75 and at the basic relief-at-source amount.
    • Round 3, REQUEST_CHANGES at ae2fe59e0: trading-loss relief was computed on the synthetic NICs pay.
    • Round 4, REQUEST_CHANGES at 998f3c5b6: three findings.
    • Round 5, REQUEST_CHANGES at b6e819662: the rebuilt s. 24A loss cap gave net-pay contributions the £3,600 basic uplift, which is for relief at source only.
    • Round 6, APPROVE with no findings, at this head (Subfleet 20261017-132008-uk2441-review-r6).
      • Round 5's patch is applied unchanged.
      • The £3,600 net-pay case gives £200,000 / £302,000 / AA £60,000 / charge £25,245 in 2026, 2029 and 2030, at both caps.
      • 432 of 432 shared-helper worlds pass, including above-earnings net pay, basic RAS, mixed payments, zero earnings, and ages 74, 75 and 80.
      • 403 YAML and 100 Python tests pass.
      • The NICs-only salary sacrifice cap changes ordinary income tax after age 75 #2454 reproducer is unchanged.
  • Law:
    • The annual allowance taper's two income tests, FA 2004 s. 228ZA, with employer contributions and salary sacrifice counted as the section requires.
    • Default DC input leaves out member payments from age 75, under ss. 233(1)(a) and 188(3)(a).
    • The basic £3,600 amount applies to relief at source only, under ss. 190–191.
    • Trading-loss relief is rebuilt from actual earnings and contributions, under ITA 2007 ss. 24A and 64.
  • Impact: measured in round 5 on the certified uk-data 1.58.1, against main 1bdc7a0ab.
    • Annual allowance charge: +£830.53m in 2026 and +£909.16m in 2030. The rise comes from counting employer contributions and salary sacrifice in adjusted income.
    • Round 6's change can't be reached on 1.58.1, which has no trading-loss or supplied-ANI input. Its effect is exactly zero, shown by an input proof rather than a new run.
    • The change also corrects pre-2029 results for the narrow group whose loss relief is income-limited and who pay net-pay contributions.
    • Data caveat: the model's AA charge is already far above HMRC's provisional 2024–25 £0.3bn, mainly because uk-data imputes employer contributions as 3 × employee (uk-data#606). This fix corrects the formulas, so it moves the total further from HMRC's figure.
  • Follow-ups:

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.

Annual allowance taper ignores the s. 228ZA threshold-income test and employer contributions in adjusted income

1 participant