Measured, not asserted
Published
Every lookup this series has measured so far can miss. A flat table returns undefined; an effective-dated series returns a stale row; a grouping key silently opens a new bucket. A dial-prefix rate deck cannot miss in that sense, because its keys nest by construction: 4477 sits inside 447 sits inside 44, and every one of those is a real published destination at a real published price. Lose the specific key and the match does not fall through, does not return null and does not pick a wrong-dated row. It succeeds against the next key up. This test measured what a coverage assertion is worth when the lookup always succeeds, and the answer is nothing at all.
A published deck has no miss to detect
The fixture rates twelve call detail records against a six-entry deck: 44 for UK fixed at 0.0092, 447 for UK mobile at 0.0421, 4477 for one particular mobile allocation at 0.0685, 449 for premium rate at 0.4400, plus two French destinations. Alongside it sits what the terminating carriers actually invoice, which arrives as text from them and which nothing in the bundle can move. The correct run bills 6.24, is invoiced 5.68 and earns a margin of 0.56 -- thin, which is what interconnect margins are.
The carrier's own rating rule is the one anybody would write, and it is a good rule: no call may be billed without a published rate behind it. In the correct run it prints rating-check(12 of 12 call(s) matched a published rate).
Every dialled number in the fixture is inside at least two published keys. That is not a property of the fixture; it is the structure of the international numbering plan. Specific allocations live inside mobile ranges live inside country codes, and each layer is separately published and separately priced.
Losing one specific prefix moves the money and nothing else
Rename the quoted key 4477 and the four calls on that allocation no longer match it. They match 447 instead -- UK mobile, a genuine destination, a genuine published rate, 0.0421 instead of 0.0685. The run reports matched=12 of 12. The carrier's rating rule prints its passing sentence word for word. Total billed falls from 6.24 to 5.77 while the invoice from the terminating carrier stays exactly 5.68, because their deck is theirs. Margin goes from 0.56 to 0.08.
Now rename 449, the premium range. Those calls fall to 44, UK fixed, at 0.0122 against a published 0.4430 -- a thirty-six-fold difference, because a premium range and a landline are only similar in that both begin with the same two digits. Total billed collapses from 6.24 to 1.93. The invoice is still 5.68. The margin is minus 3.75, and the coverage line still reads twelve of twelve.
Both keys together: billed 1.46, margin minus 4.23. On twelve calls. Scale that to a mediation run and the deck has stopped being a price list and started being a subsidy.
This is a failure shape worth naming, because it is not any of the ones a careful engineer already guards against. The value is not missing. It is not NaN. It is not stale. It is not implausible. It is a correct, current, published rate for a real destination -- just a broader one than the call actually went to. Every check that asks "is this a real rate?" answers yes, truthfully.
The direction of failure is decided by how specific the lost key was
The sharpest measurement in this area is the inversion. Rename the broadest key, 44, and the run is refused outright: rating-rejected(rating-check(2 of 12 call(s) had no published rate)). Two calls -- the ones that only ever matched the country code -- now match nothing, the coverage rule notices immediately, and the run stops.
Rename the narrowest key and nothing is refused, because everything below it still has a parent to land on. The general rule this produces is uncomfortable: in a hierarchical lookup, the more specific the key you lose, the quieter the failure, and the more specific the key, the more likely it is the expensive one. Specific prefixes exist precisely because that traffic is priced differently from the range around it.
Losing the whole deck is loud too: twelve of twelve unmatched, refused. So the option-shaped failure is caught and the key-shaped failure is not, on the same object, by the same rule.
Two arms confirm the value-versus-meaning contrast on adjacent lines of the same table. Renaming ratePerMin -- the number inside each entry -- gives rating-check(the billed total is not a number) and refuses. Renaming seconds on the CDR does the same. Lose a value and the run stops; lose which entry applies and the run pays out.
The caller's rule is byte-identical whether it runs or not
This is the cell that decides how much comfort to take from a coverage assertion. Rename the specific prefix and the flag that enables the carrier's rating rule, so the rule never runs at all: billed 5.77, invoiced 5.68, margin 0.08 -- byte-identical to renaming the prefix alone. Rename the prefix and the rule function itself, so the library substitutes its own accept-anything builtin: byte-identical again.
The rule made no difference because it could not. It is defined over coverage, and coverage is perfect in both worlds. Switching it off changes nothing precisely because leaving it on changes nothing.
That is worth sitting with. A control which is genuinely well designed, genuinely enabled, genuinely running and genuinely reporting true sentences can be exactly as useful as no control at all, if the quantity it is defined over is one the failure does not disturb. Reasoning of the form "we would have caught that, we check coverage" is only valid when the failure and the check are about the same thing.
Three independent routes to one wrong answer
The surcharge table gives a clean demonstration that option keys and record fields are not two different risks. Renaming the surcharge option, renaming the jurisdiction field on the CDRs, and renaming the quoted jurisdiction keys inside the table all produced the same total: 6.09 billed, margin 0.41. Three unrelated patterns, one answer.
Two arms were inert and are worth reporting honestly. Renaming the minimum prefix length changed nothing, because no key in this deck is shorter than the minimum anyway. Renaming the default rate for unmatched calls changed nothing, because in the correct run no call is unmatched -- and that is the whole point of the area. The default rate is dead code until the moment the deck stops covering something, at which point the coverage rule has already refused the run.
Renaming destination, the human-readable label on each entry, left every figure identical and only changed the display. Not everything on a record is load-bearing.
One arm moved both sides at once: renaming the rounding increment changed billed to 5.58 and invoiced to 5.09, because billable seconds feed the wholesale calculation too. Margin 0.49. It is the only arm in the area where the invoice moved, and it moved because the quantity is genuinely shared.
Why the reconciliation you already run will not see this
Interconnect operations reconcile constantly, and the reconciliations in common use are all closed by construction against this failure. Minutes billed against minutes recorded: identical in every arm, because no call was added or dropped. Calls rated against calls received: twelve and twelve. Coverage: perfect. Every rate applied is on the published deck and can be shown to a regulator.
The quantity that moved is which published entry applied, and the only way to see it is to assert the match itself: for a sample of calls, the prefix the engine selected must equal the longest published prefix of the dialled digits, computed independently. That check is four lines and it is the one nobody writes, because the lookup never fails.
The second check that works is the margin one, per destination rather than in aggregate. Aggregate margin on this run went from 0.56 to 0.08 -- a thin margin getting thinner, which reads as market pressure. Per destination it is unmissable: one destination band earning nothing while its wholesale cost is unchanged. Operators running this under a formal operational-resilience regime will recognise the shape from our DORA notes on JavaScript front ends, where the requirement is to evidence that a control actually functions rather than that it is configured.
What to change, whether or not you obfuscate
Assert the match, not the coverage. Record the selected prefix on every rated call and verify a sample against an independently computed longest prefix. A dropped key over a serialisation hop, a mistyped deck import and a partial rate-deck update all produce the same wrong-specificity match, so this check earns its keep with no obfuscation in sight.
Publish a per-destination margin, not just a total. The aggregate hides exactly the destinations that matter, because the expensive bands are a small share of call volume and a large share of revenue.
Alarm on deck cardinality. If the deck should hold six entries and holds five, that is a fact you can check cheaply at start-up and it catches the entire class before a single call is rated.
And if you use member renaming, exclude the deck. A rate deck's keys are data, not code, and there is no obfuscation benefit to renaming them -- the digits are public, published by regulators. Identifier renaming, string encoding and control-flow transforms all remain available and none of them touch this.
How this was measured
Five telecommunications areas were driven through the real obfuscation engine on five presets -- the ES5 default, the modern target, the emit gate's option set on both targets, and the string transforms. All twenty-five combinations produced output byte-identical to the unprotected run, so protection on its own changed nothing.
The member-renaming column is ninety-six patterns on two presets: one hundred and ninety-two verdicts, one hundred and seventy-eight of which moved the output. The fourteen that did not are the seven options pinned deliberately equal to the library's own default, measured on both presets. Nothing threw and the two targets agreed on every arm.
The prediction for this area was written down before the run: that renaming a specific prefix would move the money while the coverage rule kept passing, and that pairing the rename with the rule's enabling flag would be byte-identical to the rename alone. Both landed exactly. Pre-registering the prediction is what makes a confirming result worth anything.
Every figure above was read from executing the protected artifact, not from a summary. The deciding fields -- the totals, the margin and the rule's own sentence -- all sit past the point where a capture line truncates.
Frequently asked questions
Did obfuscation on its own break interconnect rating?
No. Five presets -- the ES5 default, the modern target, the emit gate's option set on both targets, and the string transforms -- all produced output byte-identical to the unprotected run across all five areas measured this pass.
What actually broke it, then?
Member renaming with a pattern that reached the quoted dial-prefix keys of the published rate deck, or the record fields the rating is computed from.
Why did the coverage check not catch it?
Because coverage was never disturbed. A prefix deck's keys nest, so losing a specific key means the call matches the general key above it -- a real published rate. Every call still matched something, so the check truthfully reported twelve of twelve.
Which is more dangerous, losing a specific prefix or a general one?
The specific one. Losing the broadest key left two calls with no rate at all and the run was refused immediately. Losing the narrowest key left everything matched and moved the margin from plus 0.56 to minus 3.75.
Did enabling the carrier's own rating rule help?
No. Renaming the prefix with the rule switched off was byte-identical to renaming it with the rule running, because the rule is defined over coverage and coverage was perfect in both arms.
What check would have caught this?
Asserting the match rather than the coverage: record which prefix the engine selected and verify a sample against an independently computed longest prefix of the dialled digits.
Can I still obfuscate a rating engine?
Yes. Keep the rate deck out of any member-renaming pattern -- its keys are public published data and renaming them buys nothing -- and identifier renaming, string encoding and control-flow transforms remain fully available.
Related reading