Skip to content

Component Naming Forms

naming specs/naming/forms.kmd

Forms of reference for every Koder Stack component: type, display name, bare name, slug, package, path, and aliases array. Every form derives from the bare name with no exception per type: display = "Koder <Bare>", slug = <bare-lowercase> (never prefixed — it is the repo, path and subdomain, namespaces that are already ours), package = koder-<slug> (the .deb/apt/Hub-catalog id, where the namespace is NOT ours and the prefix buys real disambiguation). Defines the regex per form and global uniqueness. §7 binds each rendering surface (landing title/hero/navbar, app title bar, Hub card, CLI, URLs, metric prefix) to a concrete form, closing the bare-vs-display ambiguity. Registry lives at meta/docs/stack/registries/stack-manifest.kmd and is CI-checked by `koder-spec-audit naming`.

When this spec applies

Primary triggers

All triggers

Specification body

Spec: Component Naming Forms

Defines the forms of reference every component of the Koder Stack must declare: a structural type, a prosaic display name, a contextual bare name, a canonical slug, a package id, a filesystem path, and an optional array of aliases for compact/colloquial reference.

Every form derives from the bare name, with no exception per type. There is no rule that inspects the name and decides — display is always Koder <Bare>, slug is always the lowercase bare, package is always koder-<slug>. The one thing the forms distinguish is whose namespace they land in: the slug names things that are already ours (the repo, the path, the .koder.dev subdomain), so prefixing it only stutters; the package lands in apt — shared with the world, where hub and talk are real Debian packages — so it carries the prefix. That distinction is the whole spec; the rest is regexes. See the rationale under R4.5 for the measurement that produced it.

Applies to every entry in meta/docs/stack/registries/stack-manifest.kmd.


1. The 7 forms

FormExample (Flow)Example (Koda)Example (Drafts)Example (Kdb umbrella)Where it's used
typeserviceenginemetaumbrellaclassification
displayKoder FlowKoder KodaKoder DraftsKoder KdbUI, marketing, prose docs
bareFlowKodaDraftsKdbconversation where Koder context is implied
slugflowkodadraftskdbrepo, path segment, subdomain
packagekoder-flowkoder-kodakoder-draftskoder-kdb.deb/.kpkg Package:, apt, Hub catalog
pathservices/foundation/flowengines/lang/kodameta/context/draftsinfra/data/kdbfilesystem reference
aliases[kflow][][kdrafts][]CLI binary, D-Bus tail, oral speech

The "Where it's used" column is a summary. The exhaustive, normative surface→form mapping (which surface uses display vs bare vs slug) lives in §7.


2. Rules

R1 — type is required (enum)

type ∈ { product, service, engine, meta, umbrella }
  • product — user-facing app/CLI (products/**)
  • service — API-consumed backend (services/**)
  • engine — embeddable runtime/lib (engines/**)
  • meta — reference material, not deployable (meta/**)
  • umbrella — Sector grouping 2+ deployable components (e.g. infra/data/kdb grouping kdb-kv/kdb-ts/kdb-vec). Has a name but no binary/CLI.

R2 — display is required and globally unique

  • R2.1display = Koder <Bare>, for every type, with no exception (e.g. Koder Flow, Koder Koda, Koder Kdb, Koder Drafts). Owner, 2026-07-16: o mesmo motivo que matou as exceções da R4 — uma regra que decide por categoria produz superfícies que discordam. Kdb e Koder Kdb eram dois nomes para a mesma coisa, e nada dizia qual usar num título.
  • R2.3 — Title Case with single spaces. ASCII only.

R3 — bare is required

Equals display minus the Koder prefix (when present). Title Case.

R4 — slug is required, kebab-case lowercase, globally unique

  • R4.1slug = <bare-lowercase>, for every type, with no exception (e.g. flow, hub, koda, kdb, drafts). The koder- prefix never appears in a slug.
  • R4.4slug matches ^[a-z][a-z0-9]*(-[a-z0-9]+)*$. It must START with a letter (a slug is a DNS label and a path segment), and digits are legal anywhere after it.

Por que a R4.4 mudou junto (owner, 2026-07-16). A regex anterior era ^[a-z]+(-[a-z0-9]+)*$: dígito só era admitido depois de um hífen. Ela só aceitava koder-bip39 e koder-s3 porque o prefixo fornecia o primeiro segmento puro-letras e empurrava o dígito pro segundo. Com o prefixo fora do slug (R4.1), bip39 e s3 — dois componentes reais — passariam a violar a própria spec. A regra velha não estava protegendo nada: nunca houve intenção de proibir dígito num nome, era efeito colateral de uma regex escrita quando todo slug era prefixado. A nova continua recusando o que importa (9foo, -foo, foo_bar, maiúscula).

  • R4.5Proximity is not collision. Uniqueness (R2/R4/R6, enforced by T3–T5) is exact-match only. Phonetic or orthographic similarity between names is explicitly allowed and is not grounds for audit failure — koda (engine), koru (product) and kora (product) may all coexist. The "distinctive" notes in the registry rationale column and in policies/naming-aliases.kmd are advisory branding guidance, never a hard gate.

Por que R4.1/R4.2/R4.3 morreram (owner, 2026-07-16). A regra decidia o prefixo pela inicial do bare — K isenta, não-K prefixa. Medido nos 186 componentes contra as superfícies vivas, o registry obedecia 186/186… e a realidade discordava dele:

superfícieusa o slug?
DNS (81 hosts)0 de 81 — todos servem <bare>.koder.dev, nenhum koder-x.koder.dev existe
apt (22 pacotes)14 koder-x, 3 k<bare>, 5 bare — três formas para uma regra
API do Hub (17)15 slug, 2 k-form — e divergia do apt em khubkoder-hub, ktermkoder-term

A inicial K era um proxy: nomes K aqui são inventados (não colidem), nomes não-K tendem a ser palavras comuns (colidem). O proxy correlaciona com o risco real — colisão em namespace compartilhado — mas não é ele, e erra nas pontas: mediant não começa com K, não colide com nada (sources.debian.org: exact: null), e carregava o prefixo mais caro da Stack (host inexistente, canonical quebrada). Enquanto isso hub e talk são pacotes Debian reais — o risco existe, mas só onde o namespace não é nosso.

Então o prefixo saiu do slug e foi para onde o risco vive: o package (R7-bis). O .koder.dev e o path do monorepo já são namespaces Koder — prefixá-los é gaguejar (koder-hub.koder.dev).

Verificado antes de ratificar: com slug = bare, 0 colisões de unicidade (T4) entre os 186 — o prefixo não estava desambiguando nada dentro do registry.

R4-bis — package is required: the name in namespaces that are NOT ours

  • R4b.1package = koder-<slug>, for every type, with no exception (e.g. koder-flow, koder-hub, koder-koda, koder-kdb, koder-kvs). This is the Package: of the .deb/.kpkg, what apt install resolves, and what the Hub's catalog API must serve under the same name it ships.
  • R4b.2 — A component that publishes no package still declares it. The name is derivable, and a field that only exists for publishers is a field nobody fills until the day it is needed.
  • R4b.3package matches ^koder-[a-z]+(-[a-z0-9]+)*$.

Por que package existe como forma própria (owner, 2026-07-16). O slug servia quatro senhores — "repo, path segment, package id, og:url host, subdomain" — e essas superfícies têm propriedades de colisão opostas:

  • apt é compartilhado com o mundo: hub e talk já são pacotes Debian reais (hub = o CLI do GitHub, v2.14.2). Publicar hub colidiria. O prefixo aqui compra desambiguação real.
  • .koder.dev e o path do monorepo já são nossos: koder-hub.koder.dev lê "koder hub koder dev". O prefixo aqui não compra nada e gagueja.

Um campo não consegue estar certo nos dois. Antes desta separação a divergência não era nem declarável: o registry dizia koder-hub, o apt servia khub, e nenhuma linha da spec estava errada — porque não havia onde escrever a diferença. Medido: khubkoder-hub e ktermkoder-term no ar, o catálogo 404ando o nome que o apt de fato instala.

koder-koda, koder-kvs, koder-kbox gaguejam o K, e é aceito. O owner ratificou a regra "sem exceções" sabendo disto e sabendo que reverte a direção do rename koder-kruzekruze (2026-07-12): a exceção custa mais que a gagueira. Uma regra com ramo por inicial foi o que produziu, em 186 componentes, três formas de nome no apt e um DNS que ignorava o slug 81 a 0.

R5 — path is required and must exist

  • R5.1 — Absolute from monorepo root (e.g. services/foundation/flow).
  • R5.2 — Path exists at audit time (CI verifies), unless the entry is status: planned (R5.4).
  • R5.3 — Derived from the L1/L2/L3 taxonomy (meta/docs/stack/taxonomy.md).
  • R5.4 — status (optional) reserves a planned name. An entry MAY carry an optional status form, expressed as an 8th registry column. Values:
    • active (default; also the meaning of an absent/empty cell) — a shipping component; path must exist (R5.2 / T8).
    • planned — a name reserved for a component that is part of a ratified architecture (e.g. an RFC-prescribed suite member) but not yet coded. A planned entry is exempt from T8 (its path legitimately does not exist yet) but still subject to every other rule (unique display/slug/aliases, prefix rules, regexes). When the component is built, drop the status cell (it becomes active) and T8 then enforces the path. This prevents the anti-pattern of either (a) deleting a real, ratified name reservation just to pass an incomplete schema, or (b) registering a fake path. Sections without any planned member keep the 7-column layout; only a section that has one adds the status column. Example: services/crypto/cipher (Krypt-suite member, krypt-RFC #060) reserved before code exists.

R6 — aliases is required (may be [])

  • R6.1 — Array of strings, each globally unique across the entire registry (including cross-component, cross-type).
  • R6.2 — Maximum 3 aliases per component.
  • R6.3 — Each alias matches ^[a-z][a-z0-9]*$ (compact only, no hyphens — alias is the compact form by design).
  • R6.4 — Each alias requires a 1-line aliases_reason in the registry entry.
  • R6.5type = umbrella defaults to aliases: [] and never associates a binary/CLI. Aliases on umbrella entries are advisory only (for oral/written reference).
  • R6.6 — Aliases have no rigid prefix rule. Convention is k<bare> compacted (kflow, kdrafts) when the bare name is a common English word, but flux, flowy etc. are valid if registered. See policies/naming-aliases.kmd for when to create an alias.

3. Test cases (run by koder-spec-audit naming)

#CheckSeverity
T1Every registry entry has type, display, bare, slug, package, path. aliases field exists (may be []).hard
T2type matches the enum (R1).hard
T3display is unique across the registry.hard
T4slug is unique across the registry.hard
T5Each alias is unique across the registry (cross-component, cross-type).hard
T6slug matches ^[a-z]+(-[a-z0-9]+)*$ (R4.4).hard
T7Each alias matches ^[a-z][a-z0-9]*$ (R6.3).hard
T8path exists in the monorepo (R5.2), unless status: planned (R5.4).hard
T9display == Koder <Bare> for EVERY type — no per-type exception (R2.1).hard
T10slug == <bare-lowercase> for EVERY type, and carries no koder- prefix (R4.1).hard
T10bpackage == koder-<slug> for EVERY entry (R4b.1, R4b.3).hard
T11len(aliases) ≤ 3 (R6.2). Each alias has a non-empty aliases_reason (R6.4).hard

All severity is hard. Failure blocks PR merge via .gitea/workflows/audit-naming.yml.


4. Relationship to other specs

  • specs/binaries-and-cli/naming.kmd consumes this spec. The binary name for a component is aliases[0] if present, else slug. Updates §1, §3, §11 to reflect the new derivation.
  • specs/naming/brand-score.kmd is orthogonal: it scores candidate names for branding fitness; this spec governs how an already-chosen name appears in its 6 forms.
  • docs/stack/taxonomy.md defines where the component lives (L1/L2/L3); this spec defines how to name it.
  • docs/stack/vocabulary.md defines the conceptual category (componente/documento/módulo/artefato/variante); this spec defines the surface forms.

5. Examples — full registry entries

product / bare-non-K

type: product
display: Koder Hub
bare: Hub
slug: koder-hub
path: products/dev/hub
aliases: [khub]
aliases_reason:
  khub: "'hub' is a common English word; CLI binary already named khub"

engine / bare-K

type: engine
display: Koder Koda
bare: Koda
slug: koda
path: engines/lang/koda
aliases: []
aliases_reason: {}

meta

type: meta
display: Drafts
bare: Drafts
slug: drafts
path: meta/context/drafts
aliases: [kdrafts]
aliases_reason:
  kdrafts: "'drafts' is a common English word; k-prefix signals meta-Koder context"

umbrella

type: umbrella
display: Kdb
bare: Kdb
slug: kdb
path: infra/data/kdb
aliases: []
aliases_reason: {}
note: "Groups kdb-kv, kdb-ts, kdb-vec, kdb-search, kdb-stream"

6. Migration

The current specs/binaries-and-cli/naming.kmd §1 rule (binary = k<slug>) is superseded by this spec. New derivation: binary = aliases[0] || slug. See specs/binaries-and-cli/naming.kmd for the updated derivation rules.

Existing components keep their current binary names. The registry's seed entries reflect the current state — no renames forced by this spec.


7. Form per surface — which form appears where

§1's "Where it's used" column is coarse. This section is the normative, exhaustive mapping from rendering surface to form. It binds the surfaces that the consuming specs left only structurally described ("nome do produto") to a concrete form, closing the bare-vs-display ambiguity. Consuming specs (landing-pages/products.kmd, landing-pages/packages.kmd, desktop-apps/title-bar.kmd, koder-app/behaviors.kmd) reference this section instead of re-stating the rule.

Surface → form

SurfaceSpec that renders itFormExample (Mediant)
Landing <title>, OG/Twitter title + image:altlanding-pages/products.kmd §HeaddisplayKoder Mediant — …
Landing navbar .nav-brand textlanding-pages/products.kmd §NavbardisplayKoder Mediant
Landing hero <h1>landing-pages/products.kmd §Herobare (or distinctive brand form) — falls under R7.2: navbar + <title> anchor display on the same pageMeet Mediant / Mediant (also Koder Mediant)
Desktop window title bardesktop-apps/title-bar.kmd; koder-app §7.3displayKoder Mediant
Application-menu / launcher tile (.desktop Name=)binaries-and-cli/naming.kmd §1.0bare — R7.2: a launcher grid is the densest repetition there is; every tile is a Koder tile, so Koder carries no information and pushes the distinguishing word off the label. The icon carries the attribution.Mediant
In-app primary wordmark / brand headerkoder-app/behaviors.kmd §7displayKoder Mediant
Hub package-page heading + OG name (happy path)landing-pages/packages.kmddisplay (catalog name)Koder Mediant
Hub OG fallback title (catalog data-integrity failure only)landing-pages/packages.kmd §2.4slug — Koder Hubmediant — Koder Hub
Dense in-app chrome where display already on-screen: breadcrumb, tab label, compact secondary header, in-product nav itemper-appbareMediant
Prose docs / conversation where Koder context is impliedbareMediant
Repo, path segment, og:url host, subdomainslugmediantmediant.koder.dev
Package id (.deb/.kpkg Package:), apt, Hub catalog APIR4-bispackagekoder-mediant
Namespace de métrica (prefixo de exposição)R7.6slug, -_mediant_…, kdb_…
CLI binary, D-Bus name, deep-link scheme, IPC addressbinaries-and-cli/naming.kmd; koder-app §8alias || slugkmediant

Rules

  • R7.1 — display is the identity-anchor form. The surfaces that anchor the product's identity — page <title>, OG/Twitter title, navbar/.nav-brand, desktop window title bar, in-app primary wordmark, Hub package-page heading / catalog namemust use display (Koder <Bare>). These are the surfaces that establish which product this is; they are never bare, slug, or alias.

  • R7.2 — bare/distinctive is for surfaces where display is already anchored on-screen. Once R7.1 puts display on the page/screen, a secondary surface may use bare (<Bare>) — or a deliberate distinctive brand form — as expression. This covers (a) the landing hero <h1>, whose fleet convention is the Meet <Bare> template (e.g. Meet Jet, Meet Cron) or a distinctive lowercase brand (kicon, Kodix), since the navbar + <title> already anchor display on the same page; and (b) dense, repetitive in-app chrome where repeating "Koder" is noise — breadcrumbs, tab labels, compact secondary headers, in-product nav items; and (c) the application-menu / launcher tile, added 2026-07-12 — see below. display itself remains valid on the surfaces in (a) and (b) (e.g. a hero reading Koder Mediant is fine) — R7.2 permits bare there, it does not forbid display. The launcher tile is the exception: bare is required, per R7.5.

  • R7.5 — the launcher tile is bare, and it is derived from the REGISTRY. Searching "kode" in the GNOME menu used to surface five tiles reading Koder Kruze / Koder Term / Koder Dek / Koder Mosaic / Koder … — the word that distinguishes them came after the word that does not. In a launcher grid every tile is a Koder tile, so Koder carries no information and costs the label its first word; the icon is what carries the attribution.

    The tile takes the component's bare form from registries/stack-manifest.kmd. It is NOT TitleCase(slug). That was the original mechanism (kicon#025) and it does not work: the slug carries the koder- prefix or not for reasons that have nothing to do with this surface, so TitleCase(slug) yields Koder Term for kterm, Koder Kruze for kruze, Koder Icon for kicon — and Kode for kode, whose slug happens to be bare. The five tiles the rule was written to fix would have stayed exactly as they were. A rule derived from the wrong field does not become right by being enforced.

  • R7.3 — slug/aliases are never a human brand label. slug and aliases never appear as the visible name of the product in UI or marketing copy. Their surfaces are machine/address/CLI only (URLs, package ids, subdomains, binaries, IPC, deep links). The Hub OG fallback ({slug} — Koder Hub, packages.kmd §2.4) is the lone exception and fires only on a catalog data-integrity failure, never on the happy path.

  • R7.4 — REVOGADA (owner, 2026-07-16). Dizia que meta/umbrella não levam o prefixo Koder (R2.2), colapsando display == bare. A R2.1 agora vale para todo type sem exceção: Koder Kdb, Koder Drafts. Não há colapso — display e bare são formas distintas em toda entrada.

  • R7.6 — o namespace de métrica é o slug, com - virando _. Uma métrica exposta por um componente Koder começa por <slug com _>_: slug jetjet_requests_total; kdbkdb_rpc_total; mediantmediant_…. A troca de - por _ é mecânica — o formato de exposição do Prometheus não admite - em nome de métrica; nada mais do slug muda.

    Esta linha foi escrita duas vezes no mesmo dia, e a primeira estava certa (2026-07-16). Ao criar o package, eu a reapontei slugpackage por um raciocínio que soava bem: "um scraper Prometheus vê todos os alvos de uma vez, logo é namespace compartilhado, logo leva prefixo, como o apt". Errado, e a spec consumidora — observability/instrumentation-contract.kmd — já tinha a medição que derruba isso:

    • O prefixo compra desambiguação contra o que a gente REALMENTE raspa. O Prometheus é self-hosted (infra/observe/) e raspa componentes Koder mais exporters padrão (node_*, pg_*). Não existe um kdb_* de terceiro no alvo — o kdb+ da KX não é raspado por nós. O namespace é nosso, como o .koder.dev e o path do monorepo. É o caso do DNS, não o do apt.
    • Proveniência num scrape se responde pelo label job/target, nunca pelo prefixo do nome. Se o prefixo fosse a resposta, node_exporter seria prometheus_node_*.
    • Custo medido de repontar: kdb_* aparece 126× na árvore, kode_* 30×. Todas virariam koder_kdb_*/koder_kode_* — dashboard e alerta quebrados em massa, por zero ganho de desambiguação.
    • Já foi tentado e registrado como erro. O instrumentation-contract dizia koder_<service>_ e exigia koder_kdb_* de um componente cujo slug é kdb; o resultado foi um segundo sistema de identidade, paralelo ao registry, e 86 das 125 "violações" eram conformes o tempo todo. Repontar pro package reintroduziria exatamente esse erro — desta vez com a spec o abençoando.

    A regra que decide continua a mesma: prefixo onde o namespace não é nosso. O apt é o único que satisfaz isso hoje. Métrica não é.

    Por que esta linha existe. A §7 se diz exaustiva e nunca ligou esta superfície a forma nenhuma — então o instrumentation-contract.kmd fez o que o preâmbulo desta seção manda não fazer: re-enunciou a regra por conta própria. Ligar a superfície ao slug não afrouxa nada: aperta, porque o prefixo deixa de ser string livre e passa a ser um slug registrado, sujeito a T1–T11.

References