@happyvertical/smrt-config 0.43.6 → 0.43.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +6 -0
- package/README.md +71 -1
- package/dist/index.d.ts +213 -2
- package/dist/index.js +488 -9
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +95 -0
- package/package.json +2 -2
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/loader.ts","../src/merge.ts","../src/export.ts","../src/index.ts"],"sourcesContent":["import type { Loader } from 'cosmiconfig';\nimport { cosmiconfig } from 'cosmiconfig';\nimport { createJiti } from 'jiti';\nimport type { LoadConfigOptions, SmrtConfig } from './types.js';\n\nconst MODULE_NAME = 'smrt';\n\n/**\n * Lazily-created jiti instance used to load TypeScript config files.\n *\n * cosmiconfig ships no TypeScript loader (dropped in v8+), so a project whose\n * config is `smrt.config.ts` — the SvelteKit template default — silently fell\n * back to empty config, breaking `smrt db:setup` / `db:migrate` (#1783). jiti\n * transpiles and imports the `.ts`/`.mts`/`.cts` file on demand (it is the same\n * engine cosmiconfig-typescript-loader wraps), honoring the config's own\n * relative imports and `process.env` reads. Created once and reused so repeated\n * `loadConfig()` calls don't rebuild the transform pipeline.\n */\nlet jitiInstance: ReturnType<typeof createJiti> | undefined;\n\nfunction getJiti(): ReturnType<typeof createJiti> {\n if (!jitiInstance) {\n // moduleCache: false so a re-read after the file changes on disk (hot\n // reload, or `loadConfig({ cache: false })`) re-evaluates instead of\n // returning jiti's in-memory copy. The common `cache: true` path is already\n // short-circuited by our own config cache before it reaches jiti, so this\n // adds no cost to steady-state loads. fsCache (default) still disk-caches\n // the content-hashed transpile output.\n jitiInstance = createJiti(import.meta.url, { moduleCache: false });\n }\n return jitiInstance;\n}\n\n/**\n * cosmiconfig loader for TypeScript config files. Returns the config's default\n * export (or the module namespace when there is no default), matching how the\n * JS loaders treat `export default` / `module.exports`.\n */\nconst typeScriptLoader: Loader = async (filepath: string) => {\n return getJiti().import(filepath, { default: true });\n};\n\n/**\n * Extend globalThis to include loader cache properties.\n * Using globalThis ensures all module instances share the same loader state,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * @see https://github.com/happyvertical/smrt/issues/543\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtLoaderCachedConfig: SmrtConfig | null | undefined;\n // eslint-disable-next-line no-var\n var __smrtLoaderExplorer: ReturnType<typeof cosmiconfig> | null | undefined;\n}\n\n/** Retrieve the cached config from the globalThis singleton store. */\nfunction getCachedConfig(): SmrtConfig | null {\n return globalThis.__smrtLoaderCachedConfig ?? null;\n}\n\n/** Write the config (or null to invalidate) into the globalThis singleton store. */\nfunction setCachedConfig(config: SmrtConfig | null): void {\n globalThis.__smrtLoaderCachedConfig = config;\n}\n\n/** Retrieve the cosmiconfig explorer instance from the globalThis singleton store. */\nfunction getExplorer(): ReturnType<typeof cosmiconfig> | null {\n return globalThis.__smrtLoaderExplorer ?? null;\n}\n\n/** Write the cosmiconfig explorer instance into the globalThis singleton store. */\nfunction setExplorer(exp: ReturnType<typeof cosmiconfig> | null): void {\n globalThis.__smrtLoaderExplorer = exp;\n}\n\n/**\n * Distinguish \"config file does not exist\" (a benign, expected condition that\n * falls back to empty config) from \"config file exists but failed to load/parse\"\n * (a real error that should surface). cosmiconfig reads via Node `fs`, so a\n * missing explicit path raises an `ENOENT` error.\n */\nfunction isFileNotFound(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n if ((error as { code?: unknown }).code === 'ENOENT') {\n return true;\n }\n const message = (error as { message?: unknown }).message;\n return (\n typeof message === 'string' &&\n (message.includes('ENOENT') || message.includes('no such file'))\n );\n}\n\n/**\n * Load and parse configuration from the project root using cosmiconfig.\n *\n * Searches for `smrt.config.{js,mjs,cjs,ts,mts,cts,json}` starting from `cwd`,\n * walking up the directory tree unless `searchParents` is `false`. TypeScript\n * configs are transpiled on demand via jiti (#1783). The result is\n * cached in `globalThis.__smrtLoaderCachedConfig` so that all modules sharing\n * the same runtime (including pnpm workspace symlinks) see the same config.\n *\n * Returns an empty object (`{}`) when no config file is found, so callers\n * should always treat every field as optional. A config file that **exists but\n * fails to load/parse** (syntax error, bad import, invalid JSON) throws instead\n * of being silently ignored (#1579).\n *\n * @param options - Search and caching options.\n * @returns The parsed {@link SmrtConfig}, or `{}` if no config file is found.\n * @throws If a config file exists but cannot be loaded or parsed.\n *\n * @example\n * ```ts\n * import { loadConfig } from '@happyvertical/smrt-config';\n *\n * const config = await loadConfig();\n * console.log(config.smrt?.logLevel); // 'debug'\n * ```\n *\n * @example Load a specific file (useful in tests):\n * ```ts\n * const config = await loadConfig({ configPath: './fixtures/smrt.config.js', cache: false });\n * ```\n *\n * @see {@link LoadConfigOptions}\n * @see {@link clearConfigCache}\n */\nexport async function loadConfig(\n options: LoadConfigOptions = {},\n): Promise<SmrtConfig> {\n const {\n configPath,\n searchParents = true,\n searchFrom,\n cache = true,\n } = options;\n\n // Return cached config if available\n const cached = getCachedConfig();\n if (cache && cached) {\n return cached;\n }\n\n // Initialize or reuse cosmiconfig explorer\n let explorer = getExplorer();\n if (!explorer || !cache) {\n explorer = cosmiconfig(MODULE_NAME, {\n searchPlaces: [\n `${MODULE_NAME}.config.js`,\n `${MODULE_NAME}.config.mjs`,\n `${MODULE_NAME}.config.cjs`,\n `${MODULE_NAME}.config.ts`,\n `${MODULE_NAME}.config.mts`,\n `${MODULE_NAME}.config.cts`,\n `${MODULE_NAME}.config.json`,\n ],\n // cosmiconfig has no built-in TypeScript loader; register jiti for the TS\n // extensions so a `smrt.config.ts` scaffold loads end to end (#1783).\n loaders: {\n '.ts': typeScriptLoader,\n '.mts': typeScriptLoader,\n '.cts': typeScriptLoader,\n },\n // When not searching parents, stop at the actual search root so\n // `searchParents: false` is honored even if `searchFrom` is outside the\n // cwd tree (otherwise the upward walk from `searchFrom` never hits cwd and\n // runs to the filesystem root).\n stopDir: searchParents ? undefined : (searchFrom ?? process.cwd()),\n cache: cache, // Respect cache option\n });\n setExplorer(explorer);\n }\n\n let result: Awaited<ReturnType<typeof explorer.load>> = null;\n\n // Load from specific path or search\n try {\n if (configPath) {\n result = await explorer.load(configPath);\n } else {\n result = await explorer.search(searchFrom ?? process.cwd());\n }\n } catch (error) {\n // A genuinely-absent config file is not an error: an explicit `configPath`\n // that doesn't exist (ENOENT) legitimately falls back to empty config, and\n // `search()` returns null (never throws) when no file is found.\n if (isFileNotFound(error)) {\n return {};\n }\n // A config file that EXISTS but fails to load/parse — syntax error, bad\n // import, invalid JSON — is a real problem. Surface it loudly instead of\n // silently returning {} and falling back to defaults, which masks broken\n // config and leaves the developer thinking their settings applied (#1579).\n const where = configPath ? ` \"${configPath}\"` : '';\n throw new Error(\n `Failed to load smrt config${where}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n { cause: error },\n );\n }\n\n const config: SmrtConfig = result?.config || {};\n\n // Cache the config\n if (cache) {\n setCachedConfig(config);\n }\n\n return config;\n}\n\n/**\n * Clear the internal loader cache.\n *\n * Resets `globalThis.__smrtLoaderCachedConfig` and invalidates the cosmiconfig\n * explorer so that the next `loadConfig()` call performs a fresh file search.\n *\n * This is a low-level helper. Consumer code should call {@link clearCache}\n * from `index.ts` instead, which also clears the runtime-override store.\n *\n * @see {@link clearCache}\n */\nexport function clearConfigCache(): void {\n setCachedConfig(null);\n\n // Clear cosmiconfig's cache\n const explorer = getExplorer();\n if (explorer) {\n explorer.clearCaches();\n setExplorer(null);\n }\n}\n","import type { SmrtConfig } from './types.js';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtRuntimeConfig: Partial<SmrtConfig> | undefined;\n}\n\n// Runtime config overrides\nglobalThis.__smrtRuntimeConfig ??= {};\n\n// Keys that must never be written when merging untrusted / DB-exported config,\n// to avoid prototype pollution.\nconst UNSAFE_KEYS = new Set<string>(['__proto__', 'constructor', 'prototype']);\n\n/**\n * True only for plain config maps (prototype is `Object.prototype` or `null`),\n * not arrays, `Date`/`RegExp`, or class instances.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n\n/**\n * Recursively clone arrays and plain objects so the result shares no mutable\n * container with its input (#1579). Non-plain leaves — primitives, and crucially\n * **functions / class instances** — are passed through by reference: config\n * sections are typed `Record<string, unknown>` and may legitimately hold\n * callbacks, which are neither structured-cloneable nor the mutable containers\n * the aliasing guarantee targets.\n */\nfunction deepClone<V>(value: V): V {\n if (Array.isArray(value)) {\n return value.map((item) => deepClone(item)) as V;\n }\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (UNSAFE_KEYS.has(key)) {\n continue;\n }\n out[key] = deepClone(value[key]);\n }\n return out as V;\n }\n return value;\n}\n\n/**\n * Deep-merge two plain objects, with `source` values taking precedence over\n * `target` values at each key level.\n *\n * Rules:\n * - Both values are plain objects → recurse.\n * - `source` value is `null` or `undefined` → keep the `target` value.\n * - Otherwise → `source` value replaces `target` value (including `false`, `0`, `''`).\n *\n * The returned object owns all of its data: arrays and plain objects from both\n * inputs are deep-cloned, so mutating an input later can't leak into the result\n * (or the global runtime store via {@link setConfig}) and vice versa (#1579).\n * Each value is cloned exactly once — carried-over target keys and overriding\n * source leaves are cloned here, and shared object keys are produced by the\n * recursive call rather than re-cloned.\n *\n * @param target - Base object (lower priority).\n * @param source - Override object (higher priority).\n * @returns A new merged object.\n */\nfunction deepMerge<T extends Record<string, unknown>>(\n target: T,\n source: Partial<T>,\n): T {\n // Typed as Record<string, unknown> (not the generic T) so the own-key writes\n // below are well-typed — indexing a generic T for *write* is a TS2862 error.\n const result: Record<string, unknown> = {};\n const tgt = target as Record<string, unknown>;\n const src = source as Record<string, unknown>;\n\n // Carry over target-only keys, cloned so the result never aliases `target`.\n for (const key of Object.keys(tgt)) {\n if (UNSAFE_KEYS.has(key) || key in src) {\n continue;\n }\n result[key] = deepClone(tgt[key]);\n }\n\n for (const key of Object.keys(src)) {\n if (UNSAFE_KEYS.has(key)) {\n continue;\n }\n const sourceValue = src[key];\n const targetValue = tgt[key];\n\n if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {\n // Recurse — the child call returns a fully-owned object (no re-clone).\n result[key] = deepMerge(targetValue, sourceValue);\n } else if (sourceValue !== undefined && sourceValue !== null) {\n // `source` wins — clone its (possibly nested) value so we don't alias it.\n result[key] = deepClone(sourceValue);\n } else if (targetValue !== undefined) {\n // `source` is null/undefined — keep the (cloned) target value.\n result[key] = deepClone(targetValue);\n }\n }\n\n return result as T;\n}\n\n/**\n * Deep-merge `config` into the in-memory runtime override store.\n *\n * Runtime overrides take the highest priority in the merge order:\n * runtime > file config > defaults. Subsequent calls accumulate — they do not\n * replace previous overrides. Call {@link clearRuntimeConfig} to reset.\n *\n * This is the low-level setter; consumer code should call {@link setConfig}\n * from `index.ts` which delegates here.\n *\n * @param config - Partial config to merge into the runtime store.\n *\n * @see {@link clearRuntimeConfig}\n * @see {@link getRuntimeConfig}\n */\nexport function setConfig(config: Partial<SmrtConfig>): void {\n globalThis.__smrtRuntimeConfig = deepMerge(\n globalThis.__smrtRuntimeConfig || {},\n config,\n );\n}\n\n/**\n * Return the current runtime configuration override store.\n *\n * Used internally by {@link getModuleConfig} and {@link getPackageConfig} to\n * layer runtime overrides on top of file-based config. Prefer those helpers\n * for reading config; this is a low-level accessor.\n *\n * @returns The accumulated runtime config partial.\n */\nexport function getRuntimeConfig(): Partial<SmrtConfig> {\n return globalThis.__smrtRuntimeConfig || {};\n}\n\n/**\n * Reset the runtime configuration override store to an empty object.\n *\n * Called by {@link clearCache} in `index.ts` as part of a full cache reset.\n * Useful in tests to ensure a clean state between cases.\n *\n * @see {@link setConfig}\n */\nexport function clearRuntimeConfig(): void {\n globalThis.__smrtRuntimeConfig = {};\n}\n\n/**\n * Merge three config layers in ascending priority order.\n *\n * Priority (highest to lowest):\n * 1. `runtime` — runtime overrides set via {@link setConfig}\n * 2. `fileConfig` — values loaded from `smrt.config.js`\n * 3. `defaults` — caller-supplied fallback values\n *\n * Uses {@link deepMerge} internally, so `null` / `undefined` source values\n * never overwrite existing target values.\n *\n * @param defaults - Lowest-priority base values (caller defaults).\n * @param fileConfig - Mid-priority values from the loaded config file.\n * @param runtime - Highest-priority runtime override values.\n * @returns A new object with all three layers merged.\n *\n * @example\n * ```ts\n * const merged = mergeConfigs(\n * { timeout: 5000, retries: 3 },\n * { retries: 5 },\n * { timeout: 1000 },\n * );\n * // => { timeout: 1000, retries: 5 }\n * ```\n */\nexport function mergeConfigs<T extends Record<string, unknown>>(\n defaults: T,\n fileConfig: Partial<T>,\n runtime: Partial<T>,\n): T {\n let result = { ...defaults };\n result = deepMerge(result, fileConfig);\n result = deepMerge(result, runtime);\n return result;\n}\n","/**\n * Config export utilities for static site generation\n *\n * These utilities enable exporting database-backed agent configurations\n * to static files for SSG sites.\n *\n * @module\n */\n\n/**\n * Patterns that identify sensitive configuration keys. Any key matching one of\n * these patterns is stripped during {@link sanitizeConfig}.\n *\n * Redaction policy\n * ----------------\n * These exports feed static-site-generation (SSG) artifacts that are typically\n * served publicly, so the cost of leaking a secret is far higher than the cost\n * of over-redacting a benign key. We therefore **bias toward over-redaction**:\n * the patterns are mostly case-insensitive *substring* matches (no word\n * boundaries) so they catch every common casing/separator convention —\n * `apiKey`, `api_key`, `API-KEY`, `apikey`, etc. A few are deliberately scoped\n * to avoid benign collisions: `\\bcert\\b` (not `concert`), and `oauth` /\n * `authorization` rather than a bare `auth` (which would hit `author`/`authors`).\n *\n * The previous implementation used `\\b` word boundaries (e.g. `/\\bauth\\b/i`,\n * `/\\bkey\\b$/i`). In JS regex `\\b` sits between a word char and a non-word char,\n * and `_`/letters/digits are all word chars, so those boundaries never fire\n * inside `camelCase` or `snake_case` identifiers — leaking keys like `api_key`,\n * `accessKey`, `authorization`, `auth_token`, `connectionString`, and `dbUrl`.\n * See issue #1357.\n *\n * To keep over-redaction targeted (rather than redacting on a bare `key`/`url`\n * substring, which would strip benign `apiEndpoint`-style nesting that legit\n * config relies on, e.g. `db: { url }`), `key`/`url` only trigger when paired\n * with a secret-adjacent qualifier (`apiKey`, `accessKey`, `dbUrl`, …).\n */\nconst SECRET_PATTERNS = [\n // Direct secret-bearing terms (substring, any casing/separator).\n /secret/i,\n /password/i,\n /passwd/i,\n // Short credential aliases (#1381, codex audit cross-finding): `pass` / `pwd`\n // keys that `password`/`passwd` don't cover. Segment- or camelCase-anchored\n // (a capital after a letter/digit) so `smtpPass`/`databasePwd`/`db_pass`/`pwd`\n // are stripped while `compass`/`bypass`/`passive` are not.\n /(^|[_-])pass([_-]|$)/i,\n /(^|[_-])pwd([_-]|$)/i,\n /[a-z0-9]Pass([_-]|$)/,\n /[a-z0-9]Pwd([_-]|$)/,\n /^(pass|pwd)$/i,\n /token/i,\n /credential/i,\n /private/i,\n // NB: not a bare /auth/i — that also matches benign `author`/`authors`\n // metadata (common in site/content/agent config). The segment-anchored form\n // strips a key literally named `auth` (and `auth_token`, `x_auth`,\n // `oauth_token`) without hitting `author`; `oauth`/`authorization` cover the\n // no-separator camelCase cases. (authToken/authSecret also hit /token//secret/.)\n /oauth/i,\n /authorization/i,\n /(^|[_-])auth([_-]|$)/i,\n /cookie/i,\n /salt/i,\n /passphrase/i,\n /\\bcert\\b/i, // cert/certificate, but not \"uncertain\"/\"concert\" style false hits\n /certificate/i,\n // Qualified \"key\" — only when paired with a secret-adjacent prefix, so we\n // strip apiKey/accessKey/signingKey/encryptionKey/privateKey but keep benign\n // keys such as `keywords`, `monkey`, or `keyboardShortcuts`.\n /api[_-]?key/i,\n /access[_-]?key/i,\n /secret[_-]?key/i,\n /signing[_-]?key/i,\n /encryption[_-]?key/i,\n /private[_-]?key/i,\n /public[_-]?key/i,\n // camelCase `<vendor>Key` keys — `consumerKey`, `masterKey`, `sslKey`,\n // `hmacKey`, `sharedKey`, `rootKey`, `clientKey`, `serverKey`, `deployKey`,\n // `sshKey`, etc. The hardcoded prefixes above and the segment-anchored\n // standalone-`key` pattern below only fire on a `_`/`-`/start boundary, so a\n // camelCase secret key with an unlisted vendor prefix slips through (a raw\n // hex/base64 secret then exports verbatim into the public SSG artifact).\n // Mirrors the `[a-z0-9]Pass`/`[a-z0-9]Pwd` idiom: a capital `K` after a\n // letter/digit, followed by a segment/`Id`/end boundary, so `consumerKey` and\n // `masterKeyId` are stripped while benign `keywords`/`monkey`/\n // `keyboardShortcuts` (lowercase `key`) are kept.\n /[a-z0-9]Key([_-]|Id|$)/,\n /(^|[_-])key([_-]|id$|$)/i, // standalone `key`, `key_id`, `keyId`, `aws…KeyId`\n // Connection / database URLs that commonly embed credentials.\n /connection[_-]?string/i,\n /conn[_-]?str/i,\n /(^|[_-])db[_-]?url/i, // dbUrl / db_url / DB_URL\n /database[_-]?url/i,\n /datasource[_-]?url/i,\n];\n\n/** Return `true` when `key` matches any of the {@link SECRET_PATTERNS}. */\nfunction isSecretKey(key: string): boolean {\n return SECRET_PATTERNS.some((pattern) => pattern.test(key));\n}\n\n// Credentials embedded in the *value* of an otherwise-benign key — most\n// importantly DB connection strings like `postgres://user:pass@host:5432/db`\n// stored under keys such as `url` / `database.url` that the key-based\n// {@link SECRET_PATTERNS} deliberately don't match (#1381). Key-based redaction\n// alone leaks these into the published SSG artifact. Mask the userinfo\n// (`scheme://user:pass@host` → `scheme://***@host`) while preserving the\n// non-secret host so the value stays diagnosable. Not anchored/global so it\n// also catches a credential URL embedded mid-string (e.g. `dsn: postgres://u:p@h`)\n// or multiple URLs in one value (review #1549).\nconst CREDENTIAL_URL_RE = /([a-z][a-z0-9+.-]*:\\/\\/)[^/?#@\\s]+@/gi;\n\n/**\n * High-confidence secret *token* shapes that can appear in the value of an\n * otherwise-benign key (#1381). Key-based {@link SECRET_PATTERNS} only catch a\n * secret when the *key* is named like one — a provider token pasted into a\n * `note`, `header`, `instructions`, or `defaultModel`-adjacent field leaks\n * verbatim into the published SSG artifact. These patterns match the\n * distinctive prefixes/structures of real credentials so we can mask the token\n * while leaving surrounding prose intact. They are intentionally specific\n * (prefix- or structure-anchored) to avoid mangling benign config values.\n *\n * Coverage:\n * - OpenAI / Anthropic style `sk-...` and `sk-proj-...` (and `sk-ant-...`)\n * - GitHub PATs / app tokens: `ghp_ gho_ ghu_ ghs_ ghr_` + fine-grained `github_pat_`\n * - Slack tokens: `xox[abprs]-...`\n * - Google API keys: `AIza...`\n * - AWS access key IDs: `AKIA`/`ASIA` + 16 uppercase alnum\n * - Stripe live/test keys: `sk_live_` / `sk_test_` / `rk_live_` / `rk_test_`\n * - `Bearer <token>` Authorization header values\n * - PEM private-key blocks\n */\nconst VALUE_SECRET_PATTERNS: Array<{ re: RegExp; replace: string }> = [\n // PEM private key block (multi-line) — collapse the whole block.\n {\n re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z ]+ )?PRIVATE KEY-----/g,\n replace: '***REDACTED PRIVATE KEY***',\n },\n // OpenAI / Anthropic secret keys: sk-, sk-proj-, sk-ant-...\n { re: /\\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{16,}/g, replace: '***' },\n // Stripe-style prefixed keys.\n { re: /\\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g, replace: '***' },\n // GitHub tokens.\n { re: /\\bgh[pousr]_[A-Za-z0-9]{20,}/g, replace: '***' },\n { re: /\\bgithub_pat_[A-Za-z0-9_]{22,}/g, replace: '***' },\n // Slack tokens.\n { re: /\\bxox[abprs]-[A-Za-z0-9-]{10,}/g, replace: '***' },\n // Google API keys.\n { re: /\\bAIza[A-Za-z0-9_-]{35}/g, replace: '***' },\n // AWS access key IDs.\n { re: /\\b(?:AKIA|ASIA)[A-Z0-9]{16}\\b/g, replace: '***' },\n // Bearer / JWT authorization values (header strings pasted into config).\n // Case-insensitive: HTTP auth schemes are case-insensitive and clients often\n // emit lowercase `bearer ` (review #1549).\n {\n re: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{16,}/gi,\n replace: 'Bearer ***',\n },\n];\n\n/**\n * Redact credentials embedded in a string value: URL userinfo plus any\n * high-confidence secret-token shape (see {@link VALUE_SECRET_PATTERNS}).\n */\nfunction redactValueSecrets(value: string): string {\n let out = value.replace(CREDENTIAL_URL_RE, '$1***@');\n for (const { re, replace } of VALUE_SECRET_PATTERNS) {\n out = out.replace(re, replace);\n }\n return out;\n}\n\n/**\n * Deep-clone a config object, removing every key that looks like a secret.\n *\n * Keys are tested (case-insensitively, across camelCase / snake_case / kebab /\n * UPPER variants) against {@link SECRET_PATTERNS}, which cover secrets such as\n * `apiKey`/`api_key`, `password`, `secret`, `token`, `credential`, `private`,\n * `auth`/`authorization`/`oauth`, `accessKey`, `signingKey`, `encryptionKey`,\n * `connectionString`, and `dbUrl`. The policy biases toward over-redaction\n * because these exports are typically published as static, publicly served\n * assets — see {@link SECRET_PATTERNS} for the full rationale.\n *\n * Nested objects are recursively sanitized; arrays are mapped element-by-element.\n * `number` / `boolean` primitives pass through unchanged; `string` values are\n * additionally run through value-level redaction that masks credentials embedded\n * in a URL (`scheme://user:pass@host` → `scheme://***@host`) as well as\n * high-confidence secret-token shapes pasted into otherwise-benign keys\n * (OpenAI/Anthropic `sk-...`, AWS `AKIA...` access key IDs, GitHub/Slack/Google\n * tokens, `Bearer ...` headers, and PEM private-key blocks) — catching secrets\n * stored under a benign key the key-based patterns don't match.\n *\n * Own `__proto__` / `constructor` / `prototype` keys are dropped to prevent the\n * cloned result's prototype from being reassigned via the `__proto__` setter.\n *\n * This function is called automatically by {@link exportConfig} unless\n * `includeSecrets: true` is passed.\n *\n * @param config - Value to sanitize. Accepts any JSON-serializable structure.\n * @returns A new value with all secret keys omitted. `null` / `undefined` are\n * returned as-is.\n *\n * @example\n * ```ts\n * const sanitized = sanitizeConfig({\n * apiEndpoint: 'https://api.example.com',\n * apiKey: 'sk-secret-123',\n * nested: { password: 'hunter2', name: 'test' },\n * });\n * // => { apiEndpoint: 'https://api.example.com', nested: { name: 'test' } }\n * ```\n *\n * @see {@link exportConfig}\n */\nexport function sanitizeConfig(config: unknown): unknown {\n if (config === null || config === undefined) {\n return config;\n }\n\n if (Array.isArray(config)) {\n return config.map((item) => sanitizeConfig(item));\n }\n\n if (typeof config === 'object') {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(config)) {\n // Guard against prototype pollution: a `__proto__` own-key (e.g. from a\n // DB-backed config round-tripped through JSON.parse) would, via the\n // `result[key] = ...` setter, silently reassign the prototype of the\n // sanitized object we hand back to callers. `constructor`/`prototype`\n // are harmless as plain keys but dropped for parity with the merge\n // guards (deepMerge / mergeExportedConfig). See #1381.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n\n // Skip keys that match secret patterns\n if (isSecretKey(key)) {\n continue;\n }\n\n // Recursively sanitize nested objects\n result[key] = sanitizeConfig(value);\n }\n\n return result;\n }\n\n // String primitives get value-level secret redaction (credentials embedded in\n // URLs under benign keys, #1381); other primitives pass through unchanged.\n if (typeof config === 'string') {\n return redactValueSecrets(config);\n }\n return config;\n}\n\n/**\n * Options accepted by {@link exportConfig}.\n */\nexport interface ExportConfigOptions {\n /**\n * Include secrets in the export (use with caution!)\n * @default false\n */\n includeSecrets?: boolean;\n\n /**\n * Output format\n * - 'json': Plain JSON format\n * - 'js': ES module format (export default)\n * @default 'json'\n */\n format?: 'json' | 'js';\n\n /**\n * Number of spaces for indentation\n * @default 2\n */\n indent?: number;\n}\n\n/**\n * Serialize a configuration object to a formatted string for SSG file output.\n *\n * By default, {@link sanitizeConfig} is applied before serialization to strip\n * any keys that match secret patterns (API keys, passwords, tokens, etc.).\n * Pass `includeSecrets: true` only in secured server-side contexts.\n *\n * @param config - Configuration object to export. Any JSON-serializable value.\n * @param options - Format, indentation, and secret-inclusion options.\n * @returns A JSON string, or an ES module string (`export default {...};\\n`)\n * when `format: 'js'`.\n *\n * @example\n * ```ts\n * // Write a sanitized JSON export file for SSG\n * const json = exportConfig(agentConfig);\n * await fs.writeFile('./public/config.json', json);\n * ```\n *\n * @example\n * ```ts\n * // ES module format (importable via `import config from './config.js'`)\n * const js = exportConfig(agentConfig, { format: 'js' });\n * ```\n *\n * @see {@link sanitizeConfig}\n * @see {@link parseExportedConfig}\n * @see {@link ExportConfigOptions}\n */\nexport function exportConfig(\n config: unknown,\n options: ExportConfigOptions = {},\n): string {\n const { includeSecrets = false, format = 'json', indent = 2 } = options;\n\n // Sanitize unless secrets are explicitly requested\n const exportData = includeSecrets ? config : sanitizeConfig(config);\n\n const jsonString = JSON.stringify(exportData, null, indent);\n\n if (format === 'js') {\n return `export default ${jsonString};\\n`;\n }\n\n return jsonString;\n}\n\n/**\n * Parse a config string previously produced by {@link exportConfig} back into\n * a plain JavaScript object.\n *\n * Handles both output formats:\n * - **JSON** — standard `JSON.parse`.\n * - **JS module** — strips the `export default` prefix and trailing semicolon\n * before parsing as JSON.\n *\n * Throws a `SyntaxError` if the content is not valid JSON after stripping.\n *\n * @param content - Exported config string (`'json'` or `'js'` format).\n * @returns Parsed configuration value.\n *\n * @example\n * ```ts\n * const raw = await fs.readFile('./smrt.exported.json', 'utf8');\n * const config = parseExportedConfig(raw);\n * ```\n *\n * @see {@link exportConfig}\n * @see {@link mergeExportedConfig}\n */\nexport function parseExportedConfig(content: string): unknown {\n // Handle JS module format (strip export default and trailing semicolon)\n const trimmed = content.trim();\n if (trimmed.startsWith('export default')) {\n const jsonPart = trimmed\n .replace(/^export default\\s*/, '')\n .replace(/;\\s*$/, '');\n return JSON.parse(jsonPart);\n }\n\n // Plain JSON\n return JSON.parse(content);\n}\n\n/**\n * Shallow-merge an exported (DB-backed) config into a base (file-backed) config.\n *\n * Object values are deep-merged recursively. Primitive values and arrays from\n * `exportedConfig` replace their counterparts in `baseConfig`. This is a\n * one-level-smarter spread that lets file-based env overrides win when placed\n * after the spread.\n *\n * Intended for the SSG pattern where an agent exports its runtime config to a\n * static JSON file and `smrt.config.js` imports it as a base:\n *\n * @example\n * ```js\n * // smrt.config.js\n * import exported from './smrt.exported.json' with { type: 'json' };\n *\n * export default defineConfig({\n * modules: {\n * praeco: mergeExportedConfig(exported, {\n * apiEndpoint: process.env.API_URL, // env override wins\n * }),\n * },\n * });\n * ```\n *\n * @param baseConfig - Lower-priority base (typically from the DB export file).\n * @param exportedConfig - Higher-priority overrides (typically env / file-based).\n * @returns A new merged object of type `T`.\n *\n * @see {@link exportConfig}\n * @see {@link parseExportedConfig}\n */\nexport function mergeExportedConfig<T extends Record<string, unknown>>(\n baseConfig: T,\n exportedConfig: Partial<T>,\n): T {\n const result = { ...baseConfig };\n\n for (const [key, value] of Object.entries(exportedConfig)) {\n // Guard against prototype pollution when merging untrusted/DB-exported\n // input: never write to __proto__/constructor/prototype keys.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n result[key] !== null &&\n typeof result[key] === 'object' &&\n !Array.isArray(result[key])\n ) {\n // Deep merge objects\n (result as Record<string, unknown>)[key] = mergeExportedConfig(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n // Override primitive values and arrays\n (result as Record<string, unknown>)[key] = value;\n }\n }\n\n return result;\n}\n","import { loadConfig as _loadConfig, clearConfigCache } from './loader.js';\nimport {\n setConfig as _setConfig,\n clearRuntimeConfig,\n getRuntimeConfig,\n mergeConfigs,\n} from './merge.js';\nimport type { LoadConfigOptions, SmrtConfig } from './types.js';\n\n// Re-export config export utilities\nexport {\n type ExportConfigOptions,\n exportConfig,\n mergeExportedConfig,\n parseExportedConfig,\n sanitizeConfig,\n} from './export.js';\nexport { clearRuntimeConfig, getRuntimeConfig, mergeConfigs } from './merge.js';\n\n// Re-export types\nexport type {\n // CLI and migrations configuration types\n CliConfig,\n DatabaseConfig,\n // Export configuration types\n ExportConfig,\n ExportFileConfig,\n ExportFilterValue,\n LoadConfigOptions,\n MigrationsConfig,\n MigrationsPostgresConfig,\n SchemaContractConfig,\n // Site configuration types\n SiteConfig,\n SiteLocation,\n SiteNavigation,\n SiteNavigationLink,\n SitePublisher,\n SiteTheme,\n SmrtConfig,\n SmrtGlobalConfig,\n} from './types.js';\n\n/**\n * Extend globalThis to include our config cache properties.\n * Using globalThis ensures all module instances share the same config,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * This fixes issue #543: external packages not receiving user config\n * when smrt-cli loads them.\n *\n * @see https://github.com/happyvertical/smrt/issues/543\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtConfigCache: SmrtConfig | null | undefined;\n // eslint-disable-next-line no-var\n var __smrtRuntimeConfig: Partial<SmrtConfig> | undefined;\n}\n\n// Use globalThis for cross-module config sharing\n// This ensures loadConfig() in smrt-cli affects all packages that use smrt-config\nglobalThis.__smrtConfigCache ??= null;\n\n/**\n * Get the cached config from globalThis\n */\nfunction getLoadedConfig(): SmrtConfig | null {\n return globalThis.__smrtConfigCache ?? null;\n}\n\n/**\n * Set the cached config in globalThis\n */\nfunction setLoadedConfig(config: SmrtConfig | null): void {\n globalThis.__smrtConfigCache = config;\n}\n\n/**\n * Load and parse configuration from the project root.\n *\n * Searches for `smrt.config.{js,mjs,cjs,json}` starting from the current\n * working directory and walking up the tree (unless `searchParents: false`).\n * The result is stored in `globalThis.__smrtConfigCache` so every module in\n * the process — including pnpm workspace packages loaded from different paths\n * — sees the same instance.\n *\n * Must be called before {@link getConfig}, {@link getModuleConfig}, or\n * {@link getPackageConfig} return meaningful values (they fall back to an\n * empty config if called before loading).\n *\n * @param options - Optional search path, parent traversal, and cache control.\n * @returns The parsed {@link SmrtConfig}. Returns `{}` if no file is found.\n *\n * @example\n * ```ts\n * // Typical app startup\n * import { loadConfig } from '@happyvertical/smrt-config';\n * await loadConfig();\n * ```\n *\n * @example\n * ```ts\n * // Load a specific file (tests / scripts)\n * const config = await loadConfig({ configPath: './fixtures/smrt.config.js', cache: false });\n * ```\n *\n * @see {@link getConfig}\n * @see {@link clearCache}\n * @see {@link LoadConfigOptions}\n */\nexport async function loadConfig(\n options?: LoadConfigOptions,\n): Promise<SmrtConfig> {\n const config = await _loadConfig(options);\n // Always update config cache (even if caching is disabled)\n setLoadedConfig(config);\n return config;\n}\n\n/**\n * Return the full merged configuration synchronously.\n *\n * Reads from `globalThis.__smrtConfigCache` without triggering a file search.\n * Returns `null` when {@link loadConfig} has not been called yet (or after\n * {@link clearCache} resets the cache).\n *\n * @returns The cached {@link SmrtConfig}, or `null` if not yet loaded.\n *\n * @example\n * ```ts\n * const config = getConfig();\n * if (config?.smrt?.logLevel === 'debug') {\n * console.log('Debug logging enabled');\n * }\n * ```\n *\n * @see {@link loadConfig}\n * @see {@link getSiteConfig}\n * @see {@link getModuleConfig}\n */\nexport function getConfig(): SmrtConfig | null {\n return getLoadedConfig();\n}\n\n/**\n * Return the `site` section of the loaded configuration.\n *\n * Equivalent to `getConfig()?.site ?? null`. Returns `null` both when no\n * config has been loaded and when the loaded config has no `site` key.\n *\n * @returns The {@link SiteConfig} object, or `null` if not defined.\n *\n * @example\n * ```ts\n * const site = getSiteConfig();\n * const title = site?.name ?? 'Untitled Site';\n * ```\n *\n * @see {@link SiteConfig}\n * @see {@link getConfig}\n */\nexport function getSiteConfig(): import('./types.js').SiteConfig | null {\n const config = getLoadedConfig() || {};\n return config.site || null;\n}\n\n/**\n * Return the resolved configuration for a named module.\n *\n * Merges four layers in ascending priority order:\n * 1. `defaults` (caller-supplied fallbacks)\n * 2. Global `smrt` section of the loaded config\n * 3. `modules[moduleName]` section of the loaded config\n * 4. Runtime `modules[moduleName]` overrides set via {@link setConfig}\n *\n * Works synchronously — call {@link loadConfig} first to populate the cache.\n * If no config has been loaded, only `defaults` are returned.\n *\n * @param moduleName - Key in `config.modules` (e.g., `'praeco'`).\n * @param defaults - Optional fallback values used when a key is absent from all\n * higher-priority layers.\n * @returns The fully merged module config typed as `T`.\n *\n * @example\n * ```ts\n * const config = getModuleConfig('praeco', {\n * rssLimit: 50,\n * fetchTimeout: 10_000,\n * });\n * // config.rssLimit may be overridden by smrt.config.js or setConfig()\n * ```\n *\n * @see {@link getPackageConfig}\n * @see {@link setConfig}\n * @see {@link SmrtConfig.modules}\n */\nexport function getModuleConfig<T extends Record<string, unknown>>(\n moduleName: string,\n defaults?: T,\n): T {\n // Ensure config is loaded (will use empty config if not loaded)\n const fileConfig = getLoadedConfig() || {};\n const runtime = getRuntimeConfig();\n\n // Get global smrt config\n const globalConfig = (fileConfig.smrt || {}) as Partial<T>;\n\n // Get module-specific config\n const moduleConfig = (fileConfig.modules?.[moduleName] || {}) as Partial<T>;\n\n // Get runtime module config\n const runtimeModuleConfig = (runtime.modules?.[moduleName] ||\n {}) as Partial<T>;\n\n // Merge: defaults < global < module < runtime\n const defaultsWithGlobal = mergeConfigs(\n defaults || ({} as T),\n globalConfig,\n {},\n );\n const withModuleConfig = mergeConfigs(defaultsWithGlobal, moduleConfig, {});\n const final = mergeConfigs(withModuleConfig, runtimeModuleConfig, {});\n\n return final;\n}\n\n/**\n * Return the resolved configuration for a named package.\n *\n * Identical to {@link getModuleConfig} but reads from `config.packages` instead\n * of `config.modules`. Use this inside `@happyvertical/smrt-*` packages that\n * want to expose their own config section without conflicting with user-defined\n * module names.\n *\n * Merges four layers in ascending priority order:\n * 1. `defaults` (caller-supplied fallbacks)\n * 2. Global `smrt` section of the loaded config\n * 3. `packages[packageName]` section of the loaded config\n * 4. Runtime `packages[packageName]` overrides set via {@link setConfig}\n *\n * @param packageName - Key in `config.packages` (e.g., `'ai'`, `'spider'`).\n * @param defaults - Optional fallback values.\n * @returns The fully merged package config typed as `T`.\n *\n * @example\n * ```ts\n * // Inside @happyvertical/smrt-ai package\n * const config = getPackageConfig('ai', {\n * defaultModel: 'gpt-4o-mini',\n * maxTokens: 2048,\n * });\n * ```\n *\n * @see {@link getModuleConfig}\n * @see {@link setConfig}\n * @see {@link SmrtConfig.packages}\n */\nexport function getPackageConfig<T extends Record<string, unknown>>(\n packageName: string,\n defaults?: T,\n): T {\n // Ensure config is loaded (will use empty config if not loaded)\n const fileConfig = getLoadedConfig() || {};\n const runtime = getRuntimeConfig();\n\n // Get global smrt config\n const globalConfig = (fileConfig.smrt || {}) as Partial<T>;\n\n // Get package-specific config\n const packageConfig = (fileConfig.packages?.[packageName] ||\n {}) as Partial<T>;\n\n // Get runtime package config\n const runtimePackageConfig = (runtime.packages?.[packageName] ||\n {}) as Partial<T>;\n\n // Merge: defaults < global < package < runtime\n const defaultsWithGlobal = mergeConfigs(\n defaults || ({} as T),\n globalConfig,\n {},\n );\n const withPackageConfig = mergeConfigs(defaultsWithGlobal, packageConfig, {});\n const final = mergeConfigs(withPackageConfig, runtimePackageConfig, {});\n\n return final;\n}\n\n/**\n * Apply runtime configuration overrides.\n *\n * Deep-merges `config` into an in-memory store that takes the highest priority\n * in every subsequent {@link getModuleConfig} / {@link getPackageConfig} call.\n * Successive calls accumulate — they do not replace earlier overrides.\n *\n * Common uses:\n * - Inject test doubles or environment-specific values without a config file.\n * - Apply feature flags from a remote source after startup.\n *\n * @param config - Partial {@link SmrtConfig} to merge into the runtime store.\n * `null` values are ignored (they do not clear existing keys).\n *\n * @example\n * ```ts\n * setConfig({ modules: { ai: { defaultModel: 'gpt-4o' } } });\n * ```\n *\n * @see {@link clearCache}\n * @see {@link getModuleConfig}\n */\nexport function setConfig(config: Partial<SmrtConfig>): void {\n _setConfig(config);\n}\n\n/**\n * Reset all cached configuration state.\n *\n * Clears three independent caches:\n * 1. `globalThis.__smrtConfigCache` — the merged config loaded by {@link loadConfig}.\n * 2. The internal loader cache (`globalThis.__smrtLoaderCachedConfig`) and the\n * cosmiconfig explorer instance.\n * 3. The runtime override store populated by {@link setConfig}.\n *\n * After calling this, {@link getConfig} returns `null` and a fresh\n * {@link loadConfig} call will re-read from disk.\n *\n * WARNING: This is a global reset — it affects every module sharing the same\n * runtime, not just the caller. Use with care outside of test teardown.\n *\n * @example\n * ```ts\n * afterEach(() => {\n * clearCache(); // reset between tests\n * });\n * ```\n *\n * @see {@link loadConfig}\n * @see {@link setConfig}\n */\nexport function clearCache(): void {\n setLoadedConfig(null);\n clearConfigCache(); // Clear loader.ts cache\n clearRuntimeConfig(); // Clear runtime config\n}\n\n/**\n * Type-safe helper for authoring `smrt.config.js` files.\n *\n * This is an identity function — it returns its argument unchanged. Its sole\n * purpose is to provide TypeScript type-checking and IDE auto-completion when\n * writing the config object literal.\n *\n * @param config - The config object to validate against {@link SmrtConfig}.\n * @returns The same object, unchanged.\n *\n * @example\n * ```js\n * // smrt.config.js\n * import { defineConfig } from '@happyvertical/smrt-config';\n *\n * export default defineConfig({\n * smrt: { logLevel: 'info' },\n * site: { name: 'My Site', ... },\n * });\n * ```\n *\n * @see {@link SmrtConfig}\n * @see {@link loadConfig}\n */\nexport function defineConfig(config: SmrtConfig): SmrtConfig {\n return config;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/loader.ts","../src/runtime-profile.ts","../src/merge.ts","../src/export.ts","../src/index.ts"],"sourcesContent":["import type { Loader } from 'cosmiconfig';\nimport { cosmiconfig } from 'cosmiconfig';\nimport { createJiti } from 'jiti';\nimport type { LoadConfigOptions, SmrtConfig } from './types.js';\n\nconst MODULE_NAME = 'smrt';\n\n/**\n * Lazily-created jiti instance used to load TypeScript config files.\n *\n * cosmiconfig ships no TypeScript loader (dropped in v8+), so a project whose\n * config is `smrt.config.ts` — the SvelteKit template default — silently fell\n * back to empty config, breaking `smrt db:setup` / `db:migrate` (#1783). jiti\n * transpiles and imports the `.ts`/`.mts`/`.cts` file on demand (it is the same\n * engine cosmiconfig-typescript-loader wraps), honoring the config's own\n * relative imports and `process.env` reads. Created once and reused so repeated\n * `loadConfig()` calls don't rebuild the transform pipeline.\n */\nlet jitiInstance: ReturnType<typeof createJiti> | undefined;\n\nfunction getJiti(): ReturnType<typeof createJiti> {\n if (!jitiInstance) {\n // moduleCache: false so a re-read after the file changes on disk (hot\n // reload, or `loadConfig({ cache: false })`) re-evaluates instead of\n // returning jiti's in-memory copy. The common `cache: true` path is already\n // short-circuited by our own config cache before it reaches jiti, so this\n // adds no cost to steady-state loads. fsCache (default) still disk-caches\n // the content-hashed transpile output.\n jitiInstance = createJiti(import.meta.url, { moduleCache: false });\n }\n return jitiInstance;\n}\n\n/**\n * cosmiconfig loader for TypeScript config files. Returns the config's default\n * export (or the module namespace when there is no default), matching how the\n * JS loaders treat `export default` / `module.exports`.\n */\nconst typeScriptLoader: Loader = async (filepath: string) => {\n return getJiti().import(filepath, { default: true });\n};\n\n/**\n * Extend globalThis to include loader cache properties.\n * Using globalThis ensures all module instances share the same loader state,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * @see https://github.com/happyvertical/smrt/issues/543\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtLoaderCachedConfig: SmrtConfig | null | undefined;\n // eslint-disable-next-line no-var\n var __smrtLoaderExplorer: ReturnType<typeof cosmiconfig> | null | undefined;\n}\n\n/** Retrieve the cached config from the globalThis singleton store. */\nfunction getCachedConfig(): SmrtConfig | null {\n return globalThis.__smrtLoaderCachedConfig ?? null;\n}\n\n/** Write the config (or null to invalidate) into the globalThis singleton store. */\nfunction setCachedConfig(config: SmrtConfig | null): void {\n globalThis.__smrtLoaderCachedConfig = config;\n}\n\n/** Retrieve the cosmiconfig explorer instance from the globalThis singleton store. */\nfunction getExplorer(): ReturnType<typeof cosmiconfig> | null {\n return globalThis.__smrtLoaderExplorer ?? null;\n}\n\n/** Write the cosmiconfig explorer instance into the globalThis singleton store. */\nfunction setExplorer(exp: ReturnType<typeof cosmiconfig> | null): void {\n globalThis.__smrtLoaderExplorer = exp;\n}\n\n/**\n * Distinguish \"config file does not exist\" (a benign, expected condition that\n * falls back to empty config) from \"config file exists but failed to load/parse\"\n * (a real error that should surface). cosmiconfig reads via Node `fs`, so a\n * missing explicit path raises an `ENOENT` error.\n */\nfunction isFileNotFound(error: unknown): boolean {\n if (!error || typeof error !== 'object') {\n return false;\n }\n if ((error as { code?: unknown }).code === 'ENOENT') {\n return true;\n }\n const message = (error as { message?: unknown }).message;\n return (\n typeof message === 'string' &&\n (message.includes('ENOENT') || message.includes('no such file'))\n );\n}\n\n/**\n * Load and parse configuration from the project root using cosmiconfig.\n *\n * Searches for `smrt.config.{js,mjs,cjs,ts,mts,cts,json}` starting from `cwd`,\n * walking up the directory tree unless `searchParents` is `false`. TypeScript\n * configs are transpiled on demand via jiti (#1783). The result is\n * cached in `globalThis.__smrtLoaderCachedConfig` so that all modules sharing\n * the same runtime (including pnpm workspace symlinks) see the same config.\n *\n * Returns an empty object (`{}`) when no config file is found, so callers\n * should always treat every field as optional. A config file that **exists but\n * fails to load/parse** (syntax error, bad import, invalid JSON) throws instead\n * of being silently ignored (#1579).\n *\n * @param options - Search and caching options.\n * @returns The parsed {@link SmrtConfig}, or `{}` if no config file is found.\n * @throws If a config file exists but cannot be loaded or parsed.\n *\n * @example\n * ```ts\n * import { loadConfig } from '@happyvertical/smrt-config';\n *\n * const config = await loadConfig();\n * console.log(config.smrt?.logLevel); // 'debug'\n * ```\n *\n * @example Load a specific file (useful in tests):\n * ```ts\n * const config = await loadConfig({ configPath: './fixtures/smrt.config.js', cache: false });\n * ```\n *\n * @see {@link LoadConfigOptions}\n * @see {@link clearConfigCache}\n */\nexport async function loadConfig(\n options: LoadConfigOptions = {},\n): Promise<SmrtConfig> {\n const {\n configPath,\n searchParents = true,\n searchFrom,\n cache = true,\n } = options;\n\n // Return cached config if available\n const cached = getCachedConfig();\n if (cache && cached) {\n return cached;\n }\n\n // Initialize or reuse cosmiconfig explorer\n let explorer = getExplorer();\n if (!explorer || !cache) {\n explorer = cosmiconfig(MODULE_NAME, {\n searchPlaces: [\n `${MODULE_NAME}.config.js`,\n `${MODULE_NAME}.config.mjs`,\n `${MODULE_NAME}.config.cjs`,\n `${MODULE_NAME}.config.ts`,\n `${MODULE_NAME}.config.mts`,\n `${MODULE_NAME}.config.cts`,\n `${MODULE_NAME}.config.json`,\n ],\n // cosmiconfig has no built-in TypeScript loader; register jiti for the TS\n // extensions so a `smrt.config.ts` scaffold loads end to end (#1783).\n loaders: {\n '.ts': typeScriptLoader,\n '.mts': typeScriptLoader,\n '.cts': typeScriptLoader,\n },\n // When not searching parents, stop at the actual search root so\n // `searchParents: false` is honored even if `searchFrom` is outside the\n // cwd tree (otherwise the upward walk from `searchFrom` never hits cwd and\n // runs to the filesystem root).\n stopDir: searchParents ? undefined : (searchFrom ?? process.cwd()),\n cache: cache, // Respect cache option\n });\n setExplorer(explorer);\n }\n\n let result: Awaited<ReturnType<typeof explorer.load>> = null;\n\n // Load from specific path or search\n try {\n if (configPath) {\n result = await explorer.load(configPath);\n } else {\n result = await explorer.search(searchFrom ?? process.cwd());\n }\n } catch (error) {\n // A genuinely-absent config file is not an error: an explicit `configPath`\n // that doesn't exist (ENOENT) legitimately falls back to empty config, and\n // `search()` returns null (never throws) when no file is found.\n if (isFileNotFound(error)) {\n return {};\n }\n // A config file that EXISTS but fails to load/parse — syntax error, bad\n // import, invalid JSON — is a real problem. Surface it loudly instead of\n // silently returning {} and falling back to defaults, which masks broken\n // config and leaves the developer thinking their settings applied (#1579).\n const where = configPath ? ` \"${configPath}\"` : '';\n throw new Error(\n `Failed to load smrt config${where}: ${\n error instanceof Error ? error.message : String(error)\n }`,\n { cause: error },\n );\n }\n\n const config: SmrtConfig = result?.config || {};\n\n // Cache the config\n if (cache) {\n setCachedConfig(config);\n }\n\n return config;\n}\n\n/**\n * Clear the internal loader cache.\n *\n * Resets `globalThis.__smrtLoaderCachedConfig` and invalidates the cosmiconfig\n * explorer so that the next `loadConfig()` call performs a fresh file search.\n *\n * This is a low-level helper. Consumer code should call {@link clearCache}\n * from `index.ts` instead, which also clears the runtime-override store.\n *\n * @see {@link clearCache}\n */\nexport function clearConfigCache(): void {\n setCachedConfig(null);\n\n // Clear cosmiconfig's cache\n const explorer = getExplorer();\n if (explorer) {\n explorer.clearCaches();\n setExplorer(null);\n }\n}\n","/**\n * Stable application-runtime profile contract.\n *\n * Profiles select infrastructure composition only. Domain models, generated\n * surfaces, effect metadata, and approval policy are deliberately outside the\n * override surface so moving an application cannot silently change behavior.\n */\n\nexport type ApplicationRuntimeProfile = 'local' | 'self-hosted' | 'cloud';\n\nexport type DatabaseEngine = 'sqlite' | 'postgres';\nexport type ConnectionOwnership = 'user' | 'operator' | 'managed';\nexport type AuthenticationProvider =\n | 'owner-bootstrap'\n | 'oidc'\n | 'magic-link'\n | 'hosted-identity';\nexport type OwnerBootstrapMode = 'single-use' | 'disabled';\nexport type TenancyMode = 'default-tenant' | 'single-tenant' | 'multi-tenant';\nexport type TenantContextMode = 'defaulted' | 'required';\nexport type TenantIsolation = 'application' | 'database-rls';\nexport type AssetStorageProvider =\n | 'local-files'\n | 's3-compatible'\n | 'managed-object-storage';\nexport type AssetOwnership = 'user' | 'operator' | 'managed';\nexport type SecretProvider =\n | 'local-file'\n | 'environment'\n | 'external'\n | 'managed';\nexport type SecretOwnership = 'user' | 'operator' | 'managed';\nexport type JobTopology = 'inline' | 'embedded' | 'external' | 'scalable';\nexport type NetworkExposure = 'loopback' | 'public';\nexport type BackupProvider = 'file-snapshot' | 'operator' | 'managed';\n\nexport interface RuntimeDatabaseProvider {\n readonly engine: DatabaseEngine;\n readonly connectionOwnership: ConnectionOwnership;\n}\n\nexport interface RuntimeAuthenticationProvider {\n readonly provider: AuthenticationProvider;\n readonly ownerBootstrap: OwnerBootstrapMode;\n}\n\nexport interface RuntimeTenancyProvider {\n readonly mode: TenancyMode;\n readonly context: TenantContextMode;\n readonly isolation: TenantIsolation;\n}\n\nexport interface RuntimeAssetProvider {\n readonly provider: AssetStorageProvider;\n readonly ownership: AssetOwnership;\n}\n\nexport interface RuntimeSecretProvider {\n readonly provider: SecretProvider;\n readonly ownership: SecretOwnership;\n}\n\nexport interface RuntimeJobProvider {\n readonly topology: JobTopology;\n}\n\nexport interface RuntimeNetworkProvider {\n readonly exposure: NetworkExposure;\n readonly tls: boolean;\n}\n\nexport interface RuntimePortabilityProvider {\n readonly backup: BackupProvider;\n readonly logicalExport: true;\n readonly logicalImport: true;\n}\n\n/** Fully selected infrastructure providers for an application runtime. */\nexport interface RuntimeProviders {\n readonly database: RuntimeDatabaseProvider;\n readonly authentication: RuntimeAuthenticationProvider;\n readonly tenancy: RuntimeTenancyProvider;\n readonly assets: RuntimeAssetProvider;\n readonly secrets: RuntimeSecretProvider;\n readonly jobs: RuntimeJobProvider;\n readonly network: RuntimeNetworkProvider;\n readonly portability: RuntimePortabilityProvider;\n}\n\n/**\n * Explicit provider changes layered over a profile's safe preset.\n *\n * Every override is revalidated with the completed composition. Capability and\n * surface-policy fields are intentionally absent: providers cannot use this\n * seam to weaken cross-profile invariants.\n */\nexport interface RuntimeProviderOverrides {\n database?: Partial<RuntimeDatabaseProvider>;\n authentication?: Partial<RuntimeAuthenticationProvider>;\n tenancy?: Partial<RuntimeTenancyProvider>;\n assets?: Partial<RuntimeAssetProvider>;\n secrets?: Partial<RuntimeSecretProvider>;\n jobs?: Partial<RuntimeJobProvider>;\n network?: Partial<RuntimeNetworkProvider>;\n}\n\n/** Canonical config stored at `runtime` in `smrt.config.ts`. */\nexport interface ApplicationRuntimeConfig {\n profile: ApplicationRuntimeProfile;\n providers?: RuntimeProviderOverrides;\n}\n\n/** Derived capabilities used by startup, doctor, tests, and agent inspection. */\nexport interface RuntimeCapabilities {\n readonly ownerBootstrap: boolean;\n readonly publicAuthentication: boolean;\n readonly defaultTenant: boolean;\n readonly multiTenant: boolean;\n readonly localAssets: boolean;\n readonly externalWorkers: boolean;\n readonly backup: true;\n readonly logicalExport: true;\n readonly logicalImport: true;\n readonly diagnostics: true;\n readonly orchestrationHealth: boolean;\n}\n\n/** Behaviors that provider/profile selection is never allowed to change. */\nexport interface RuntimeInvariants {\n readonly domainModels: 'identical';\n readonly generatedRest: 'identical';\n readonly generatedCli: 'identical';\n readonly generatedMcp: 'identical';\n readonly generatedWebMcp: 'identical';\n readonly mcpExposurePolicy: 'identical';\n readonly webMcpExposurePolicy: 'identical';\n readonly actionEffects: 'identical';\n readonly approvalPolicy: 'identical';\n readonly authorizationRecords: 'required';\n readonly jobInvocation: 'identical';\n readonly portability: 'logical-export-import';\n}\n\nexport interface RuntimeOverrideReport {\n readonly path: string;\n readonly from: string | boolean;\n readonly to: string | boolean;\n}\n\n/** Deterministic, secret-free runtime diagnostic snapshot. */\nexport interface ResolvedApplicationRuntime {\n readonly schemaVersion: 1;\n readonly profile: ApplicationRuntimeProfile;\n readonly providers: RuntimeProviders;\n readonly capabilities: RuntimeCapabilities;\n readonly invariants: RuntimeInvariants;\n readonly diagnostics: {\n readonly secretValuesIncluded: false;\n readonly overrides: readonly RuntimeOverrideReport[];\n readonly unsafeOverrides: readonly [];\n };\n}\n\nexport interface RuntimeProfileValidationIssue {\n code: 'invalid_config' | 'unsupported_field' | 'incompatible_provider';\n path: string;\n message: string;\n recovery: string;\n}\n\nfunction isApplicationRuntimeProfile(\n value: unknown,\n): value is ApplicationRuntimeProfile {\n return value === 'local' || value === 'self-hosted' || value === 'cloud';\n}\n\nfunction invalidProfileIssue(): RuntimeProfileValidationIssue {\n return {\n code: 'invalid_config',\n path: 'profile',\n message: 'must be local, self-hosted, or cloud.',\n recovery: 'Select one documented runtime profile.',\n };\n}\n\n/** Aggregates deterministic, actionable failures before application startup. */\nexport class RuntimeProfileValidationError extends Error {\n readonly issues: readonly RuntimeProfileValidationIssue[];\n\n constructor(issues: RuntimeProfileValidationIssue[]) {\n super(\n `Invalid application runtime profile:\\n${issues\n .map(\n (issue) =>\n `- ${issue.path}: ${issue.message} Recovery: ${issue.recovery}`,\n )\n .join('\\n')}`,\n );\n this.name = 'RuntimeProfileValidationError';\n this.issues = Object.freeze(\n issues.map((issue) => Object.freeze({ ...issue })),\n );\n }\n}\n\nconst PROFILE_DEFAULTS: Record<ApplicationRuntimeProfile, RuntimeProviders> = {\n local: {\n database: { engine: 'sqlite', connectionOwnership: 'user' },\n authentication: {\n provider: 'owner-bootstrap',\n ownerBootstrap: 'single-use',\n },\n tenancy: {\n mode: 'default-tenant',\n context: 'defaulted',\n isolation: 'application',\n },\n assets: { provider: 'local-files', ownership: 'user' },\n secrets: { provider: 'local-file', ownership: 'user' },\n jobs: { topology: 'embedded' },\n network: { exposure: 'loopback', tls: false },\n portability: {\n backup: 'file-snapshot',\n logicalExport: true,\n logicalImport: true,\n },\n },\n 'self-hosted': {\n database: { engine: 'postgres', connectionOwnership: 'operator' },\n authentication: { provider: 'oidc', ownerBootstrap: 'disabled' },\n tenancy: {\n mode: 'single-tenant',\n context: 'defaulted',\n isolation: 'application',\n },\n assets: { provider: 's3-compatible', ownership: 'operator' },\n secrets: { provider: 'environment', ownership: 'operator' },\n jobs: { topology: 'external' },\n network: { exposure: 'public', tls: true },\n portability: {\n backup: 'operator',\n logicalExport: true,\n logicalImport: true,\n },\n },\n cloud: {\n database: { engine: 'postgres', connectionOwnership: 'managed' },\n authentication: {\n provider: 'hosted-identity',\n ownerBootstrap: 'disabled',\n },\n tenancy: {\n mode: 'multi-tenant',\n context: 'required',\n isolation: 'database-rls',\n },\n assets: { provider: 'managed-object-storage', ownership: 'managed' },\n secrets: { provider: 'managed', ownership: 'managed' },\n jobs: { topology: 'scalable' },\n network: { exposure: 'public', tls: true },\n portability: {\n backup: 'managed',\n logicalExport: true,\n logicalImport: true,\n },\n },\n};\n\nconst INVARIANTS: RuntimeInvariants = {\n domainModels: 'identical',\n generatedRest: 'identical',\n generatedCli: 'identical',\n generatedMcp: 'identical',\n generatedWebMcp: 'identical',\n mcpExposurePolicy: 'identical',\n webMcpExposurePolicy: 'identical',\n actionEffects: 'identical',\n approvalPolicy: 'identical',\n authorizationRecords: 'required',\n jobInvocation: 'identical',\n portability: 'logical-export-import',\n};\n\nconst PROVIDER_FIELDS = {\n database: ['engine', 'connectionOwnership'],\n authentication: ['provider', 'ownerBootstrap'],\n tenancy: ['mode', 'context', 'isolation'],\n assets: ['provider', 'ownership'],\n secrets: ['provider', 'ownership'],\n jobs: ['topology'],\n network: ['exposure', 'tls'],\n} as const;\n\ntype OverridableProvider = keyof typeof PROVIDER_FIELDS;\n\nconst PROVIDER_VALUE_DOMAINS = {\n database: {\n engine: ['sqlite', 'postgres'],\n connectionOwnership: ['user', 'operator', 'managed'],\n },\n authentication: {\n provider: ['owner-bootstrap', 'oidc', 'magic-link', 'hosted-identity'],\n ownerBootstrap: ['single-use', 'disabled'],\n },\n tenancy: {\n mode: ['default-tenant', 'single-tenant', 'multi-tenant'],\n context: ['defaulted', 'required'],\n isolation: ['application', 'database-rls'],\n },\n assets: {\n provider: ['local-files', 's3-compatible', 'managed-object-storage'],\n ownership: ['user', 'operator', 'managed'],\n },\n secrets: {\n provider: ['local-file', 'environment', 'external', 'managed'],\n ownership: ['user', 'operator', 'managed'],\n },\n jobs: { topology: ['inline', 'embedded', 'external', 'scalable'] },\n network: { exposure: ['loopback', 'public'], tls: [false, true] },\n} as const satisfies {\n [Provider in OverridableProvider]: Record<\n (typeof PROVIDER_FIELDS)[Provider][number],\n readonly unknown[]\n >;\n};\n\nfunction isPlainRecord(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction cloneDefaults(profile: ApplicationRuntimeProfile): RuntimeProviders {\n const defaults = PROFILE_DEFAULTS[profile];\n return {\n database: { ...defaults.database },\n authentication: { ...defaults.authentication },\n tenancy: { ...defaults.tenancy },\n assets: { ...defaults.assets },\n secrets: { ...defaults.secrets },\n jobs: { ...defaults.jobs },\n network: { ...defaults.network },\n portability: { ...defaults.portability },\n };\n}\n\nfunction rejectUnknownFields(\n input: Record<string, unknown>,\n): RuntimeProfileValidationIssue[] {\n const issues: RuntimeProfileValidationIssue[] = [];\n for (const key of Object.keys(input).sort()) {\n if (key !== 'profile' && key !== 'providers') {\n issues.push({\n code: 'unsupported_field',\n path: key,\n message: 'is not part of the runtime-profile contract.',\n recovery:\n 'Move provider selection under providers or remove this field.',\n });\n }\n }\n\n if (\n Object.hasOwn(input, 'profile') &&\n !isApplicationRuntimeProfile(input.profile)\n ) {\n issues.push(invalidProfileIssue());\n }\n\n const providers = Object.hasOwn(input, 'providers')\n ? input.providers\n : undefined;\n if (providers === undefined) return issues;\n if (!isPlainRecord(providers)) {\n issues.push({\n code: 'invalid_config',\n path: 'providers',\n message: 'must be an object with Object.prototype or null.',\n recovery: 'Use providers: { database: { ... } } or omit providers.',\n });\n return issues;\n }\n\n for (const providerName of Object.keys(providers).sort()) {\n if (!Object.hasOwn(PROVIDER_FIELDS, providerName)) {\n issues.push({\n code: 'unsupported_field',\n path: `providers.${providerName}`,\n message: 'is not an overridable infrastructure provider.',\n recovery:\n 'Use database, authentication, tenancy, assets, secrets, jobs, or network.',\n });\n continue;\n }\n const value = providers[providerName];\n if (!isPlainRecord(value)) {\n issues.push({\n code: 'invalid_config',\n path: `providers.${providerName}`,\n message: 'must be an object with Object.prototype or null.',\n recovery: `Provide a partial ${providerName} provider object or omit it.`,\n });\n continue;\n }\n const allowed = PROVIDER_FIELDS[\n providerName as OverridableProvider\n ] as readonly string[];\n const valueDomains = PROVIDER_VALUE_DOMAINS[\n providerName as OverridableProvider\n ] as Record<string, readonly unknown[]>;\n for (const field of Object.keys(value).sort()) {\n if (!allowed.includes(field)) {\n issues.push({\n code: 'unsupported_field',\n path: `providers.${providerName}.${field}`,\n message: 'is not a supported provider override.',\n recovery:\n 'Remove secret values and application-policy fields; configure only documented provider selectors.',\n });\n continue;\n }\n const suppliedValue = Object.hasOwn(value, field)\n ? value[field]\n : undefined;\n const allowedValues = valueDomains[field];\n if (\n suppliedValue !== undefined &&\n !allowedValues.includes(suppliedValue)\n ) {\n issues.push({\n code: 'invalid_config',\n path: `providers.${providerName}.${field}`,\n message: `must be one of ${allowedValues.map(String).join(', ')}.`,\n recovery:\n 'Choose a documented provider selector value; supplied values are omitted from diagnostics.',\n });\n }\n }\n }\n return issues;\n}\n\n/**\n * Validate only the runtime contract's object and field shape.\n *\n * @internal Used while composing configuration layers before a profile is\n * necessarily available. Full profile/provider compatibility validation stays\n * in {@link resolveApplicationRuntime}.\n */\nexport function validateApplicationRuntimeConfigShape(\n config: unknown,\n): readonly RuntimeProfileValidationIssue[] {\n if (!isPlainRecord(config)) {\n return [\n {\n code: 'invalid_config',\n path: 'runtime',\n message: 'must be an object with Object.prototype or null.',\n recovery:\n 'Provide runtime: { profile: \"local\" | \"self-hosted\" | \"cloud\" }.',\n },\n ];\n }\n return rejectUnknownFields(config);\n}\n\nfunction applyOverrides(\n providers: RuntimeProviders,\n input: Record<string, unknown>,\n): RuntimeOverrideReport[] {\n const overrideRoot =\n Object.hasOwn(input, 'providers') && isPlainRecord(input.providers)\n ? input.providers\n : {};\n const report: RuntimeOverrideReport[] = [];\n\n for (const providerName of Object.keys(\n PROVIDER_FIELDS,\n ) as OverridableProvider[]) {\n if (!Object.hasOwn(overrideRoot, providerName)) continue;\n const supplied = overrideRoot[providerName];\n if (!isPlainRecord(supplied)) continue;\n const target = providers[providerName] as unknown as Record<\n string,\n unknown\n >;\n for (const field of PROVIDER_FIELDS[providerName]) {\n if (!Object.hasOwn(supplied, field)) continue;\n const value = supplied[field];\n if (value === undefined) continue;\n const previous = target[field];\n target[field] = value;\n if (value !== previous) {\n report.push({\n path: `providers.${providerName}.${field}`,\n from: previous as string | boolean,\n to: value as string | boolean,\n });\n }\n }\n }\n return report;\n}\n\nfunction incompatible(\n issues: RuntimeProfileValidationIssue[],\n path: string,\n expected: string,\n recovery: string,\n): void {\n issues.push({\n code: 'incompatible_provider',\n path,\n message: `is incompatible; ${expected}.`,\n recovery,\n });\n}\n\nfunction requireAllowed(\n issues: RuntimeProfileValidationIssue[],\n path: string,\n actual: unknown,\n allowed: readonly unknown[],\n profile: ApplicationRuntimeProfile,\n): void {\n if (!allowed.includes(actual)) {\n incompatible(\n issues,\n path,\n `${profile} requires one of ${allowed.map(String).join(', ')}`,\n `Choose a supported ${profile} provider or select a different profile.`,\n );\n }\n}\n\nfunction validateComposition(\n profile: ApplicationRuntimeProfile,\n providers: RuntimeProviders,\n): RuntimeProfileValidationIssue[] {\n const issues: RuntimeProfileValidationIssue[] = [];\n const allow = (path: string, actual: unknown, values: readonly unknown[]) =>\n requireAllowed(issues, path, actual, values, profile);\n\n if (profile === 'local') {\n allow('providers.database.engine', providers.database.engine, ['sqlite']);\n allow(\n 'providers.database.connectionOwnership',\n providers.database.connectionOwnership,\n ['user'],\n );\n allow(\n 'providers.authentication.provider',\n providers.authentication.provider,\n ['owner-bootstrap'],\n );\n allow(\n 'providers.authentication.ownerBootstrap',\n providers.authentication.ownerBootstrap,\n ['single-use'],\n );\n allow('providers.tenancy.mode', providers.tenancy.mode, ['default-tenant']);\n allow('providers.tenancy.context', providers.tenancy.context, [\n 'defaulted',\n ]);\n allow('providers.tenancy.isolation', providers.tenancy.isolation, [\n 'application',\n ]);\n allow('providers.assets.provider', providers.assets.provider, [\n 'local-files',\n ]);\n allow('providers.assets.ownership', providers.assets.ownership, ['user']);\n allow('providers.secrets.provider', providers.secrets.provider, [\n 'local-file',\n ]);\n allow('providers.secrets.ownership', providers.secrets.ownership, ['user']);\n allow('providers.jobs.topology', providers.jobs.topology, [\n 'inline',\n 'embedded',\n ]);\n allow('providers.network.exposure', providers.network.exposure, [\n 'loopback',\n ]);\n allow('providers.network.tls', providers.network.tls, [false, true]);\n } else if (profile === 'self-hosted') {\n allow('providers.database.engine', providers.database.engine, ['postgres']);\n allow(\n 'providers.database.connectionOwnership',\n providers.database.connectionOwnership,\n ['operator'],\n );\n allow(\n 'providers.authentication.provider',\n providers.authentication.provider,\n ['oidc', 'magic-link'],\n );\n allow(\n 'providers.authentication.ownerBootstrap',\n providers.authentication.ownerBootstrap,\n ['disabled'],\n );\n allow('providers.tenancy.mode', providers.tenancy.mode, [\n 'single-tenant',\n 'multi-tenant',\n ]);\n allow(\n 'providers.tenancy.context',\n providers.tenancy.context,\n providers.tenancy.mode === 'multi-tenant' ? ['required'] : ['defaulted'],\n );\n allow('providers.tenancy.isolation', providers.tenancy.isolation, [\n 'application',\n 'database-rls',\n ]);\n allow('providers.assets.provider', providers.assets.provider, [\n 'local-files',\n 's3-compatible',\n ]);\n allow('providers.assets.ownership', providers.assets.ownership, [\n 'operator',\n ]);\n allow('providers.secrets.provider', providers.secrets.provider, [\n 'environment',\n 'local-file',\n 'external',\n ]);\n allow('providers.secrets.ownership', providers.secrets.ownership, [\n 'operator',\n ]);\n allow('providers.jobs.topology', providers.jobs.topology, ['external']);\n allow('providers.network.exposure', providers.network.exposure, ['public']);\n allow('providers.network.tls', providers.network.tls, [true]);\n } else {\n allow('providers.database.engine', providers.database.engine, ['postgres']);\n allow(\n 'providers.database.connectionOwnership',\n providers.database.connectionOwnership,\n ['managed'],\n );\n allow(\n 'providers.authentication.provider',\n providers.authentication.provider,\n ['hosted-identity'],\n );\n allow(\n 'providers.authentication.ownerBootstrap',\n providers.authentication.ownerBootstrap,\n ['disabled'],\n );\n allow('providers.tenancy.mode', providers.tenancy.mode, ['multi-tenant']);\n allow('providers.tenancy.context', providers.tenancy.context, ['required']);\n allow('providers.tenancy.isolation', providers.tenancy.isolation, [\n 'application',\n 'database-rls',\n ]);\n allow('providers.assets.provider', providers.assets.provider, [\n 'managed-object-storage',\n 's3-compatible',\n ]);\n allow('providers.assets.ownership', providers.assets.ownership, [\n 'managed',\n ]);\n allow('providers.secrets.provider', providers.secrets.provider, [\n 'managed',\n 'external',\n ]);\n allow('providers.secrets.ownership', providers.secrets.ownership, [\n 'managed',\n ]);\n allow('providers.jobs.topology', providers.jobs.topology, ['scalable']);\n allow('providers.network.exposure', providers.network.exposure, ['public']);\n allow('providers.network.tls', providers.network.tls, [true]);\n }\n\n return issues;\n}\n\nfunction deriveCapabilities(\n profile: ApplicationRuntimeProfile,\n providers: RuntimeProviders,\n): RuntimeCapabilities {\n return {\n ownerBootstrap: providers.authentication.ownerBootstrap === 'single-use',\n publicAuthentication:\n providers.authentication.provider !== 'owner-bootstrap',\n defaultTenant: providers.tenancy.context === 'defaulted',\n multiTenant: providers.tenancy.mode === 'multi-tenant',\n localAssets: providers.assets.provider === 'local-files',\n externalWorkers:\n providers.jobs.topology === 'external' ||\n providers.jobs.topology === 'scalable',\n backup: true,\n logicalExport: true,\n logicalImport: true,\n diagnostics: true,\n orchestrationHealth: profile !== 'local',\n };\n}\n\nfunction deepFreeze<T>(value: T): Readonly<T> {\n if (value && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.freeze(value);\n for (const child of Object.values(value as Record<string, unknown>)) {\n deepFreeze(child);\n }\n }\n return value;\n}\n\n/**\n * Resolve a safe preset and explicit provider overrides into a stable snapshot.\n *\n * The resolver is pure: it reads no environment variables, paths, credentials,\n * clocks, or process state. Invalid and unknown fields fail before startup.\n */\nexport function resolveApplicationRuntime(\n config: ApplicationRuntimeConfig,\n): Readonly<ResolvedApplicationRuntime> {\n if (!isPlainRecord(config)) {\n throw new RuntimeProfileValidationError([\n ...validateApplicationRuntimeConfigShape(config),\n ]);\n }\n\n const unknownIssues = validateApplicationRuntimeConfigShape(config);\n const profile = Object.hasOwn(config, 'profile') ? config.profile : undefined;\n if (!isApplicationRuntimeProfile(profile)) {\n throw new RuntimeProfileValidationError([\n ...unknownIssues,\n ...(Object.hasOwn(config, 'profile') ? [] : [invalidProfileIssue()]),\n ]);\n }\n\n const providers = cloneDefaults(profile);\n const overrides = applyOverrides(providers, config);\n const issues = [...unknownIssues, ...validateComposition(profile, providers)];\n if (issues.length > 0) throw new RuntimeProfileValidationError(issues);\n\n return deepFreeze({\n schemaVersion: 1,\n profile,\n providers,\n capabilities: deriveCapabilities(profile, providers),\n invariants: { ...INVARIANTS },\n diagnostics: {\n secretValuesIncluded: false,\n overrides,\n unsafeOverrides: [],\n },\n }) as Readonly<ResolvedApplicationRuntime>;\n}\n\n/** Return a fresh copy of the documented preset for inspection and tooling. */\nexport function getApplicationRuntimePreset(\n profile: ApplicationRuntimeProfile,\n): Readonly<RuntimeProviders> {\n if (!isApplicationRuntimeProfile(profile)) {\n throw new RuntimeProfileValidationError([invalidProfileIssue()]);\n }\n return deepFreeze(cloneDefaults(profile));\n}\n","import {\n RuntimeProfileValidationError,\n validateApplicationRuntimeConfigShape,\n} from './runtime-profile.js';\nimport type { SmrtConfig } from './types.js';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtRuntimeConfig: Partial<SmrtConfig> | undefined;\n}\n\n// Runtime config overrides\nglobalThis.__smrtRuntimeConfig ??= {};\n\n// Keys that must never be written when merging untrusted / DB-exported config,\n// to avoid prototype pollution.\nconst UNSAFE_KEYS = new Set<string>(['__proto__', 'constructor', 'prototype']);\n\nfunction defineOwn(\n target: Record<string, unknown>,\n key: string,\n value: unknown,\n): void {\n Object.defineProperty(target, key, {\n configurable: true,\n enumerable: true,\n value,\n writable: true,\n });\n}\n\n/**\n * True only for plain config maps (prototype is `Object.prototype` or `null`),\n * not arrays, `Date`/`RegExp`, or class instances.\n */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n\n/**\n * Recursively clone arrays and plain objects so the result shares no mutable\n * container with its input (#1579). Non-plain leaves — primitives, and crucially\n * **functions / class instances** — are passed through by reference: config\n * sections are typed `Record<string, unknown>` and may legitimately hold\n * callbacks, which are neither structured-cloneable nor the mutable containers\n * the aliasing guarantee targets.\n */\nfunction deepClone<V>(value: V): V {\n if (Array.isArray(value)) {\n return value.map((item) => deepClone(item)) as V;\n }\n if (isPlainObject(value)) {\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(value)) {\n if (UNSAFE_KEYS.has(key)) {\n continue;\n }\n defineOwn(out, key, deepClone(value[key]));\n }\n return out as V;\n }\n return value;\n}\n\n/**\n * Deep-merge two plain objects, with `source` values taking precedence over\n * `target` values at each key level.\n *\n * Rules:\n * - Both values are plain objects → recurse.\n * - `source` value is `null` or `undefined` → keep the `target` value.\n * - Otherwise → `source` value replaces `target` value (including `false`, `0`, `''`).\n *\n * The returned object owns all of its data: arrays and plain objects from both\n * inputs are deep-cloned, so mutating an input later can't leak into the result\n * (or the global runtime store via {@link setConfig}) and vice versa (#1579).\n * Each value is cloned exactly once — carried-over target keys and overriding\n * source leaves are cloned here, and shared object keys are produced by the\n * recursive call rather than re-cloned.\n *\n * @param target - Base object (lower priority).\n * @param source - Override object (higher priority).\n * @returns A new merged object.\n */\nfunction deepMerge<T extends Record<string, unknown>>(\n target: T,\n source: Partial<T>,\n): T {\n // Typed as Record<string, unknown> (not the generic T) so the own-key writes\n // below are well-typed — indexing a generic T for *write* is a TS2862 error.\n const result: Record<string, unknown> = {};\n const tgt = target as Record<string, unknown>;\n const src = source as Record<string, unknown>;\n\n // Carry over target-only keys, cloned so the result never aliases `target`.\n for (const key of Object.keys(tgt)) {\n if (UNSAFE_KEYS.has(key) || Object.hasOwn(src, key)) {\n continue;\n }\n defineOwn(result, key, deepClone(tgt[key]));\n }\n\n for (const key of Object.keys(src)) {\n if (UNSAFE_KEYS.has(key)) {\n continue;\n }\n const sourceValue = src[key];\n const targetValue = Object.hasOwn(tgt, key) ? tgt[key] : undefined;\n\n if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {\n // Recurse — the child call returns a fully-owned object (no re-clone).\n defineOwn(result, key, deepMerge(targetValue, sourceValue));\n } else if (sourceValue !== undefined && sourceValue !== null) {\n // `source` wins — clone its (possibly nested) value so we don't alias it.\n defineOwn(result, key, deepClone(sourceValue));\n } else if (targetValue !== undefined) {\n // `source` is null/undefined — keep the (cloned) target value.\n defineOwn(result, key, deepClone(targetValue));\n }\n }\n\n return result as T;\n}\n\n/**\n * Deep-merge `config` into the in-memory runtime override store.\n *\n * Runtime overrides take the highest priority in the merge order:\n * runtime > file config > defaults. Subsequent calls accumulate — they do not\n * replace previous overrides, except that explicitly switching runtime profile\n * resets the stored runtime provider subtree. Call {@link clearRuntimeConfig}\n * to reset all overrides.\n *\n * This is the low-level setter; consumer code should call {@link setConfig}\n * from `index.ts` which delegates here.\n *\n * @param config - Partial config to merge into the runtime store.\n *\n * @see {@link clearRuntimeConfig}\n * @see {@link getRuntimeConfig}\n */\nexport function setConfig(config: Partial<SmrtConfig>): void {\n const incomingRuntime = Object.hasOwn(config, 'runtime')\n ? (config.runtime as unknown)\n : undefined;\n if (\n incomingRuntime !== null &&\n (typeof incomingRuntime === 'object' ||\n typeof incomingRuntime === 'function') &&\n Object.hasOwn(incomingRuntime, 'profile')\n ) {\n const profileIssues = validateApplicationRuntimeConfigShape(\n incomingRuntime,\n ).filter((issue) => issue.path === 'runtime' || issue.path === 'profile');\n if (profileIssues.length > 0) {\n throw new RuntimeProfileValidationError([...profileIssues]);\n }\n }\n\n let current = globalThis.__smrtRuntimeConfig || {};\n const currentRuntime =\n Object.hasOwn(current, 'runtime') && isPlainObject(current.runtime)\n ? current.runtime\n : undefined;\n const incomingRuntimeMap =\n Object.hasOwn(config, 'runtime') && isPlainObject(config.runtime)\n ? config.runtime\n : undefined;\n const switchesProfile =\n currentRuntime !== undefined &&\n incomingRuntimeMap !== undefined &&\n Object.hasOwn(currentRuntime, 'profile') &&\n Object.hasOwn(incomingRuntimeMap, 'profile') &&\n currentRuntime.profile !== incomingRuntimeMap.profile;\n\n if (switchesProfile) {\n current = deepClone(current);\n const resetRuntime = current.runtime as unknown as Record<string, unknown>;\n delete resetRuntime.providers;\n }\n\n globalThis.__smrtRuntimeConfig = deepMerge(current, config);\n}\n\n/**\n * Return the current runtime configuration override store.\n *\n * Used internally by {@link getModuleConfig} and {@link getPackageConfig} to\n * layer runtime overrides on top of file-based config. Prefer those helpers\n * for reading config; this is a low-level accessor.\n *\n * @returns The accumulated runtime config partial.\n */\nexport function getRuntimeConfig(): Partial<SmrtConfig> {\n return globalThis.__smrtRuntimeConfig || {};\n}\n\n/**\n * Reset the runtime configuration override store to an empty object.\n *\n * Called by {@link clearCache} in `index.ts` as part of a full cache reset.\n * Useful in tests to ensure a clean state between cases.\n *\n * @see {@link setConfig}\n */\nexport function clearRuntimeConfig(): void {\n globalThis.__smrtRuntimeConfig = {};\n}\n\n/**\n * Merge three config layers in ascending priority order.\n *\n * Priority (highest to lowest):\n * 1. `runtime` — runtime overrides set via {@link setConfig}\n * 2. `fileConfig` — values loaded from `smrt.config.js`\n * 3. `defaults` — caller-supplied fallback values\n *\n * Uses {@link deepMerge} internally, so `null` / `undefined` source values\n * never overwrite existing target values.\n *\n * @param defaults - Lowest-priority base values (caller defaults).\n * @param fileConfig - Mid-priority values from the loaded config file.\n * @param runtime - Highest-priority runtime override values.\n * @returns A new object with all three layers merged.\n *\n * @example\n * ```ts\n * const merged = mergeConfigs(\n * { timeout: 5000, retries: 3 },\n * { retries: 5 },\n * { timeout: 1000 },\n * );\n * // => { timeout: 1000, retries: 5 }\n * ```\n */\nexport function mergeConfigs<T extends Record<string, unknown>>(\n defaults: T,\n fileConfig: Partial<T>,\n runtime: Partial<T>,\n): T {\n let result = { ...defaults };\n result = deepMerge(result, fileConfig);\n result = deepMerge(result, runtime);\n return result;\n}\n","/**\n * Config export utilities for static site generation\n *\n * These utilities enable exporting database-backed agent configurations\n * to static files for SSG sites.\n *\n * @module\n */\n\n/**\n * Patterns that identify sensitive configuration keys. Any key matching one of\n * these patterns is stripped during {@link sanitizeConfig}.\n *\n * Redaction policy\n * ----------------\n * These exports feed static-site-generation (SSG) artifacts that are typically\n * served publicly, so the cost of leaking a secret is far higher than the cost\n * of over-redacting a benign key. We therefore **bias toward over-redaction**:\n * the patterns are mostly case-insensitive *substring* matches (no word\n * boundaries) so they catch every common casing/separator convention —\n * `apiKey`, `api_key`, `API-KEY`, `apikey`, etc. A few are deliberately scoped\n * to avoid benign collisions: `\\bcert\\b` (not `concert`), and `oauth` /\n * `authorization` rather than a bare `auth` (which would hit `author`/`authors`).\n *\n * The previous implementation used `\\b` word boundaries (e.g. `/\\bauth\\b/i`,\n * `/\\bkey\\b$/i`). In JS regex `\\b` sits between a word char and a non-word char,\n * and `_`/letters/digits are all word chars, so those boundaries never fire\n * inside `camelCase` or `snake_case` identifiers — leaking keys like `api_key`,\n * `accessKey`, `authorization`, `auth_token`, `connectionString`, and `dbUrl`.\n * See issue #1357.\n *\n * To keep over-redaction targeted (rather than redacting on a bare `key`/`url`\n * substring, which would strip benign `apiEndpoint`-style nesting that legit\n * config relies on, e.g. `db: { url }`), `key`/`url` only trigger when paired\n * with a secret-adjacent qualifier (`apiKey`, `accessKey`, `dbUrl`, …).\n */\nconst SECRET_PATTERNS = [\n // Direct secret-bearing terms (substring, any casing/separator).\n /secret/i,\n /password/i,\n /passwd/i,\n // Short credential aliases (#1381, codex audit cross-finding): `pass` / `pwd`\n // keys that `password`/`passwd` don't cover. Segment- or camelCase-anchored\n // (a capital after a letter/digit) so `smtpPass`/`databasePwd`/`db_pass`/`pwd`\n // are stripped while `compass`/`bypass`/`passive` are not.\n /(^|[_-])pass([_-]|$)/i,\n /(^|[_-])pwd([_-]|$)/i,\n /[a-z0-9]Pass([_-]|$)/,\n /[a-z0-9]Pwd([_-]|$)/,\n /^(pass|pwd)$/i,\n /token/i,\n /credential/i,\n /private/i,\n // NB: not a bare /auth/i — that also matches benign `author`/`authors`\n // metadata (common in site/content/agent config). The segment-anchored form\n // strips a key literally named `auth` (and `auth_token`, `x_auth`,\n // `oauth_token`) without hitting `author`; `oauth`/`authorization` cover the\n // no-separator camelCase cases. (authToken/authSecret also hit /token//secret/.)\n /oauth/i,\n /authorization/i,\n /(^|[_-])auth([_-]|$)/i,\n /cookie/i,\n /salt/i,\n /passphrase/i,\n /\\bcert\\b/i, // cert/certificate, but not \"uncertain\"/\"concert\" style false hits\n /certificate/i,\n // Qualified \"key\" — only when paired with a secret-adjacent prefix, so we\n // strip apiKey/accessKey/signingKey/encryptionKey/privateKey but keep benign\n // keys such as `keywords`, `monkey`, or `keyboardShortcuts`.\n /api[_-]?key/i,\n /access[_-]?key/i,\n /secret[_-]?key/i,\n /signing[_-]?key/i,\n /encryption[_-]?key/i,\n /private[_-]?key/i,\n /public[_-]?key/i,\n // camelCase `<vendor>Key` keys — `consumerKey`, `masterKey`, `sslKey`,\n // `hmacKey`, `sharedKey`, `rootKey`, `clientKey`, `serverKey`, `deployKey`,\n // `sshKey`, etc. The hardcoded prefixes above and the segment-anchored\n // standalone-`key` pattern below only fire on a `_`/`-`/start boundary, so a\n // camelCase secret key with an unlisted vendor prefix slips through (a raw\n // hex/base64 secret then exports verbatim into the public SSG artifact).\n // Mirrors the `[a-z0-9]Pass`/`[a-z0-9]Pwd` idiom: a capital `K` after a\n // letter/digit, followed by a segment/`Id`/end boundary, so `consumerKey` and\n // `masterKeyId` are stripped while benign `keywords`/`monkey`/\n // `keyboardShortcuts` (lowercase `key`) are kept.\n /[a-z0-9]Key([_-]|Id|$)/,\n /(^|[_-])key([_-]|id$|$)/i, // standalone `key`, `key_id`, `keyId`, `aws…KeyId`\n // Connection / database URLs that commonly embed credentials.\n /connection[_-]?string/i,\n /conn[_-]?str/i,\n /(^|[_-])db[_-]?url/i, // dbUrl / db_url / DB_URL\n /database[_-]?url/i,\n /datasource[_-]?url/i,\n];\n\n/** Return `true` when `key` matches any of the {@link SECRET_PATTERNS}. */\nfunction isSecretKey(key: string): boolean {\n return SECRET_PATTERNS.some((pattern) => pattern.test(key));\n}\n\n// Credentials embedded in the *value* of an otherwise-benign key — most\n// importantly DB connection strings like `postgres://user:pass@host:5432/db`\n// stored under keys such as `url` / `database.url` that the key-based\n// {@link SECRET_PATTERNS} deliberately don't match (#1381). Key-based redaction\n// alone leaks these into the published SSG artifact. Mask the userinfo\n// (`scheme://user:pass@host` → `scheme://***@host`) while preserving the\n// non-secret host so the value stays diagnosable. Not anchored/global so it\n// also catches a credential URL embedded mid-string (e.g. `dsn: postgres://u:p@h`)\n// or multiple URLs in one value (review #1549).\nconst CREDENTIAL_URL_RE = /([a-z][a-z0-9+.-]*:\\/\\/)[^/?#@\\s]+@/gi;\n\n/**\n * High-confidence secret *token* shapes that can appear in the value of an\n * otherwise-benign key (#1381). Key-based {@link SECRET_PATTERNS} only catch a\n * secret when the *key* is named like one — a provider token pasted into a\n * `note`, `header`, `instructions`, or `defaultModel`-adjacent field leaks\n * verbatim into the published SSG artifact. These patterns match the\n * distinctive prefixes/structures of real credentials so we can mask the token\n * while leaving surrounding prose intact. They are intentionally specific\n * (prefix- or structure-anchored) to avoid mangling benign config values.\n *\n * Coverage:\n * - OpenAI / Anthropic style `sk-...` and `sk-proj-...` (and `sk-ant-...`)\n * - GitHub PATs / app tokens: `ghp_ gho_ ghu_ ghs_ ghr_` + fine-grained `github_pat_`\n * - Slack tokens: `xox[abprs]-...`\n * - Google API keys: `AIza...`\n * - AWS access key IDs: `AKIA`/`ASIA` + 16 uppercase alnum\n * - Stripe live/test keys: `sk_live_` / `sk_test_` / `rk_live_` / `rk_test_`\n * - `Bearer <token>` Authorization header values\n * - PEM private-key blocks\n */\nconst VALUE_SECRET_PATTERNS: Array<{ re: RegExp; replace: string }> = [\n // PEM private key block (multi-line) — collapse the whole block.\n {\n re: /-----BEGIN (?:[A-Z ]+ )?PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z ]+ )?PRIVATE KEY-----/g,\n replace: '***REDACTED PRIVATE KEY***',\n },\n // OpenAI / Anthropic secret keys: sk-, sk-proj-, sk-ant-...\n { re: /\\bsk-(?:proj-|ant-)?[A-Za-z0-9_-]{16,}/g, replace: '***' },\n // Stripe-style prefixed keys.\n { re: /\\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g, replace: '***' },\n // GitHub tokens.\n { re: /\\bgh[pousr]_[A-Za-z0-9]{20,}/g, replace: '***' },\n { re: /\\bgithub_pat_[A-Za-z0-9_]{22,}/g, replace: '***' },\n // Slack tokens.\n { re: /\\bxox[abprs]-[A-Za-z0-9-]{10,}/g, replace: '***' },\n // Google API keys.\n { re: /\\bAIza[A-Za-z0-9_-]{35}/g, replace: '***' },\n // AWS access key IDs.\n { re: /\\b(?:AKIA|ASIA)[A-Z0-9]{16}\\b/g, replace: '***' },\n // Bearer / JWT authorization values (header strings pasted into config).\n // Case-insensitive: HTTP auth schemes are case-insensitive and clients often\n // emit lowercase `bearer ` (review #1549).\n {\n re: /\\bBearer\\s+[A-Za-z0-9._~+/=-]{16,}/gi,\n replace: 'Bearer ***',\n },\n];\n\n/**\n * Redact credentials embedded in a string value: URL userinfo plus any\n * high-confidence secret-token shape (see {@link VALUE_SECRET_PATTERNS}).\n */\nfunction redactValueSecrets(value: string): string {\n let out = value.replace(CREDENTIAL_URL_RE, '$1***@');\n for (const { re, replace } of VALUE_SECRET_PATTERNS) {\n out = out.replace(re, replace);\n }\n return out;\n}\n\n/**\n * Deep-clone a config object, removing every key that looks like a secret.\n *\n * Keys are tested (case-insensitively, across camelCase / snake_case / kebab /\n * UPPER variants) against {@link SECRET_PATTERNS}, which cover secrets such as\n * `apiKey`/`api_key`, `password`, `secret`, `token`, `credential`, `private`,\n * `auth`/`authorization`/`oauth`, `accessKey`, `signingKey`, `encryptionKey`,\n * `connectionString`, and `dbUrl`. The policy biases toward over-redaction\n * because these exports are typically published as static, publicly served\n * assets — see {@link SECRET_PATTERNS} for the full rationale.\n *\n * Nested objects are recursively sanitized; arrays are mapped element-by-element.\n * `number` / `boolean` primitives pass through unchanged; `string` values are\n * additionally run through value-level redaction that masks credentials embedded\n * in a URL (`scheme://user:pass@host` → `scheme://***@host`) as well as\n * high-confidence secret-token shapes pasted into otherwise-benign keys\n * (OpenAI/Anthropic `sk-...`, AWS `AKIA...` access key IDs, GitHub/Slack/Google\n * tokens, `Bearer ...` headers, and PEM private-key blocks) — catching secrets\n * stored under a benign key the key-based patterns don't match.\n *\n * Own `__proto__` / `constructor` / `prototype` keys are dropped to prevent the\n * cloned result's prototype from being reassigned via the `__proto__` setter.\n *\n * This function is called automatically by {@link exportConfig} unless\n * `includeSecrets: true` is passed.\n *\n * @param config - Value to sanitize. Accepts any JSON-serializable structure.\n * @returns A new value with all secret keys omitted. `null` / `undefined` are\n * returned as-is.\n *\n * @example\n * ```ts\n * const sanitized = sanitizeConfig({\n * apiEndpoint: 'https://api.example.com',\n * apiKey: 'sk-secret-123',\n * nested: { password: 'hunter2', name: 'test' },\n * });\n * // => { apiEndpoint: 'https://api.example.com', nested: { name: 'test' } }\n * ```\n *\n * @see {@link exportConfig}\n */\nexport function sanitizeConfig(config: unknown): unknown {\n if (config === null || config === undefined) {\n return config;\n }\n\n if (Array.isArray(config)) {\n return config.map((item) => sanitizeConfig(item));\n }\n\n if (typeof config === 'object') {\n const result: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(config)) {\n // Guard against prototype pollution: a `__proto__` own-key (e.g. from a\n // DB-backed config round-tripped through JSON.parse) would, via the\n // `result[key] = ...` setter, silently reassign the prototype of the\n // sanitized object we hand back to callers. `constructor`/`prototype`\n // are harmless as plain keys but dropped for parity with the merge\n // guards (deepMerge / mergeExportedConfig). See #1381.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n\n // Skip keys that match secret patterns\n if (isSecretKey(key)) {\n continue;\n }\n\n // Recursively sanitize nested objects\n result[key] = sanitizeConfig(value);\n }\n\n return result;\n }\n\n // String primitives get value-level secret redaction (credentials embedded in\n // URLs under benign keys, #1381); other primitives pass through unchanged.\n if (typeof config === 'string') {\n return redactValueSecrets(config);\n }\n return config;\n}\n\n/**\n * Options accepted by {@link exportConfig}.\n */\nexport interface ExportConfigOptions {\n /**\n * Include secrets in the export (use with caution!)\n * @default false\n */\n includeSecrets?: boolean;\n\n /**\n * Output format\n * - 'json': Plain JSON format\n * - 'js': ES module format (export default)\n * @default 'json'\n */\n format?: 'json' | 'js';\n\n /**\n * Number of spaces for indentation\n * @default 2\n */\n indent?: number;\n}\n\n/**\n * Serialize a configuration object to a formatted string for SSG file output.\n *\n * By default, {@link sanitizeConfig} is applied before serialization to strip\n * any keys that match secret patterns (API keys, passwords, tokens, etc.).\n * Pass `includeSecrets: true` only in secured server-side contexts.\n *\n * @param config - Configuration object to export. Any JSON-serializable value.\n * @param options - Format, indentation, and secret-inclusion options.\n * @returns A JSON string, or an ES module string (`export default {...};\\n`)\n * when `format: 'js'`.\n *\n * @example\n * ```ts\n * // Write a sanitized JSON export file for SSG\n * const json = exportConfig(agentConfig);\n * await fs.writeFile('./public/config.json', json);\n * ```\n *\n * @example\n * ```ts\n * // ES module format (importable via `import config from './config.js'`)\n * const js = exportConfig(agentConfig, { format: 'js' });\n * ```\n *\n * @see {@link sanitizeConfig}\n * @see {@link parseExportedConfig}\n * @see {@link ExportConfigOptions}\n */\nexport function exportConfig(\n config: unknown,\n options: ExportConfigOptions = {},\n): string {\n const { includeSecrets = false, format = 'json', indent = 2 } = options;\n\n // Sanitize unless secrets are explicitly requested\n const exportData = includeSecrets ? config : sanitizeConfig(config);\n\n const jsonString = JSON.stringify(exportData, null, indent);\n\n if (format === 'js') {\n return `export default ${jsonString};\\n`;\n }\n\n return jsonString;\n}\n\n/**\n * Parse a config string previously produced by {@link exportConfig} back into\n * a plain JavaScript object.\n *\n * Handles both output formats:\n * - **JSON** — standard `JSON.parse`.\n * - **JS module** — strips the `export default` prefix and trailing semicolon\n * before parsing as JSON.\n *\n * Throws a `SyntaxError` if the content is not valid JSON after stripping.\n *\n * @param content - Exported config string (`'json'` or `'js'` format).\n * @returns Parsed configuration value.\n *\n * @example\n * ```ts\n * const raw = await fs.readFile('./smrt.exported.json', 'utf8');\n * const config = parseExportedConfig(raw);\n * ```\n *\n * @see {@link exportConfig}\n * @see {@link mergeExportedConfig}\n */\nexport function parseExportedConfig(content: string): unknown {\n // Handle JS module format (strip export default and trailing semicolon)\n const trimmed = content.trim();\n if (trimmed.startsWith('export default')) {\n const jsonPart = trimmed\n .replace(/^export default\\s*/, '')\n .replace(/;\\s*$/, '');\n return JSON.parse(jsonPart);\n }\n\n // Plain JSON\n return JSON.parse(content);\n}\n\n/**\n * Shallow-merge an exported (DB-backed) config into a base (file-backed) config.\n *\n * Object values are deep-merged recursively. Primitive values and arrays from\n * `exportedConfig` replace their counterparts in `baseConfig`. This is a\n * one-level-smarter spread that lets file-based env overrides win when placed\n * after the spread.\n *\n * Intended for the SSG pattern where an agent exports its runtime config to a\n * static JSON file and `smrt.config.js` imports it as a base:\n *\n * @example\n * ```js\n * // smrt.config.js\n * import exported from './smrt.exported.json' with { type: 'json' };\n *\n * export default defineConfig({\n * modules: {\n * praeco: mergeExportedConfig(exported, {\n * apiEndpoint: process.env.API_URL, // env override wins\n * }),\n * },\n * });\n * ```\n *\n * @param baseConfig - Lower-priority base (typically from the DB export file).\n * @param exportedConfig - Higher-priority overrides (typically env / file-based).\n * @returns A new merged object of type `T`.\n *\n * @see {@link exportConfig}\n * @see {@link parseExportedConfig}\n */\nexport function mergeExportedConfig<T extends Record<string, unknown>>(\n baseConfig: T,\n exportedConfig: Partial<T>,\n): T {\n const result = { ...baseConfig };\n\n for (const [key, value] of Object.entries(exportedConfig)) {\n // Guard against prototype pollution when merging untrusted/DB-exported\n // input: never write to __proto__/constructor/prototype keys.\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n result[key] !== null &&\n typeof result[key] === 'object' &&\n !Array.isArray(result[key])\n ) {\n // Deep merge objects\n (result as Record<string, unknown>)[key] = mergeExportedConfig(\n result[key] as Record<string, unknown>,\n value as Record<string, unknown>,\n );\n } else {\n // Override primitive values and arrays\n (result as Record<string, unknown>)[key] = value;\n }\n }\n\n return result;\n}\n","import { loadConfig as _loadConfig, clearConfigCache } from './loader.js';\nimport {\n setConfig as _setConfig,\n clearRuntimeConfig,\n getRuntimeConfig,\n mergeConfigs,\n} from './merge.js';\nimport {\n RuntimeProfileValidationError as _RuntimeProfileValidationError,\n resolveApplicationRuntime as _resolveApplicationRuntime,\n validateApplicationRuntimeConfigShape as _validateApplicationRuntimeConfigShape,\n type ApplicationRuntimeConfig,\n type ResolvedApplicationRuntime,\n} from './runtime-profile.js';\nimport type { LoadConfigOptions, SmrtConfig } from './types.js';\n\n// Re-export config export utilities\nexport {\n type ExportConfigOptions,\n exportConfig,\n mergeExportedConfig,\n parseExportedConfig,\n sanitizeConfig,\n} from './export.js';\nexport { clearRuntimeConfig, getRuntimeConfig, mergeConfigs } from './merge.js';\nexport type {\n ApplicationRuntimeConfig,\n ApplicationRuntimeProfile,\n AssetOwnership,\n AssetStorageProvider,\n AuthenticationProvider,\n BackupProvider,\n ConnectionOwnership,\n DatabaseEngine,\n JobTopology,\n NetworkExposure,\n OwnerBootstrapMode,\n ResolvedApplicationRuntime,\n RuntimeAssetProvider,\n RuntimeAuthenticationProvider,\n RuntimeCapabilities,\n RuntimeDatabaseProvider,\n RuntimeInvariants,\n RuntimeJobProvider,\n RuntimeNetworkProvider,\n RuntimeOverrideReport,\n RuntimePortabilityProvider,\n RuntimeProfileValidationIssue,\n RuntimeProviderOverrides,\n RuntimeProviders,\n RuntimeSecretProvider,\n RuntimeTenancyProvider,\n SecretOwnership,\n SecretProvider,\n TenancyMode,\n TenantContextMode,\n TenantIsolation,\n} from './runtime-profile.js';\nexport {\n getApplicationRuntimePreset,\n RuntimeProfileValidationError,\n resolveApplicationRuntime,\n} from './runtime-profile.js';\n\n// Re-export types\nexport type {\n // CLI and migrations configuration types\n CliConfig,\n DatabaseConfig,\n // Export configuration types\n ExportConfig,\n ExportFileConfig,\n ExportFilterValue,\n LoadConfigOptions,\n MigrationsConfig,\n MigrationsPostgresConfig,\n SchemaContractConfig,\n // Site configuration types\n SiteConfig,\n SiteLocation,\n SiteNavigation,\n SiteNavigationLink,\n SitePublisher,\n SiteTheme,\n SmrtConfig,\n SmrtGlobalConfig,\n} from './types.js';\n\n/**\n * Extend globalThis to include our config cache properties.\n * Using globalThis ensures all module instances share the same config,\n * which is critical in monorepos where the same package can be loaded\n * from different paths (e.g., pnpm store vs workspace symlink).\n *\n * This fixes issue #543: external packages not receiving user config\n * when smrt-cli loads them.\n *\n * @see https://github.com/happyvertical/smrt/issues/543\n */\ndeclare global {\n // eslint-disable-next-line no-var\n var __smrtConfigCache: SmrtConfig | null | undefined;\n // eslint-disable-next-line no-var\n var __smrtRuntimeConfig: Partial<SmrtConfig> | undefined;\n}\n\n// Use globalThis for cross-module config sharing\n// This ensures loadConfig() in smrt-cli affects all packages that use smrt-config\nglobalThis.__smrtConfigCache ??= null;\n\n/**\n * Get the cached config from globalThis\n */\nfunction getLoadedConfig(): SmrtConfig | null {\n return globalThis.__smrtConfigCache ?? null;\n}\n\n/**\n * Set the cached config in globalThis\n */\nfunction setLoadedConfig(config: SmrtConfig | null): void {\n globalThis.__smrtConfigCache = config;\n}\n\n/**\n * Load and parse configuration from the project root.\n *\n * Searches for `smrt.config.{js,mjs,cjs,json}` starting from the current\n * working directory and walking up the tree (unless `searchParents: false`).\n * The result is stored in `globalThis.__smrtConfigCache` so every module in\n * the process — including pnpm workspace packages loaded from different paths\n * — sees the same instance.\n *\n * Must be called before {@link getConfig}, {@link getModuleConfig}, or\n * {@link getPackageConfig} return meaningful values (they fall back to an\n * empty config if called before loading).\n *\n * @param options - Optional search path, parent traversal, and cache control.\n * @returns The parsed {@link SmrtConfig}. Returns `{}` if no file is found.\n *\n * @example\n * ```ts\n * // Typical app startup\n * import { loadConfig } from '@happyvertical/smrt-config';\n * await loadConfig();\n * ```\n *\n * @example\n * ```ts\n * // Load a specific file (tests / scripts)\n * const config = await loadConfig({ configPath: './fixtures/smrt.config.js', cache: false });\n * ```\n *\n * @see {@link getConfig}\n * @see {@link clearCache}\n * @see {@link LoadConfigOptions}\n */\nexport async function loadConfig(\n options?: LoadConfigOptions,\n): Promise<SmrtConfig> {\n const config = await _loadConfig(options);\n // Always update config cache (even if caching is disabled)\n setLoadedConfig(config);\n return config;\n}\n\n/**\n * Return the loaded file configuration synchronously.\n *\n * Reads from `globalThis.__smrtConfigCache` without triggering a file search.\n * Returns `null` when {@link loadConfig} has not been called yet (or after\n * {@link clearCache} resets the cache).\n *\n * @returns The cached {@link SmrtConfig}, or `null` if not yet loaded.\n *\n * @example\n * ```ts\n * const config = getConfig();\n * if (config?.smrt?.logLevel === 'debug') {\n * console.log('Debug logging enabled');\n * }\n * ```\n *\n * @see {@link loadConfig}\n * @see {@link resolveConfiguredApplicationRuntime}\n * @see {@link getSiteConfig}\n * @see {@link getModuleConfig}\n */\nexport function getConfig(): SmrtConfig | null {\n return getLoadedConfig();\n}\n\n/**\n * Resolve the effective application runtime using the documented config\n * priority: runtime overrides set by {@link setConfig} > loaded file config >\n * profile defaults.\n *\n * This accessor is intentionally separate from {@link getConfig}, whose legacy\n * contract returns the loaded file object only. When a runtime override selects\n * a different profile, lower-priority provider overrides are discarded so they\n * cannot leak profile-specific choices (for example local files or inline jobs)\n * into the new profile. Provider overrides still deep-merge when the profile is\n * unchanged.\n *\n * @returns A validated, deterministic, secret-free runtime snapshot.\n * @throws {RuntimeProfileValidationError} When no profile is configured or the\n * effective provider composition violates a profile invariant.\n */\nexport function resolveConfiguredApplicationRuntime(): Readonly<ResolvedApplicationRuntime> {\n const loadedConfig = getLoadedConfig();\n const runtimeConfig = getRuntimeConfig();\n const fileRuntime = (\n loadedConfig && Object.hasOwn(loadedConfig, 'runtime')\n ? loadedConfig.runtime\n : undefined\n ) as (ApplicationRuntimeConfig & Record<string, unknown>) | undefined;\n const runtimeOverride = (\n Object.hasOwn(runtimeConfig, 'runtime') ? runtimeConfig.runtime : undefined\n ) as\n | (Partial<ApplicationRuntimeConfig> & Record<string, unknown>)\n | undefined;\n\n for (const layer of [fileRuntime, runtimeOverride]) {\n if (layer === undefined) continue;\n const shapeIssues = _validateApplicationRuntimeConfigShape(layer);\n if (shapeIssues.length > 0) {\n throw new _RuntimeProfileValidationError([...shapeIssues]);\n }\n }\n\n const fileHasProfile =\n fileRuntime !== undefined && Object.hasOwn(fileRuntime, 'profile');\n const runtimeSelectsProfile =\n runtimeOverride !== undefined && Object.hasOwn(runtimeOverride, 'profile');\n const profileChanged =\n runtimeSelectsProfile &&\n runtimeOverride.profile !==\n (fileHasProfile ? fileRuntime.profile : undefined);\n\n // A profile switch intentionally resets provider selectors from the lower\n // priority profile, but it must not make invalid file configuration vanish.\n // Validate that layer before removing its profile-specific providers, then\n // preserve every root field so the effective resolver remains fail-closed.\n if (profileChanged && fileHasProfile) {\n _resolveApplicationRuntime(fileRuntime as ApplicationRuntimeConfig);\n }\n const fileRuntimeWithoutProviders = { ...(fileRuntime ?? {}) };\n delete fileRuntimeWithoutProviders.providers;\n\n const effective = mergeConfigs<Record<string, unknown>>(\n {},\n profileChanged ? fileRuntimeWithoutProviders : (fileRuntime ?? {}),\n runtimeOverride ?? {},\n );\n\n return _resolveApplicationRuntime(\n effective as unknown as ApplicationRuntimeConfig,\n );\n}\n\n/**\n * Return the `site` section of the loaded configuration.\n *\n * Equivalent to `getConfig()?.site ?? null`. Returns `null` both when no\n * config has been loaded and when the loaded config has no `site` key.\n *\n * @returns The {@link SiteConfig} object, or `null` if not defined.\n *\n * @example\n * ```ts\n * const site = getSiteConfig();\n * const title = site?.name ?? 'Untitled Site';\n * ```\n *\n * @see {@link SiteConfig}\n * @see {@link getConfig}\n */\nexport function getSiteConfig(): import('./types.js').SiteConfig | null {\n const config = getLoadedConfig() || {};\n return config.site || null;\n}\n\n/**\n * Return the resolved configuration for a named module.\n *\n * Merges four layers in ascending priority order:\n * 1. `defaults` (caller-supplied fallbacks)\n * 2. Global `smrt` section of the loaded config\n * 3. `modules[moduleName]` section of the loaded config\n * 4. Runtime `modules[moduleName]` overrides set via {@link setConfig}\n *\n * Works synchronously — call {@link loadConfig} first to populate the cache.\n * If no config has been loaded, only `defaults` are returned.\n *\n * @param moduleName - Key in `config.modules` (e.g., `'praeco'`).\n * @param defaults - Optional fallback values used when a key is absent from all\n * higher-priority layers.\n * @returns The fully merged module config typed as `T`.\n *\n * @example\n * ```ts\n * const config = getModuleConfig('praeco', {\n * rssLimit: 50,\n * fetchTimeout: 10_000,\n * });\n * // config.rssLimit may be overridden by smrt.config.js or setConfig()\n * ```\n *\n * @see {@link getPackageConfig}\n * @see {@link setConfig}\n * @see {@link SmrtConfig.modules}\n */\nexport function getModuleConfig<T extends Record<string, unknown>>(\n moduleName: string,\n defaults?: T,\n): T {\n // Ensure config is loaded (will use empty config if not loaded)\n const fileConfig = getLoadedConfig() || {};\n const runtime = getRuntimeConfig();\n\n // Get global smrt config\n const globalConfig = (fileConfig.smrt || {}) as Partial<T>;\n\n // Get module-specific config\n const moduleConfig = (fileConfig.modules?.[moduleName] || {}) as Partial<T>;\n\n // Get runtime module config\n const runtimeModuleConfig = (runtime.modules?.[moduleName] ||\n {}) as Partial<T>;\n\n // Merge: defaults < global < module < runtime\n const defaultsWithGlobal = mergeConfigs(\n defaults || ({} as T),\n globalConfig,\n {},\n );\n const withModuleConfig = mergeConfigs(defaultsWithGlobal, moduleConfig, {});\n const final = mergeConfigs(withModuleConfig, runtimeModuleConfig, {});\n\n return final;\n}\n\n/**\n * Return the resolved configuration for a named package.\n *\n * Identical to {@link getModuleConfig} but reads from `config.packages` instead\n * of `config.modules`. Use this inside `@happyvertical/smrt-*` packages that\n * want to expose their own config section without conflicting with user-defined\n * module names.\n *\n * Merges four layers in ascending priority order:\n * 1. `defaults` (caller-supplied fallbacks)\n * 2. Global `smrt` section of the loaded config\n * 3. `packages[packageName]` section of the loaded config\n * 4. Runtime `packages[packageName]` overrides set via {@link setConfig}\n *\n * @param packageName - Key in `config.packages` (e.g., `'ai'`, `'spider'`).\n * @param defaults - Optional fallback values.\n * @returns The fully merged package config typed as `T`.\n *\n * @example\n * ```ts\n * // Inside @happyvertical/smrt-ai package\n * const config = getPackageConfig('ai', {\n * defaultModel: 'gpt-4o-mini',\n * maxTokens: 2048,\n * });\n * ```\n *\n * @see {@link getModuleConfig}\n * @see {@link setConfig}\n * @see {@link SmrtConfig.packages}\n */\nexport function getPackageConfig<T extends Record<string, unknown>>(\n packageName: string,\n defaults?: T,\n): T {\n // Ensure config is loaded (will use empty config if not loaded)\n const fileConfig = getLoadedConfig() || {};\n const runtime = getRuntimeConfig();\n\n // Get global smrt config\n const globalConfig = (fileConfig.smrt || {}) as Partial<T>;\n\n // Get package-specific config\n const packageConfig = (fileConfig.packages?.[packageName] ||\n {}) as Partial<T>;\n\n // Get runtime package config\n const runtimePackageConfig = (runtime.packages?.[packageName] ||\n {}) as Partial<T>;\n\n // Merge: defaults < global < package < runtime\n const defaultsWithGlobal = mergeConfigs(\n defaults || ({} as T),\n globalConfig,\n {},\n );\n const withPackageConfig = mergeConfigs(defaultsWithGlobal, packageConfig, {});\n const final = mergeConfigs(withPackageConfig, runtimePackageConfig, {});\n\n return final;\n}\n\n/**\n * Apply runtime configuration overrides.\n *\n * Deep-merges `config` into an in-memory store that takes the highest priority\n * in every subsequent {@link getModuleConfig} / {@link getPackageConfig} call.\n * Successive calls accumulate. When a call explicitly changes the runtime\n * profile, the stored runtime provider subtree is reset before merging so\n * profile-specific selectors cannot leak into the new profile.\n *\n * Common uses:\n * - Inject test doubles or environment-specific values without a config file.\n * - Apply feature flags from a remote source after startup.\n *\n * @param config - Partial {@link SmrtConfig} to merge into the runtime store.\n * `null` values are ignored (they do not clear existing keys).\n *\n * @example\n * ```ts\n * setConfig({ modules: { ai: { defaultModel: 'gpt-4o' } } });\n * ```\n *\n * @see {@link clearCache}\n * @see {@link getModuleConfig}\n */\nexport function setConfig(config: Partial<SmrtConfig>): void {\n _setConfig(config);\n}\n\n/**\n * Reset all cached configuration state.\n *\n * Clears three independent caches:\n * 1. `globalThis.__smrtConfigCache` — the merged config loaded by {@link loadConfig}.\n * 2. The internal loader cache (`globalThis.__smrtLoaderCachedConfig`) and the\n * cosmiconfig explorer instance.\n * 3. The runtime override store populated by {@link setConfig}.\n *\n * After calling this, {@link getConfig} returns `null` and a fresh\n * {@link loadConfig} call will re-read from disk.\n *\n * WARNING: This is a global reset — it affects every module sharing the same\n * runtime, not just the caller. Use with care outside of test teardown.\n *\n * @example\n * ```ts\n * afterEach(() => {\n * clearCache(); // reset between tests\n * });\n * ```\n *\n * @see {@link loadConfig}\n * @see {@link setConfig}\n */\nexport function clearCache(): void {\n setLoadedConfig(null);\n clearConfigCache(); // Clear loader.ts cache\n clearRuntimeConfig(); // Clear runtime config\n}\n\n/**\n * Type-safe helper for authoring `smrt.config.js` files.\n *\n * This is an identity function — it returns its argument unchanged. Its sole\n * purpose is to provide TypeScript type-checking and IDE auto-completion when\n * writing the config object literal.\n *\n * @param config - The config object to validate against {@link SmrtConfig}.\n * @returns The same object, unchanged.\n *\n * @example\n * ```js\n * // smrt.config.js\n * import { defineConfig } from '@happyvertical/smrt-config';\n *\n * export default defineConfig({\n * smrt: { logLevel: 'info' },\n * site: { name: 'My Site', ... },\n * });\n * ```\n *\n * @see {@link SmrtConfig}\n * @see {@link loadConfig}\n */\nexport function defineConfig(config: SmrtConfig): SmrtConfig {\n return config;\n}\n"],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import { DomainKnowledgeConfig } from '@happyvertical/smrt-types';
|
|
2
2
|
|
|
3
|
+
/** Canonical config stored at `runtime` in `smrt.config.ts`. */
|
|
4
|
+
declare interface ApplicationRuntimeConfig {
|
|
5
|
+
profile: ApplicationRuntimeProfile;
|
|
6
|
+
providers?: RuntimeProviderOverrides;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Stable application-runtime profile contract.
|
|
11
|
+
*
|
|
12
|
+
* Profiles select infrastructure composition only. Domain models, generated
|
|
13
|
+
* surfaces, effect metadata, and approval policy are deliberately outside the
|
|
14
|
+
* override surface so moving an application cannot silently change behavior.
|
|
15
|
+
*/
|
|
16
|
+
declare type ApplicationRuntimeProfile = 'local' | 'self-hosted' | 'cloud';
|
|
17
|
+
|
|
18
|
+
declare type AssetOwnership = 'user' | 'operator' | 'managed';
|
|
19
|
+
|
|
20
|
+
declare type AssetStorageProvider = 'local-files' | 's3-compatible' | 'managed-object-storage';
|
|
21
|
+
|
|
22
|
+
declare type AuthenticationProvider = 'owner-bootstrap' | 'oidc' | 'magic-link' | 'hosted-identity';
|
|
23
|
+
|
|
3
24
|
/**
|
|
4
25
|
* CLI package configuration.
|
|
5
26
|
*
|
|
@@ -34,6 +55,8 @@ export declare interface CliConfig {
|
|
|
34
55
|
schemaContract?: SchemaContractConfig;
|
|
35
56
|
}
|
|
36
57
|
|
|
58
|
+
declare type ConnectionOwnership = 'user' | 'operator' | 'managed';
|
|
59
|
+
|
|
37
60
|
/**
|
|
38
61
|
* Database connection configuration for CLI commands.
|
|
39
62
|
*
|
|
@@ -58,6 +81,8 @@ export declare interface DatabaseConfig {
|
|
|
58
81
|
url?: string;
|
|
59
82
|
}
|
|
60
83
|
|
|
84
|
+
declare type DatabaseEngine = 'sqlite' | 'postgres';
|
|
85
|
+
|
|
61
86
|
/**
|
|
62
87
|
* Export configuration for static site generation.
|
|
63
88
|
*
|
|
@@ -210,6 +235,8 @@ export declare interface ExportFilterValue {
|
|
|
210
235
|
contains?: string;
|
|
211
236
|
}
|
|
212
237
|
|
|
238
|
+
declare type JobTopology = 'inline' | 'embedded' | 'external' | 'scalable';
|
|
239
|
+
|
|
213
240
|
/**
|
|
214
241
|
* Options accepted by {@link loadConfig}.
|
|
215
242
|
*
|
|
@@ -352,6 +379,62 @@ export declare interface MigrationsPostgresConfig {
|
|
|
352
379
|
statementTimeout?: string;
|
|
353
380
|
}
|
|
354
381
|
|
|
382
|
+
declare type NetworkExposure = 'loopback' | 'public';
|
|
383
|
+
|
|
384
|
+
declare type OwnerBootstrapMode = 'single-use' | 'disabled';
|
|
385
|
+
|
|
386
|
+
declare interface RuntimeAssetProvider {
|
|
387
|
+
readonly provider: AssetStorageProvider;
|
|
388
|
+
readonly ownership: AssetOwnership;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
declare interface RuntimeAuthenticationProvider {
|
|
392
|
+
readonly provider: AuthenticationProvider;
|
|
393
|
+
readonly ownerBootstrap: OwnerBootstrapMode;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
declare interface RuntimeDatabaseProvider {
|
|
397
|
+
readonly engine: DatabaseEngine;
|
|
398
|
+
readonly connectionOwnership: ConnectionOwnership;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
declare interface RuntimeJobProvider {
|
|
402
|
+
readonly topology: JobTopology;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
declare interface RuntimeNetworkProvider {
|
|
406
|
+
readonly exposure: NetworkExposure;
|
|
407
|
+
readonly tls: boolean;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Explicit provider changes layered over a profile's safe preset.
|
|
412
|
+
*
|
|
413
|
+
* Every override is revalidated with the completed composition. Capability and
|
|
414
|
+
* surface-policy fields are intentionally absent: providers cannot use this
|
|
415
|
+
* seam to weaken cross-profile invariants.
|
|
416
|
+
*/
|
|
417
|
+
declare interface RuntimeProviderOverrides {
|
|
418
|
+
database?: Partial<RuntimeDatabaseProvider>;
|
|
419
|
+
authentication?: Partial<RuntimeAuthenticationProvider>;
|
|
420
|
+
tenancy?: Partial<RuntimeTenancyProvider>;
|
|
421
|
+
assets?: Partial<RuntimeAssetProvider>;
|
|
422
|
+
secrets?: Partial<RuntimeSecretProvider>;
|
|
423
|
+
jobs?: Partial<RuntimeJobProvider>;
|
|
424
|
+
network?: Partial<RuntimeNetworkProvider>;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
declare interface RuntimeSecretProvider {
|
|
428
|
+
readonly provider: SecretProvider;
|
|
429
|
+
readonly ownership: SecretOwnership;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
declare interface RuntimeTenancyProvider {
|
|
433
|
+
readonly mode: TenancyMode;
|
|
434
|
+
readonly context: TenantContextMode;
|
|
435
|
+
readonly isolation: TenantIsolation;
|
|
436
|
+
}
|
|
437
|
+
|
|
355
438
|
/**
|
|
356
439
|
* Semantic schema contract enforced by SMRT schema commands.
|
|
357
440
|
*
|
|
@@ -370,6 +453,10 @@ export declare interface SchemaContractConfig {
|
|
|
370
453
|
};
|
|
371
454
|
}
|
|
372
455
|
|
|
456
|
+
declare type SecretOwnership = 'user' | 'operator' | 'managed';
|
|
457
|
+
|
|
458
|
+
declare type SecretProvider = 'local-file' | 'environment' | 'external' | 'managed';
|
|
459
|
+
|
|
373
460
|
/**
|
|
374
461
|
* Site identity and configuration for SMRT site templates.
|
|
375
462
|
*
|
|
@@ -534,6 +621,8 @@ export declare interface SmrtConfig {
|
|
|
534
621
|
export?: ExportConfig;
|
|
535
622
|
/** Domain-scoped agent/developer knowledge generation and exposure. */
|
|
536
623
|
knowledge?: DomainKnowledgeConfig;
|
|
624
|
+
/** Validated application infrastructure composition. */
|
|
625
|
+
runtime?: ApplicationRuntimeConfig;
|
|
537
626
|
/**
|
|
538
627
|
* Module-scoped configurations keyed by module name.
|
|
539
628
|
* Retrieved via {@link getModuleConfig}.
|
|
@@ -679,4 +768,10 @@ export declare interface SmrtGlobalConfig {
|
|
|
679
768
|
[key: string]: unknown;
|
|
680
769
|
}
|
|
681
770
|
|
|
771
|
+
declare type TenancyMode = 'default-tenant' | 'single-tenant' | 'multi-tenant';
|
|
772
|
+
|
|
773
|
+
declare type TenantContextMode = 'defaulted' | 'required';
|
|
774
|
+
|
|
775
|
+
declare type TenantIsolation = 'application' | 'database-rls';
|
|
776
|
+
|
|
682
777
|
export { }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@happyvertical/smrt-config",
|
|
3
|
-
"version": "0.43.
|
|
3
|
+
"version": "0.43.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Centralized configuration management for SMRT modules",
|
|
6
6
|
"author": "HappyVertical",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"cosmiconfig": "^9.0.2",
|
|
23
23
|
"jiti": "^2.7.0",
|
|
24
|
-
"@happyvertical/smrt-types": "0.43.
|
|
24
|
+
"@happyvertical/smrt-types": "0.43.7"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@types/node": "24.13.2",
|