Measured, not asserted

Does obfuscation break hazardous waste manifest tracking?

A generator's regulatory category is not a property of the site. It is derived from how much waste the site produces in a month, and it decides how long that site may hold waste without a storage permit. This test lost the thresholds that make the derivation, leaving the quantity itself perfectly readable, and a large quantity generator holding waste 118 days against a 90-day limit reported IN COMPLIANCE -- with the operator's own guard passing and printing 1850kg/month => vsqg, 270d limit on the same line.

A category nobody assigns

Hazardous waste rules do something software people should find familiar: they classify an entity by a number rather than by a flag. Generate under 100 kg in a calendar month and you are a very small quantity generator. Between 100 and 1,000 and you are small. Above 1,000 and you are large. The category is never written down as a fact about the site; it is recomputed from the quantity, and it changes when the quantity does.

What hangs off it is the accumulation time limit -- how long waste may sit on site before storing it requires a permit. Roughly 270 days for the smallest category, 180 for small, 90 for large. Get the category wrong and every quantity, every date and every manifest number can still be correct while the deadline moves by six months.

The fixture is two sites. One generates 1,850 kg a month, which makes it a large quantity generator on a 90-day limit, and it has held waste for 118 days: storing without a permit. The other generates 640 kg, which is small and gives it 180 days, and it has held waste for 150 -- lawful -- but one of its manifests has been out for 58 days with no signed copy back from the designated facility, which is an exception report.

The tracking engine is copied in unprotected. Its defaults are the shape a library's defaults always are: no thresholds, so every site is the smallest category, and no time limits at all.

Protection on its own moved nothing

Twenty-five comparisons across five areas and five presets, every protected run byte-identical to the unprotected one, each original run twice before comparison. Everything below required member renaming reaching names shared with the installed engine.

The guard caught one route to the wrong category and certified the other

The operator's rule of record here is the mitigation four previous passes in this series recommended: refuse a category that was not derived from a quantity that exists. It is a good rule, and this pass measured exactly what it covers.

Rename the monthly quantity field and it catches it cold: site-rejected(category-check(the site reports no monthly quantity, so its category was not derived)). Nothing is cleared, somebody looks.

Rename the thresholds instead -- the option carrying the numbers the derivation compares against -- and the site reports category=vsqg monthly=1850kg of waste held=118d limit=270d => IN COMPLIANCE, checked by category-check(1850kg/month => vsqg, 270d limit).

The guard ran. The guard passed. Its passing sentence prints the quantity, 1,850 kg a month, correct to the kilogram, immediately before classifying that site as a very small quantity generator. Nothing is missing, nothing is undefined, nothing is unreadable. The thresholds simply are not there to be exceeded, so no threshold is exceeded, so the site falls into the smallest category -- which is the most permissive one, because that is what the bottom of a ladder is.

That is the shape to carry away. A guard defined over whether a value EXISTS is blind to a rename that leaves the value existing and removes what it is compared against. The two routes to the same wrong category are not equally visible, and the invisible one is the one that leaves the data intact.

The flag in front of the guard, again

For completeness, the quiet version of the route the guard caught. Renaming the monthly quantity together with the flag that enables the check produces category=vsqg monthly=undefinedkg held=118d limit=270d => IN COMPLIANCE, unchecked. The word undefined is right there in the output and nothing consults it.

So the same wrong answer is reachable three ways: with the guard catching it, with the guard switched off beside it, and with the guard passing and endorsing it. Only the last of those leaves a record that looks entirely healthy.

The accumulation clock, and a ceiling that fails open

Renaming the accumulation limits reverted them to no limit at all: limit=Infinityd and the 118-day site reads compliant. Loud enough to spot in a configuration review, if anybody reviews it.

Renaming the accumulation start date is quieter and it is a shape this series has measured before. The elapsed time becomes held=NaNd, and the test that would refuse the site asks whether the days held EXCEED the limit. A comparison against NaN is false, so the excess is never established, so the site clears: held=NaNd limit=90d => IN COMPLIANCE.

The direction is decided by which branch the comparison's true outcome guards. Here the true outcome refuses, so skipping it permits. A floor phrased the other way -- days remaining must be at least zero -- would have refused everything instead. Requiring the number to be finite before comparing it either way is the one-line fix, and it is worth having regardless of obfuscation, because a hand-edited date field produces the same NaN.

The exception reports, and a document this rename cannot touch

Renaming the exception window reverted it to infinity and the pending exception report disappeared: the site with a manifest out 58 days and no signed copy back reads exception-reports-due=[] => IN COMPLIANCE. Renaming the shipped date did the same by making the elapsed time uncomparable. Renaming the manifest number went the other way -- no returned copy matched any shipment, so both of the first site's completed manifests joined the pending list.

The signed copies themselves are the honest negative in this area, and they are worth a paragraph. They arrive as a document from the national manifest system: the designated facility wrote those field names, and this bundle only reads them. Renaming moved the read and could not move the document, so the verdicts did not change at all -- and the generator's own summary of what came back went blank: signed-copies-returned=undefined@d22/r.okafor.

That is the mirror of every other arm in this article. Where both the write and the read live inside your bundle, a rename moves them together and nothing breaks. Where the write is somebody else's document, only your read moves. Before predicting which direction a lost name fails in, name the party that WROTE the field.

What this means if you protect a waste or EHS front end

The rule of thumb that comes out of this area is about derived values rather than about obfuscation. Any classification computed from a number is a name-shaped dependency between the number and the table it is compared against, and losing either one produces a category rather than an error. Categories do not look wrong. They look like categories.

Concretely: scope RenameMembers with a MemberRegexp that excludes every option key and record field an installed library reads; assert the DERIVED value, not the inputs, because a check that the quantity is present passes in the arm that matters here; and make a missing threshold table an error rather than a fallback to the bottom of the ladder.

The supply-chain and reporting-duty framing in NIS2 and your JavaScript supply chain covers the organisational half of the same problem. The protected build classified both sites correctly on all five presets; what this article maps is which names are shared with code that is not rebuilt with yours.

Frequently asked questions

Did obfuscation change any compliance finding 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 tracking engine also reads.

What exactly happened when the category thresholds were renamed?

The engine reverted to having no thresholds, so no threshold could be exceeded and every site fell into the smallest generator category. A site generating 1,850 kg a month was classified as a very small quantity generator, its 90-day storage limit became 270 days, and 118 days of accumulation read as compliant.

Did the site's own guard notice?

It passed and endorsed the result. Its sentence prints the correct quantity -- 1850kg/month -- immediately before the wrong category. The guard asks whether the quantity exists, and the quantity did exist; what went missing was what it is compared against.

So the recommended mitigation does not work?

It works against one of the two routes. Renaming the monthly quantity field itself was caught cold. The mitigation is blind to a rename that leaves the value intact and removes the table, which is why the useful assertion is on the derived category rather than on the presence of its inputs.

What happened to the accumulation clock?

Renaming the accumulation start date made the elapsed days NaN, and the test asking whether days held exceed the limit is false against NaN, so the excess was never established and the site cleared. Requiring the number to be finite before comparing it is the one-line fix, and it is worth having regardless of obfuscation.

Were the signed manifest copies affected?

No, and that is instructive. They arrive as a document the designated facility wrote, so renaming moved this bundle's read and could not move the document. The verdicts were unchanged and only the generator's own display of what came back went blank. Where the far side writes the name, only your read can move.

What is the cheapest test that would have caught this?

Assert the derived category and the resulting day limit for a site of known monthly quantity. That fails in every arm in this article. A test that checks the quantity is present, or that the threshold table is configured, passes in the ones that matter.

Related reading