Measured, not asserted

Does obfuscation break interest accrual and day count?

Protection on its own did not move a single posting in this test. Member renaming did -- and the interesting part is not the size of the error, it is that the control which exists to catch exactly this kind of error is arithmetically incapable of seeing it.

A year is not a fixed number of days

Interest is principal multiplied by rate multiplied by time, and time is the part that is not arithmetic. A year is 365 days, or 360, or twelve months of thirty days each, depending on which convention the note was written under. All three are ordinary. None of them is inferable from the numbers, because the same principal and the same rate over the same calendar produce three different figures that all look like interest.

The sample used here is a small servicing engine with a caller that configures actual/365, the convention on this note. The engine's own default is the money-market actual/360. It is copied in unprotected, because an installed servicing library is not rebuilt when your bundle is.

The first measurement is worth stating before anything else. Protection alone -- five presets, no member renaming -- left every posting in this file identical, and so did the other four areas measured this pass: twenty-five comparisons, no differences at all. Nothing below is an argument that obfuscating a servicing front end changes what it computes.

The convention moved and nothing objected

Renaming dayCountBasis is the option-key case: the name never goes missing, because the library substitutes its own default. January's interest went from $1290.11 to $1308.03. Over a full year, the same loan went from $15190.00 to $15400.97 -- an extra $210.97 on one $248,000 mortgage, every year, in the lender's favour.

That is a small number by design. It is 1.39%, which is exactly the ratio between 365 and 360, and it is the reason this failure is worth a page of its own. Every figure the engine can produce under any of these conventions is an ordinary interest amount. There is no non-finite value, no missing field, no implausible total and nothing to raise an exception about.

The servicer in this sample does what servicers actually do: it reconciles every posting against the per-diem stated on the note, and the posting record prints the two figures side by side. After the rename that line read at $42.1944/day vs note $41.62/day. The check passed. It was always going to pass, because a tolerance wide enough to allow for partial months, rounding and mid-period balance changes has to be wider than five per cent, and this error is 1.39%.

The guard that could not have helped

There is a pattern in this series where a strict control sits behind an enabling flag, and losing both names is worse than losing either. It was worth checking here, so the pair was measured: renaming dayCountBasis and requireAccrualCheck together produced output identical to renaming the basis alone, except for the line recording that a check had run.

That is not the enabling-flag shape. It is something quieter. The check was never going to fire, so switching it off cost nothing -- the guard was already worthless against this particular error while looking, on every dashboard, like a control that was in place and passing.

The lesson generalises past obfuscation. A reconciliation with a tolerance can only catch errors larger than its tolerance, and the errors most likely to survive years of production are precisely the ones that sit just underneath it. If a difference matters at book scale but not at loan scale -- and $210.97 on four thousand loans is $843,880 a year -- a per-record tolerance is the wrong instrument entirely.

The reference figure went missing and the money stayed right

One arm inverted the whole picture and is worth reporting for that reason. Renaming perDiemUsd, the per-diem the note states, left every posted amount exactly correct -- $15190.00 for the year, on the right convention -- while the posting record read vs note $undefined/day.

The caller's own rule reads that field from a record the caller also writes, so both sides moved together and the comparison stayed valid. Only the library's display of it broke. So one field carried the money and read fine; the field beside it carried the auditor's reference figure and read undefined while nothing was wrong.

Put the two arms together and you have the practical warning. A posting record showing a populated, sensible per-diem is not evidence that the convention is right, and a posting record full of undefined is not evidence that anything is wrong. The screen a reviewer checks is evidence in neither direction.

What failed loudly, and what to do about it

Several arms failed closed, and they are worth reporting because a test that only finds silent problems is confirming a belief rather than measuring anything. Renaming principalUsd, annualRatePct or days made the arithmetic unreadable and the engine refused: non-finite-accrual(refused before posting). Renaming the loan identifier broke the binding between a period and its loan, and the engine refused that too, with an accurate reason. Renaming the accrual rule's return flag made every posting rejected.

The pattern across all of them is the one this series keeps measuring: losing a VALUE fails closed and loudly, and losing what a value MEANS fails open and quietly. A day-count basis is not a quantity. It is a statement about what the quantities mean.

None of this is an argument against protecting a servicing front end. Member renaming is opt-in and the pattern is yours to write. Keep option keys read by an installed library out of it, and keep the field names on records that cross into one out of it too. Then add the control this file says you actually need: reconcile the book, not only the loan. A convention error is invisible per record and unmistakable in aggregate.

Frequently asked questions

Does obfuscation change what a loan accrues?

Not on its own. Across five presets and five areas measured this pass, protection alone left every output identical -- twenty-five comparisons, no differences. What changed behaviour here was member renaming, a separate opt-in option whose pattern you write yourself.

What actually went wrong in the measured case?

Renaming the option key that names the day-count convention made the library substitute its own default, actual/360 instead of the note's actual/365. A full year's interest on one $248,000 loan went from $15,190.00 to $15,400.97.

Why is a $210.97 error worth an article?

Because of what it survives. It is 1.39%, which is smaller than any tolerance a servicing reconciliation can reasonably enforce, so the control that exists to catch this class of error passed it -- and printed the two per-diem figures side by side while doing so.

Did switching off that reconciliation make it worse?

No, and that is the finding. Renaming the enabling flag as well produced output identical to renaming the basis alone. The check was never going to fire, so it cost nothing to lose -- while still appearing, on every dashboard, as a control that was in place and passing.

Do the amounts themselves fail safely?

They do. Renaming the principal, the rate or the day count made the arithmetic unreadable and the engine refused before posting anything, and renaming the loan identifier was refused with an accurate reason. Losing a value tends to fail closed; losing what a value means tends to fail open.

Is a clean posting record evidence that the convention is right?

No, in either direction. One arm left every amount correct while the note per-diem printed as undefined, and another left the reference figure looking perfectly sensible while the amounts were wrong. The screen a reviewer checks cannot settle this question.

What is the practical mitigation?

Scope member renaming to names your own code owns on both sides, and keep out option keys and record fields an installed library reads. Then reconcile at book level as well as loan level, because a convention error is invisible per record and obvious in aggregate.

Related reading