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
- @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>
90 lines
3.5 KiB
Markdown
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
|