import type { EventBus } from '../kernel/events.js'; import { type Config, type ConfigLoader, type SyncConfig } from '../types/config.js'; import type { Logger } from '../types/logger.js'; import type { SecretVault } from '../types/secret-vault.js'; import { type WstackPaths } from '../utils/wstack-paths.js'; /** * Defaults express *behavior*, not identity. Provider and model are NOT * hardcoded — they must be resolved at runtime from config + env + the * ModelsRegistry. A bare Config returned by this loader will throw when * the agent tries to construct a provider, with a message that points * users at `wstack init`. */ export declare const CONFIG_BEHAVIOR_DEFAULTS: Omit; export interface ConfigDefaultRepair { path: string; action: 'added' | 'replaced'; } export interface ConfigDefaultRepairReport { fixed: Record; changes: ConfigDefaultRepair[]; changed: boolean; } /** * Materialize the canonical behavior defaults into a persisted profile config. * Add+replace variant: missing fields are added AND values whose JSON shape is * incompatible with the default are replaced (not just skipped). This is useful * for the config-doctor repair path where a user-edited file may have wrong * types. For the normal boot path (add-only) see `fillMissingDefaults`. * * Identity fields (`provider`, `model`) are intentionally not invented. * * NOTE: This function assumes JSON-roundtripped input. Non-JSON values such * as `Buffer` or `Uint8Array` are objects and may not be correctly detected * as type-incompatible with primitive defaults. */ export declare function repairConfigDefaults(input: Record): ConfigDefaultRepairReport; type PartialConfig = Partial & { providers?: Record; /** Fields that came from environment variables — must not be persisted. */ _envSource?: Set | undefined; }; /** * Assert that the allow-list and deny-list together cover every top-level * field of `Config`. Throws on drift so the failure is loud at test time and * at first boot, not a silent widening of the attack surface. Exported so * tests (and any consumer building tooling on top of this) can call it * explicitly; `stripUnsafeInProjectFields()` also calls it lazily on its * first invocation so the guarantee is structural, not test-only. * * The check is two-sided: * 1. Every key in `KNOWN_CONFIG_TOP_LEVEL_KEYS` is either allowed or * explicitly documented as denied (catches: "added a new field but * forgot to decide"). * 2. Every entry in `KNOWN_DENIED_IN_PROJECT` actually exists on Config * (catches: "left a stale denied-field entry behind after a rename"). * 3. The two lists are disjoint (catches: "put the same field in both * lists; allow-list silently wins and the deny docs lie"). */ export declare function assertInProjectAllowListComplete(): void; /** * Remove forbidden top-level keys from a repo-committed in-project config * before it is merged. Returns a new object; the original is not mutated. * Emits a warning (and a `config.read` failure-style event) naming the * stripped keys so the behavior is observable rather than silent. * * On first invocation, runs `assertInProjectAllowListComplete()` to verify * the allow-list + deny-list together still cover every top-level field of * `Config`. The check is idempotent and the result is memoized so the cost * is paid at most once per process. The assertion throws on drift, which * surfaces the issue at boot in production and at first test invocation in * CI — both observable, never silent. */ export declare function stripUnsafeInProjectFields(inProject: PartialConfig, sourcePath: string, warn?: (msg: string) => void): PartialConfig; /** * A single config source. Higher priority wins in merges. * Sources are applied in priority order (lowest first), so a source * with priority=10 overrides one with priority=1. */ export interface ConfigSource { /** Unique name for debugging and error messages. */ name: string; /** Lower numbers merge first, higher numbers override lower. Default: 50. */ priority?: number | undefined; /** * Read the raw config patch. Return an empty object if unavailable. * Errors are surfaced but do not abort loading — the source is skipped. */ read(): Promise>; } export interface ConfigLoaderOptions { paths: WstackPaths; strict?: boolean | undefined; vault?: SecretVault | undefined; /** Extra sources merged after the built-in layers. */ sources?: ConfigSource[] | undefined; events?: EventBus; traceId?: string; /** Logger for structured warning/error events. When omitted, falls back to console.warn. */ logger?: Logger | undefined; } export declare class DefaultConfigLoader implements ConfigLoader { private readonly paths; private readonly strict; private readonly vault; private readonly extraSources; private readonly events; private readonly traceId; private readonly logger; private readonly jsonCache; constructor(opts: ConfigLoaderOptions); /** * Emit a structured warning. Uses the configured Logger when available; * falls back to console.warn(JSON) so warnings are never silently dropped * during early boot (before a Logger is constructed). */ private logWarn; load(opts?: { cliFlags?: Partial | undefined; cwd?: string | undefined; skipIdentityValidation?: boolean; }): Promise; /** Check whether a config object contains only the two bootstrap keys. */ private static isBootstrapOnly; private ensureGlobalDefaults; /** * Ensure a profile config file exists at the given path. * On first boot: migrate content from the old flat global config, or seed * with behavior defaults. Subsequent boots: fill any missing keys from * CONFIG_BEHAVIOR_DEFAULTS (keeping user settings intact). * * Once a profile file exists, settings found in the root bootstrap are * ignored. This prevents stale or accidentally-written root values from * becoming a hidden settings source alongside profiles. */ private ensureProfileConfig; /** * Persist sync config to the active profile's sync.json, with the token encrypted * by the vault (if provided). The file is isolated from the main config * hierarchy to prevent accidental commits. */ persistSyncConfig(cfg: SyncConfig): Promise; /** * Read the active profile's sync.json (encrypted GitHub token storage) and decrypt * the token if a vault is available. Returns null if the file doesn't exist. * This is separate from main config loading because sync.json is intentionally * isolated — it should never be part of project-local or env-driven config. */ loadSyncConfig(): Promise; private readJson; private validateBehavior; private validateIdentity; } export {}; //# sourceMappingURL=config-loader.d.ts.map