@happyvertical/smrt-config 0.53.3 → 0.53.4

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/dist/index.js.map CHANGED
@@ -1 +1 @@
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 PostgresPermissionsConfig,\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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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/ai.ts","../src/loader.ts","../src/runtime-profile.ts","../src/merge.ts","../src/export.ts","../src/index.ts"],"sourcesContent":["/**\n * Shared AI provider resolution (#3372).\n *\n * One resolver for every AI consumer. Default precedence, highest first, applied\n * per field (`provider`, `apiKey`, `baseUrl`, `model`):\n *\n * 1. `explicit` — values the caller passes in (for example `options.ai`).\n * 2. The `ai` block of `smrt.config.ts` (`apiKeyEnv` names the key variable).\n * 3. Environment variables, one prefix at a time, in `prefixes` order:\n * `<PREFIX>_PROVIDER`, `<PREFIX>_API_KEY`, `<PREFIX>_BASE_URL`,\n * `<PREFIX>_MODEL`. Default prefixes: `SMRT_AI`, then `HAVE_AI`.\n * 4. Provider-specific key variable for the *selected* provider only\n * (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GEMINI_API_KEY`).\n * 5. Auto-detect: with no provider selected, the first provider whose key\n * variable is set (openai, anthropic, gemini).\n *\n * `envOverridesConfig` swaps (2) and (3) for call sites that historically let\n * env win.\n *\n * Credential binding: a key or base URL is bound to the provider named by its\n * own source (explicit, the config block, or one env prefix). When a\n * higher-priority source selects a different provider, the lower source's key\n * and base URL are discarded, then the selected provider's own key variable is\n * used. A source that supplies a key but names no provider is generic and\n * binds to whichever provider is selected.\n *\n * Redaction: displayed base URLs expose only the origin. Secrets are never included in errors, `toJSON()` or `describe`.\n */\nimport type { AIConfigBlock } from './types.js';\n\n/** Provider -> conventional API key variable. */\nexport const AI_PROVIDER_KEY_ENV: Readonly<Record<string, string>> =\n Object.freeze({\n openai: 'OPENAI_API_KEY',\n anthropic: 'ANTHROPIC_API_KEY',\n gemini: 'GEMINI_API_KEY',\n });\n\n/** Auto-detection order. */\nconst AUTO_DETECT_ORDER = ['openai', 'anthropic', 'gemini'] as const;\n\n/** Default env prefixes, highest priority first. */\nexport const DEFAULT_AI_ENV_PREFIXES: readonly string[] = Object.freeze([\n 'SMRT_AI',\n 'HAVE_AI',\n]);\n\n/** Conventional key variable for a provider name, if it has one. */\nexport function getDefaultAIKeyEnvName(\n provider: string | undefined | null,\n): string | undefined {\n const normalized = nonEmpty(provider)?.toLowerCase();\n return normalized && Object.hasOwn(AI_PROVIDER_KEY_ENV, normalized)\n ? AI_PROVIDER_KEY_ENV[normalized]\n : undefined;\n}\n\nexport type AIConfigField = 'provider' | 'apiKey' | 'baseUrl' | 'model';\n\n/** Where a field came from. Names only; never values. */\nexport type AIConfigSource =\n | 'explicit'\n | 'config'\n | `env:${string}`\n | 'provider-key-env'\n | 'auto-detect';\n\nexport interface AIExplicitConfig {\n provider?: string;\n /** Alias of `provider` accepted for `AIClientOptions` compatibility. */\n type?: string;\n apiKey?: string;\n baseUrl?: string;\n model?: string;\n /** Alias of `model` accepted for `AIClientOptions` compatibility. */\n defaultModel?: string;\n}\n\nexport interface ResolveAIProviderOptions {\n /** Caller-supplied values; highest priority. */\n explicit?: AIExplicitConfig | null;\n /**\n * The `smrt.config.ts` `ai` block. Omit to use the loaded config (via\n * `resolveConfiguredAIProvider`); pass `null` to ignore config entirely.\n */\n config?: AIConfigBlock | null;\n /** Environment to read. Defaults to `process.env`. */\n env?: Readonly<Record<string, string | undefined>>;\n /** Env prefixes, highest priority first. Default `['SMRT_AI', 'HAVE_AI']`. */\n prefixes?: readonly string[];\n /**\n * Fall back to the selected provider's own key variable (`OPENAI_API_KEY`...).\n * Default `true`.\n */\n providerKeyEnvFallback?: boolean;\n /** Auto-detect provider from well-known key variables. Default `true`. */\n autoDetect?: boolean;\n /** Let env prefixes beat the config block. Default `false`. */\n envOverridesConfig?: boolean;\n /**\n * Treat \"no provider\" as unconfigured even when an API key is present.\n * Default `true`; when `false`, a key alone is enough (`provider` stays\n * undefined and the provider layer applies its own default).\n */\n requireProvider?: boolean;\n}\n\nexport interface ResolvedAIProviderConfig {\n provider?: string;\n apiKey?: string;\n baseUrl?: string;\n model?: string;\n /** Which source supplied each field (names only). */\n sources: Partial<Record<AIConfigField, AIConfigSource>>;\n /** Redacted view: safe to log or serialize. */\n toJSON(): Record<string, unknown>;\n}\n\n/** Options for `getAI` (from `@happyvertical/ai`). */\nexport interface AIProviderClientOptions {\n type?: string;\n provider?: string;\n apiKey?: string;\n baseUrl?: string;\n defaultModel?: string;\n}\n\n/** Thrown when no provider is configured. The message names variables only. */\nexport class AIProviderNotConfiguredError extends Error {\n readonly code = 'SMRT_AI_NOT_CONFIGURED';\n constructor(message: string) {\n super(message);\n this.name = 'AIProviderNotConfiguredError';\n }\n}\n\nfunction nonEmpty(value: unknown): string | undefined {\n return typeof value === 'string' && value.trim().length > 0\n ? value.trim()\n : undefined;\n}\n\n/**\n * Reduce a base URL to its origin (scheme, host, port) for display. Userinfo,\n * path, query and fragment can all carry credentials, so none are shown. The\n * real URL is still what is passed to the client.\n */\nexport function redactBaseUrl(value: string | undefined): string | undefined {\n if (!value) return undefined;\n try {\n const url = new URL(value);\n return `${url.protocol}//${url.host}`;\n } catch {\n return '[unparseable-url]';\n }\n}\n\ninterface Candidate {\n source: AIConfigSource;\n value: string | undefined;\n}\n\nexport function tryResolveAIProviderConfig(\n options: ResolveAIProviderOptions = {},\n): ResolvedAIProviderConfig | undefined {\n const env = options.env ?? process.env;\n const read = (name: string) => nonEmpty(env[name]);\n const prefixes = options.prefixes ?? DEFAULT_AI_ENV_PREFIXES;\n const explicit = options.explicit ?? {};\n const block = options.config ?? {};\n\n const fromEnv = (suffix: string): Candidate[] =>\n prefixes.map((prefix) => ({\n source: `env:${prefix}_${suffix}` as const,\n value: read(`${prefix}_${suffix}`),\n }));\n\n const explicitCand = (value: unknown): Candidate => ({\n source: 'explicit',\n value: nonEmpty(value),\n });\n const configCand = (value: unknown): Candidate => ({\n source: 'config',\n value: nonEmpty(value),\n });\n const layered = (\n exp: Candidate[],\n cfg: Candidate[],\n envs: Candidate[],\n ): Candidate[] =>\n options.envOverridesConfig\n ? [...exp, ...envs, ...cfg]\n : [...exp, ...cfg, ...envs];\n const first = (\n candidates: Candidate[],\n ): { value: string; source: AIConfigSource } | undefined => {\n for (const c of candidates) {\n if (c.value) return { value: c.value, source: c.source };\n }\n return undefined;\n };\n\n const sources: ResolvedAIProviderConfig['sources'] = {};\n const result: Partial<Record<AIConfigField, string>> = {};\n const set = (\n field: AIConfigField,\n hit: { value: string; source: AIConfigSource } | undefined,\n ) => {\n if (hit) {\n result[field] = hit.value;\n sources[field] = hit.source;\n }\n };\n\n set(\n 'provider',\n first(\n layered(\n [explicitCand(nonEmpty(explicit.provider) ?? nonEmpty(explicit.type))],\n [configCand(block.provider)],\n fromEnv('PROVIDER'),\n ),\n ),\n );\n set(\n 'model',\n first(\n layered(\n [\n explicitCand(\n nonEmpty(explicit.model) ?? nonEmpty(explicit.defaultModel),\n ),\n ],\n [configCand(block.model)],\n fromEnv('MODEL'),\n ),\n ),\n );\n\n // Credentials (key, base URL) are bound to the provider of the source they\n // come from. A source that names a different provider than the one finally\n // selected is skipped, so one provider's secret or endpoint is never sent to\n // another. A source that supplies a credential but names no provider is\n // generic: it is bound to whichever provider is selected.\n const autoDetected =\n result.provider || options.autoDetect === false\n ? undefined\n : AUTO_DETECT_ORDER.find((p) => read(AI_PROVIDER_KEY_ENV[p]));\n if (autoDetected) {\n result.provider = autoDetected;\n sources.provider = 'auto-detect';\n }\n const selected = result.provider?.toLowerCase();\n\n interface CredentialGroup {\n source: AIConfigSource;\n provider?: string;\n apiKey?: string;\n baseUrl?: string;\n }\n const configKeyEnv = nonEmpty(block.apiKeyEnv);\n const explicitGroup: CredentialGroup = {\n source: 'explicit',\n provider: nonEmpty(explicit.provider) ?? nonEmpty(explicit.type),\n apiKey: nonEmpty(explicit.apiKey),\n baseUrl: nonEmpty(explicit.baseUrl),\n };\n const configGroup: CredentialGroup = {\n source: 'config',\n provider: nonEmpty(block.provider),\n apiKey:\n nonEmpty(block.apiKey) ?? (configKeyEnv ? read(configKeyEnv) : undefined),\n baseUrl: nonEmpty(block.baseUrl),\n };\n const envGroups: CredentialGroup[] = prefixes.map((prefix) => ({\n source: `env:${prefix}` as AIConfigSource,\n provider: read(`${prefix}_PROVIDER`),\n apiKey: read(`${prefix}_API_KEY`),\n baseUrl: read(`${prefix}_BASE_URL`),\n }));\n const groups = options.envOverridesConfig\n ? [explicitGroup, ...envGroups, configGroup]\n : [explicitGroup, configGroup, ...envGroups];\n const usable = (g: CredentialGroup) =>\n !g.provider || g.provider.toLowerCase() === selected;\n const sourceName = (\n g: CredentialGroup,\n suffix: 'API_KEY' | 'BASE_URL',\n ): AIConfigSource =>\n g.source.startsWith('env:')\n ? (`${g.source}_${suffix}` as AIConfigSource)\n : g.source;\n\n for (const g of groups) {\n if (!usable(g)) continue;\n if (g.baseUrl && !result.baseUrl) {\n result.baseUrl = g.baseUrl;\n sources.baseUrl = sourceName(g, 'BASE_URL');\n }\n if (g.apiKey && !result.apiKey) {\n result.apiKey = g.apiKey;\n sources.apiKey = sourceName(g, 'API_KEY');\n }\n }\n const providerKeyEnv = getDefaultAIKeyEnvName(result.provider);\n if (\n !result.apiKey &&\n providerKeyEnv &&\n options.providerKeyEnvFallback !== false &&\n read(providerKeyEnv)\n ) {\n result.apiKey = read(providerKeyEnv);\n sources.apiKey = 'provider-key-env';\n }\n\n if (!result.provider && (options.requireProvider ?? true)) {\n return undefined;\n }\n if (!result.provider && !result.apiKey) {\n return undefined;\n }\n\n const resolved: ResolvedAIProviderConfig = {\n ...result,\n sources,\n toJSON: () => describeAIProviderConfig(resolved),\n };\n // Keep `toJSON` out of enumeration so spreads and Object.keys stay clean.\n Object.defineProperty(resolved, 'toJSON', { enumerable: false });\n return resolved;\n}\n\n/** Resolve or throw {@link AIProviderNotConfiguredError}. */\nexport function resolveAIProviderConfig(\n options: ResolveAIProviderOptions = {},\n): ResolvedAIProviderConfig {\n const resolved = tryResolveAIProviderConfig(options);\n if (resolved) return resolved;\n const prefixes = options.prefixes ?? DEFAULT_AI_ENV_PREFIXES;\n const envNames = prefixes.map((p) => `${p}_PROVIDER`).join(', ');\n const keyNames = Object.values(AI_PROVIDER_KEY_ENV).join(', ');\n throw new AIProviderNotConfiguredError(\n 'No AI provider is configured. Set the `ai` block in smrt.config.ts ' +\n `(provider, model, apiKeyEnv), or set ${envNames}` +\n (options.autoDetect === false\n ? '.'\n : `, or one of ${keyNames} to auto-detect the provider.`),\n );\n}\n\n/** Redacted description: presence flags and source names, never secrets. */\nexport function describeAIProviderConfig(\n resolved: Pick<\n ResolvedAIProviderConfig,\n 'provider' | 'apiKey' | 'baseUrl' | 'model' | 'sources'\n >,\n): Record<string, unknown> {\n return {\n provider: resolved.provider,\n model: resolved.model,\n baseUrl: redactBaseUrl(resolved.baseUrl),\n apiKey: resolved.apiKey ? '[redacted]' : undefined,\n sources: { ...resolved.sources },\n };\n}\n\n/** Convert a resolved config into `getAI()` options, omitting unset fields. */\nexport function toAIClientOptions(\n resolved: Pick<\n ResolvedAIProviderConfig,\n 'provider' | 'apiKey' | 'baseUrl' | 'model'\n >,\n): AIProviderClientOptions {\n const out: AIProviderClientOptions = {};\n if (resolved.provider) {\n out.type = resolved.provider;\n out.provider = resolved.provider;\n }\n if (resolved.apiKey) out.apiKey = resolved.apiKey;\n if (resolved.baseUrl) out.baseUrl = resolved.baseUrl;\n if (resolved.model) out.defaultModel = resolved.model;\n return out;\n}\n\nconst OWNED_AI_FIELDS = ['apiKey', 'apiKeyEnv', 'baseUrl', 'model'] as const;\n\n/**\n * Canonicalise one AI config object: blank strings are removed, `type` folds\n * into `provider` and `defaultModel` into `model` (`provider`/`model` win when\n * both are non-blank). Other keys pass through untouched.\n */\nexport function canonicalizeAIConfig(input: unknown): Record<string, unknown> {\n if (!input || typeof input !== 'object') return {};\n const out: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\n if (key === '__proto__' || key === 'constructor' || key === 'prototype') {\n continue;\n }\n if (typeof value === 'string' && value.trim().length === 0) continue;\n out[key] = value;\n }\n const provider = nonEmpty(out.provider) ?? nonEmpty(out.type);\n const model = nonEmpty(out.model) ?? nonEmpty(out.defaultModel);\n delete out.type;\n delete out.defaultModel;\n if (provider) out.provider = provider;\n else delete out.provider;\n if (model) out.model = model;\n else delete out.model;\n return out;\n}\n\n/**\n * The one merge for two AI config objects (`lower` < `higher`). Both sides are\n * canonicalised first. When `higher` names a provider and `lower` names a\n * different one, `lower`'s provider, key, key variable, base URL and model are\n * dropped so no credential is paired with another provider. Everything else is\n * a shallow overlay with `higher` winning. The result carries canonical\n * `provider` / `model` only; use {@link withAIAliases} for client-facing aliases.\n */\nexport function mergeAIConfigObjects(\n lower: unknown,\n higher: unknown,\n): Record<string, unknown> {\n const low = canonicalizeAIConfig(lower);\n const high = canonicalizeAIConfig(higher);\n const lowName = nonEmpty(low.provider)?.toLowerCase();\n const highName = nonEmpty(high.provider)?.toLowerCase();\n if (lowName && highName && lowName !== highName) {\n delete low.provider;\n for (const f of OWNED_AI_FIELDS) delete low[f];\n }\n return { ...low, ...high };\n}\n\n/** Set both client-facing aliases (`provider`+`type`, `model`+`defaultModel`). */\nexport function withAIAliases<T extends Record<string, unknown>>(config: T): T {\n const out: Record<string, unknown> = { ...config };\n const provider = nonEmpty(out.provider) ?? nonEmpty(out.type);\n const model = nonEmpty(out.model) ?? nonEmpty(out.defaultModel);\n for (const k of ['provider', 'type'] as const) {\n if (provider) out[k] = provider;\n else delete out[k];\n }\n for (const k of ['model', 'defaultModel'] as const) {\n if (model) out[k] = model;\n else delete out[k];\n }\n return out as T;\n}\n","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 { mergeAIConfigObjects } from './ai.js';\nimport {\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 const merged = deepMerge(current, config) as Record<string, unknown>;\n // The `ai` blocks follow provider ownership: accumulating setConfig() calls\n // must not pair a later provider with an earlier provider's credentials.\n const aiOf = (c: unknown) =>\n isPlainObject(c) && isPlainObject(c.ai) ? c.ai : undefined;\n const pkgAiOf = (c: unknown) =>\n isPlainObject(c) &&\n isPlainObject(c.packages) &&\n isPlainObject(c.packages.ai)\n ? c.packages.ai\n : undefined;\n if (aiOf(config)) {\n merged.ai = mergeAIConfigObjects(aiOf(current) ?? {}, aiOf(config));\n }\n if (pkgAiOf(config)) {\n (merged.packages as Record<string, unknown>).ai = mergeAIConfigObjects(\n pkgAiOf(current) ?? {},\n pkgAiOf(config),\n );\n }\n globalThis.__smrtRuntimeConfig = merged as Partial<SmrtConfig>;\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","import { mergeAIConfigObjects } from './ai.js';\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 key === 'ai' &&\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 // AI blocks merge with provider ownership (see mergeAIConfigObjects).\n (result as Record<string, unknown>)[key] = mergeAIConfigObjects(\n result[key],\n value,\n );\n } else 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 {\n type ResolveAIProviderOptions,\n type ResolvedAIProviderConfig,\n resolveAIProviderConfig,\n tryResolveAIProviderConfig,\n} from './ai.js';\nimport { 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 { AIConfigBlock, LoadConfigOptions, SmrtConfig } from './types.js';\n\n// Re-export AI provider resolution\nexport {\n AI_PROVIDER_KEY_ENV,\n type AIConfigField,\n type AIConfigSource,\n type AIExplicitConfig,\n type AIProviderClientOptions,\n AIProviderNotConfiguredError,\n canonicalizeAIConfig,\n DEFAULT_AI_ENV_PREFIXES,\n describeAIProviderConfig,\n getDefaultAIKeyEnvName,\n mergeAIConfigObjects,\n type ResolveAIProviderOptions,\n type ResolvedAIProviderConfig,\n redactBaseUrl,\n resolveAIProviderConfig,\n toAIClientOptions,\n tryResolveAIProviderConfig,\n withAIAliases,\n} from './ai.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 AIConfigBlock,\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 PostgresPermissionsConfig,\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\n/**\n * The effective `ai` block: runtime `ai` > runtime `packages.ai` > file `ai` >\n * file `packages.ai` (`smrt init` historically wrote `packages.ai`), merged\n * field by field. Returns `null` when none is declared.\n */\nexport function getAIConfigBlock(): AIConfigBlock | null {\n const file = getLoadedConfig();\n const runtime = getRuntimeConfig();\n return mergeAIConfigLayers([\n file?.packages?.ai,\n file?.ai,\n runtime.packages?.ai,\n runtime.ai,\n ]);\n}\n\n/**\n * Merge `ai` block layers, lowest priority first, preserving provider\n * ownership: `apiKey`, `apiKeyEnv`, `baseUrl` and `model` belong to the\n * provider their layer names. When a higher layer names a different provider,\n * the credentials and model already collected from lower layers for another\n * provider are dropped (a model id is provider-specific, so it is dropped\n * too). A layer that names no provider contributes generic values that bind to\n * the provider selected so far.\n */\nexport function mergeAIConfigLayers(\n layers: ReadonlyArray<unknown>,\n): AIConfigBlock | null {\n const FIELDS = ['model', 'baseUrl', 'apiKeyEnv', 'apiKey'] as const;\n let provider: string | undefined;\n const values: Partial<Record<(typeof FIELDS)[number], string>> = {};\n const owners: Partial<Record<(typeof FIELDS)[number], string | undefined>> =\n {};\n const text = (v: unknown) =>\n typeof v === 'string' && v.trim() ? v.trim() : undefined;\n for (const layer of layers) {\n if (!layer || typeof layer !== 'object') continue;\n const rec = layer as Record<string, unknown>;\n const named = text(rec.provider) ?? text(rec.type);\n if (named && provider && named.toLowerCase() !== provider.toLowerCase()) {\n for (const f of FIELDS) {\n const owner = owners[f];\n if (owner && owner.toLowerCase() !== named.toLowerCase()) {\n delete values[f];\n delete owners[f];\n }\n }\n }\n if (named) provider = named;\n for (const f of FIELDS) {\n const v =\n f === 'model'\n ? (text(rec.model) ?? text(rec.defaultModel))\n : text(rec[f]);\n if (v) {\n values[f] = v;\n owners[f] = provider;\n }\n }\n }\n if (!provider && FIELDS.every((f) => !values[f])) return null;\n const merged: AIConfigBlock = { ...values };\n if (provider) merged.provider = provider;\n return merged;\n}\n\n/**\n * {@link resolveAIProviderConfig} against the loaded `smrt.config.ts` `ai`\n * block. Throws `AIProviderNotConfiguredError` when nothing is configured.\n */\nexport function resolveConfiguredAIProvider(\n options: ResolveAIProviderOptions = {},\n): ResolvedAIProviderConfig {\n return resolveAIProviderConfig({\n ...options,\n config: options.config === undefined ? getAIConfigBlock() : options.config,\n });\n}\n\n/** Non-throwing variant of {@link resolveConfiguredAIProvider}. */\nexport function tryResolveConfiguredAIProvider(\n options: ResolveAIProviderOptions = {},\n): ResolvedAIProviderConfig | undefined {\n return tryResolveAIProviderConfig({\n ...options,\n config: options.config === undefined ? getAIConfigBlock() : options.config,\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,mBAAmB,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;CAC1D;AAuHD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CAyC3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAAC,UAAU,CAAC,CAEtD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,QAAQ,EAAE,CAAC,EACX,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,EACtB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAClB,CAAC,CAKH"}
1
+ {"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../src/merge.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,mBAAmB,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;CAC1D;AAuHD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CA6D3D;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAAC,UAAU,CAAC,CAEtD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAEzC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,QAAQ,EAAE,CAAC,EACX,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC,EACtB,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,GAClB,CAAC,CAKH"}
package/dist/types.d.ts CHANGED
@@ -661,6 +661,20 @@ export interface SmrtConfig {
661
661
  knowledge?: DomainKnowledgeConfig;
662
662
  /** Validated application infrastructure composition. */
663
663
  runtime?: ApplicationRuntimeConfig;
664
+ /**
665
+ * Build-time declaration of the SMRT packages whose object manifests this
666
+ * app consumes. Read by the `@happyvertical/smrt-core/vite` preset so runtime
667
+ * registration is deterministic; tooling and UI packages are never inferred
668
+ * as model providers.
669
+ */
670
+ consumer?: {
671
+ packages?: string[];
672
+ };
673
+ /**
674
+ * Assistant model declaration, read by {@link resolveAIProviderConfig}.
675
+ * Secrets come from the environment (`apiKeyEnv`), never the committed file.
676
+ */
677
+ ai?: AIConfigBlock;
664
678
  /**
665
679
  * Module-scoped configurations keyed by module name.
666
680
  * Retrieved via {@link getModuleConfig}.
@@ -676,6 +690,36 @@ export interface SmrtConfig {
676
690
  [packageName: string]: Record<string, unknown>;
677
691
  };
678
692
  }
693
+ /**
694
+ * The `ai` block of `smrt.config.ts`.
695
+ *
696
+ * Declare the provider and model here; keep the secret in the environment and
697
+ * name its variable with `apiKeyEnv`. A literal `apiKey` is honoured for
698
+ * programmatic configs (for example `process.env.X`) but is never exported.
699
+ */
700
+ export interface AIConfigBlock {
701
+ /** Provider name passed to `getAI` (`openai`, `anthropic`, `gemini`, ...). */
702
+ provider?: string;
703
+ /** Model identifier (maps to `defaultModel`). */
704
+ model?: string;
705
+ /** Provider endpoint override. Must not embed credentials. */
706
+ baseUrl?: string;
707
+ /** Name of the environment variable holding the API key. */
708
+ apiKeyEnv?: string;
709
+ /** Literal key (discouraged); prefer `apiKeyEnv`. */
710
+ apiKey?: string;
711
+ /**
712
+ * Legacy alias of `provider`, as written by older `smrt init` configs.
713
+ * The resolver canonicalises it; prefer `provider`.
714
+ * @deprecated
715
+ */
716
+ type?: string;
717
+ /**
718
+ * Legacy alias of `model`. The resolver canonicalises it; prefer `model`.
719
+ * @deprecated
720
+ */
721
+ defaultModel?: string;
722
+ }
679
723
  /**
680
724
  * Options accepted by {@link loadConfig}.
681
725
  *