Config Management
Read & hot-patch config at runtime
configManagement exposes a small, godmin-only API for inspecting and adjusting the running configuration without a redeploy. It's the operator's window into the live service: see exactly what's configured (with secrets masked), patch a hot-reloadable section, or trigger a graceful restart.
The implementation is fully generic — sections are derived from the live config's keys at runtime, and sensitive fields are detected by name patterns rather than a hard-coded list, so it stays correct as the config evolves. Beyond name-pattern key masking, string values are also scanned for credential-bearing connection URLs (e.g. database.url, redis.url) and the embedded password is masked — so a secret living under a non-sensitive key name like 'url' is not leaked.
Enabling#
Two fields turn the feature on and choose its route prefix. It's off by default because it's a privileged surface.
1{2 "configManagement": {3 "enabled": true,4 "basePath": "/nucleus/config"5 }6}enabledbooleanOptionalMount the config-management endpoints. Off by default — enable deliberately, and only behind godmin auth.
falsebasePathstringOptionalRoute prefix for the management endpoints.
"/nucleus/config"What it exposes#
With the feature enabled, the following godmin-only routes are mounted under basePath. Reads always mask secret-bearing fields; writes deep-merge into the named section and persist to disk.
Under the hood — the runtime config API#
The routes operate on the live, resolved config object. Reads are always masked; writes choose between an instant hot-reload and a restart, and persist as durably as the environment allows.
godmin guardheader + DB fallbackOptionalEvery route checks for the godmin role in x-user-roles, and if the JWT predates the role assignment, falls back to the user's isGod flag in the database — otherwise 403. There is no non-godmin path in.
masking & env diagnosticsGET / · /envOptionalGET / returns the whole config with sensitive fields masked. GET /env returns two merged surfaces: (a) DECLARED refs — UPPER_SNAKE_CASE env-var references walked from the config tree (recognized:true) — reporting which resolved and which are missing, and (b) UNDECLARED vars — every other var present in process.env but not referenced by config (recognized:false), minus Kubernetes service-link and OS/interpreter infra noise, so orphaned/junk config is visible for cleanup. The response adds an unrecognizedCount field and its message reads 'Found N env vars (X resolved, Y missing, Z unrecognized)'. AI-provider *_API_KEY vars are never surfaced (not even masked), and high-entropy/base64/PEM values are masked by value even under a non-sensitive name — on top of name-pattern and URL-credential masking. The fastest way to debug a bad deploy's environment.
hot-reload vs restartisRestartRequiredOptionalPATCH /:section deep-merges into object sections (arrays like entities are replaced). The request body is always an envelope: the body schema requires a top-level `config` object (`{ config: Record<string, unknown> }`), so an object section takes `{ "config": { <partial-to-deep-merge> } }`. Because `config` must be an object, an array or primitive section cannot be sent raw — the handler unwraps `payload.config.value` when present, so replacing an array section (e.g. entities) or a primitive takes `{ "config": { "value": <replacement> } }`. Hot-reloadable sections are mutated in the live in-memory config (resolvedOptions) immediately via applySectionUpdate, and an onConfigUpdate callback fires — but the built-in callback only LOGS the change (it does not re-initialize any service), so whether the new value actually takes effect depends on the section being read live at request time rather than captured at startup; services that snapshot their config at boot will not pick it up until a restart. Restart-required sections are saved and flagged, taking effect only after a restart.
persistence fallbackdisk → Redis → memoryOptionalAn update is written to the config file on disk if a path is known; the Redis-override fallback is attempted only when a disk write is attempted and FAILS (merged on next restart). If no config file path is configured at all, the update is kept in-memory only — Redis is not consulted on that no-path branch — and the response says so. GET/DELETE /overrides inspect and clear those Redis overrides. Note: the disk write (persistConfigToDisk) intentionally omits the configManagement section itself — it copies every top-level key EXCEPT configManagement before overwriting the file. A disk path is only ever known when config.options is a string file path (that same JSON file is re-read to build the running config at startup), so if you enable this feature via a file-based config rather than a code/deploy object, any PATCH that persists to disk rewrites the file WITHOUT the configManagement block. After the next restart configManagement (including enabled) is undefined and the feature silently disables itself, its own routes disappearing. Object-config deploys have no disk path and go memory-only, so this footgun is scoped to file-based configs.
graceful restartPOST /restartOptionalSchedules process.exit(0) after a configurable delay — an optional { delayMs } request body sets it (default 1000ms) — and relies on the orchestrator (K8s/PM2/systemd) to bring the process back. The supported way to apply restart-required changes without a manual deploy.
Related sections