Files
factory/vibe/guidebooks/lab-ecosystem/naming-conventions.md
Gabriel Radureau 7647a68cdc docs(vibe): bootstrap vibe/ knowledge tree + ecosystem AGENTS.md
Add a root AGENTS.md (ecosystem map of factory/tools/cms + agent operating
rules + the persona cohort & workflow) and a new vibe/ knowledge base for LLM
agents, modeled on tree-docs conventions and the factory house style.

vibe/ folders (each with a README hub + contribution rules):
- ADR/      optimized MADR-lite; canonical home going forward (doc/adr stays historical)
- PRD/      one subfolder per PRD, mandatory STATUS.md, QA strategy for big ones
- investigations/  single INV-NNN-slug.md, or stub + folder w/ notebooks
- guidebooks/      tree-docs maps; lab-ecosystem guidebook of factory+tools+cms
- runbooks/        [AGENT]/[HUMAN] step procedures (EN; doc/runbooks stays FR)
- shareouts/       dated FR handouts (decks/mp4)

Seed content (first ADR + PRD): a safe, production-like environment to rehearse
risky changes and recovery without touching real prod — local-only sandbox
(k3d + arm64 VMs) with a hard prod/sandbox isolation boundary. Includes
INV-001 (prod blast-radius couplings), the ecosystem guidebook, and a FR shareout.

Conventions enforced: no-tombstone rule, breadcrumb spine, bidirectional
cross-links, theme:base mermaid (MCP-validated) + ordered-list-after-diagram.
Built with a Workflow + persona cohort; 24 files, zero dead links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:52:37 +02:00

97 lines
8.4 KiB
Markdown

[vibe](../../README.md) > [Guidebooks](../README.md) > [Lab ecosystem](README.md) > **Naming conventions (the `<app>` join key)**
# Naming conventions — the `<app>` join key
> **Status**: 🟢 Active
> **Last Updated**: 2026-06-23
> **Related**: [Lab ecosystem](README.md) · [Factory brick](01-factory.md) · [Secrets & Vault](secrets-and-vault.md) · [PRD — isolation boundary](../../PRD/safe-prod-like-environment/isolation-boundary.md)
> **Upstream (source of truth)**: [doc/runbooks/new-web-app/conventions.md](../../../doc/runbooks/new-web-app/conventions.md) (French, authoritative)
## TL;DR
Every application on the platform is pinned to **one** kebab-case identifier — `<app>` (e.g. `erp`, `webapp`, `url-shortener`, `dance-lessons-coach`). That single string is reused **verbatim**, with no transformation, as the name of the app's Gitea repo, its PostgreSQL database and role, its Vault roles and policies, its Kubernetes namespace and ServiceAccount, its ArgoCD Application, its OpenTofu state prefix, and its DNS records. The bricks of the stack do not point at each other through explicit configuration; they **wire together by guessing each other's names from `<app>`**. Pick the name once, get it right, and the whole chain self-assembles. One typo anywhere, and the chain breaks silently.
## What `<app>` is
`<app>` is a **lowercase, kebab-case** slug. It is the join key of the entire platform — the one value that lets a dozen otherwise-independent systems agree on which resources belong to the same application without ever exchanging a config pointer. The canonical, authoritative definition (in French) lives in the runbook: [doc/runbooks/new-web-app/conventions.md](../../../doc/runbooks/new-web-app/conventions.md). This page is the English concept summary inside the ecosystem guidebook.
## The mapping — one name, every system
The table below shows how each system derives its identifier from `<app>`, with the `erp` application as the worked example.
| System | Identifier derived from `<app>` | Example (`erp`) |
| --- | --- | --- |
| Gitea repository | `arcodange-org/<app>` | `arcodange-org/erp` |
| PostgreSQL database | `<app>` | `erp` |
| PostgreSQL owner role (non-login) | `<app>_role` | `erp_role` |
| Vault dynamic DB role | `postgres/creds/<app>` | `postgres/creds/erp` |
| Vault Kubernetes auth role | `<app>` | `erp` |
| Vault runtime policy (pod) | `<app>` | `erp` |
| Vault CI/ops policy | `<app>-ops` | `erp-ops` |
| Vault CI JWT role (Gitea OIDC) | `gitea_cicd_<app>` | `gitea_cicd_erp` |
| Vault KV config path | `kvv2/<app>/config` | `kvv2/erp/config` |
| Kubernetes namespace | `<app>` | `erp` |
| Kubernetes ServiceAccount | `<app>` | `erp` |
| ArgoCD Application | `<app>` | `erp` |
| OpenTofu state prefix (GCS) | `<app>/main` | `erp/main` |
| Internal DNS | `<app>.arcodange.lab` | `erp.arcodange.lab` |
| Public DNS | `<app>.arcodange.fr` | `erp.arcodange.fr` |
> [!NOTE]
> The `_role` suffix (PG owner role) and the `-ops` suffix (Vault CI policy/identity group) are the only two *systematic* transformations of `<app>`. Everything else uses the bare slug. Note the suffix style differs: PostgreSQL uses an underscore (`erp_role`) because hyphens are awkward in SQL identifiers, whereas Vault and Kubernetes use a hyphen (`erp-ops`).
## Why uniformity is structuring
The platform is a set of loosely-coupled bricks (Gitea, Postgres, Vault, k3s/ArgoCD, OpenTofu, DNS). They were deliberately built **not** to hold explicit references to one another. Instead, each brick reconstructs the names it needs from `<app>` at the moment it runs:
```mermaid
%%{init: {'theme':'base'}}%%
flowchart LR
APP["&lt;app&gt;<br/>(one kebab-case slug)"]:::src
APP --> GIT["Gitea repo<br/>arcodange-org/&lt;app&gt;"]:::brick
APP --> PG["PostgreSQL<br/>db &lt;app&gt; · role &lt;app&gt;_role"]:::brick
APP --> VAULT["Vault<br/>postgres/creds/&lt;app&gt;<br/>policy &lt;app&gt; · gitea_cicd_&lt;app&gt;"]:::brick
APP --> K8S["Kubernetes<br/>namespace + SA &lt;app&gt;"]:::brick
APP --> ARGO["ArgoCD<br/>Application &lt;app&gt;"]:::brick
APP --> GCS["OpenTofu state<br/>&lt;app&gt;/main"]:::brick
APP --> DNS["DNS<br/>&lt;app&gt;.arcodange.lab / .fr"]:::brick
VAULT -.->|"GRANT &lt;app&gt;_role<br/>assumes PG role name"| PG
K8S -.->|"VaultDynamicSecret reads<br/>postgres/creds/&lt;app&gt;"| VAULT
ARGO -.->|"repoURL=.../&lt;app&gt;<br/>namespace=&lt;app&gt;"| GIT
classDef src fill:#2563eb,stroke:#1e40af,color:#fff
classDef brick fill:#059669,stroke:#047857,color:#fff
```
1. The chosen slug `<app>` is the single input.
2. From it, each brick names its own resource: Gitea names the repo `arcodange-org/<app>`; Postgres names the database `<app>` and its owner role `<app>_role`; Vault names the dynamic-creds role `postgres/creds/<app>`, the runtime policy `<app>`, and the CI JWT role `gitea_cicd_<app>`; Kubernetes names the namespace and ServiceAccount `<app>`; ArgoCD names the Application `<app>`; OpenTofu writes state under `<app>/main`; DNS publishes `<app>.arcodange.lab` and `<app>.arcodange.fr`.
3. The dashed arrows are the cross-brick assumptions that make it work: the Vault `app_roles` module issues a dynamic PG user with `GRANT <app>_role TO …`, **assuming** the Postgres owner role is named exactly `<app>_role`; the chart's `VaultDynamicSecret` reads `postgres/creds/<app>`, **assuming** the Vault role is named exactly `<app>`; the ArgoCD Application derives `repoURL=.../<app>` and `namespace=<app>` from the slug alone, **assuming** the Gitea repo and the namespace match.
4. None of these links is configured by hand. They hold purely because every brick was given the same `<app>` to reconstruct from.
## The failure mode of a typo
Because the wiring is by name and not by explicit reference, **nothing validates the join key end-to-end**. A single divergence — `my_app` vs `my-app`, a stray capital (`MyApp`), an accidental plural (`erps`) — does not raise an error at creation time. The mismatched brick simply builds a resource under a name no one else looks for:
- A Postgres owner role created as `erp-role` (hyphen) instead of `erp_role` → Vault's `GRANT erp_role` fails or grants nothing → the pod gets a DB user with no privileges.
- A Gitea repo named `erp-app` instead of `erp` → ArgoCD's derived `repoURL=.../erp` 404s → the Application never syncs.
- A namespace typo → the `VaultDynamicSecret` and ServiceAccount land in the wrong place → silent auth failure at pod start.
The symptom is always the same: a brick that *looks* provisioned but never connects, with no single component to blame. This is why the slug must be **short, stable, and correct from the first step** — there is no safety net downstream.
✅ Choose a short, stable, lowercase kebab-case name up front and reuse it character-for-character.
❌ Never introduce variants (case, separators, plurals); nothing will warn you.
## Why this makes a sandbox safe
The `<app>` convention is also the reason a **production-like sandbox can reuse the exact same names** without colliding with production. Because every brick derives its resource names from `<app>` and from nothing else, an entire parallel universe of the platform — its own Vault, its own Postgres instance, its own k3s namespace scope — can host an `erp` named identically to the production `erp`, provided the two universes never share a backing store. Identity comes from the *environment boundary*, not from the name; the name is free to repeat. This is what lets QA and recovery drills run against `erp`, `webapp`, etc. with realistic identifiers instead of mangled `erp-staging`-style aliases that would themselves break the name-wiring. See the PRD's [isolation boundary](../../PRD/safe-prod-like-environment/isolation-boundary.md) for how that environment fence is drawn.
## See also
- [doc/runbooks/new-web-app/conventions.md](../../../doc/runbooks/new-web-app/conventions.md) — the authoritative French source, with per-step references into the 8-step "new web app" runbook.
- [Secrets & Vault](secrets-and-vault.md) — how `gitea_cicd_<app>` and the `<app>` / `<app>-ops` policies fit the auth model.
- [Factory brick](01-factory.md) — where the ArgoCD app-of-apps, the Postgres OpenTofu, and the IaC live.
- [PRD — isolation boundary](../../PRD/safe-prod-like-environment/isolation-boundary.md) — why identical names are safe across environments.
- [ADR 0001 — Safe, production-like environment](../../ADR/0001-safe-prod-like-environment.md).