/** * daemon-config-read.ts, reading config by OWNERSHIP, the other half of the * routing table in daemon-config-route.ts. * * Correct write routing with wrong read routing is still a system that lies. * The failure that produced this module: a daemon-owned setting was written, * the user asked a client to confirm it, the client read its OWN store, saw * nothing there, and reported the setting as not set. * * So reads follow the same rule writes do: * - daemon-owned key, daemon reachable → the daemon's LIVE value. * - daemon-owned key, this process IS the daemon, or none is running → the * local config manager, which resolves the daemon store. * - daemon-owned key, a daemon is running but unreachable → `unavailable`. * NEVER a default and never a stale local mirror: a default presented as * the current setting is indistinguishable from a lie. * - client-owned / user-level key → the local config manager. * * Batching matters. A settings listing covers hundreds of keys, so the daemon * is asked ONCE (`GET /config` returns the whole resolved config) and every * daemon-owned key is read out of that one snapshot. */ import { type ConfigScope } from './config-ownership.js'; import { type DaemonConfigEndpoint, type DaemonConfigRouterDeps } from './daemon-config-route.js'; /** Where a read for `key` must go. Mirrors the write route exactly. */ export type ConfigReadRoute = { readonly mode: 'local'; readonly scope: ConfigScope; readonly reason: string; } | { readonly mode: 'daemon'; readonly scope: 'daemon'; readonly endpoint: DaemonConfigEndpoint; readonly reason: string; }; /** The narrow local reader this module needs, satisfied by ConfigManager. */ export interface LocalConfigReader { get(key: string): unknown; getConfigPath(): string; getDaemonTierPath?(): string | null; } /** One key's effective value, and the store it actually came from. */ export interface EffectiveConfigEntry { readonly key: string; readonly scope: ConfigScope; /** Which runtime answered. */ readonly source: 'daemon' | 'local'; /** * `ok`, `value` is the live value from the owning runtime. * `unavailable`, the owning runtime could not be reached; there is NO value, * deliberately, rather than a default that would read as the current setting. */ readonly status: 'ok' | 'unavailable'; readonly value?: unknown; /** Absolute file path, or the daemon base URL that answered. */ readonly store: string; readonly reason: string; readonly error?: string; } /** Decide where a read for `key` belongs. Same ownership rules as writes. */ export declare function resolveConfigReadRoute(key: string, deps: DaemonConfigRouterDeps): ConfigReadRoute; /** * A one-shot cache of the daemon's resolved config, so a listing over hundreds * of keys costs one HTTP call. `error` is set when the daemon was expected but * did not answer, every daemon-owned key in that listing then reports * `unavailable` instead of silently degrading to a local default. */ export interface DaemonConfigSnapshot { readonly endpoint: DaemonConfigEndpoint | null; readonly config: Record | null; readonly error: string | null; } /** * Fetch the daemon's config once. Never throws, the error is reported per key. * * A connection this process already holds is used FIRST when one was supplied * (`readDaemonSnapshot`). That is what puts reads and writes on one resolution: * writes go out over the connected host's `config.set`, so reads must come back * from that same host, or a setting can be written successfully and then read * as missing. Address discovery is the path for a process that holds no * connection, not a second opinion for one that does. */ export declare function loadDaemonConfigSnapshot(deps: DaemonConfigRouterDeps): Promise; /** * Read one key from whichever runtime owns it. Throws * {@link DaemonConfigUnreachableError} when the daemon owns the key and cannot * be reached, the caller must not be handed a default it would present as the * current setting. Use {@link readEffectiveConfig} when a per-key * `unavailable` is more useful than an exception (listings). */ export declare function readConfigValue(key: string, local: LocalConfigReader, deps: DaemonConfigRouterDeps): Promise; /** * Read many keys at once, resolving each by ownership. One daemon round-trip * for the whole set. Every entry names the store it came from, so a listing can * show WHY the same key name reads differently in two places, the question * nobody could answer before. */ export declare function readEffectiveConfig(keys: readonly string[], local: LocalConfigReader, deps: DaemonConfigRouterDeps): Promise; /** * A synchronous, ownership-aware view of config. * * The daemon is asked ONCE up front, then `get(key)` answers immediately, * daemon-owned keys from the daemon's live config, everything else from the * local manager. That shape matters: the existing describe/list code paths take * a plain `{ get(key) }` reader and are synchronous all the way down, so this * drops straight in without turning a settings listing into an async cascade. * * When the daemon was expected and did not answer, `unavailable` lists the keys * whose value is genuinely unknown and `get` returns `undefined` for them. A * caller MUST check `unavailable` before presenting a value as "the current * setting", that is the whole point. */ export interface EffectiveConfigView { get(key: string): unknown; /** Where `key` resolves from, and whether the answer is trustworthy. */ describe(key: string): EffectiveConfigEntry; /** Daemon-owned keys whose live value could not be read. */ readonly unavailable: ReadonlySet; /** The daemon's reachability error, or null. */ readonly daemonError: string | null; /** Base URL of the daemon consulted, or null when none was. */ readonly daemonBaseUrl: string | null; } /** Build an {@link EffectiveConfigView}: one daemon round-trip, then sync reads. */ export declare function createEffectiveConfigView(local: LocalConfigReader, deps: DaemonConfigRouterDeps): Promise; //# sourceMappingURL=daemon-config-read.d.ts.map