Measured, not asserted
Published
A limitation period is not a number in a configuration file. It is a row, selected by the type of claim, and a row that cannot be matched is not an error in any docketing engine written to be reused -- it is a fallback. This test renamed one row of that table and watched a one-year claim be reported in time until a date five years after it had actually expired, with the claim type, the accrual date, the filing date and the court holiday calendar all still perfectly correct.
A deadline is a row, not a number
Every docketing system in a law firm does the same arithmetic. Take the day the cause of action accrued, add the limitation period for that kind of claim, add anything that tolls it, and if the result lands on a day the court is shut, roll it forward to the next open day. Diarise the answer. Chase the file before it.
Only one of those inputs is a plain number. The limitation period is a lookup: defamation one year, personal injury two, simple contract six. The fixture used here carries exactly that table, a fallback of six years for claim types the table does not cover, a court holiday list, a roll-forward rule, and a tolling table adding a year while a claimant is a minor.
Three live matters are measured, each turning on a different part of the calculation. A contract claim whose six-year deadline lands on a court holiday and is filed two days later, on the next open day: in time only if the holidays are known. A defamation claim filed 405 days after accrual: time-barred, and the firm needs to know today. A personal-injury claim brought by a minor and filed five days inside the tolled deadline: in time.
Each matter fails on at most one axis, so the axes cannot mask each other. That matters more than it sounds: a fixture in which two controls can both catch the same fault measures the fixture, not the system.
Protection on its own moved nothing
Before any renaming, all five areas of this pass were run through five presets: the ES5 default path, the modern target, the emit gate's own option set on both targets, and the string transforms. Twenty-five comparisons, every one identical to the unprotected run, including the guard that runs each original twice and refuses to compare anything against a fixture that does not reproduce itself.
So nothing below is caused by protecting the code. Every result in this article required member renaming aimed at a property name that both the firm's docketing code and the installed calculation engine read -- the failure mode this whole series exists to map.
One row, and a time-barred claim was reported in time
The narrowest pattern measured here renamed a single entry inside the limitation table: the defamation row. Nothing else in the file was touched. The lookup for that claim type stopped matching, and the engine did what a reusable engine does -- it fell back to the firm's own configured default of six years.
On its own that was caught. The firm's rule of record refuses a deadline whose limitation row could not be matched, and it reported deadline-rejected(row-check(no limitation row for defamation)). Worth noticing why that check worked, because it is the difference between a real assertion and a rehearsal: the value it inspects -- which row was applied -- is written by the engine, on the far side of the boundary, so renaming the caller's names could not move both halves of the comparison at once.
Now switch that check off, which is one more name in the same pattern. The result was IN TIME, printing row=undefined period-read=2190d tolled=+0d due=22230 diarised=22232 filed=20445. A defamation claim filed 405 days after it accrued was diarised as live until a date five years after the real deadline. The claim type printed correctly. The accrual day printed correctly. The filing day printed correctly. Only the row was gone, and the number that replaced it was a real, published, entirely plausible limitation period.
Renaming the caller's rule instead of the flag produced the same wrong answer with the word checked still in the output: checked by library-builtin(accepts any computed deadline).
The fallback is the whole problem, and it looks like prudence
A firm that configures a generous default limitation period is being careful. If a claim type is not in the table, assume the longest period rather than the shortest, so nothing gets diarised too aggressively. That instinct is exactly what makes this failure silent.
The proof is one arm away. Renaming the fallback as well as the row produced unbounded(no limitation period could be established for defamation => IN TIME) -- the library's own default is positive infinity, because a general engine cannot know your jurisdiction. That output is wrong too, but it says so in plain language, and the firm's rule never even ran, because the engine returns before consulting it.
So a fallback that is a real number defeats every sanity check you would think to write, and a fallback that is unusable is caught by the same check. That is the second pass running in which the safest-looking configuration produced the quietest failure.
An error message can name a value the failing half never saw
Renaming the claim type on the matter records refused all three matters, which is the loud outcome. But the message is worth reading closely: row-check(no limitation row for contract).
Contract is in the table. A developer reading that message will open the configuration, find the row, and conclude the message is wrong. It is not. The message is built by the firm's own code, which is protected, so its read of the claim type moved together with the record key and still returns the right string. The lookup that missed happened inside the installed engine, which is not protected, so its read of the same field returned undefined.
This is the general shape and it is worth carrying into any debugging session on a protected build: the half of the system that can still read a field is often the half that writes the error message about it. Diagnostics printed by your own bundle are not evidence about what a library saw.
What failed loudly, and why that is not reassurance
Every value in the area failed closed and said so. Renaming the accrual day produced undated-accrual(refused before computation). Renaming the matter identifier produced unidentified-matter. Renaming the whole limitation table refused all three matters against the same row check.
The court calendar failed in the other direction and that is the cheap failure: with the holiday list or the roll-forward rule renamed, the contract claim diarised at day 20490 instead of 20492 and a claim filed on the next open day was reported TIME-BARRED. Wrong, but wrong in the direction somebody complains about the same week. The tolling table behaved the same way -- the minor's claim went from a deadline of 20435 to 20070 and reported time-barred.
The pattern is now thirteen areas old and it held again: losing a VALUE fails closed and loud, losing a MEANING -- a row, an arm, a scope, a unit -- fails open and quiet. The loud half is the one people find in testing.
If you protect a docketing or practice-management application
Three things came out of this area that are worth having whether or not you obfuscate anything, because a hand-edited configuration file, a dropped key over a serialisation hop and a library upgrade all produce the same miss.
First, make an unmatched limitation row an error, not a fallback. An engine that cannot establish which period governs a claim should refuse to diarise it, exactly as it refuses a claim with no accrual date. That single change turns the dangerous cell in this area into the loud one.
Second, assert WHICH ROW was applied rather than whether the number it carries looks sensible. In a published limitation table every number is sensible. The check that worked here compared the row the engine reported against the claim type on the matter, and it worked precisely because the engine wrote that value.
Third, keep the names your protected build shares with an installed engine out of the rename pattern. A member regexp scoped to the fields your own code owns is a five-minute edit and it removes the whole class. If you are protecting practice software of this kind, our security evidence pack covers what to keep from a build so a reviewer can see which names moved.
Frequently asked questions
Did obfuscation change any deadline on its own?
No. Five areas were run through five presets and every protected output matched the unprotected run -- twenty-five comparisons, no differences. Every failure in this article required member renaming aimed at a property name the installed calculation engine also reads.
What exactly went wrong when one limitation row was renamed?
The lookup for that claim type stopped matching, so the engine used the firm's configured fallback of six years. A defamation claim filed 405 days after accrual was reported in time, with a diary date of day 22232 against a real deadline of day 20405.
Was anything else about the matter wrong?
No, and that is the point. The claim type, the accrual date, the filing date, the tolling status and the court calendar all printed correctly. Only the row that selects the period was gone, and the number that replaced it was a real published limitation period.
Why did the firm's own check catch some cases and not others?
The check refuses a deadline whose limitation row could not be matched, and it reads which row was used from the engine -- the far side of the boundary. It caught the renamed row on its own. It was skipped entirely when the flag enabling it was lost in the same pattern, and it was replaced by a permissive built-in when the rule itself was renamed.
Is a generous default limitation period a safe configuration?
It is the least safe one measured here. A fallback of six years is real, plausible and published, so every finiteness or plausibility check accepts it. Renaming the fallback as well produced an infinite period, which the engine reported in plain language as unbounded. The unusable fallback was easier to notice than the sensible one.
Which fields failed loudly?
The accrual date, the matter identifier and the whole limitation table all refused before computation. The court holiday list, the roll-forward rule and the tolling table failed in the opposite direction -- a claim filed in time was reported time-barred, which somebody complains about within the week.
What is the cheapest fix?
Make an unmatched limitation row an error rather than a fallback, and assert which row was applied instead of whether the number looks reasonable. Both are worth doing regardless of obfuscation, because a dropped key in a configuration file produces exactly the same result.
Related reading