Measured, not asserted
Published
A discharge permit usually carries two limits over one set of samples: a ceiling no single result may breach, and an average the month as a whole must clear. They are independent, and a month can violate either alone. This test renamed both and the report printed a maximum of 12.80 mg/L against a 10.00 limit, an average of 6.49 against a 6.00 limit, and the words NO VIOLATIONS REPORTED -- with all eight results genuine and the plant's own sampling-frequency check passing on the same line.
Two limits, one dataset, and a month can breach either
The arithmetic on a discharge monitoring report is not complicated, which is exactly why the interesting failures are not arithmetic ones. Collect the month's samples for an outfall. Decide what a non-detect is worth -- most permits say half the reporting limit rather than zero, because calling an unmeasured value zero flatters the average. Take the maximum and compare it against the daily maximum. Take the mean and compare it against the monthly average. Report whichever limits were breached, and certify the result.
The fixture is a zinc permit with a 10.0 mg/L daily maximum, a 6.0 mg/L monthly average, non-detects counted at half the 5.0 mg/L reporting limit, and eight samples a month required. Outfall 001 has eight results: one at 12.8 mg/L, four more detects, and three non-detects. That is two violations from one set of eight numbers -- 12.80 breaches the ceiling and the resulting 6.49 average breaches the mean. Outfall 002 has two samples in a month the permit says needs eight, so the violation there is the monitoring frequency itself.
The reporting engine is copied in unprotected, and its defaults are the usual permissive ones: no daily maximum, no monthly average, non-detects worth zero, and any number of samples counting as a full month.
Protection on its own moved nothing
Twenty-five comparisons across five areas and five presets, every protected output byte-identical to the unprotected run. Everything below required member renaming that reached names the installed reporting engine also reads.
Each half looks cosmetic and the union is the report certifying nothing
Rename the daily maximum alone and the report still finds a violation -- the monthly average survives and catches the month. Rename the monthly average alone and the same thing happens in reverse. Either arm on its own produces a report with one violation on it instead of two, which is the finding a reviewer closes as a display problem.
Rename both, which one slightly wider pattern does, and the report reads n=8 max-result=12.80@d2 avg=6.49 [] => NO VIOLATIONS REPORTED, checked by frequency-check(8 sample(s) over the month).
Look at what is on that line. The maximum result is printed: 12.80 mg/L, on day 2, correct. The average is printed: 6.49 mg/L, correct. Eight samples, correct, and the plant's own frequency check says so. The report is not empty and it is not obviously wrong. It carries every number a regulator would want and asserts that none of them broke a limit, because the limits it would have been compared against are no longer there.
That is why testing the halves and the union separately matters. The dangerous cell in a rename matrix is reliably at neither end: each half alone reads as noise, and only their union removes the finding.
A second route to the same empty report, without the tell
The first empty report has one weakness as a deception: 6.49 is printed beside a claim of no violations, and a reader who knows the permit is 6.00 could catch it. The non-detect rule removes even that.
Renaming it reverts the engine to counting non-detects as zero. The three non-detects stop contributing 2.5 mg/L each and the month's average falls from 6.49 to 5.55. That is under the 6.00 limit, so the monthly-average violation disappears on its own -- and 5.55 is a perfectly plausible number for a compliant month. Renamed together with the daily maximum, the report reads max-result=12.80@d2 avg=5.55 [] => NO VIOLATIONS REPORTED and there is nothing on it a reviewer could challenge without recomputing from the raw results.
Renaming the non-detect flag on the samples themselves does the same thing by the other route: no sample is recognised as a non-detect, so each is taken at its stored value of zero. The substitution convention and the flag that triggers it are two names for one control.
The frequency check, and a grouping key that merges two outfalls
Outfall 002 exists to test the sampling-frequency floor, which is inert on an outfall that was sampled properly. Renamed together with the flag enabling the plant's own check, the outfall with two samples reads n=2 as=composite max-result=4.40@d20 avg=4.20 [] => NO VIOLATIONS REPORTED. A month with a quarter of the required sampling certifies clean, and both of the results on it are genuine.
The outfall identifier produced the sharpest structural result in the area. Renaming it collapsed the grouping key, and the two outfalls merged into one report: n=10 as=grab/composite avg=6.03. Ten samples from two different discharge points, taken by two different methods, averaged together. The merged average happens to stay over 6.00 so a violation is still reported -- but outfall 002's report has vanished entirely, and outfall 001's has been diluted by water it has nothing to do with.
The plain numbers failed loudly, as they have in every area of this series. Renaming the result field or the reporting limit refused the report outright with non-finite-result(refused before the report is built).
What this means if you protect an environmental reporting front end
A discharge monitoring report is a self-certification. It is signed, it is submitted, and it is the record the regulator works from. That makes the difference between an empty report and a wrong report matter more than usual: a wrong number invites a question, and a report with no violations on it invites nothing at all.
The concrete measures are the same three this series keeps arriving at. Scope RenameMembers with a MemberRegexp matching your internal names and excluding every option key and record field an installed library reads. Assert the OUTCOME on known data -- one month of fixed sample values whose expected violation list you write down -- because that fails in every arm here, while a test asserting the limits are configured passes in most of them. And treat any option whose absence means "no limit" as a name that must never move, because the library cannot distinguish an unread option from one you never supplied.
The technical-documentation and reporting duties described in the Cyber Resilience Act and your JavaScript rest on the same assumption that a submitted artefact reflects the system that produced it. Protection did not break any of these reports -- the protected build certified both outfalls correctly on all five presets.
Frequently asked questions
Did obfuscation change any reported violation on its own?
No. Five areas through five presets, twenty-five comparisons, every protected output identical to the unprotected run. Everything in this article required member renaming aimed at names the installed reporting engine also reads.
What did the report look like when both limits were renamed?
It printed a maximum of 12.80 mg/L on day 2, an average of 6.49 mg/L, eight samples, and NO VIOLATIONS REPORTED, with the plant's own sampling-frequency check passing. Every figure on it is true. The limits those figures would have been compared against had reverted to infinity.
Is renaming just one of the two limits safe?
It is less bad and it is more misleading. Either limit alone still leaves the other catching the month, so the report carries one violation instead of two -- which reads like a display problem. The union removes the finding entirely, so testing only the wide pattern or only the narrow one misses the dangerous cell.
What did the non-detect rule do?
Reverted to counting non-detects as zero, which dropped the month's average from 6.49 to 5.55 and made the monthly-average violation disappear on its own. Combined with the daily maximum it produces a report with no implausible number anywhere on it, which is worse than the first empty report.
What happened when the outfall identifier was renamed?
The two outfalls merged into a single report of ten samples across two discharge points and two sampling methods, averaging 6.03 mg/L. One outfall's report vanished and the other was diluted with water it has nothing to do with.
Which names failed safely?
The measured values. Renaming the result field or the reporting limit refused the report before it was built, with a non-finite result. Losing a value fails closed and loudly; losing a limit, a convention or a grouping key fails open and quietly.
What is the cheapest test that would have caught all of this?
Run one month of fixed sample values and assert the exact violation list, not the configuration. That fails in every arm in this article. A test that checks the daily maximum and monthly average are set passes in the arms where the report certifies nothing.
Related reading