Decision
Each entity type's facts are governed by data in the schema registry, not by
constants compiled into the renderer. The registry (schemas/registry) is the
single source of truth for, per type:
- which predicates are valid (
types.<t>.valid/relationships/attributes), - how they're labelled (
predicate_labels), - how variant spellings fold to a canonical key (
attribute_aliases), - how they group into page sections (
types.<t>.groups).
The public entity renderer builds its fact vocabulary from the registry at request time, over an in-memory cache that reloads when the registry changes — no restart, no redeploy. Adding or relabelling a fact type is a registry edit.
Why
A film page (solo-a-star-wars-story) displayed a near-empty body: one
"Performer" and nothing else — no director, composer, producer, box office,
runtime, writer, or cast — even though every one of those facts was present in
the entity's data and declared valid in the registry's film schema.
The cause was an architectural shortcut: the renderer carried a hardcoded,
character-shaped vocabulary (KEY_ALIASES / KEY_LABEL). Its own comment was
explicit — any source key it didn't recognise "silently drops out of the merged
view." The map knew actor → Performer, homeworld, lightsaber, species…
but had no entry for any film predicate. So films, episodes, books, and
games all lost their facts at the last step before render. The data layer was
fine; the presentation layer was quietly throwing most of it away — and worse,
fixing it meant editing and redeploying code every time a new kind of fact
appeared.
That is exactly backwards for a knowledge graph whose whole job is to accrue new kinds of facts. The vocabulary of facts must be data the system curates, not a constant a developer maintains.
How
- The registry already declared
predicate_labelsand per-typegroups; the renderer simply didn't read them. It now does. Any predicate a type declares self-maps (survives the merge under its own name) and is labelled and grouped from the registry. The curated Wikidata-PID / semantic-folding base is kept as a fallback, extended by the registry rather than replaced. - The registry is read Firestore-primary with the GCS-mounted file as a safety fallback, cached in memory on a short TTL and refreshed fire-and-forget so rendering never blocks on a fetch. A registry edit in Firestore surfaces within the TTL.
- The feedback loop that proposes new valid predicates already exists: the attribute-QA pass surfaces real-but-unregistered predicates per type as candidates. That becomes propose → approve → write-to-registry — all data.
Implementation: MB-404. The same change fixes every non-character type at once, because the gap was systemic, not film-specific.
Principle
The set of facts the platform can express is curated data, not compiled code. New knowledge shapes arrive as registry edits that take effect on a cache reload — never as a renderer patch and a deploy.