Kiwifruit Broke the Canonical ID: A Migration Pattern for Food Taxonomies
Fresh Open Food Facts work shows how a preferred-name edit can split grocery prices, constrain AI output to stale categories, and complicate linked-data identifiers—and how recipe APIs can migrate without rewriting history.
One fruit exposed four clocks
In June, Open Food Facts changed the preferred English category name from Kiwis to Kiwifruits. Because its canonical tags are derived from preferred labels, the operational category ID changed from en:kiwis to en:kiwifruits while the former name remained a synonym.
Two proposals from this weekend show what happened downstream. An Open Prices machine-learning fix opened August 29 found that a hard-coded structured-output enum still allowed Gemini to return only en:kiwis. Its audit found five stale IDs among 83 category values, including mandarin oranges, garlic, pineapple, and rhubarb. A separate data repair opened August 30 addresses prices already stored under an earlier canonical tag: without re-normalization, old and new records for the same category do not match.
At the same time, an Open Food Facts Taxonomy Editor RDF export proposal, opened August 28 and updated August 30, documents the root trade-off explicitly. Existing identifiers come from a preferred label and can therefore change. Its architecture decision considers new immutable identifiers, but the current proposal uses familiar label-derived suffixes for compatibility with existing APIs and facet pages.
The thesis: food APIs should treat an upstream canonical tag as a versioned address for a concept, not necessarily the concept’s permanent identity; an internal immutable entity key, temporal alias graph, and coordinated migration ledger are needed to keep model outputs, stored facts, URLs, indexes, and exports joined while each updates on its own clock.
Source map and the repeated angle to avoid
| Fresh primary evidence | Status on August 30, 2026 | What it contributes |
|---|---|---|
| Open Prices ML category-enum repair | Open; created August 29 | Shows stale taxonomy IDs constraining AI output and reports five drifted values in an 83-value enum. |
| Open Prices stored-price re-normalization | Open; created August 30 | Shows old and new canonical tags splitting persisted grocery-price records and proposes a daily repair task. |
| Taxonomy Editor RDF export and ontology decisions | Open; created August 28, updated August 30 | States that preferred-label-derived identifiers can change, weighs immutable IDs against compatibility, and represents preferred and alternate labels with SKOS. |
| Open Prices frontend scheduled generated-data refresh | Open; regenerated August 30 | Shows a separate downstream taxonomy artifact: generated category data refreshed by workflow, with its own release and review path. |
The ten most recent posts here already cover empty states, search counts, parsing, ingredient-tree exports, nutrition gates, evidence metrics, safety-event cardinality, policy scope, food-table migration, and chemical rules. Older posts already say that taxonomies drift, aliases need provenance, and localized labels should not become identities.
The repeated angle to avoid is simply “version your taxonomy.” The new issue is referential continuity across asynchronous surfaces. A concept can resolve correctly in the current taxonomy while an AI schema, database row, saved URL, search index, and customer export still carry different formerly canonical strings.
Canonical is not the same as immutable
“Canonical” answers: which representation should be preferred under this taxonomy snapshot? “Immutable” answers: can this identifier keep denoting the same entity forever? Those properties are independent.
The RDF proposal makes that distinction concrete. Its URL decision records that current taxonomy IDs are generated from the first label. It considers generated immutable IDs but notes their incompatibility with existing APIs and facets. The proposed converter instead builds concept URIs from current IDs and emits preferred labels, alternate labels, and hierarchy relations using SKOS. That is an interoperability choice, not a promise that the URI can never move.
For a recipe or grocery platform integrating any external taxonomy, keep three values separate:
| Value | Purpose | Change behavior |
|---|---|---|
| Internal entity key | Joins, foreign keys, saved preferences, lineage | Never changes or gets reused |
| Upstream canonical tag | Current exchange value understood by the provider | Can be superseded under a named snapshot |
| Labels and aliases | Display, parsing, autocomplete, backward-compatible input | Can be added, deprecated, localized, or reordered |
An internal key such as foodcat_01K4... is intentionally boring. It can retain both en:kiwis and en:kiwifruits as external assertions without making either string the database identity.
Model renames as edges, not replacements
A plain alias table is insufficient when meanings can split or merge. The migration record needs direction, validity, and relation:
{
"entityId": "foodcat_01K4_KIWIFRUIT",
"externalSystem": "openfoodfacts-category",
"identifiers": [
{
"value": "en:kiwis",
"status": "superseded",
"validToSnapshot": "off-taxonomy-2026-06-28",
"successor": "en:kiwifruits",
"relation": "same_concept"
},
{
"value": "en:kiwifruits",
"status": "current",
"validFromSnapshot": "off-taxonomy-2026-06-29"
}
],
"labels": [
{"locale": "en", "value": "Kiwifruits", "role": "preferred"},
{"locale": "en", "value": "Kiwis", "role": "alternate"}
]
}
same_concept must be asserted, not guessed from textual similarity. A rename is different from:
- split: one old category becomes several narrower entities;
- merge: several prior entities become one current concept;
- scope correction: a tag keeps similar wording but changes meaning;
- language promotion: a non-English source ID acquires an English preferred name;
- deletion: an invalid concept has no safe successor.
Only an asserted one-to-one rename supports automatic rewriting. A split should return candidates or preserve the legacy entity until evidence resolves it; otherwise a repair can produce tidy joins and wrong food meaning.
Coordinate four migrations, not one deployment
The fresh changes expose four independently deployed surfaces.
Producer schemas
The ML enum constrained what the model could say. A stronger design generates such enums from a pinned snapshot and publishes a schema digest. Store the enum version, raw model token, normalized entity, and resolver result. For large vocabularies, resolve model candidates through a taxonomy service; for small enums, use CI to reject deprecated aliases.
Persisted facts and current projections
The proposed daily task re-normalizes category-price tags so old kiwi prices join new ones. That restores a current projection, but should not erase what was submitted or which taxonomy interpreted it.
A durable record can retain:
{
"observedCategoryTag": "en:kiwis",
"currentCategoryTag": "en:kiwifruits",
"entityId": "foodcat_01K4_KIWIFRUIT",
"resolvedWith": "off-taxonomy-2026-08-30",
"migrationId": "taxmig_2026_08_30_kiwifruit"
}
The observed value supports audit and replay; the projection supports current search and aggregation.
URLs, filters, and caches
Saved links using /products/en:kiwis should not become empty pages. Resolve aliases at the route boundary and redirect only asserted one-to-one renames. Preserve the original query in analytics while recording the resolved entity key.
Cache keys and search documents should use the internal entity. During migration, dual-read old and new tags, write only the current projection, and monitor legacy reads.
Generated clients and exports
The frontend’s August 30 generated-data pull request illustrates another clock: taxonomy-derived JSON has its own workflow and deployment path. Publish snapshot metadata with generated artifacts. Clients should tolerate unknown IDs and avoid exhaustive enums for evolving vocabularies. Exports should include stable keys, current tags, and superseded-value migrations.
A release pattern for food-category renames
| Phase | Action | Proof required |
|---|---|---|
| Detect | Diff concepts, preferred labels, aliases, and parent edges | Machine-readable taxonomy delta |
| Classify | Mark rename, split, merge, scope change, or deletion | Reviewed relation and successor rules |
| Prepare | Update resolver, model schemas, redirects, SDK fixtures, and generated data | Cross-surface compatibility tests |
| Shadow | Resolve stored recipe, product, and price tags without writing | Counts by outcome and ambiguous records |
| Migrate | Update current projections in idempotent batches | Migration ledger, checkpoints, and before/after totals |
| Dual-read | Accept current and superseded asserted aliases | Same entity and query results for rename pairs |
| Observe | Compare facets, prices, carts, recommendations, and error rates | No unexplained population loss or duplication |
| Retire | Stop emitting old tags after client adoption | Measured legacy-read threshold and rollback path |
For recipe APIs, also test ingredient mentions, saved exclusions, facet URLs, recommendations, substitutions, shopping-list aggregation, and AI fixtures. Do not recompute nutrition for a label-only rename unless a food-reference mapping or entity scope changed.
Edge cases that should block automatic repair
A preferred-label edit also reparents the concept. Treat identity and hierarchy separately; search expansion may change even if identity does not.
Two old tags normalize to one current tag. Check whether aggregation now creates duplicate rows.
One old tag has several successors. Do not choose by lexical similarity. Require product form, market, ingredient context, or review.
A stale client submits an old tag. Normalize it, return the current tag and entity key, and emit a deprecation warning.
A model enum lags the backend. Normalize accepted legacy values, but expose the stale schema in telemetry.
A user’s wording is rewritten. Keep recipe text and grocery notes unchanged; canonicalize structured fields, not evidence.
Validation checklist for API producers and buyers
Before trusting food taxonomy IDs across recipe, nutrition, AI, and grocery systems, verify:
- Is “canonical” defined as current preferred representation or permanent identity?
- Is there an immutable internal key independent of labels and upstream tags?
- Are aliases temporal, localized, sourced, and directional?
- Can migrations distinguish rename, split, merge, reparent, and deletion?
- Do AI schemas and generated clients declare their taxonomy snapshot?
- Are observed values retained separately from current normalized projections?
- Can old URLs and filters resolve without creating duplicate analytics identities?
- Are database repairs idempotent, checkpointed, auditable, and reversible?
- Do facets and totals reconcile before and after one-to-one migrations?
- Can clients accept unknown new concepts without dropping the whole response?
- Do exports include stable keys, current tags, snapshot IDs, and migration deltas?
- Does a label-only rename avoid unnecessary nutrition or safety recomputation?
The practical lesson from kiwis becoming kiwifruits is not that preferred names should stop improving. It is that readable taxonomy tags are excellent exchange values and poor permanent foreign keys unless the provider explicitly guarantees immutability.
A builder-grade Recipe API should absorb vocabulary corrections without splitting yesterday’s grocery prices, trapping tomorrow’s model inside an old enum, or breaking a saved ingredient filter. Keep identity immutable, make canonical addresses versioned, retain observations, and migrate every dependent surface through one ledger. Then the food name can improve while the concept—and the products built on it—remain joined.
Sources
- Open Prices, “fix: update stale hardcoded category taxonomy ids in RawCategory (ml)”, opened August 29, 2026; open at publication.
- Open Prices, “fix: re-normalize price category tags when the taxonomy renames a canonical id”, opened August 30, 2026; open at publication.
- Open Food Facts Taxonomy Editor, “feat: Tool to Export OFF Taxonomies to RDF”, opened August 28 and updated August 30, 2026; open at publication.
- Open Prices frontend, scheduled generated taxonomy data update, regenerated August 30, 2026; open at publication.
- Open Food Facts Server, kiwifruit taxonomy edit, merged June 29, 2026; older background for the original preferred-name change.
- W3C, SKOS Simple Knowledge Organization System Reference, background standard for preferred labels, alternate labels, and concept relationships.
Start Building
One consistent schema on every response. Get a free key and ship in minutes.