Measured, not asserted

Does obfuscation break required minimum distributions?

The IRS revised the uniform lifetime table effective 2022. The old divisors did not become wrong when it did; they became superseded. Every row of both tables is a genuine published figure, which means a plausibility check on the divisor cannot separate them -- only the date can. This test renamed the field carrying that date, and a 75-year-old who had taken exactly the right distribution was told they were $1,241.27 short and owed $310.32 in excise tax, on a line whose own check reported the divisor as verified.

A number that is right for a year that is not this one

A required minimum distribution is one division: last year's closing balance over a life-expectancy divisor for the account holder's age. The divisor comes from a table the IRS publishes, and that table has been revised. Under the pre-2022 table a 75-year-old divides by 22.9; under the 2022 revision they divide by 24.6. Both are real. Neither is a placeholder, a rounding error or a corrupt value.

The fixture holds both publications, dated 2002 and 2022, and two accounts. IRA-88214 is 75 with $412,000 at last year end and took $16,750 against a required $16,747.97 -- satisfied, narrowly. IRA-88377 is 73 with $88,500 and took $2,000 against a required $3,339.62, a genuine $1,339.62 shortfall carrying $334.91 of excise at 25%.

The distribution engine is copied in unprotected. It selects the newest table whose effective date has arrived, and it takes the caller's word for what that date is.

Protection on its own moved nothing

Twenty-five comparisons across five areas and five presets -- the ES5 default path, the modern target, the emit gate's option set on both targets, and the string transforms -- every protected run identical to the unprotected one, with each original run twice before anything was compared against it.

Everything below required member renaming aimed at a name the unprotected engine reads.

The wrong row, and a check that certifies it by name

Renaming the effective-date field on the published tables means no row can be established as in force, so the engine falls back to the first row it was given -- the 2002 publication. IRA-88214 now divides $412,000 by 22.9 instead of 24.6 and reads table=ULT-2002 divisor=22.9 required=$17991.27 taken=$16750 shortfall=$1241.27 excise=$310.32 => MISSED DISTRIBUTION.

This retiree did nothing wrong. They took the amount the plan told them to take, and the plan now reports them to have missed a required distribution and assesses a penalty. IRA-88377's genuine shortfall is overstated too, from $1,339.62 to $1,583.00.

The plan's own rule of record passed. It refuses an amount that was not divided by a divisor a published table actually carries for that age, and 22.9 is exactly that: a real divisor, for age 75, from a real IRS table. The check reports checked by divisor-check(age 75 => 22.9 from ULT-2002) and every clause of that sentence is true.

This is why a plausibility bound cannot help here. A band tight enough to reject 22.9 would reject correct divisors for other ages; a band loose enough to accept every genuine divisor accepts this one. The thing to assert is not whether the divisor looks sensible but which ROW was applied -- and the row is identified by the date that just went missing.

The opposite direction, and the guard that switches itself off

Renaming the account holder's age is caught cold. No table carries a divisor for an undefined age, so the plan's rule refuses the year: year-rejected(divisor-check(no published table carries a divisor for age undefined)). That is the mitigation working.

Rename the age together with the enabling flag in front of that rule -- one adjacent line in the same options object -- and the outcome inverts completely. The unfound divisor falls back to the library's own default of infinity, the division yields zero, and both accounts read divisor=Infinity required=$0.00 => DISTRIBUTION SATISFIED. IRA-88377's real $1,339.62 shortfall and $334.91 excise vanish.

This is the fifth pass in this series in which a quiet name switches off a loud one. The flag is consulted first, so the strict control behind it is never reached, and any reasoning of the form "if this broke we would know" only holds when the thing that would tell you is not downstream of the thing that broke.

One record field, no pairing, and the shortfall disappears

The cleanest arm in the area needs no flag at all. Renaming the amount actually distributed leaves the requirement computed perfectly and makes the comparison against it unanswerable: required=$3339.62 taken=$undefined shortfall=$NaN excise=$0.00 => DISTRIBUTION SATISFIED.

The test that would have caught the shortfall asks whether the shortfall is greater than zero. A comparison against NaN is false, so the branch that reports a missed distribution is skipped, and skipping a refusal permits. The plan's divisor check passes because it was written about the divisor and the divisor is fine.

Note what survives on that line. The required amount is right to the cent. The balance is right. The divisor and the table are right. The only unreadable figure is the one the account holder actually took, and the verdict printed beside it is the reassuring one.

By contrast, renaming the prior year-end balance refuses the whole calculation with non-finite-balance(refused before the division). Losing the number the answer is built FROM fails closed and loudly; losing the number the answer is COMPARED AGAINST fails open and silently. That contrast has now held for eleven consecutive shapes in this series.

The penalty and the paperwork

Two arms move less than they look like they should, and both are worth naming so they are not mistaken for safety. Renaming the excise percentage leaves the shortfall correctly reported at $1,339.62 and reduces the penalty to excise=$0.00 -- the plan still knows the distribution was missed and no longer prices it. Renaming the table's label leaves every figure correct and prints table=undefined, which is a paperwork loss with no money attached.

The far side is a clean negative, and deliberately so. The paying agent's confirmations arrive as text the agent wrote, so renaming the names in them moved only this plan's display of what came back, never a verdict. Where the other party writes the name, only your read of it can move.

What this means if you protect a distribution or custody front end

The exposure is a rename pattern wide enough to reach an installed library's option keys or the fields of the records you hand it. Keep member renaming off that boundary with a MemberRegexp scoped to your internal names.

Then make one design change that is worth having regardless of obfuscation, because a dropped key over a serialisation hop or a hand-edited configuration file produces the same miss: make an undatable table selection an error rather than a fallback to the first row, and assert which row was applied rather than whether the value it carries looks reasonable. In an effective-dated table every value looks reasonable, because every value was reasonable once.

The evidence discipline is the same one described in ISO 27001 and SOC 2 evidence for JavaScript builds: an assertion about which input was in force is worth more than an assertion that the output looked plausible. The protected build computed both accounts correctly on all five presets.

Frequently asked questions

Did obfuscation change any required distribution on its own?

No. Five areas ran through five presets with every protected output byte-identical to the unprotected run. Every failure here required member renaming aimed at a name the installed distribution engine also reads.

What happened when the table's effective date was renamed?

No row could be established as in force, so the engine fell back to the first table it was given -- the superseded pre-2022 publication. A 75-year-old's divisor changed from 24.6 to 22.9 and a correctly satisfied distribution was reported as $1,241.27 short with $310.32 of excise tax.

Would a range check on the divisor have caught it?

No, and this is the point of the area. Both 22.9 and 24.6 are genuine published divisors for age 75. A band tight enough to reject one rejects correct values for other ages. Assert which row was applied, not whether the value looks sensible.

Did the plan's own divisor check pass?

Yes, and it named the wrong table as its evidence: age 75 mapped to 22.9 from the 2002 table. Every clause of that sentence is true. The check was written about the divisor, and the divisor was real.

Which arm made a real shortfall disappear?

Renaming the amount actually distributed. The requirement was still computed to the cent, but the shortfall comparison became NaN, the branch that reports a missed distribution was skipped, and a genuine $1,339.62 shortfall with $334.91 of excise read DISTRIBUTION SATISFIED.

Was anything caught by the plan's own rule?

Yes. Renaming the account holder's age alone was refused outright, because no table carries a divisor for an undefined age. Renaming it together with the flag that enables the rule produced an infinite divisor, a zero requirement, and both accounts reading satisfied.

What is the cheapest test that would have caught this?

Run one known account through a year end and assert both the required amount to the cent and the identifier of the table it came from. That fails in every arm here. A test that checks a table is configured passes in most of them.

Related reading