Measured, not asserted
Published
A loading computer answers three questions before a vessel sails: is the cargo inside the deadweight, is the sailing draft inside what the port can take, and is any single hold loaded harder than its tank top will bear. This test found a vessel drawing 12.40 metres cleared out of a 12.00 metre berth, because the number was recorded in metres and read in feet; and it found the numerator and the denominator of one tank-top ratio failing in opposite directions -- one refusing every voyage loudly, the other clearing a hold loaded past its limit in silence.
Three limits and one unit
The fixture is a loading plan for three voyages of the same 28,400 tonne parcel mix. The operator configures a 12.00 metre maximum sailing draft, a 32,000 tonne deadweight, a 14 tonne per square metre tank-top limit, and a rule of record that refuses any plan whose parcels do not sum to the booking system's manifested total. Each voyage fails on at most one axis: the first is clean and clears; the second draws 12.40 metres and does not; the third puts 5,200 tonnes on 340 square metres of hold two, which is 15.29 tonnes per square metre against a 14 tonne limit, and does not.
One line of that configuration is the whole story of this article. The draft is recorded in metres, and the engine's own default is feet. Nothing about that is unusual -- a loading computer written to be reused has to assume something, and a number is a perfectly good number in either unit. 11.58 is a plausible draft in metres for a ship of this size and a plausible draft in feet for a smaller one.
The engine is copied in unprotected, converts whatever the caller says its drafts are recorded in, and has no way to notice that a number arrived in the wrong unit.
Protection on its own moved nothing
Twenty-five comparisons across five areas and five presets, all identical to the unprotected runs, each original run twice before comparison. Everything below required member renaming reaching names the loading engine reads.
A 12.40 metre draft in a 12.00 metre berth
With the draft-unit option renamed, the engine reverted to feet. The second voyage printed draft=12.4ft=3.78m and read CLEARED TO SAIL. A vessel drawing 12.40 metres was cleared out of a berth that can take 12.00, because 12.40 feet is 3.78 metres and 3.78 metres is comfortably inside any limit.
Everything else on that line was right. The cargo total was right. The three hold loadings were right and inside the tank top. The manifest reconciliation passed. The first voyage, which was correct anyway, stayed correct -- its draft simply printed as 3.53 metres instead of 11.58, and a correct verdict on a wrong number is the most forgettable kind of evidence there is.
One field along the same axis, the DRAFT ITSELF, failed the other way: renamed, it produced non-finite-draft(refused before clearance) on all three voyages. Nothing sails. The bridge calls within the hour.
So losing the value fails closed and loud, losing its unit fails open and silent, and the standard mitigation -- require the number to be finite before comparing it -- is structurally blind to the second, because a number in the wrong unit is a perfectly good number. That was measured before in a clinical setting; it holds here with a ratio of 3.28 instead of 2.2, and with a ship instead of a dose.
Both operands of one ratio, opposite directions
The tank-top check is a division: tonnes in the hold over square metres of tank top. Both operands are fields on the parcel records. They were renamed separately, and they failed in opposite directions.
The numerator, weightTonnes, is summed into a cargo total, and the engine checks that total is finite before doing anything else. Renamed, the sum became NaN and every voyage refused: non-finite-cargo(refused before clearance). Loud, immediate, safe.
The denominator, footprintM2, is never summed into anything the engine guards. Renamed, each hold printed 9800t/NaNm2=NaN, and NaN > 14 is false, so the third voyage -- with a hold loaded to 15.29 tonnes per square metre -- read CLEARED TO SAIL with an empty reason list.
Two fields, one arithmetic expression, one guard. The guard covers the operand that flows into a total and has nothing to say about the operand that does not. That is worth generalising past this fixture: a finiteness check written on an accumulator protects the values that accumulate, and a ratio has one operand that never does.
The grouping key averaged the danger away
Renaming the hold identifier collapsed all three holds of a voyage into a single bucket, which is a shape this series has measured in claims adjudication and trust accounting. Here it has a physical meaning: undefined[P-7+P-8+P-9]=28400t/2120m2=13.40. The third voyage's 15.29 tonnes per square metre disappeared into an average of 13.40, which is inside the limit, and the voyage read CLEARED TO SAIL.
Nothing was lost. Every tonne is still there and every square metre is still there. The total is exactly right. What moved is which records the comparison is taken over, and a point load is precisely the thing an average cannot see. The first two voyages kept their correct verdicts, so a spot check on either of them proves nothing about the third.
The voyage identifier failed the other way and loudly: renamed, every parcel matched every plan and the operator's manifest check reported 85200t loaded against 28400t manifested and refused. Three times the cargo is the kind of wrong that gets caught.
What the configuration screen said in every arm
Renaming the three limits individually reverted each to the library's infinity, and each one cleared exactly its own failing voyage: the draft limit cleared the deep-draft voyage, the tank-top limit cleared the point-loaded one. Those arms at least print the limit as Infinity, which is visible to anybody who reads the configuration line.
The record-field arms do not offer that. With the footprint renamed, the tank-top limit still displayed as 14t/m2. With the draft unit renamed, the maximum draft still displayed as 12m. Every limit correctly configured, correctly displayed, and enforcing nothing -- which is the third pass running in which a populated configuration screen turned out to be evidence that entries exist and never that anything finds them.
The manifest total on the plan record measured SAME on both targets, and that was predicted rather than discovered: it is written by this file and read only by this file's own rule, so both halves moved together. It is a robust field and a worthless piece of provenance, in exactly the sense an earlier pass established.
What this means if you protect a loading computer or a stowage front end
Never let a unit live in a different key from the number it describes. Carry the quantity with its unit, or normalise at the boundary where the value enters the system. A unit that can be separated from its number is the entire precondition for the first result in this article, and a hand-edited configuration file or a library upgrade produces it just as readily as a rename does.
Then check plausibility, not just finiteness. A sailing draft of 3.53 metres for a vessel carrying 28,400 tonnes is not a finite-number problem, it is a physics problem, and a range check on the draft catches what an isFinite check cannot. The same bound catches the wrong-unit arm and misses nothing.
And keep the rename pattern off record fields entirely -- a loading computer is an appliance, so the constraints in protecting JavaScript on kiosks, digital signage and embedded devices apply to it directly. Every open result in this article came from a field on a parcel or a plan, not from an option key. A MemberRegexp that matches your internal method names and excludes the vocabulary of your data records removes the whole class.
Frequently asked questions
Did obfuscation change any clearance decision on its own?
No. Five areas across five presets, twenty-five comparisons, every one identical to the unprotected run. Every failure below required member renaming reaching names the loading engine also reads.
How did a 12.40 metre draft clear a 12.00 metre berth?
The draft-unit option was renamed, so the engine used its own default of feet. 12.40 was read as feet, converted to 3.78 metres, and cleared every limit. The cargo total, the hold loadings and the manifest reconciliation were all still correct on the same line.
Why does requiring the number to be finite not help there?
Because a number in the wrong unit is a perfectly good number. Finiteness checks catch a value that went missing; they are blind to a value that arrived meaning something else. One field along the same axis, renaming the draft itself did produce a non-finite refusal on every voyage.
What happened to the tank-top check?
Its two operands failed in opposite directions. The weight is summed into a cargo total the engine guards, so renaming it refused every voyage loudly. The hold area is never summed into anything, so renaming it made every ratio NaN and cleared a hold loaded to 15.29 tonnes per square metre against a 14 tonne limit.
What did renaming the hold identifier do?
It merged three holds into one bucket, so a 15.29 tonne per square metre point load averaged out to 13.40 and the voyage cleared. Every tonne and every square metre was still present and the total was exactly right; only the set the comparison was taken over changed.
Would the configuration screen have shown a problem?
Not in the record-field arms. The tank-top limit still read 14t/m2 and the maximum draft still read 12m while neither was enforcing anything. Only the arms that renamed the limits themselves displayed the library's Infinity.
What is the practical mitigation?
Carry a quantity together with its unit rather than in a neighbouring key, normalise at the boundary, and add a plausibility range on top of the finiteness check -- a 3.53 metre draft under 28,400 tonnes of cargo fails a range check and passes an isFinite check.
Related reading