Three-name discipline + glossary as source-of-truth + EN-no-PL rule
D-017: Three-name discipline + glossary as source-of-truth + EN-no-PL rule
Anchors (historical): ops/decisions/2026-05-18-eng-handles-and-transcreation.md (W-level bootstrap-only application of the three-name rule on 17 entities)
Codifies as a D-level standing rule the three-name discipline established W-level on 2026-05-18 and applied only to the bootstrap-conforming 17 entries. Since then drift has accumulated across post-bootstrap content; D-017 ratifies the rule, names a single source-of-truth (the glossary), and constrains EN bodies against unregistered Polish loanwords.
Three named drift cases motivating the ratification:
- Inconsistent NPC slugs in the same directory.
data/src/content/npcs/stary-marno-niedoczas.ts(introduced post-2026-05-18) uses a PL slug, while the bootstrap-conforming siblingdata/src/content/npcs/three-dots-clerk.ts(renamed frombertranda-kropka-wieliska.tsby the 2026-05-18 decision) uses an ENG slug. The rule applied to bootstrap was abandoned in the next NPC card written. - Hybrid PL+ENG slug.
data/src/content/npcs/kepka-witness-bookkeeper.tsis a hybrid: the first segment (kepka) is the PL surname; the second and third (witness-bookkeeper) are the ENG role. Neither half-rule consistent — neither the bootstrap pattern nor the drifted pattern. - Quest filename divergence.
data/src/content/quests/quest-006-najstarsze-biuro.tsuses a PL slug whiledata/src/content/quests/quest-001-first-road.tsuses an ENG slug. Quests 002-006 inherit the PL pattern; only quest 001 inherits the bootstrap pattern. The rule was applied to bootstrap and abandoned thereafter.
These cases generalise: the 2026-05-18 W-level decision was correct in shape but limited in scope to the 17 bootstrap entries it analysed. Every entity created after 2026-05-18 was authored without referring back to that decision. D-017 raises the rule from “applied to bootstrap” to “applies to all entities”, and names the mechanism that prevents this drift from recurring.
Four binding choices ratified at D-017 (with one-line “why” each):
- Glossary = source-of-truth. All three names (ENG handle, PL canonical, EN transcreation) for every entity live in
data/src/content/_glossary.ts. Entity files import from the glossary rather than re-stating names inline. One place to change a name; one place to read a name; one place to record the four-phase pick’s reasoning. - EN body = full anglicisation. No Polish words in
LocalizedString.enunless registered in the glossary. Proper-noun toponyms (Plenny,Targosie,Mokrych Stempli) and load-bearing PL concepts (okazja,pieczętarka,Wolnokupiectwo) stay because the glossary registers them withpl == en. Untracked Polish loanwords are linted out of EN bodies — the EN locale player gets a coherent reading experience, not a half-translated one. - Full four-phase pick on post-bootstrap renames. Every rename of a drifted entity follows the four-phase discipline (gather → propose three → critique → pick) and produces a non-empty
transcreationNoterecording the pick’s reasoning. The discipline is owned by narrative-designer under Phase C; it is NOT part of this batch. - Phase A starts now. Wave 1 (this batch) lands the schema, the empty glossary file, the lint, the D-decision, and the CLAUDE.md carve-out. Wave 2 (narrative-designer, next dispatch) populates the glossary with the 17 bootstrap entries and the post-bootstrap drift entries. Wave 3+ (narrative-designer) executes the post-bootstrap renames under the four-phase pick. Lint is in WARN mode until wave 3+ closes; then it flips to ERROR mode in a separate commit.
Schema acceptance gate:
GlossaryEntrySchema.transcreationNote is z.string().min(10, ...). Empty notes are rejected at parse time. This is the load-bearing acceptance gate — the four-phase pick only works if the reasoning is captured at pick time, not retroactively. The .min(10) floor protects against single-word stubs (“tbd”, “idk”).
Reasoning:
- D-006 Pillar 3 / D-014 / D-016 coherence. The Walker’s progression surface (Home page) reads as
Poziom N(PL canonical) orLevel N(EN transcreation) — both anchored by a stable ENG handle (walker.level). The three-name shape that exists at the canon layer for canon entities (D-012’sdefining-image.self-rewriting-map, D-013’swalking-rule.redaction) generalises to ALL entities. D-017 makes the implicit pattern explicit. - D-011 (quest-name policy) generalised. D-011 ratified that quest chrome uses
Quest.name.enwhile quest prose usesQuest.name.pl. D-017 generalises the same locale-separation discipline to every entity in the data layer (factions, NPCs, items, materials, regions, places). The schema field is already there (LocalizedString.{en,pl}) — D-017 names the glossary as the place where the two strings are picked (vs stated inline by whoever wrote the file). - Operational discipline matches what already works in wiki/.
wiki/src/content/docs/lore/factions/unfinished-guild.md(PL content, ENG slug) is the canonical example of the correct pattern.wiki/src/content/docs/lore/frakcje.mdx(PL slug) is the drift example. The directory tree already implements the separation in production; D-017 names it.
No supersession of any prior D-decision. D-006, D-011, D-012, D-013, D-014, D-016 are all native fits — D-017 is a layer below them (data-layer naming hygiene), not a competitor.
Open questions answered at ratification:
- Decision number = D-017. Last numbered entry in
decisions.mdis D-016; D-017 is the next available. - Glossary shape = single flat file
_glossary.ts, not a per-handle directory. The glossary is a flat lookup table; per-handle files would fragment four-phase-pick reasoning and require 100+ imports for what is conceptually a JSON dictionary. pl/enschema shape =z.string().min(1)(NOT nestedLocalizedString). The glossary entry IS the source of those two strings for the rest of the data layer; nesting would duplicate them.- Lint mode = WARN this batch, ERROR after wave 3+. Phase A acceptance is “lint runs cleanly without crashing on the current repo”; flipping to ERROR before wave 3+ would block CI on known drift.
Follow-up work (post-ratification):
- narrative-designer Phase A wave 2 (next dispatch): populate
data/src/content/_glossary.tswith the 17 bootstrap entries from the 2026-05-18 decision (regions, factions, the 5 bootstrap NPCs, quest 001), plus any other already-conforming entities (items, materials, nodes, etc.). Each entry includes the non-emptytranscreationNote. - narrative-designer Phase C waves 3+: four-phase pick on post-bootstrap drift entities (Marno, Brodek, Pan Bezelec, full-sentence-elder, pine-half-century, kepka-hybrid, quests 002-006). Each rename = filename move + id-string update + glossary entry with
supersedesfield carrying the prior handle for provenance. - tech-architect wave 4 (post wave 3+): flip
lint:namingfrom WARN to ERROR mode in a separate commit, after the warn-list has been driven to zero by the rename pass. - backend-engineer / mobile-developer: no immediate action. Their consumers benefit from the glossary post-population. String-literal references to ids (e.g.
quest.006-najstarsze-biuroin seed scripts) will need updating when the renames land; that is a coordinated change at wave 3+ ship time, not now.
Lint warn-list output captured at this batch (run on the current repo, post-this-batch edits, glossary EMPTY): 100 warnings — 6 filename-matches-handle (the quest-NNN-* prefix divergence), 70 handle-in-glossary (expected — glossary empty pending wave 2), 24 en-body-no-untracked-pl (Marno, Pan Kępka, quest-006 et al — the EN-body anglicisation work that wave 3+ resolves).