Skip to content

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:

  1. 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 sibling data/src/content/npcs/three-dots-clerk.ts (renamed from bertranda-kropka-wieliska.ts by the 2026-05-18 decision) uses an ENG slug. The rule applied to bootstrap was abandoned in the next NPC card written.
  2. Hybrid PL+ENG slug. data/src/content/npcs/kepka-witness-bookkeeper.ts is 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.
  3. Quest filename divergence. data/src/content/quests/quest-006-najstarsze-biuro.ts uses a PL slug while data/src/content/quests/quest-001-first-road.ts uses 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):

  1. 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.
  2. EN body = full anglicisation. No Polish words in LocalizedString.en unless 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 with pl == en. Untracked Polish loanwords are linted out of EN bodies — the EN locale player gets a coherent reading experience, not a half-translated one.
  3. 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 transcreationNote recording the pick’s reasoning. The discipline is owned by narrative-designer under Phase C; it is NOT part of this batch.
  4. 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) or Level 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’s defining-image.self-rewriting-map, D-013’s walking-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.en while quest prose uses Quest.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:

  1. Decision number = D-017. Last numbered entry in decisions.md is D-016; D-017 is the next available.
  2. 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.
  3. pl/en schema shape = z.string().min(1) (NOT nested LocalizedString). The glossary entry IS the source of those two strings for the rest of the data layer; nesting would duplicate them.
  4. 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.ts with 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-empty transcreationNote.
  • 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 supersedes field carrying the prior handle for provenance.
  • tech-architect wave 4 (post wave 3+): flip lint:naming from 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-biuro in 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).