Amended by 0264: A
module key is one snake_case segment (decision 1), and model keys are snake_case (decision 2).
Dotted module keys are not used. Token constants for interface ports and
defineConfig values
take the type’s name (0263 decision 3); SCREAMING_SNAKE applies to other module-level constants.Context
Casing was decided piece by piece: files in ADR 0006, code shapes in 0128, permission keys in 0132, event names in 0141, machine codes in 0137, public ids in 0135. On 2026-10-20 (night) the owner agreed one convention table, recorded in the decision file as “Naming convention FINAL”. This ADR is that table.Decision
- Code style (TypeScript). Whole words; acronyms are cased as words (
userId,ApiClient,loadHttpUrl). NoIprefix on interfaces. Abbreviations are limited to an allow-list:id,url,api,db,i18n,ui. Booleans readis,hasorcan. Event handlers areonorhandle. Hooks start withuse. Module-level constants are SCREAMING_SNAKE. - The table.
- Namespace lookups use
::. Content owned by a module is looked up with<module>::<path>: i18n keys, assets (ADR 0234), templates and stubs. Wire identifiers keep dots: permissions, events, action keys and DI token keys. - Environment prefixes.
- Public values (never secrets; a lint and a validator check enforce this):
VITE_,EXPO_PUBLIC_,RENDERER_VITE_. - Server values:
STACKRA_,FIGENTRA_,MAIN_VITE_. - Secrets are named
<PROVIDER>_<PURPOSE>, for exampleSTRIPE_SECRET_KEY. - Reserved, owned by tools:
ENCORE_,TURBO_,NODE_,CI,GITHUB_,INFISICAL_,EAS_.
- Public values (never secrets; a lint and a validator check enforce this):
- DI tokens. A token is a constant made with
defineToken, with a dotted key of the form<module>.<TypeName>:export const INVOICE_REPOSITORY = defineToken<InvoiceRepository>("billing.InvoiceRepository"). Code injects the constant. String injection is banned. A concrete@Injectableclass is its own token.
Consequences
- One table answers a naming question. The always-on rule carries the same table, so agents see it in every session.
- The secret example in
.agents/rules/secrets.mdnow usesSTRIPE_SECRET_KEY, which matches decision 4. - Text elsewhere that uses another form is stale and is fixed when it is next touched.
Enforcement
planned — the oxlintunicorn/filename-case rule (kebab-case files), the house lint rules of 0129
for code style and DI tokens, the public-prefix lint for VITE_, EXPO_PUBLIC_ and RENDERER_VITE_, and the validator checks for machine codes,
event names, permission keys and namespace lookups.
Alternatives
- Acronyms in full caps (
userID,loadHTTPURL): rejected; acronyms are cased as words. - Dots for every identifier, including asset and i18n keys: rejected;
::separates the module from its content.
Supersedes and sources
Supersedes: none. Restates the forms of 0132, 0141, 0137 and 0135. Sources: the owner’s “Naming convention FINAL” (stackra-decisions-2026-10-19.md, end of file, 2026-10-20); A10 (L627); C27 and the module-key confirmation (L855, L930); ADR 0006; ADR 0128; the
code-conventions proposal (stackra-notes/code-conventions-proposal.md, section 2).