Measured Behaviour

Does obfuscation break permit to work authorization?

A permit to work does not prevent a hazardous activity. It authorises one, under stated conditions, for a stated window. That makes the assertion everybody writes first - no ignition sources in the flammable zone - false on the correct run, and it changes what a rename can quietly do.

The alarm is a dial, not a switch

We built a shift the way one arrives at a permit desk: fourteen requests in arrival order, a table saying which activities need a permit and of what class, a table of maximum durations by class, a post-work fire watch priced in minutes, a table of activity pairs that may not run concurrently, and a ceiling on how many permits may be live at once.

The correct run issues nine and refuses five. Three refusals come from the pair table - a welder and a grinder that would have run beside a tank purge, and a radiography shot that would have run beside a vessel entry. One comes from a confined-space entry requested for seven hours against a four-hour maximum. One comes from the concurrency ceiling.

And the correct run authorises two ignition sources inside a flammable zone, on purpose, because that is what a hot-work permit is. So hot permits live in the flammable zone reads two when everything works. It is not an alarm that is off or on; it is a dial, and the only mark on the dial is the permit configuration itself.

Protected with default settings on all five profiles, behaviour was identical. Nothing below was caused by obfuscation alone.

Two controls, each looking harmless, falling together

The pair table and the concurrency ceiling are separate controls. They were written by different people for different reasons and they are exactly the sort of thing a safety case calls defence in depth. They are also two keys on one object, which means one member pattern reaches both.

Renaming the pair table alone moves the issued count from nine to ten and drives the pair-refusal counter from three to zero. Renaming the ceiling alone moves issued from nine to ten and puts a third ignition source into the flammable zone. Each half, on its own, is the kind of one-permit difference a reviewer closes as noise.

Renaming both issues thirteen of fourteen. One refusal is left standing on the whole shift. The fire watch doubles from 180 minutes to 360, the pair counter reads zero, and the area authority's own permit check passes printing: fourteen requests, thirteen issued, one refused, zero stopped by an incompatible pair. On a busy site that sentence reads like a smooth morning.

That is the shape to carry away. Two half-arms that each move only a little are not evidence that the pair is safe. They are the signature of two controls that share an object, and the mitigation is placement rather than count - a second layer only survives a rename if it lives somewhere the transformation cannot reach.

The price is the first thing to go

The fire watch is not a limit and not a policy. It is what the authorisation costs: an hour of somebody standing in the area after the torch goes out.

Renaming it changes nothing else at all. Fourteen requests, nine issued, five refused, three stopped by a pair, two hot permits in the flammable zone - every figure identical to the correct run. The only movement in the entire output is the fire watch itself, which goes from 180 minutes to zero.

So the one number a supervisor would actually notice reads zero precisely because the number that would have made it non-zero is the number that went missing. Price the damage in a field the control does not own, because the cost is the first thing to disappear.

Refused for the wrong reason, and no counter moves

The sharpest cell in this file is the one that looks like nothing. Renaming the maximum-duration table produces output that is byte-identical to the correct run on every counter we print: fourteen requests, nine issued, five refused, three stopped by a pair, two hot permits in the zone, 180 minutes of fire watch.

The seven-hour confined-space entry is still refused. It is simply refused for a different reason. In the correct run the line reads window 7h exceeds 4h for CONFINED. In the renamed run it reads 7 concurrent exceeds limit 5. An unrelated control happened to catch the same permit on this particular shift.

The duration control is entirely gone, and the only trace is the wording inside a display field, past the point where most log lines truncate. Whether that permit is refused tomorrow depends on how busy the site is, which is a property of the day rather than of the plant. A quieter shift issues it.

This is worth separating from the usual masking story. It is not that a safe default hid a destructive one. It is that a neighbouring control coincidentally covered for a missing one, and the coincidence is not stable.

What to do about it

Where a control authorises a bounded breach, assert the bound against a source outside the configuration that derives it - the safety case, the area authority's own standing instruction, the number agreed with the insurer - and never against the permit system's own idea of what is authorised.

For the pairwise half, assert the relation rather than the population. Name the closest incompatible pair live at any moment and check that specific separation against that specific requirement. A rule that counts permits examined passes identically whether the pair table has four entries or none, because a pairwise requirement has no single record to hang an assertion on.

And scope the member pattern. Use MemberRegexp to name the members you want renamed rather than accepting a pattern wide enough to cover a configuration literal; Protect Members has the matching rules. If the permit desk runs on a panel or a rugged handheld rather than a desktop browser, protecting JavaScript on kiosks, digital signage and embedded devices covers the delivery side of the same problem.

Frequently asked questions

Did obfuscation change what was authorised on its own?

No. On all five profiles - the ES5 target, the modern target, both emit-gate configurations and the string-encoding profile - the protected bundle behaved identically to the original. Every difference came from member renaming reaching the permit configuration.

What did renaming the pair table and the concurrency ceiling do?

Together they issued thirteen of fourteen permits instead of nine, left one refusal standing on the whole shift, put a third ignition source into the flammable zone and doubled the fire watch from 180 to 360 minutes.

Were the halves a warning on their own?

Barely. Each half alone moved the issued count from nine to ten - the kind of one-permit difference a reviewer closes as noise. The pair is where the shift falls apart, which is why testing halves matters more than testing the union.

What happened when the fire watch was renamed?

Nothing else moved at all. Requests, issued, refused, pair refusals and hot permits in the zone were identical to the correct run. Only the fire watch itself changed, from 180 minutes to zero - the one figure a supervisor would have noticed.

Why can we not just assert no ignition source in the flammable zone?

Because that is false on the correct run. Two hot permits are authorised inside the zone on purpose - that is what a hot-work permit is for. The control permits a bounded breach, so the obvious assertion is not merely blind but wrong.

What did renaming the duration table do?

Every counter came out byte-identical to the correct run. The seven-hour confined-space entry was still refused, but by the concurrency ceiling rather than by its duration limit. The only trace is the wording of one refusal reason, and a quieter shift would have issued it.

What should we pin in a test?

The relation, not the population. Name the closest incompatible pair live at any moment and check that separation against that requirement, and check the authorised concurrency against the safety case rather than against the permit system's own figure. Scope the rename with MemberRegexp so the permit configuration is never a candidate.

Related reading