Measured, not asserted

Does obfuscation break spill reporting thresholds?

A reportable quantity is a lookup: find the substance by its identifier, compare the mass actually released against the figure the table gives, and if it is reached, notify within twenty-four hours. The previous article in this series found that authoring a fail-safe default was what HID a failure. This test was built to check whether that finding is about defaults or about the direction they err in -- and the answer is the direction. Losing the lookup key alone over-reported, loudly. Losing the default beside it made 8,740 pounds of released chemical read NOTHING REPORTABLE.

A lookup, a product, and a clock that starts on discovery

Release reporting has three moving parts and they fail differently. First, identify the substance and look up its reportable quantity: one pound for polychlorinated biphenyls, ten for lead, five thousand for methanol. Second, work out how much of that substance was actually released, which for a mixture is the released mass times its concentration -- four hundred pounds of a 1.5% lead solution is six pounds of lead, not four hundred. Third, if the reportable quantity is reached, notify the national response centre within twenty-four hours of discovery.

The fixture holds four releases from a tank farm. Forty pounds of a 5% PCB mixture is two pounds of PCB against a one-pound threshold: reportable, and notified six hours after discovery. Four hundred pounds of a 1.5% lead solution is six pounds against a ten-pound threshold: not reportable. Eight thousand pounds of neat methanol is reportable and was notified thirty-nine hours after discovery, which is late. Three hundred pounds of a 0.4% PCB fluid is 1.2 pounds, reportable, and notified far too late.

The determination engine is copied in unprotected. Its defaults are permissive in the usual way: an empty substance table, an infinite threshold for anything unlisted, and no notification deadline at all. The operator overrides all three, and the second override is the one this article is about: an unlisted substance defaults to a ONE POUND reportable quantity. That is a fail-safe default, and it errs toward creating an obligation rather than satisfying one.

Protection on its own moved nothing

Twenty-five comparisons across five areas and five presets, every protected run byte-identical to the unprotected one. Everything below required member renaming that reached names shared with the installed engine.

Losing the lookup key alone was loud, and that is the finding

Rename the substance identifier and every lookup misses, so every release falls through to the one-pound default. The determination goes from three reportable releases to four: reportable=4 late=2 => LATE NOTIFICATION(S), with the six pounds of lead that is genuinely below its threshold now reading cas=undefined mass=6lb rq=1lb => REPORTABLE.

That is wrong, and it is wrong in the direction that gets noticed. An unnecessary notification is filed, somebody at the response centre asks why, and the identifier printing as undefined on every line makes the cause obvious in about a minute. Renaming the substance table itself produced exactly the same outcome by the other route.

Now lose the default as well. reportable=0 late=0 => NOTHING REPORTABLE, for a site that released 8,740 pounds of chemical including eight thousand pounds of methanol. And the site's own identity check passes and prints it: checked by identity-check(4 release(s), 8740lb released, 0 reportable). Every substance identifier on every line is still correct -- cas=1336-36-3, cas=67-56-1 -- because it was never the identifier that moved. What moved is what an unfound identifier falls back to, and it fell back to infinity.

So the previous pass's finding survives contact with a fixture built to challenge it, and it is now sharper. A fail-safe default does not protect the lookup; it decides which way the lookup fails. When it errs toward obligation you get an over-report you will notice. When it errs toward satisfaction you get a clean record. And in both cases losing the default alongside the key is worse than never having authored one, because a default that reverts to the vendor's is not a weaker version of your policy -- it is the vendor's policy.

The concentration is a product, and nothing accumulates it

Renaming the concentration produced the quietest result in the area: mass=NaNlb rq=1lb => not reportable on all four releases, NOTHING REPORTABLE overall, and the identity check passing.

One field away, renaming the released mass produced non-finite-release(refused before determination). The difference between them is not importance and not type. The released mass accumulates into a site total the engine guards with a finiteness check, so it is caught. The constituent mass is a product -- released mass times concentration -- and nothing sums it anywhere, so no accumulator guard ever sees it. It reaches the comparison as NaN, and a comparison against NaN is false, so no threshold is ever reached.

A previous pass found this in a ratio, where the numerator accumulated and the denominator did not. It reproduces in multiplicative form, and the generalisation is worth carrying: a finiteness check written on an accumulator protects the operands that accumulate, and a product or a ratio always has one that does not.

Three ways to lose the clock, all of them silent

The twenty-four hour notification deadline disappeared three separate ways, and every one of them reported the two late notifications as timely.

Renaming the deadline reverted it to infinity: late=0 => REPORTED, with the eight-thousand-pound methanol release that was notified thirty-nine hours after discovery reading REPORTABLE, notified in time. Renaming the discovery time or the notification time made the elapsed hours uncomparable and produced the same three lines. The releases are all still correctly identified as reportable; only the lateness went away.

That is a lateness test asking whether elapsed time EXCEEDS the deadline, so skipping it permits. It is the same ceiling-fails-open shape as the accumulation clock in this pass's waste article, and the same one-line fix applies: require the elapsed figure to be finite before comparing it, and treat non-finite as a failure to establish timeliness rather than as timeliness.

The names that only moved the paperwork

Several renames changed what the record says without changing what was decided. The site identifier, the release identifier, the substance description and the medium all printed as undefined while the determination stayed correct at three reportable releases and two late notifications.

That is not a reassurance. A release notification is a document that goes to a regulator, and a notification naming an undefined substance released to an undefined medium is not a notification anybody can act on. It is simply a different failure: the decision is right and the record of it is unusable, rather than the record being pristine and the decision wrong. Of the two, this is the one that gets fixed, because somebody reads it.

The default threshold on its own was the one arm in the area that moved nothing but the configuration display, and the reason is worth stating: every substance in this fixture is in the table, so the default is never consulted. An option whose value is never reached is not evidence that the option is safe -- it is evidence that this data did not exercise it.

What this means if you protect an EHS or incident-reporting front end

Two design points come out of this area and neither is about obfuscation. Make a missed lookup an error rather than a fallback, so that an unfindable substance stops the determination instead of quietly acquiring a threshold. And require every number entering a threshold comparison to be finite first, so that an unreadable mass or an uncomparable elapsed time refuses rather than permits. A hand-edited configuration file and a dropped key over a serialisation hop produce both of these without any renaming involved.

On the protection side the measures are the familiar ones: scope RenameMembers with a MemberRegexp that excludes the option keys and record fields your installed libraries read, and assert the outcome -- a fixed set of releases with a written-down expected determination -- rather than the configuration. A test that checks the substance table is populated passes in the arm where nothing is reportable.

Incident classification and notification deadlines are the operational core of DORA and your JavaScript resilience as well, and the same structure applies: a duty that attaches when a measured quantity crosses a published figure depends on the lookup, the arithmetic and the clock all surviving intact. The protected build determined all four releases correctly on all five presets.

Frequently asked questions

Did obfuscation change any release determination on its own?

No. Five areas through five presets, twenty-five comparisons, every protected output identical to the unprotected run. Everything here required member renaming aimed at names the installed determination engine also reads.

Does authoring a fail-safe default help or not?

It decides which way a lost lookup fails, and it does not protect the lookup. Here the default errs toward creating an obligation, so losing the substance identifier over-reported -- four reportable releases instead of three, with the identifier printing as undefined on every line. That gets noticed the same week.

And when the default was lost as well?

The site released 8,740 pounds of chemical, including eight thousand pounds of methanol, and the determination read NOTHING REPORTABLE with the site's own identity check passing. Every substance identifier on every line was still correct; what reverted was what an unfound identifier falls back to, and the vendor's fallback is infinity.

Why did renaming the concentration fail silently when renaming the released mass did not?

The released mass accumulates into a site total the engine guards for finiteness, so it was caught. The constituent mass is a product of two fields and nothing sums it, so no accumulator guard sees it. It reaches the comparison as NaN, and every comparison against NaN is false, so no threshold is reached.

What happened to the twenty-four hour notification deadline?

It disappeared three ways -- renaming the deadline, the discovery time or the notification time -- and every one reported the two late notifications as timely. A methanol release notified thirty-nine hours after discovery read notified in time in all three.

Were any renames harmless?

None were harmless, but several were merely disfiguring: the site, release, substance and medium names printed as undefined while the determination stayed correct. A notification naming an undefined substance released to an undefined medium is unusable, but it is the failure somebody reads and fixes.

What is the cheapest test that would have caught all of this?

Run a fixed set of releases and assert the exact determination -- which are reportable and which notifications were late. That fails in every arm in this article. A test that checks the substance table is populated passes in the arm where nothing is reportable at all.

Related reading