Measured Behaviour

Does obfuscation break dormancy and escheatment?

Two rows in an activity table can look identical and mean opposite things: a posting the customer caused, and a posting the bank caused. Only one of them is evidence that anybody is still there. We measured what happens when the distinction loses its name.

Interest is activity, and it is not contact

The fixture is eight deposit accounts across four states, sixteen activity rows, and a contact log with two entries, swept on day 2000 against a three-year dormancy period and a five-year escheatment period.

Eight of the sixteen activity rows are owner-generated: deposits, withdrawals, transfers out, a cheque written, an online login. The other eight are the bank acting on the account - a monthly interest credit or a maintenance fee, posted on every account including the ones whose owners have not been heard from in five years. Every one of those sixteen rows is a genuine posting. They differ only in who caused them.

The correct run classifies three accounts active, two dormant and three reportable, with $8,335.00 remittable to Ohio, Indiana and Michigan and notices due sixty days before filing.

All five default protection profiles reproduced that exactly. The failure needs a member pattern reaching the configuration object or the record shapes; the mechanics are in Protect Members.

Every account reads active, and nothing is wrong on the report

Renaming the list of owner-generated kinds does not make the library refuse. It makes it stop distinguishing, and a library that cannot tell owner activity from bank activity counts everything - which is the only defensible default, because the alternative escheats a live customer's money on its first run.

So every account inherits the monthly interest posting as its last contact. Active goes from three to eight. Dormant goes from two to zero. Reportable goes from three to zero. Remittable goes from $8,335.00 to $0.00, and three states are never told about money that belongs to their residents.

The sweep's own check passes, printing: eight accounts classified, eight active, zero dormant, zero reportable, $0.00 remittable. Every figure is true. Eight accounts really were examined and classified. The classification really was applied consistently. Nothing about the day of the run is wrong.

The loss is realised years later, by a third party. A state audits the holder, finds accounts that were reportable and were not reported, and assesses the property plus interest plus penalties - against a bank whose contemporaneous records show a correctly-run sweep with a passing control.

The mirror, one line away

Renaming the flag that honours the contact log fails the other way, and just as quietly on the day.

Two owners in this book have been in touch with no transaction behind it: a phone call to change an address, and a branch visit for a statement copy. Lose the flag and neither counts. Active falls from three to two, reportable rises from three to four, and remittable rises from $8,335.00 to $10,225.00.

The extra $1,890.00 belongs to a customer who walked into a branch and asked for a statement. Her money is remitted to Michigan as unclaimed property, and she will discover it the next time she looks at the account.

We ran the two together, expecting the union to be worse than either. It is byte-identical to the activity list alone: eight active, nothing reported. The permissive default is consulted first, and once nothing is idle the contact log decides nothing. That is the second place in this pass where a wider pattern is quieter than a narrower one, and it is the reason a matrix has to be read cell by cell rather than at its wide end.

The two periods, and which direction each fails

The dormancy period and the escheatment period sit beside each other and behave differently.

Renaming the escheatment period reverts to no stated period, so nothing is ever reportable: reportable falls from three to zero, dormant rises from two to five, and remittable reads $0.00. The accounts are still correctly identified as inactive; they are simply never escalated.

Renaming the dormancy period leaves the escheatment path untouched. Reportable stays at three and $8,335.00, dormant falls from two to zero, active rises from three to five. The money still reaches the state. What is lost is the intermediate stage - the point at which an account is flagged, statements are held, and due diligence begins - so three accounts go from apparently active straight to reportable with no dormancy period in between.

Renaming the notice period leaves every classification identical and moves only the day printed on each notice. It is the quietest arm in the file and, on a different reporting cycle, the one that misses a statutory deadline.

Why a green suite says nothing here

We ran the same eight accounts against an activity log holding only the owner-generated rows - no interest, no fees. The classification is identical to the correct run, and every arm touching the activity list is inert against it, because there is nothing in the table that needs distinguishing.

That is the fixture most teams have. Test data is written to exercise the clock, so it contains the transactions the clock is about; nobody adds sixty months of interest postings to a dormancy fixture, because they are not what the feature is for. A suite built that way cannot detect the loss of the distinction, because it never contained anything to distinguish.

The assertion that works is a comparison against something outside the sweep: take the count of accounts with no owner-generated row in the period straight from the ledger, and require the sweep's dormant-plus-reportable figure to equal it. That is defined over the data rather than over the sweep's own state, so it fails the moment the distinction stops working - and it is worth having whatever your build does, because a renamed transaction-code column produces the same result.

Unclaimed-property exposure is one of the items that tends to surface in periodic disclosure review; our notes on SEC cyber disclosure and front-end JavaScript cover how software-caused reporting gaps are usually evidenced.

Frequently asked questions

What actually broke?

The distinction between a posting the customer caused and a posting the bank caused. Every row in the activity table is genuine; only the list that says which kinds count as owner contact went missing, and a library that cannot tell counts everything.

Which setting caused it?

Member renaming reaching the owner-activity list, the contact-log flag, or either period. All five default profiles we measured produced identical behaviour with no options set.

How much money moved?

$8,335.00 stopped being reportable to three states when the activity list was renamed, and $10,225.00 became reportable - $1,890.00 more than correct - when the contact-log flag was. Same file, opposite directions.

Did anything look wrong on the day of the run?

No. Eight accounts were classified, the classification was applied consistently, and the sweep's own check passed printing true figures. The loss is realised years later by a state auditor, against contemporaneous records that look clean.

Was the union of two renames worse than one?

No. Renaming the activity list and the contact-log flag together was byte-identical to renaming the activity list alone, because once nothing is idle the contact log decides nothing. A wider pattern was the quieter one.

What assertion would have caught it?

Count the accounts with no owner-generated row in the period from the ledger, outside the sweep, and require the sweep's dormant-plus-reportable total to equal it. That is defined over the data rather than the sweep's own state.

How do I keep the rename off it?

Scope MemberRegexp to names your code owns on both sides of each boundary, and keep it off objects handed to installed libraries. The reference is Protect Members.

Related reading