Measured Behaviour

Does obfuscation break advance recoupment?

Recoupment is the one place in a royalty system where an allowlist decides how much of somebody else's money is kept. Allowlists have a fail-closed reputation. We measured what actually happens when the option carrying one loses its name - and then measured the arm where the figure everybody checks does not move at all.

An account, an advance and eighteen ledger entries

We built an artist account the way one is really shaped: a 250,000 dollar advance, three projects cross-collateralised into one account, and a cost ledger of eighteen entries across six categories. Recording, video and independent promotion are recoupable under the contract. Tour support, marketing and the label's own overhead are not - and the nine entries that are not recoupable total more than the nine that are.

Seventy-five per cent of mechanical earnings go against the deficit and the rest is paid as it goes; performance income is paid straight through, because it is collected by a different society under a different agreement. Interest of four per cent runs on any unrecouped balance from the day each cost was incurred, and the artist receives at least twelve thousand dollars whatever the account says.

The correct run charges 380,500.00 to the account, refuses nine ledger entries, pays the artist 426,500.00 and carries 286,195.29 unrecouped. The business manager's rule - no cost outside the contract schedule may be charged, and the account must foot - passes.

Protected with default settings on all five profiles, the output was byte-identical. The failures below all require member renaming to reach the names on the contract object or the ledger records.

An allowlist that fails in the direction of the money going out

recoupableCategories is an allowlist, and the library's default for it is the empty set: shown a contract it has not read, a library cannot invent the costs you are allowed to charge.

Renaming it produced a recoupment schedule with nothing on it. Costs charged went from 380,500.00 to 0.00. Costs refused went from nine to all eighteen. The artist was paid 529,000.00 instead of 426,500.00 and the account cleared, with 0.00 unrecouped against a real deficit of a quarter of a million dollars.

The interesting part is not the number. It is that the business manager's own rule passed - it reported schedule-check(0.00 charged within a schedule of 380,500.00). The rule asks whether TOO MUCH was charged. Zero is not too much. A control written as a ceiling is satisfied by everything underneath it, including nothing at all, which is why a ceiling is the wrong shape for a control you intend to catch omissions.

The same failure is produced by renaming the category field on the ledger records instead: identical figures, 0.00 charged and eighteen refused. Option side and record side, one wrong statement.

The arm where the payment does not move at all

crossCollateralize is not a threshold, not a rate and not a list. It decides how many accounts exist - a grouping key wearing a boolean's clothes. This contract pools three projects into one; the library, shown three projects and no contract, pools nothing.

Renaming it split one account into three. The hit record now pays out on its own while the two that did not sell stay in deficit and carry interest separately. Payment to the artist went from 426,500.00 to 428,000.00 - a move of fifteen hundred dollars, which any reviewer would read as rounding.

The unrecouped balance went from 286,195.29 to 800,552.99.

That balance is not a reporting line. It is the opening position of every future period: it decides when the artist next gets paid, how much interest accrues, and whether a post-recoupment escalator ever engages. The figure a reviewer checks moved by 0.35 per cent. The figure that governs the next several years moved by 514,357.70.

And then the pair makes even that fifteen hundred disappear

The fifteen hundred came from the minimum guarantee: with three separate accounts, the project that barely sold falls under the twelve thousand dollar floor and is topped up to it. Renaming minimumGuaranteeUsd on its own is inert on every money figure in this account, because a single pooled account paying 426,500.00 never comes near a twelve thousand dollar floor.

Rename both, and payment to the artist is 426,500.00 - identical to the correct run, to the cent - while the unrecouped balance is still 800,552.99.

So the narrow pattern moves a number a reviewer might notice and the wider pattern does not move it at all. This is the second independent instance of that shape in this pass and the fourth area we have measured it in. An arm that looks inert on its own is not evidence of safety; it is frequently the half that makes another half invisible.

Two more places where an option and a field agree

Renaming interestOnUnrecoupedPct takes the carried balance from 286,195.29 to 278,000.00. Renaming the incurredDay field on the cost records produces the same 278,000.00, because interest that cannot find a start date does not accrue. Different sides of the boundary, one answer.

Renaming the sourceKind field is the largest single move in this area and it runs against the artist: performance income stops being recognised as pass-through and is applied to the deficit like everything else. Payment fell from 426,500.00 to 194,750.00. Nothing threw, the schedule check passed, and the account still footed.

By contrast, renaming amountUsd - the money itself - is refused before the account is computed. The value fails closed; the meaning of the value fails open. That contrast has held in every area we have measured.

Why the review would not have caught it

The contract in this measurement requires the business manager's rule to run, and it did run, in every arm. It passed in every arm. Renaming the enabling flag together with the allowlist produced exactly the same figures as renaming the allowlist alone, with the review line reading unchecked instead - the only difference between a review that would not have caught it and no review at all.

This is worth being blunt about, because 'we have a validation step' is the usual answer to this class of risk. A validation step written as a ceiling catches over-charging. Recoupment failures of the kind measured here are under-charging, and they are invisible to it by construction.

A control that would have caught this compares the charged total against the ledger it came from and requires them to agree, rather than requiring one to be no larger than the other. That costs nothing at design time and it is worth having whether or not you obfuscate anything.

What we are not claiming

Protection with default settings did not change a single figure in this area, on any of the five profiles. The failures all required member renaming to reach a name that crosses into the unprotected recoupment library or onto the ledger records.

The practical answer is scoping. Keep MemberRegexp off third-party option literals and off your own persisted record shapes; if you want the mechanics, they are in Protect Members. If you are running artist or royalty accounting under financial controls, SOX and GLBA covers why a calculation that decides payments deserves the stricter configuration.

Frequently asked questions

Does obfuscation break recoupment calculations?

Not on its own. Protected with default settings on five profiles - the ES5 target, the modern target, both emit-gate configurations and the string-encoding profile - the account we measured produced byte-identical output. Every failure below required member renaming to reach names crossing into an unprotected library.

What happened when the recoupable cost list lost its name?

Charges against the account went from 380,500.00 to 0.00, all eighteen ledger entries were refused, and the artist was paid 529,000.00 instead of 426,500.00 with the account showing nothing unrecouped. The business manager's schedule check passed throughout.

Why didn't the validation rule catch a zero charge?

Because it was written as a ceiling: it asks whether more was charged than the contract schedule allows. Zero is not more than 380,500. A control intended to catch omissions has to require the two totals to agree, not require one to be no larger than the other.

What is the cross-collateralisation result?

Renaming it split one pooled account into three. The payment to the artist moved by 1,500 dollars, which reads as rounding - and the unrecouped balance carried into every future period went from 286,195.29 to 800,552.99.

Is a small change in the payment figure evidence that nothing broke?

No, and this area is the clearest case we have measured. Renaming the pooling flag and the minimum guarantee together left the payment identical to the cent while leaving the carried balance at 800,552.99. The narrow pattern moved a visible number; the wider one moved none.

Do the record fields fail the same way as the options?

Two pairs did exactly. Renaming the recoupableCategories option and the category field on the ledger produced identical figures, and renaming the interest option and the incurredDay field both produced a carried balance of 278,000.00.

What is the practical mitigation?

Scope MemberRegexp so it cannot reach a third-party options object or your own persisted record shapes, and reconcile the charged total against the ledger rather than testing it against a ceiling. See Protect Members for the configuration.

Related reading