import { FileConfigStore } from "@sema-agent/settings-schema/node"; import { type EffectiveConfig as AgentConfigEffective, type EffectiveReadWarning } from "@sema-agent/settings-schema"; import { fetchEffective as remoteFetchEffective, fetchSkillContent as remoteFetchSkillContent, type EffectiveConfig } from "./config-center/facade.js"; /** Result of a `fetchEffective` — EXACTLY the config-center's return: the effective config + its etag, or * `null` for "unchanged" (remote 304; local: the version matched the caller's prior etag). * `domainErrors`([ref] registry-core 0.10.12 tolerant seam,local lane only):catalog 域坏文件不再 * 连坐全包回落 env——坏域按该域 schema default 落+错误单列,好域照常生效;caller(main.ts)对每条打 * per-domain warn 点名坏域。gate 域(governance/rosters/…)坏文件 tolerant 仍 throw(READ 容错不放宽 * gate fail-loud,registry-core 半场精化)——那条路走既有的整包 catch。 */ /** 壳写在 `config.d/models.json` 条目上的装饰键(`sema*`)自 settings-schema 1.5.0 起是**声明过的扩展命名空间** * (`EXTENSION_NAMESPACES`):schema 照旧 strip、但不再报「未知键」,所以本函数收到的每一个未知键都是拼错/越界 ⇒ 候选门错误。 * 此前把这类键当「通知」放行的整套下游机制(域豁免/键路径闭集/满额特判/单独的通知面)已全部撤回——意图在声明处说,消费方不猜。 */ /** `readWarnings`:契约 `onWarning` 流的**原值**(未折叠),两条读腿同键同义。折叠是有损的—— * `hosts-grandfathered` / `legacy-governance-lifted` 按律折成 `null`,只看 `domainErrors` 的人永远看不见 * 兼容臂还开着,而契约铸出 `legacy-governance-lifted` 的理由正是「关窗的前提是有人知道它还开着」 * (`legacyGovernanceFromRuntime` SUNSET 注)。恒在场(空数组 = 这次读零告警),不用缺席表达「没有」 * ——缺席/空两形并存就是又一处二义。 * 🔐 原流的 `error` 未经 {@link redactConfigError}(那道脱敏挂在真正进日志流的 `domainErrors` 产出边界上), * 路由进日志/审计流的消费者必须自己先脱敏。 */ export type FetchEffectiveResult = { effective: EffectiveConfig; etag?: string; domainErrors?: Array<{ domain: string; error: string; }>; readWarnings: readonly EffectiveReadWarning[]; } | null; /** Sentinel returned by {@link raceBootFetch} when the boot pull outlived its budget — the caller * starts serving on the env fallback and the SAME promise settles in the background (never cancelled). */ export declare const BOOT_FETCH_DEFERRED: unique symbol; /** * Race the boot config pull against a wall-clock budget (clay 2026-07-17: a black-holed center made * the SYNCHRONOUS boot pull hang ~5.7s on the TOC lane — the "local server takes 5s to start" report). * Inside the budget → the fetch result verbatim (the healthy-center fast path is unchanged, typically * <300ms). Past it → {@link BOOT_FETCH_DEFERRED}; the fetch is NOT aborted — the caller chains a * background continuation that triggers a refresh tick when it finally settles. The timer is unref'd * (never holds the loop) and cleared as soon as the fetch wins. */ export declare function raceBootFetch(fetch: Promise, budgetMs: number): Promise; /** * The two transport calls the service makes to obtain config, behind a backend-selectable seam. Both * signatures and return shapes match `facade.fetchEffective` / `facade.fetchSkillContent` * EXACTLY so that, ONCE WIRED, the caller (main.ts) could swap remote↔local without touching applyEffective * et al. (main.ts does not swap on this seam yet — see the NOT-YET-WIRED note at the top of this module). */ export interface ConfigProvider { /** "remote" = config-center HTTP; "local" = on-disk FileConfigStore. */ readonly kind: "remote" | "local"; /** * Pull the effective config. `etag` is the caller's last-seen version token (`if-none-match` on remote; * the stable version string on local). Returns `null` when unchanged, else the config + a fresh etag. * Throws on a transport/read error (caller keeps env fallback). */ fetchEffective(etag: string | undefined): Promise; /** * Fetch one skill body by content hash (`sha256:`). Remote: GET the content endpoint + verify the * hash. Local: content-addressed lookup over the local skills domain. Returns `undefined` when the hash * is unknown (remote 404 / local miss) so the caller keeps its baseline; throws only on a hard error * (remote non-404 HTTP / hash mismatch). */ fetchSkillContent(contentHash: string): Promise; } /** The config-provider knobs read off ServiceConfig (a structural subset — avoids importing the whole * ServiceConfig type and keeps this module testable with a plain object). */ export interface ConfigProviderInput { /** Set when SEMA_REGISTRY_URL (legacy CONFIG_CENTER_URL now boot-rejects, no dual-read) is configured (config.ts `configCenter`). `dryRun` is carried through so a * remote↔local swap via this seam does NOT drop the dry-run posture main.ts branches on (config.ts * `configCenter.dryRun`); the provider itself does not consume it (the caller still branches on it). */ configCenter?: { baseUrl: string; token: string; worker?: string; dryRun?: boolean; }; /** `CONFIG_PROVIDER` env — a CLOSED enum `local` | `remote` | undefined. `local` forces the local * backend; `remote` selects the center (falls through to local if no URL); any OTHER value is treated as * a misconfiguration and defaults to local (safe, non-network) with a warn. Normalize/trim the raw env * string at parse time in config.ts when this knob is actually wired. */ provider?: string; /** `CONFIG_LOCAL_DIR` env — root holding `config.d/.json` for the local backend. Defaults to * `./config.d`'s parent convention (the FileConfigStore takes the ROOT, and reads `/config.d`). */ localDir?: string; } /** * RemoteConfigProvider — DELEGATES to the exported config-center HTTP functions. No HTTP is reimplemented; * baseUrl/token/worker are bound once and passed through. The two delegate fns are injectable (defaulting * to the real config-center exports) so a unit test can assert delegation without module-mocking. */ export declare class RemoteConfigProvider implements ConfigProvider { private readonly cc; private readonly deps; readonly kind: "remote"; constructor(cc: { baseUrl: string; token: string; worker?: string; }, deps?: { fetchEffective?: typeof remoteFetchEffective; fetchSkillContent?: typeof remoteFetchSkillContent; }); fetchEffective(etag: string | undefined): Promise; fetchSkillContent(contentHash: string): Promise; } /** * LocalConfigProvider — reads the SAME config contract from disk via `FileConfigStore`. `getEffective()` * returns the registry `EffectiveConfig` (all domains, defaults filled); `getVersion()` returns a * stable change-detector. We map that down to the service-local `EffectiveConfig` shape and surface the * version as the etag (so the caller's "skip re-apply if etag unchanged" works locally too). */ export declare class LocalConfigProvider implements ConfigProvider { #private; readonly kind: "local"; private readonly store; /** [ref]b 可观测性:config_loaded 日志要能回答「到底读的哪个目录」——复验实录:只设 LOCAL_DATA_ROOT 时 * config.d 静默读不到且 0 模型无告警(CONFIG_LOCAL_DIR 才是本 lane 的选目录旋钮)。 */ readonly root: string; constructor(root: string, store?: FileConfigStore); fetchEffective(etag: string | undefined): Promise; fetchSkillContent(contentHash: string): Promise; } /** * Map the registry `EffectiveConfig` (superset, all domains) → the service-local `EffectiveConfig` * the consumers (`applyEffective`, `resolveMcpServers`, …) expect. The two are structurally compatible * for every field the consumers read; this projection makes that explicit (and drops control-plane-only * domains the service never consumes: rosters, systems, workers, hosts, integrations, access). * * Skills are mapped to the MANIFEST shape (name/description/scenarios/contentHash/enabled) — the same * shape RemoteConfigProvider's `/effective` ships — so the lazy `fetchSkillContent`-by-hash path is * identical in both modes. The local content hash is the `contentHash` the store already computed. */ export declare function mapToServiceEffective(eff: AgentConfigEffective, version: number): EffectiveConfig; /** * Pick the backend off a CLOSED `provider` enum (`local` | `remote` | undefined): LOCAL when * `CONFIG_PROVIDER=local`, OR when no config-center URL is configured (a lone box with config.d/ on disk * and no control plane). REMOTE otherwise (a configured center URL, the default fleet posture). A * `CONFIG_PROVIDER=remote` with no URL is a misconfiguration — we fall through to local rather than * constructing a remote provider with no endpoint (the caller would have skipped the remote pull entirely * under the old code path; local-with-empty-config.d degrades to env the same way). An UNRECOGNIZED value * (typo / wrong case) is NOT treated as remote: it defaults to local (non-network) with a warn, so a * fat-fingered knob can never silently source config from an unintended/stale network endpoint. */ export declare function createConfigProvider(input: ConfigProviderInput): ConfigProvider; //# sourceMappingURL=config-provider.d.ts.map