secrets
Stores arbitrary per-tenant secrets (API keys, tokens, credentials) encrypted at rest using AES-256 with a KEK loaded from KUMIKO_SECRETS_MASTER_KEY_V1 (and successive versions for rotation). Read a secret in handlers via ctx.secrets.get(tenantId, handle), which automatically appends a tenantSecretRead audit event so every access is traceable. A rotate job re-encrypts all envelopes after a KEK version bump.
Quick example
Section titled “Quick example”From recipes-managed-config — the smallest working mount:
// Managed config — one declaration provisions everything a config key needs.//// Two keys, two backings, two scopes — same declarative surface://// payment-api-key scope:"system" backing:"secrets" platform-owned secret// → stored envelope-encrypted in the secrets store (system tenant), masked// in every query, revealed only for the owning feature's ctx.config read.//// smtp-host scope:"tenant" (config, plain) per-tenant override// → platform default (env/system-row) that a tenant admin can override;// cascade resolves tenant-row → system-row → default.//// `mask` makes each key surface in the self-populating settings hub without a// hand-written r.screen / r.nav. `env` wires the platform default from an// environment variable at boot (runProdApp) — no manual AppConfigOverrides map.
import { access, type ConfigKeyHandle, createSystemConfig, createTenantConfig, defineFeature,} from "@cosmicdrift/kumiko-framework/engine";import { z } from "zod";
const FEATURE = "integrations";
export const paymentApiKeyHandle: ConfigKeyHandle<"text"> = { name: `${FEATURE}:config:payment-api-key`, type: "text",};
export const smtpHostHandle: ConfigKeyHandle<"text"> = { name: `${FEATURE}:config:smtp-host`, type: "text",};
export const integrationsFeature = defineFeature(FEATURE, (r) => { r.requires("config"); // backing:"secrets" stores payment-api-key in the secrets envelope, which // the secrets feature provisions — declare the dependency so registry-build // enforces it instead of leaving a consumer to discover the missing // tenant_secrets table at runtime. r.requires("secrets");
r.config({ keys: { // System-only secret: backing:"secrets" routes storage to the secrets // envelope (KEK rotation + audit-on-read), never config_values. The // boot-guard rejects backing:"secrets" on any non-system scope. "payment-api-key": createSystemConfig("text", { backing: "secrets", write: access.systemAdmin, read: access.admin, mask: { title: "integrations.payment-api-key", icon: "credit-card", order: 1 }, }), // Tenant override with a platform default. env seeds the default from // SMTP_HOST at boot; a tenant admin overrides it per tenant. "smtp-host": createTenantConfig("text", { env: "SMTP_HOST", default: "smtp.platform.example", write: access.roles("SystemAdmin", "Admin"), read: access.admin, mask: { title: "integrations.smtp-host", icon: "mail", order: 2 }, }), }, });
// Internal-read probe: the owning feature reads its own secrets-backed key // via ctx.config and receives the revealed plaintext, never the mask. r.queryHandler( "peek-payment-key", z.object({}), async (_query, ctx) => { if (!ctx.config) throw new Error("ctx.config not wired"); return { value: await ctx.config(paymentApiKeyHandle) }; }, { access: { roles: ["SystemAdmin"] } }, );
r.translations({ keys: { "integrations.settings": { de: "Integrationen", en: "Integrations" }, "integrations.payment-api-key": { de: "Zahlungs-API-Schlüssel", en: "Payment API Key" }, "integrations.smtp-host": { de: "SMTP-Server", en: "SMTP Host" }, }, });});📄 On GitHub: samples/recipes/managed-config/src/feature.ts
How it fits
Section titled “How it fits”What this feature needs to run (Requires, top) and the write commands it provides (Provides, bottom).
flowchart TB
n_secrets["secrets"]
subgraph how_provides["Provides"]
n_cmd_secrets_write_delete(["delete"])
n_cmd_secrets_write_set(["set"])
end
n_secrets --> n_cmd_secrets_write_delete
n_secrets --> n_cmd_secrets_write_set
Provides — write commands this feature registers (dispatch them through the command bus):
Getting started
Section titled “Getting started”Start with recipes-managed-config for a step-by-step walkthrough with runnable code and integration tests.
Dependencies
Section titled “Dependencies”- Requires: none
- Activation: always on (not toggleable)