Measured Behaviour
Published
An affordability engine has obvious failures everybody tests for: a threshold that will not save, a deposit that will not count, a case that will not raise. It has one almost nobody tests for, because both answers are true - WHICH of a player's pounds the ladder is read against. We measured what a rename does to it.
Two true numbers for every account
Every operator in a licensed market publishes the same kind of ladder: so many pounds in so many days and a light check is due, so many more and enhanced due diligence is, and until the documents arrive the account stops. The ladder is public and uncontroversial. What decides whether anybody is ever caught by it is the BASIS it is read against.
A player who deposits GBP42,000 over a month and withdraws GBP36,000 has handled GBP42,000 of their own money. Their net loss is GBP6,000. Both figures are exact, both are defensible on a finance report, and only one of them is what an affordability band is written against.
We built three accounts over one 30-day window. A heavy churner at GBP42,000 in and GBP36,000 out. A mid-sized account at GBP9,400 in and GBP3,000 out, carrying a cancelled-withdrawal marker worth GBP1,000. A small account at GBP1,500 that clears nothing on either basis. Bands at GBP2,000, GBP10,000 and GBP25,000, the last two requiring enhanced due diligence, with no grace period before the account stops.
The correct run reads the ladder against gross deposits. The churner clears the top band, the mid-sized account clears the enhanced band on its deposits plus its marker, and both are blocked pending documents. Protected on five profiles with default settings, behaviour was identical every time, and the base column was measured twice and diffed identical.
What renaming the spend basis actually did
The vendor's default basis is net, and it is the reasonable default for a library that cannot know your regulator: net is what a finance team asks for. Renaming the option key the basis is passed under does not corrupt anything. The string is still in the bundle. The library simply reads its own default instead.
Nothing broke. Events stayed at twelve, counted at twelve, accounts at three, and every deposit and withdrawal was still recorded at the penny. Enhanced due diligence went from two accounts to zero. Blocked accounts went from two to zero. The churner's spend figure went from GBP42,000.00 to GBP6,000.00 and the account dropped from the top band to the lowest one - a light check, passed, account open.
The gross money that went through accounts allowed to keep playing went from GBP1,500.00 to GBP52,900.00. Across the book we modelled that projects to GBP2.1bn. Both renaming presets produced byte-identical output.
The number on the dashboard did not move
This is the part worth carrying away from this area, and it is the reason a monthly compliance pack would not have shown it.
The count of accounts ASSESSED is two in both runs. It is two in the correct run because two accounts cleared a band. It is two in the defective run because the same two accounts cleared a band - a lower one. The headline figure a compliance manager reports, how many accounts did we assess this month, is identical.
What moved is two counters underneath it: how many were referred for enhanced due diligence, and how many were stopped. Both went to zero. Neither is the number that gets reported upward, and the one that is reported upward is the one that did not change.
That generalises past this domain and past obfuscation. When a control has a headline count and a severity breakdown, the breakdown is where a basis error shows and the headline is where it hides. Report both, and reconcile the severities against the raw money rather than against each other.
Why a test suite would not have caught it
We re-ran the same three accounts with every withdrawal removed, which is the fixture most people would build. Test data for a deposit threshold is a list of deposits.
In that fixture the arm is inert on every counter. With nothing taken back out, net and gross are the same number, so both bases agree on every account and every band, and the two accounts are still blocked; only the reported basis moves. The defect cannot appear in test data that has no withdrawals in it.
The uncomfortable part is that withdrawal-free test data is not careless. It is what you write when you are testing whether deposits trip a threshold, which is what the ladder is nominally about. The withdrawals are the part of the story that seems irrelevant to the control right up until they are the whole of it.
Two other ways the same ladder goes quiet
We measured the neighbouring options in the same file, because a clean reading on one key says nothing about the others.
Losing the assessment window turns a 30-day view into a single-day one, and a ladder read one day at a time is a ladder nobody clears. Losing the threshold bands themselves empties the list, and an empty list triggers nothing - but that one is loud in a configuration dump, because the bands print as no bands.
The basis is the quiet one precisely because it is a single well-formed string that is replaced by another single well-formed string. net is not a corrupt value. It is a perfectly good basis that a great many systems legitimately use. It is simply not the one this operator's policy committee signed off.
What to do about it
Assert the basis rather than assuming it. Have the library report the basis it actually read, and compare that against your policy document at start-up. This costs one line and it is worth having regardless of obfuscation, because a library upgrade that changes a default produces exactly the same outcome.
Report gross alongside net on every account, always. The two figures diverging is not an error condition; it is the information. An account whose gross is seven times its net is the account the ladder exists to find.
Reconcile the severity counters against the raw deposit total, not against the assessment count. Our defective run assessed the same number of accounts as the correct one and stopped none of them, and only the money reconciliation separates the two.
On the build side, scope the renaming so option keys crossing into an installed library are excluded; the mechanics are on the RenameMembers documentation. Where the control is part of a financial reporting chain, the evidence expectations are the ones our SOX and GLBA guidance sets out.
Frequently asked questions
Does obfuscation break affordability checks by default?
No. We protected the same affordability engine on five profiles - the ES5 target, the modern target, both emit-gate configurations and the string-encoding profile - and every one produced behaviour identical to the unprotected run, measured twice. The failure comes from member renaming, which is opt-in and takes a pattern you choose.
What changed when the spend basis was renamed?
The library read its own default, which is net rather than gross deposits. Every deposit and withdrawal was still recorded exactly and every band was still applied exactly. A churner whose gross was GBP42,000 was assessed on a net figure of GBP6,000 and dropped from the top band to the lowest one.
How much exposure did that create?
Enhanced due diligence went from two accounts to none and blocked accounts from two to none. Gross deposits through accounts allowed to keep playing went from GBP1,500.00 to GBP52,900.00, which across the book we modelled projects to GBP2.1bn. Both renaming presets produced byte-identical output.
Would a compliance report have shown this?
The headline would not have. The number of accounts assessed was two in both runs, because the same two accounts cleared a band - just a lower one. Only the severity breakdown underneath moved, and the referral and block counters are not usually what gets reported upward.
Why did our test data not show it?
We re-ran the same three accounts with every withdrawal removed. The arm is inert on every counter there: with nothing taken back out, net and gross are the same number, both bases agree on every band, and both accounts are still blocked - only the reported basis moves. A deposit-threshold fixture made of deposits cannot see a netting error.
Are the other options in the same file safer?
No, but they are louder. Losing the assessment window turns 30 days into one and nobody clears a ladder a day at a time; losing the band list empties it. That last one prints as no bands in a configuration dump. The basis is the quiet one because one well-formed string is replaced by another well-formed string that many systems legitimately use.
How do we keep the protection and avoid the defect?
Assert the basis the library reports against your policy document at start-up, report gross alongside net on every account, and reconcile the referral and block counters against raw deposits rather than against the assessment count. On the build side, scope RenameMembers to exclude option keys handed to installed libraries - see the RenameMembers documentation.
Related reading