Source: ddx-documentation/naming-constitution.md
The one naming standard for every layer of the workspace (owner order 2026-10-24: naming and layer standardization is P0). It describes the TARGET. Today's outliers are grandfathered in the exception registry (§9) and fixed by the rename waves; nothing is renamed by this page. Gate: `./ddx-manage.sh naming` (`scripts/check-naming.mjs` + `scripts/naming.registry.json`). Every rule id `N-*` below is one registry row. Source of the measured drift: `findings/template-blueprint/laneH-naming-inventory.md`, `laneI-ddx-web-code-read.md`. ## 0 Principles 1. **One concept = one word.** The glossary (`CONTEXT.md`) picks the word; §2 picks the one code symbol for it. The glossary wins: when glossary and code differ, both change in the same task. 2. **The name says the domain, the layer and the file.** A reader who sees a symbol in a log, a stack trace or an import knows where it lives with no search. 3. **Acronyms are first-letter-cap words** in every PascalCase or camelCase name: `Bff`, `Sse`, `Api`, `Ddx`, `Jwt`, `Url`, `Id` (`BffApiBridge`, `SseManager`, `DdxDebugRegistry`, never `BFFClient`, `SSEInspectorStore`, `DDXDebugRegistry`). Constants stay SCREAMING_SNAKE (`SSE_EVENT_TYPES`, `PLATFORM_ROLES`). Rule `N-ACRONYM`. 4. **File case follows the export.** A React component file is `PascalCase.tsx` named after its one exported component. Every other file is kebab-case (`bff-node-pool.server.ts`, `settings.service.ts`). A hook file is `use-<kebab>.ts(x)` and exports `use<Pascal>`. 5. **One home per kind.** No twin folders for one concern (`query`/`queries`, `stream`/`sse`, `lib/hooks`/`src/hooks`, `logger/`+`logger.ts`). Rule `N-TWIN-FOLDER`. 6. **Generic words only.** This is a skeleton, not a vertical. HMS words (doctor, nurse, receptionist, patient, clinic, appointment, record) are replaced by the generic names in §2.4. 7. **No new name without a row.** A new layer, prefix, suffix or key family is added to this page and to the registry in the same change. ## 1 UI domains Five domains, no sixth. A domain is at once a route group, a component folder, an inventory-id prefix and an api counterpart. The prefix rule applies to ddx-web UI names only. ### Platform The super-admin and workspace-tooling surface (cluster, platform roles, docs, gallery, design system). It is not one org. - Route group: `app/[locale]/(app)/docs/**`, `app/[locale]/(app)/gallery/**` and every future platform-admin route under `app/[locale]/(app)/platform/**`. - Component folder: `ddx-web/src/components/platform/`. - Inventory id: `Platform<Name>` (`PlatformClusterTable`). - Api counterpart: `/platform/*` and `/cluster/*` routes, guarded by `PlatformRoleGuard`; symbols `Platform*`, `Cluster*`, `ApiNode*`. The ddx-api infra folder is `src/core/`, not this domain (§3.1). ### Site The public frame: marketing pages and the auth flow. - Route group: `app/[locale]/(marketing)/**`, `app/[locale]/(auth)/auth/**`. - Component folder: `ddx-web/src/components/site/`. - Inventory id: `Site<Name>` (`SiteShell`, `SitePageCard`, `SiteAuthFrame`). - Api counterpart: public routes and `auth/*` (`AuthController`); contracts `auth.ts`. ### Portal The signed-in frame of one org. - Route group: `app/[locale]/(app)/portal/[orgSlug]/[role]/**` (the web param is `[orgSlug]`, the same word as the api `:orgSlug`). - Component folder: `ddx-web/src/components/portal/`. - Inventory id: `Portal<Name>` (`PortalShell`, `PortalPageList`, `PortalActionBar`). - Api counterpart: `orgs/:orgSlug/*` controllers behind `OrgSlugGuard`. ### Module A pluggable feature slot inside the portal (none exists today). - Route group: `app/[locale]/(app)/portal/[orgSlug]/[role]/modules/<moduleId>/**`. - Component folder: `ddx-web/src/components/module/<moduleId>/`. - Inventory id: `Module<Name>` — `Module` is a PREFIX for ddx-web UI names. NestJS keeps its `*Module` SUFFIX (`SettingsModule`, `ClusterModule`); a ddx-web file never ends in `Module.tsx`. - Api counterpart: `orgs/:orgSlug/modules/<moduleId>/*`; settings scope `module.<moduleId>` (§6.4). ### Shared Cross-domain inventory families and primitives (tables, forms, chat, media, feedback, layout). - Route group: none — Shared code has no route. - Component folder: `ddx-web/src/components/{primitives,feedback,forms,data,media,audio,voice, call,chat,entity,layout,families,providers,consent,realtime,settings,navigation}/`. - Inventory id: the family noun with NO domain prefix (`DataTable`, `EntityForm`, `ChatThread`). - Api counterpart: none of its own; it renders kernel data through `@ddx/contracts` (`kernel*`, `descriptors/*`). Rule `N-DOMAIN`: a component under `components/<domain>/` starts with `<Domain>`; a Shared component carries no domain prefix. Rule `N-ENTRY-ID`: inventory ids match `^(Platform|Site|Portal|Module)?[A-Z][A-Za-z0-9]*$`; helpers and hooks are never registered as entries. ## 2 Glossary → symbol map One glossary term = one code symbol. "Today" lists the live drift that a rename wave removes. ### 2.1 Shells, inventory, tenancy, access | Glossary term | Symbol (target) | Today | |---|---|---| | UI domain | `Platform` · `Site` · `Portal` · `Module` · Shared (no prefix) | `Platform*`, `Module*` 0 UI symbols | | SiteShell | `SiteShell` | ok | | PortalShell | `PortalShell` | ok | | SitePage family | `SitePage<Kind>` (`SitePageCard`) | `families/Page*` | | PortalPage variant | `PortalPage<Kind>` (`PortalPageList`) | ok | | Inventory | `ddx-web/src/inventory/` | ok | | Inventory entry | type `InventoryEntry`, factory `defineEntry` | `RegistryEntry` | | Inventory family | type `InventoryFamily` | none | | Instantiation props | key `instantiation`, type `InstantiationProps` | ok | | Override props | key `overrides`, type `OverrideProps` | ok | | Org | `Org` (model, type, descriptor `org.ts`) | `Organization` (better-auth tables keep it) | | Org slug | `orgSlug` (web `[orgSlug]`, api `:orgSlug`) | ok | | Role segment | type `PortalRole`, const `PORTAL_ROLES` | ok (values: §2.4) | | Platform role | `PlatformRole`, `PLATFORM_ROLES`, `PlatformRoleGuard` | ok | | BFF | prefix `Bff`, `BffApiBridge` | `BFF*` 1 | | Credential type | `CredentialType` | ok | | JWT | `JwtCredentialStrategy` (`jwt-credential.strategy.ts`) | ok | | Service key | `ServiceKeyCredentialStrategy` (`service-key-credential.strategy.ts`) | ok | | User key | `UserKeyCredentialStrategy` (`user-key-credential.strategy.ts`) | ok | ### 2.2 Settings, kernel | Glossary term | Symbol (target) | Today | |---|---|---| | UnifiedSettings facade | `UnifiedSettings` (api const `UnifiedSettings` + type `UnifiedSettingsResolver` in `settings.resolver.ts`, served by `SettingsService.getEffective`; web `useUnifiedSettings`) | api ok; web `pickSetting` | | Setting registry | `SETTING_REGISTRY`, type `SettingRegistry` | ok | | Setting tier | `SettingTier` | ok | | Effective setting | `EffectiveSetting` | `ResolvedSetting*` | | Entity kernel | prefix `Kernel`, folder `kernel/` | ok | | Entity descriptor | `EntityDescriptor`, const `<name>Entity` | ok | | Projection | `Projection`, `ProjectionService` | ok | | Access grant | `AccessGrant`, `AccessPolicyService` | ok | | Proof chain | `ProofChain`, `ProofChainService` | ok | | Attachment type | `AttachmentType`, `AttachmentTypeId` | ok | | Capability flag | `CapabilityFlag` | none | `Setting*` names one key or tier (`SettingKey`, `SettingTier`); `Settings*` names the whole set (`UnifiedSettings`, `SettingsModule`). One read verb: `get` (`getEffective` → `UnifiedSettings.get`). ### 2.3 SSE bus, webhooks, cluster | Glossary term | Symbol (target) | Today | |---|---|---| | SSE Bus | `SseBus`, api `SseBusService` (`sse-bus.service.ts`) | api ok | | Channel | `SseChannel` | ok | | Leader tab | `LeaderTab` | `LeaderElection` | | Bridge | suffix `Bridge` in `ddx-api/src/sse/bridges/` | ok | | Webhook source | `WebhookSource`, `DDX_API_WEBHOOK_SOURCES` | ok | | Dead-letter | `WebhookDeadLetter` | none | | Cluster node | `ClusterNode` (web or api); api half `ApiNode*` | `ClusterNodeTokenGuard` only | ### 2.4 Generic words (HMS words removed) | HMS word | Generic word | Where | |---|---|---| | doctor, nurse | `staff` | org role, portal role segment | | receptionist | `member` | org role, portal role segment | | patient | `client` | org role, portal role segment | | partner | `partner` (kept) | portal role segment | | public | `public` (kept) | portal role segment | | patients | `clients` | permission resource | | appointments | `bookings` | permission resource, demo entity | | records | `items` | permission resource, demo entity | | clinic | `org` | everywhere | Final role set: org roles `org-admin · staff · member · client`; portal role segments `public · staff · member · client · partner`. ## 3 ddx-api ### 3.1 Folders - One feature folder per concern under `ddx-api/src/`, kebab, plural only for a collection noun (`members`, `entities`). Infra lives in `src/core/` (`common`, `health`, `logging`, `prisma`, `redis`). - The `Platform` word in ddx-api means the platform domain only (§1). ### 3.2 Files and classes - Rule `N-API-FILE`: `<kebab>.<role>.ts`, role ∈ `service controller module guard interceptor decorator schemas middleware filter mapper resolver bridge outputter processor strategy factory`. A helper with no role is plain `<kebab>.ts`. Specs: `<file>.spec.ts`; spec helpers `<name>.spec-helper.ts`. - Rule `N-CLASS-FILE`: the exported class = PascalCase(stem) + Role (`settings.service.ts` → `SettingsService`, `jwt-credential.strategy.ts` → `JwtCredentialStrategy`). - Errors: class suffix `Error` only (`NotFoundError`, never `NotFoundAppError` or `*Failure`); `code` is UPPER_SNAKE. ### 3.3 Routes, permissions, logs - No global prefix in ddx-api; `/api/v1` exists only in the ddx-web BFF. Org routes: `orgs/:orgSlug/<plural-noun>`; a mass noun stays singular (`settings`, `media`, `presence`). - Permission scope `<resource>:<action>`; resource plural kebab (`api-keys`, `clients`). - Log scope `api:<layer>` (`api:cluster`, `api:node-auth`, `api:settings`). ### 3.4 Cluster layer (folded from `plans/bff-layered/10-naming.md`) | Side | Prefix | Means | |---|---|---| | ddx-api, per node | `ApiNode` | this ddx-api process: identity, heartbeat, drain, node auth | | ddx-api, cluster-wide | `Cluster` | registry, lifecycle, admission, readiness of all nodes | | ddx-api, BFF contract | `Api` | api half of a BFF contract (`ApiAuthzHeaderInterceptor`) | `ddx-api/src/cluster/`: file names `cluster-*` or `api-node-*`, exported class prefix `Cluster` or `ApiNode` (`cluster.module.ts`, `cluster-registry.service.ts`, `cluster-lifecycle.ts`, `cluster-admission.service.ts`, `cluster-admin.controller.ts`, `cluster-readiness.service.ts`, `api-node-identity.service.ts`, `api-node-heartbeat.service.ts`, `api-node-auth.middleware.ts`, `api-node-drain.service.ts`). Log event codes UPPER_SNAKE with the side prefix (`API_NODE_DRAINING`, `CLUSTER_NODE_ADMITTED`). Gated by `scripts/check-bff-layers.mjs` (BL1-BL6, `./ddx-manage.sh bff-layers`). ## 4 ddx-web ### 4.1 Files - Components `PascalCase.tsx`; everything else kebab. Private route folders `_<kebab>` (`_islands`, `_lib`); an `app/` file that is a component is still `PascalCase.tsx`. - shadcn primitives keep their upstream lowercase file names (one listed exception family). - Server-only modules end in `.server.ts` and start with `import 'server-only'` — in every folder, not only `lib/bff`. - Rule `N-TEST`: specs are `*.spec.ts(x)`, never `*.test.ts(x)`. ### 4.2 Hooks - Rule `N-WEB-HOOK`: hook files match `^use-[a-z0-9-]+\.tsx?$`. Two homes only: `ddx-web/src/hooks/` (shared) and `<feature>/hooks/` (feature-local). ### 4.3 lib folders, i18n - One folder per concern under `ddx-web/src/lib/`, kebab; no loose files beside a folder of the same name. Transport is `lib/bff/` (server) and `src/services/` (browser clients); `lib/api/` holds only the thin adapters named in §4.4. - Rule `N-I18N-NS`: namespace file `messages/<locale>/<kebab>.json`. Rule `N-I18N-KEY`: each key segment is camelCase `^[a-z][a-zA-Z0-9]*$`. The 4 locales change together. - Rule `N-TOKEN`: CSS custom properties use `--token-*`; values come from vendored `dudoxx.css`. ### 4.4 BFF layer (folded from `plans/bff-layered/10-naming.md`) Prefix `Bff` = code that runs inside a ddx-web node and talks to ddx-api. One folder per layer, `ddx-web/src/lib/bff/<layer>/`, file = kebab form of the one exported symbol: `bff-<kebab>.server.ts` (spec `bff-<kebab>.spec.ts`), and every file starts with `import 'server-only'`. | Layer | Symbol | Layer | Symbol | |---|---|---|---| | `bridge/` | `BffApiBridge` | `routes/` | `BffRouteTable` | | `pool/` | `BffNodePool`, `BffCircuitBreaker` | `headers/` | `BffHeaderPolicy` | | `stream/` | `BffStreamRelay` | `origin/` | `BffOriginGate` | | `log/` | `BffCallLog` | `session/` | `BffSessionToken` | | `authz/` | `BffAuthzEcho` | `errors/` | `BffErrorMap` | | `registry/` | `BffNodeRegistry` | `rate/` | `BffRateLimit` | | `identity/` | `BffNodeIdentity` | `cache/` | `BffCacheRevalidate` | `lib/api/forward.server.ts` and `lib/api/server.ts` stay thin adapters; no new BFF logic lands in `lib/api/`. Log scopes `bff:<layer>`; log event codes `BFF_*`; metrics `ddx_<side>_<noun>_<unit>`; headers `x-ddx-<noun>`; cookies `ddx_bff_<noun>`. The allow-lists live in `packages/ddx-contracts/src/bff-bridge.ts`. Gated by `scripts/check-bff-layers.mjs`. ## 5 Contracts (`packages/ddx-contracts`) - Files kebab under `src/`, subpath exports, no barrel. A family shares a file prefix (`kernel-*`, `sse-*`, `bff-*`, `settings-*`); one concern = one file (no `members.ts` beside `membership.ts`). - Zod schemas `<Pascal>Schema`; constants SCREAMING_SNAKE; types PascalCase; descriptors `descriptors/<name>.ts` exporting `<name>Entity`. No `Dto` suffix (the glossary bans DTO). - Shared contracts carry no side prefix (`NodeId`, `NodeLifecycle`, `ClusterEnvSchema`). ## 6 Data ### 6.1 Prisma - One database per client: DB `ddx_<db>` = folder `ddx-api/prisma/<db>` = DI token `PRISMA_<DB>` = service `prisma-<db>.service.ts` = env `DATABASE_URL_<DB>`. - Models PascalCase singular; tables `@@map` snake singular; columns camelCase. better-auth owned tables keep the names better-auth needs (listed exceptions). ### 6.2 Redis keys - Rule `N-REDIS`: `ddx:<area>:<noun>[:<id>]`, area ∈ `sse · webhooks · cluster`. No short forms (the short webhook prefix became `ddx:webhooks:`); ids carry no literal type word (`ddx:sse:log:<orgId>`). Cluster keys: `ddx:cluster:<clusterId>:<noun>[:<id>]`. Keys are built from contracts constants only. ### 6.3 SSE - Event type `<noun>.<verb>` lowercase dot (`entity.changed`, `settings.changed`); channel `<kind>:<orgId>[:<id>]`. Catalog: `ddx-documentation/sse-events.md`. ### 6.4 Settings keys - Key `<scope>.<group>.<key>`, scope ∈ `platform · site · portal · module.<moduleId>`; group and key camelCase (`portal.display.timeZone`, `module.<moduleId>.feature.enabled`). One key is defined once in `SETTING_REGISTRY`. ## 7 Infra - Rule `N-ENV`: dotenv keys `DDX_<APP>_<SCOPE>_<KEY>`, APP ∈ `WEB · API · BFF · CLUSTER · DEPLOY · SMOKE`. `DEPLOY` / `SMOKE` are process variables of `deploy/scripts/*.sh` (operator shell or target shell), never keys of an app `.env`; the names are listed in `ddx-documentation/deploy/README.md`. Allow-list (fixed names an upstream tool reads): `NODE_ENV`, `NEXT_RUNTIME`, `NEXT_PUBLIC_*`, `DATABASE_URL_<DB>`, `LOG_*` (enums in `@ddx/contracts/log-env`). Any other key is an exception row. One service = one key family (no `S3_*` beside `MINIO_*`); ddx-web holds no data-tier key. - Ports: never written here or in any other page — the one source is `ddx-documentation/ports.md`. - Docker: folder `ddx-docker-<svc>`, compose project and container `ddx-web-boot-<svc>`, volume `<svc>-data`. - Rule `N-SCRIPT`: gate scripts are `check-<noun>.mjs` (root `scripts/` for repo gates). - Manage verbs: one lowercase noun or kebab noun per gate/area (`kernel`, `bff-layers`, `naming`, `i18n`); the shell function is `cmd_<verb_snake>`. ## 8 Docs and source header - ADR files: rule `N-ADR` `^\d{4}-[a-z0-9-]+\.md$`. Plan and findings folders kebab (`plans/<topic>/`, `findings/<topic>/`); task files `task_NNN.json`. - Rule `N-HEADER`: every source file starts with the © line and the `@ddx` block. Shape (from the harness source-file header contract, required tags only): ```ts // © Dudoxx UG / Acceleate Consulting - Walid Boudabbous <walid@acceleate.com> /** * @ddx service * @domain settings * @purpose Resolve the effective value of one setting key for the caller. * @status reusable */ ``` - Required: `@ddx` (kind), `@domain` (one slug — the ddx-api feature folder or the ddx-web feature), `@purpose` (one sentence), `@status`. Optional: `@topic`, `@props`, `@route`, `@duplicates`, `@depends-on`, `@used-in`. No other tag exists; the UI domain is carried by the path and the prefix, never by a tag. - Scope (`N-HEADER` and `N-TAG`): every code file, not only `src/` — `{ddx-web,ddx-api,packages/ddx-contracts}/src`, the `scripts/`, `eslint-rules/`, `prisma/`, `test/`, `vitest.stubs/` folders and root configs of both apps, root `scripts/`, `deploy/`, `ddx-manage.sh`. `.sh` files use `# @ddx …` lines; a shebang or `// @ts-check` stays line 1. `scripts/fixtures/` is out of scope. - Rule `N-TAG` (port of the harness `ddx-write-lane` header verify and the HMS `tag-inventory --verify`): the four required tags sit before the first statement (a `'use client'`/`'use server'` directive may precede them; `import 'server-only'` may not), each value comes from the lists below (registry row `N-TAG`, the one source), `@domain` is one slug, and `@purpose` never restates the file name. - `@ddx`: `component page layout hook util helper client service provider type schema model config constants registry i18n test test-util spec script task controller module dto guard interceptor decorator pipe filter middleware resolver repository factory strategy subscriber scheduler processor job probe metric check tool mapper router contract` (lint rules = `check`). - `@status`: `reusable feature-local page foundation` (reuse) · `production-ready active development-only legacy deprecated` (lifecycle). Retired: `production`, `spec-support`. - `@domain`: `api auth bff cluster config data deploy entities framework i18n infra integration kernel media naming observability platform realtime settings shell tenancy ui`. A new domain is added to the registry row and this list in the same change. - Measured 2026-10-26 (`./ddx-manage.sh naming`): 0 of 1020 code files fail `N-HEADER` or `N-TAG`. ## 9 Gates and exceptions Gate: `./ddx-manage.sh naming` runs `scripts/check-naming.mjs` over `scripts/naming.registry.json` and fails with `file:line rule-id`. It also runs inside `./ddx-manage.sh lint`. | Rule id | Chapter | Rule id | Chapter | |---|---|---|---| | `N-ACRONYM` | §0.3 | `N-I18N-KEY` | §4.3 | | `N-TWIN-FOLDER` | §0.5 | `N-TOKEN` | §4.3 | | `N-DOMAIN` | §1 | `N-REDIS` | §6.2 | | `N-ENTRY-ID` | §1 | `N-ENV` | §7 | | `N-API-FILE` | §3.2 | `N-SCRIPT` | §7 | | `N-CLASS-FILE` | §3.2 | `N-ADR` | §8 | | `N-TEST` | §4.1 | `N-HEADER` | §8 | | `N-WEB-HOOK` | §4.2 | `N-I18N-NS` | §4.3 | | `N-TAG` | §8 | | | Exceptions: - An exception is a row in its rule's `exceptions` array (the rule is the parent, so the row has no `rule` field) with `path`, `reason` and `expires` (default `2026-12-31`). A row without both `reason` and `expires` fails the gate. - An expired row fails the gate; it is fixed or re-justified, never silently extended. - Target: at most 25 grouped exception rows. Today's outliers (15 camelCase hook files, 14 unprefixed `components/portal` files, 9 all-caps acronym exports) are grandfathered there until their rename wave. - Listed exception families: shadcn primitive file names; better-auth owned model and table names; upstream fixed env keys outside the §7 allow-list.
The one naming standard for every layer of the workspace (owner order 2026-10-24: naming and
layer standardization is P0). It describes the TARGET. Today's outliers are grandfathered in
the exception registry (§9) and fixed by the rename waves; nothing is renamed by this page.
Gate: ./ddx-manage.sh naming (scripts/check-naming.mjs + scripts/naming.registry.json).
Every rule id N-* below is one registry row. Source of the measured drift:
findings/template-blueprint/laneH-naming-inventory.md, laneI-ddx-web-code-read.md.
CONTEXT.md) picks the word; §2 picks the one
code symbol for it. The glossary wins: when glossary and code differ, both change in the
same task.Bff,
Sse, Api, Ddx, Jwt, Url, Id (BffApiBridge, SseManager, DdxDebugRegistry,
never BFFClient, SSEInspectorStore, DDXDebugRegistry). Constants stay SCREAMING_SNAKE
(SSE_EVENT_TYPES, PLATFORM_ROLES). Rule N-ACRONYM.PascalCase.tsx named after its
one exported component. Every other file is kebab-case (bff-node-pool.server.ts,
settings.service.ts). A hook file is use-<kebab>.ts(x) and exports use<Pascal>.query/queries, stream/sse,
lib/hooks/src/hooks, logger/+logger.ts). Rule N-TWIN-FOLDER.Five domains, no sixth. A domain is at once a route group, a component folder, an inventory-id prefix and an api counterpart. The prefix rule applies to ddx-web UI names only.
The super-admin and workspace-tooling surface (cluster, platform roles, docs, gallery, design system). It is not one org.
app/[locale]/(app)/docs/**, app/[locale]/(app)/gallery/** and every future
platform-admin route under app/[locale]/(app)/platform/**.ddx-web/src/components/platform/.Platform<Name> (PlatformClusterTable)./platform/* and /cluster/* routes, guarded by PlatformRoleGuard; symbols
Platform*, Cluster*, ApiNode*. The ddx-api infra folder is src/core/, not this
domain (§3.1).The public frame: marketing pages and the auth flow.
app/[locale]/(marketing)/**, app/[locale]/(auth)/auth/**.ddx-web/src/components/site/.Site<Name> (SiteShell, SitePageCard, SiteAuthFrame).auth/* (AuthController); contracts auth.ts.The signed-in frame of one org.
app/[locale]/(app)/portal/[orgSlug]/[role]/** (the web param is [orgSlug],
the same word as the api :orgSlug).ddx-web/src/components/portal/.Portal<Name> (PortalShell, PortalPageList, PortalActionBar).orgs/:orgSlug/* controllers behind OrgSlugGuard.A pluggable feature slot inside the portal (none exists today).
app/[locale]/(app)/portal/[orgSlug]/[role]/modules/<moduleId>/**.ddx-web/src/components/module/<moduleId>/.Module<Name> — Module is a PREFIX for ddx-web UI names. NestJS keeps its
*Module SUFFIX (SettingsModule, ClusterModule); a ddx-web file never ends in Module.tsx.orgs/:orgSlug/modules/<moduleId>/*; settings scope module.<moduleId> (§6.4).Cross-domain inventory families and primitives (tables, forms, chat, media, feedback, layout).
ddx-web/src/components/{primitives,feedback,forms,data,media,audio,voice, call,chat,entity,layout,families,providers,consent,realtime,settings,navigation}/.DataTable, EntityForm, ChatThread).@ddx/contracts (kernel*,
descriptors/*).Rule N-DOMAIN: a component under components/<domain>/ starts with <Domain>; a Shared
component carries no domain prefix. Rule N-ENTRY-ID: inventory ids match
^(Platform|Site|Portal|Module)?[A-Z][A-Za-z0-9]*$; helpers and hooks are never registered as
entries.
One glossary term = one code symbol. "Today" lists the live drift that a rename wave removes.
Setting* names one key or tier (SettingKey, SettingTier); Settings* names the whole set
(UnifiedSettings, SettingsModule). One read verb: get (getEffective → UnifiedSettings.get).
Final role set: org roles org-admin · staff · member · client; portal role segments
public · staff · member · client · partner.
ddx-api/src/, kebab, plural only for a collection noun
(members, entities). Infra lives in src/core/ (common, health, logging, prisma,
redis).Platform word in ddx-api means the platform domain only (§1).N-API-FILE: <kebab>.<role>.ts, role ∈ service controller module guard interceptor decorator schemas middleware filter mapper resolver bridge outputter processor strategy factory. A helper with no role is plain <kebab>.ts. Specs: <file>.spec.ts; spec helpers
<name>.spec-helper.ts.N-CLASS-FILE: the exported class = PascalCase(stem) + Role
(settings.service.ts → SettingsService, jwt-credential.strategy.ts → JwtCredentialStrategy).Error only (NotFoundError, never NotFoundAppError or *Failure);
code is UPPER_SNAKE./api/v1 exists only in the ddx-web BFF. Org routes:
orgs/:orgSlug/<plural-noun>; a mass noun stays singular (settings, media, presence).<resource>:<action>; resource plural kebab (api-keys, clients).api:<layer> (api:cluster, api:node-auth, api:settings).plans/bff-layered/10-naming.md)ddx-api/src/cluster/: file names cluster-* or api-node-*, exported class prefix
Cluster or ApiNode (cluster.module.ts, cluster-registry.service.ts,
cluster-lifecycle.ts, cluster-admission.service.ts, cluster-admin.controller.ts,
cluster-readiness.service.ts, api-node-identity.service.ts, api-node-heartbeat.service.ts,
api-node-auth.middleware.ts, api-node-drain.service.ts). Log event codes UPPER_SNAKE with the
side prefix (API_NODE_DRAINING, CLUSTER_NODE_ADMITTED). Gated by
scripts/check-bff-layers.mjs (BL1-BL6, ./ddx-manage.sh bff-layers).
PascalCase.tsx; everything else kebab. Private route folders _<kebab>
(_islands, _lib); an app/ file that is a component is still PascalCase.tsx..server.ts and start with import 'server-only' — in every
folder, not only lib/bff.N-TEST: specs are *.spec.ts(x), never *.test.ts(x).N-WEB-HOOK: hook files match ^use-[a-z0-9-]+\.tsx?$. Two homes only:
ddx-web/src/hooks/ (shared) and <feature>/hooks/ (feature-local).ddx-web/src/lib/, kebab; no loose files beside a folder of the
same name. Transport is lib/bff/ (server) and src/services/ (browser clients); lib/api/
holds only the thin adapters named in §4.4.N-I18N-NS: namespace file messages/<locale>/<kebab>.json. Rule N-I18N-KEY: each key
segment is camelCase ^[a-z][a-zA-Z0-9]*$. The 4 locales change together.N-TOKEN: CSS custom properties use --token-*; values come from vendored dudoxx.css.plans/bff-layered/10-naming.md)Prefix Bff = code that runs inside a ddx-web node and talks to ddx-api. One folder per layer,
ddx-web/src/lib/bff/<layer>/, file = kebab form of the one exported symbol:
bff-<kebab>.server.ts (spec bff-<kebab>.spec.ts), and every file starts with
import 'server-only'.
lib/api/forward.server.ts and lib/api/server.ts stay thin adapters; no new BFF logic lands
in lib/api/. Log scopes bff:<layer>; log event codes BFF_*; metrics
ddx_<side>_<noun>_<unit>; headers x-ddx-<noun>; cookies ddx_bff_<noun>. The allow-lists
live in packages/ddx-contracts/src/bff-bridge.ts. Gated by scripts/check-bff-layers.mjs.
packages/ddx-contracts)src/, subpath exports, no barrel. A family shares a file prefix
(kernel-*, sse-*, bff-*, settings-*); one concern = one file (no members.ts beside
membership.ts).<Pascal>Schema; constants SCREAMING_SNAKE; types PascalCase; descriptors
descriptors/<name>.ts exporting <name>Entity. No Dto suffix (the glossary bans DTO).NodeId, NodeLifecycle, ClusterEnvSchema).ddx_<db> = folder ddx-api/prisma/<db> = DI token
PRISMA_<DB> = service prisma-<db>.service.ts = env DATABASE_URL_<DB>.@@map snake singular; columns camelCase. better-auth owned
tables keep the names better-auth needs (listed exceptions).N-REDIS: ddx:<area>:<noun>[:<id>], area ∈ sse · webhooks · cluster. No short forms
(the short webhook prefix became ddx:webhooks:); ids carry no literal type word (ddx:sse:log:<orgId>). Cluster
keys: ddx:cluster:<clusterId>:<noun>[:<id>]. Keys are built from contracts constants only.<noun>.<verb> lowercase dot (entity.changed, settings.changed); channel
<kind>:<orgId>[:<id>]. Catalog: ddx-documentation/sse-events.md.<scope>.<group>.<key>, scope ∈ platform · site · portal · module.<moduleId>; group and
key camelCase (portal.display.timeZone, module.<moduleId>.feature.enabled). One key is
defined once in SETTING_REGISTRY.N-ENV: dotenv keys DDX_<APP>_<SCOPE>_<KEY>, APP ∈ WEB · API · BFF · CLUSTER · DEPLOY · SMOKE.
DEPLOY / SMOKE are process variables of deploy/scripts/*.sh (operator shell or target
shell), never keys of an app .env; the names are listed in ddx-documentation/deploy/README.md.
Allow-list (fixed names an upstream tool reads): NODE_ENV, NEXT_RUNTIME, NEXT_PUBLIC_*,
DATABASE_URL_<DB>, LOG_* (enums in @ddx/contracts/log-env). Any other key is an
exception row. One service = one key family (no S3_* beside MINIO_*); ddx-web holds no
data-tier key.ddx-documentation/ports.md.ddx-docker-<svc>, compose project and container ddx-web-boot-<svc>, volume
<svc>-data.N-SCRIPT: gate scripts are check-<noun>.mjs (root scripts/ for repo gates).kernel, bff-layers, naming,
i18n); the shell function is cmd_<verb_snake>.N-ADR ^\d{4}-[a-z0-9-]+\.md$. Plan and findings folders kebab
(plans/<topic>/, findings/<topic>/); task files task_NNN.json.N-HEADER: every source file starts with the © line and the @ddx block. Shape (from the
harness source-file header contract, required tags only):@ddx (kind), @domain (one slug — the ddx-api feature folder or the ddx-web
feature), @purpose (one sentence), @status. Optional: @topic, @props, @route,
@duplicates, @depends-on, @used-in. No other tag exists; the UI domain is carried by the
path and the prefix, never by a tag.N-HEADER and N-TAG): every code file, not only src/ — {ddx-web,ddx-api,packages/ddx-contracts}/src,
the scripts/, eslint-rules/, prisma/, test/, vitest.stubs/ folders and root configs of both apps,
root scripts/, deploy/, ddx-manage.sh. .sh files use # @ddx … lines; a shebang or
// @ts-check stays line 1. scripts/fixtures/ is out of scope.N-TAG (port of the harness ddx-write-lane header verify and the HMS
tag-inventory --verify): the four required tags sit before the first statement (a
'use client'/'use server' directive may precede them; import 'server-only' may not),
each value comes from the lists below (registry row N-TAG, the one source), @domain is
one slug, and @purpose never restates the file name.
@ddx: component page layout hook util helper client service provider type schema model config constants registry i18n test test-util spec script task controller module dto guard interceptor decorator pipe filter middleware resolver repository factory strategy subscriber scheduler processor job probe metric check tool mapper router contract (lint rules = check).@status: reusable feature-local page foundation (reuse) · production-ready active development-only legacy deprecated (lifecycle). Retired: production, spec-support.@domain: api auth bff cluster config data deploy entities framework i18n infra integration kernel media naming observability platform realtime settings shell tenancy ui. A new domain
is added to the registry row and this list in the same change../ddx-manage.sh naming): 0 of 1020 code files fail N-HEADER or N-TAG.Gate: ./ddx-manage.sh naming runs scripts/check-naming.mjs over
scripts/naming.registry.json and fails with file:line rule-id. It also runs inside
./ddx-manage.sh lint.
Exceptions:
exceptions array (the rule is the parent, so the
row has no rule field) with path, reason and expires (default 2026-12-31). A row without both reason and expires fails the gate.components/portal files, 9 all-caps acronym exports) are grandfathered there until their rename wave.adr/
deploy/
research/
// © Dudoxx UG / Acceleate Consulting - Walid Boudabbous <walid@acceleate.com>/** * @ddx service * @domain settings * @purpose Resolve the effective value of one setting key for the caller. * @status reusable */