Files
Nexus/docs/architecture-decision-records/0001-runtime-secret-provisioning.md
T
Hartmut 4a5edeef3e
CI / Unit Tests (pull_request) Successful in 5m46s
CI / Lint (pull_request) Failing after 3m49s
CI / E2E Tests (pull_request) Has been skipped
CI / Fresh-Linux Docker Deploy (pull_request) Has been skipped
CI / Assistant Split Regression (pull_request) Failing after 35s
CI / Architecture Guardrails (pull_request) Failing after 2m14s
CI / Typecheck (pull_request) Successful in 4m22s
CI / Build (pull_request) Has been skipped
CI / Release Images (pull_request) Has been skipped
rename(phase 1): CapaKraken → Nexus across code, UI, docs, CI
- @capakraken/* → @nexus/* across 12 packages (root + 11 workspaces),
  1551 import lines migrated via codemod
- User-visible brand strings renamed (emails, page titles, PWA
  manifest, mobile header, MFA backup-codes header, tooltips, signin
  page, invite page, weekly digest, install prompt)
- TOTP issuer "CapaKraken" → "Nexus" (existing secrets still valid;
  re-enrollment relabels them in users' authenticator apps)
- Function rename: assertCapaKrakenDbTarget → assertNexusDbTarget
- LocalStorage migration shim in apps/web/src/app/layout.tsx copies
  capakraken_* → nexus_* on first load (guarded by nexus_migrated_v1
  sentinel; runs once per browser, then never again)
- Service-worker cache name capakraken-v2 → nexus-v2 with one-time
  caches.delete('capakraken-v2') from the same shim
- Email-domain fixtures @capakraken.{dev,app} → @nexus.{dev,app} in
  seed data, e2e specs, SMTP default fallback
- Dockerfile.dev / Dockerfile.prod / all .github/workflows/*.yml
  pnpm --filter @capakraken/* → @nexus/*
- README, CLAUDE.md, LEARNINGS.md, all docs/*.md, .env.example,
  tooling/deploy/.env.production.example brand sweep

Phase 1 deliberately leaves untouched (handled in Phase 3 cutover):
- PostgreSQL DB name "capakraken" and POSTGRES_USER "capakraken"
- Volume names capakraken_pgdata etc.
- Compose project name "capakraken" / "capakraken-prod"
- db-target-guard default expectedDatabase
- env-var CAPAKRAKEN_EXPECTED_DB_NAME
- Container DNS names in docker-compose.ci.yml

Quality gates green: pnpm typecheck (7/7), pnpm test:unit (7/7),
pnpm lint (0 errors), check:exports/imports/architecture all pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 15:10:44 +02:00

90 lines
3.5 KiB
Markdown

# ADR 0001: Runtime Secret Provisioning
**Status:** Accepted
**Date:** 2026-03-30
## Context
Nexus historically allowed some operational runtime secrets to be persisted through `SystemSettings`.
That included values such as:
- primary AI API credentials
- dedicated DALL-E credentials
- Gemini credentials
- SMTP password
- anonymization seed
This was convenient for fast iteration, but it coupled operational secret material to the main application data plane and blurred the line between configuration metadata and deployment secrets.
The project is moving toward a production model where the running artifact should be immutable and environment-driven. That model is weakened if operators can still rotate runtime secrets through normal application writes.
## Decision
Operational runtime secrets must be provisioned outside the application database.
Allowed sources:
- deployment environment variables
- host-level secret files such as `.env.production` on self-managed infrastructure
- platform secret managers or encrypted environment facilities
Disallowed source for new secret values:
- admin updates that write runtime secrets into `SystemSettings`
`SystemSettings` remains valid for non-secret runtime metadata such as:
- provider selection
- endpoints
- model names
- SMTP host/user/from settings
- anonymization mode and domain
Legacy secret values that already exist in `SystemSettings` may still be read during migration for compatibility, but they are not the target state and should be cleared after equivalent deployment secrets are provisioned.
## Consequences
Positive:
- production updates become more predictable because images and runtime secrets are managed as separate deployment concerns
- operational secrets stop depending on ordinary application write paths
- admin tooling can expose status and diagnostics without pretending to be the system of record for secrets
- secret rotation becomes an infrastructure operation rather than a product mutation
Tradeoffs:
- smaller self-managed installs need a disciplined host bootstrap process
- operators must understand that updating app settings is no longer sufficient for secret rotation
- migration requires visibility into which secrets are still backed by database residue
## Implementation Notes
The implementation should follow these rules:
1. runtime consumers resolve supported secret values from environment first
2. admin settings reads expose presence and source status, not secret values
3. admin settings updates ignore incoming secret payloads
4. the UI explains the expected environment variables for each runtime secret
5. a dedicated cleanup action removes legacy database-stored secret values after migration
## Operational Guidance
For staging and production:
1. provision runtime secrets on the host or platform before starting a new release
2. deploy the already-built application image
3. restart the application so the new process reads the current secret source
4. verify runtime status in admin settings
5. clear any leftover legacy database secret values once the environment-backed source is confirmed
Secret rotation should follow the same model. In most cases, no application data mutation is needed. The operator updates the deployment secret source and restarts or redeploys the app.
## Follow-up
Still required after this decision:
- complete the canonical image-based staging/production rollout
- ensure staging and production hosts both use the same secret provisioning rules
- periodically verify that legacy database secret fields remain empty