/** * EffectiveConfig application — mutate the boot `ServiceConfig` OVER with a pulled `EffectiveConfig` * (models/roles/tiers/projects `applyEffective`, the boot-only approval gate + runtime limit residue * `applyRuntimeGates`, the per-request-hot governance baton `applyRuntimeHot`) plus their shared helpers * (`toModel`, `mutateInPlace`, `resolveDefaultModelName`, `logEffectiveDiff`). Split out of `facade.ts` * ([ref] A13, internal-lossless — the facade re-exports every symbol below unchanged). */ import type { Model } from "@sema-agent/core"; import type { ServiceConfig } from "../config-types.js"; import { type SealedKeyOpener } from "../sealed-key.js"; import type { Logger } from "../observability/logger.js"; import type { EffectiveConfig } from "./types.js"; /** Apply center config OVER the env-derived config (mutates `config`). Empty domains keep env defaults. */ /** Replace a record's contents IN PLACE (clear keys, copy source) so holders of the SAME reference see the * change. This is what makes models/roles/pricing HOT-apply: core's Runner reads * `this.deps.models/roles/pricing` LIVE per-task and `/v1/models` reads `config.models` — both share the * reference captured at boot, so mutating it (vs reassigning) updates both with no split, no Runner rebuild. */ export declare function mutateInPlace(target: Record, source: Record): void; /** * [ref] / [ref] —— 无法解读的名单条目的**代表元**。 * * 为什么需要它(codex 复审 R1 真 finding):把坏条目**丢掉**会让 `[7, null]` 这种「在场但一条都读不 * 懂」的名单塌成**空数组**,而空数组在消费端的语义恰恰是「治理关闭 = 全放行」—— 一个表达"我要限制" * 的配置,因为写错了类型,反而把门整个打开。方向必须反过来:名单在场 = operator 意图限制;读不懂的 * 条目 ⇒ 换成一个**永不可能是 catalog name** 的代表元(前导 NUL,registry-core 的 name 词法不可能产 * 出),于是它在归一时解不到任何目录条目 ⇒ 谁都准不了。 * * 后果是**有界的**:被拒的只有「用户逐 turn 显式点模型」这一件事;`default` / roles / 档位表照常, * 任务照跑。写错配置的代价是"点不了模型"而不是"服务不可用",且 warn 当场点名成因。 * 🔴 只活在内存判定面:不进 wire(`/v1/models` 只发布尔)、不进日志(warn 打的是原始坏值的类型标记)。 */ export declare const UNPARSABLE_AT_MODEL_ALLOWLIST_ENTRY = "\0"; /** 返回值=是否 COMMIT(false 两臂:[ref]③ 世代序 CAS 拒绝 / 7.10.0 swapPlane 换装拒绝——两臂都在 * commit 之前,活配置零触碰)。caller 收到 false 必须把**同一世代的旁路消费**(prompts 采用/pricing/ * keyResolver/etag 推进/LKG 落盘)一并跳过——否则 applyEffective 拒了主面、旁路却半应用同一个被拒 * 世代,混合世代从侧门回来。首次 apply(live 未登记)恒 true。 */ /** [ref]:一次 apply 的**世代账载荷**(post-commit 发,[ref]② —— commit 段仍是零回调零发射)。 */ export interface ApplyReport { readonly version: number; /** [ref]:本候选的收货序数(= opts.generation 原样带回;世代账按它记拒收,身份标仍是 version)。 */ readonly ordinal?: number; readonly limits: { /** 本世代真正换了值的键。 */ readonly applied: readonly string[]; /** 值合法但**不热**的键(逐键 defer:变更只能重启生效)。 */ readonly deferred?: ReadonlyArray<{ readonly key: string; readonly reason: string; }>; /** 整批被拒时的成因(消毒:键名 + 判据文案,绝不带值)。 */ readonly rejected?: { readonly keys: string[]; readonly reason: string; readonly restartReason?: string; }; }; /** 🔴 codex R2-F1:本候选是否**完整**应用 —— 有任何一组被拒 ⇒ false。调用方据此决定要不要把它发布成 * LKG / 推进 etag(一个「其余面提交了但某组被拒」的候选覆盖掉好 LKG,重启后就再也回不去了)。 */ readonly fullyApplied: boolean; } export declare function applyEffective(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger, opts?: { teamsOnly?: boolean; sealedKeys?: SealedKeyOpener; deferModelPlane?: boolean; generation?: number; report?: (r: ApplyReport) => void; swapPlane?: (plane: { models: Record; tiers: Record; }) => void; }): boolean; /** * UNIFORM presence semantics across the center gates (center `f95438a` adopted this from our sentinel flag — * SERVICE-INTEGRATION「Runtime 治理/限额闸」): every gate field is center `.optional()`, so * `undefined` = center isn't managing it → KEEP env; * present (any value, INCLUDING an explicit 0 / [] = "publish the gate OFF") → OVERRIDE env. * (Earlier center had `rateLimitPerMin`/`approvalRequire` as `.default(0/[])`, which would have let a default * silently disable an env-set gate — we flagged it, center made them optional, so a plain presence rule is * now both correct and uniform. No sentinel special-casing.) * * Only guard: `costQuotaWindowSec` must be `> 0` (a 0 window is nonsensical — core divides by it). Center's * schema is `.positive()` so it can't send 0; this is belt-and-suspenders. * * Per-worker overrides ride the per-worker `/effective?worker=` response (center merges WorkerSpec.runtime over * the fleet default server-side), so this single "effective beats env" rule already honors per-worker > fleet. */ /** `runtime` 域的限额五闸(= settings-schema `RuntimeConfig` 的全部键,[ref] 件3 对账门钉集合等式),ONE place: * legacy 位的 presence 判据({@link runtimeGatePresent} → stage-limits 的 `legacyGates`)、config-catalog 的 * center 在管键、dry-run 现值快照都从这一张表派生。[ref] 批1 起五键**全热**(属主 stage-limits,boot+refresh * 双腿),所以本表与 restart-to-apply 再无关系——审批闸不在这里:它住 governance 域({@link BOOT_GATE_KEYS})。 */ export declare const RUNTIME_GATE_KEYS: readonly ["rateLimitPerMin", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"]; export type RuntimeGateKey = (typeof RUNTIME_GATE_KEYS)[number]; /** boot-only(restart-to-apply)的治理闸:今天只有 `governance.approvalRequire` 一键——它被 main.ts 在本函数之后 * 用 `config` 构造进 approval gate,refresh 腿(teamsOnly)不写,变更由 restart-signal 的 `runtime-gates` 片通告。 * ([ref] 批2 若把它转热,这张表清空、那个片随之退役——一处改动。) */ export declare const BOOT_GATE_KEYS: readonly ["approvalRequire"]; export type BootGateKey = (typeof BOOT_GATE_KEYS)[number]; /** Uniform presence rule (center f95438a) for the `runtime` limit gates: "managed" when the field is present — * any number (incl. 0). Sole guard: costQuotaWindowSec must be > 0 (a 0 window is nonsensical — core divides by * it; center's `.positive()` already enforces, belt-and-suspenders here). The approval gate's presence rule * (`Array.isArray`) lives with its owner, {@link stageApprovalGate}. */ /** * DESIGN-278 §5 S2([ref])—— center 这份 effective **当前在管**哪些 `ServiceConfig` 键(配置目录端点 * `GET /v1/config/catalog` 判 `effectiveLane` 的 center 半场)。 * * 判据与各消费腿**同源**,不自铸第二份: * · limits 域 = `hasOwnProperty`(stage-limits 的 published 判据逐字——present 含显式 0); * · 六闸 legacy 位 = {@link runtimeGatePresent}(旧位 presence 规则的唯一属主); * · governance/runtime 热二件 + approvalRequire = 键在场(applyRuntimeHot 的 non-sticky 判据同门); * · readFace 域成员 = 键在场(read-face.ts 的逐键 env-wins 合成看的就是成员在场性); * · models 面 = 有启用行(markRosterLanded 同判据);atModelAllowlist 单列(整表重算键)。 * 纯投影零副作用;`undefined` eff(还没拉到 / 纯 env 形)⇒ 空集。 */ export declare function centerManagedConfigKeys(eff: EffectiveConfig | undefined): ReadonlySet; export declare function runtimeGatePresent(rt: NonNullable, key: RuntimeGateKey): boolean; /** 独立调用面的兼容壳(测试/外部):stage → 就地赋值 → 通知。applyEffective 不走这里——它把 * assignments 并进自己的 commit 段以保住整世代原子性([ref]②)。入参是 eff 的两个域([ref] 件3:审批闸 * 住 governance,限额五闸住 runtime——两位各自的属主判据,这里不再合成视图)。 * [ref]:限额五闸的属主已移交热腿,所以本壳**同样**要过 stage-limits —— 否则一个仍叫 applyRuntimeGates * 的函数会静默漏掉五个限额键(比改名更坏的那种沉默)。 */ export declare function applyRuntimeGates(config: ServiceConfig, eff: Pick | undefined, logger?: Logger): void; export declare function applyRuntimeHot(config: ServiceConfig, gov: EffectiveConfig["governance"], logger?: Logger): void; /** * [ref]① 显式默认解析——applyEffective 与 dry-run(logEffectiveDiff.wouldDefaultModel)共用的单源。优先级 * (显式源指向不在目录/未启用的名 ⇒ onDangling 回调后落下一级,绝不静默指错模型): * 1) eff.models.default 结构键 —— ✅ 既得路径([ref] registry-core 0.10.11 落 `default?: string` schema * 键+per-worker /effective 直通;本地 lane 由 config-provider mapToServiceEffective 直通)。写面悬空 ref * 由 registry-core superRefine fail-loud;存量/未经 schema 路径仍走本函数的 warn 降级(双层姿势与 * activeTierGroup 同构)。(1.200-1.202 期间此级恒空=当时如实的前行 seam,codex H2 注记已兑现。) * 2) roles.default 的显式 {model} 绑定(现有 wire 真源)。 * 3) activeTierGroup 档绑定,按 {@link DEFAULT_ROLE_TIER_LADDER}(= core 对 "default" 角色的档语义)。 * 4) fallback(enabled[0]——原兜底,唯一不触发 onDangling 的落点)。 */ export declare function resolveDefaultModelName(eff: EffectiveConfig, has: (name: string) => boolean, fallback: string, onDangling?: (source: string, name: string) => void): { name: string; source: string; }; /** * Read-only comparison (SEMA_REGISTRY_DRY_RUN): log what the center config WOULD change vs the current * (env-derived) config, WITHOUT applying it. The safe-rollout step the config-center recommends — verify * the center's models/roles/collab match (or intentionally differ from) the env baseline before going live. */ export declare function logEffectiveDiff(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger): void; //# sourceMappingURL=apply-effective.d.ts.map