← Blog

A food can't be saltier than salt

About 400 rows in our catalog carried more sodium per gram than pure table salt. Where those numbers came from, the physical rule we now enforce at every write, and the part that isn't finished.

Our curation agent flagged a set of food rows in August with an obvious tell: they contained more sodium per gram than salt does. Not “high sodium”. Physically impossible sodium. Around four hundred of them, and 98% were wrong by exactly a factor of a thousand.

The interesting part isn’t the bug. It’s where the numbers came from.

They weren’t ours

The easy assumption is that an importer multiplied something somewhere. We checked before believing it, because the fix depends entirely on which it is.

No writer in our codebase scales sodium. We re-fetched the affected rows from their sources, and the ×1000 values were already there. USDA FoodData Central’s Branded set had them. Open Food Facts had them. One example: a sliced white bread entry listing 588,235 mg of sodium per 100 g, with a label field reading 200,000.

Our importers had copied upstream faithfully. That is normally the behaviour you want from an importer, and here it’s exactly how a bad number reaches a person’s diary wearing a source URL and an authoritative-looking badge.

The rule we enforce now

Table salt is 39.3% sodium by mass. So a serving of anything cannot carry more than 393 mg of sodium per gram, because the most salt-like thing it could possibly be is salt. Labels round, and real salt products land at 1.00–1.09× that bound, so we allow 1.2× and reject past it.

We chose a physical constant instead of a list of suspicious categories on purpose. A category list is a maintenance treadmill and it always has a hole in it. A law of chemistry doesn’t need updating, and it can’t be argued with by a vendor’s spreadsheet.

Inside the salt band there’s a second test, because a row that claims to be salt should behave like salt: its salt-equivalent mass plus its macro mass has to fit in the serving, and it can’t carry meaningful calories. That’s what separates a sea-salt grinder from a milk entry that’s been multiplied by a thousand.

The guardrail runs at every path that can write sodium — custom foods, the barcode cache, the research worker, the importer, and the tool Claude uses to save a food, which previously had no validation at all.

Repairing the existing rows, without guessing

A bulk ÷1000 would have been fast and wrong. Corroborating against USDA rows sharing the same barcode showed that Open Food Facts errors aren’t systematic the way FDC’s were: one Oreo entry divided by a thousand gives 11 mg, while its USDA sibling says 105.

So the repair is evidence-first. Every correction is derived from that row’s own upstream record, re-fetched at repair time. If the source now agrees, we correct. If it doesn’t, we clear the value to null and mark the row for a human to read the label — an honest “we don’t know” beats a confident guess, and null is a state the app already knows how to display.

Every change writes a revision row with its evidence, so any number can be traced back to why it changed.

What’s actually done, and what isn’t

The inflow is closed. Since the guardrail shipped on 17 August, zero new rows have been written that break the rule — we checked this morning rather than assuming it.

The backlog isn’t finished. 82 legacy rows still sit above the line, left over from the May bulk imports: the salty tail the automatic repair deliberately refuses to touch, plus rows whose upstream record is still wrong today. Those need a human with the actual package. We’d rather say that than round it up to “fixed”.

If you ever see a number in Macroline that looks impossible, tap it. Every food shows where it came from, and that link is the fastest way to tell our mistake from the manufacturer’s.