import { type Model, type PathRuleBases, type RuleBehavior } from "@sema-agent/core"; import { type Autonomy } from "./runtime-governance.js"; import type { ServiceConfig, ServiceConfigFlat, ServiceConfigGroups } from "./config-types.js"; /** [ref] A1: the config TYPE face lives in the leaf module config-types.ts; re-exported here so * every existing `from "./config.js"` importer compiles unchanged (pure-type consumers should prefer * importing config-types.js directly — a type-only leaf, no loader baggage). */ export type { ServiceConfig, ScopedMcpServer, ImageBakeConfig, ServiceConfigFlat, ServiceConfigGroups, ServiceStoreConfig, ServiceModelPlaneConfig, ServiceApprovalConfig, ServiceMemoryConfig, ServiceAuthConfig, ServiceOrchestrationConfig, ServiceLimitsHttpConfig, ServiceObservabilityConfig, ServiceIntegrationsConfig, MemoryEmbedderConfig, // [ref]:embedder 坐标(plugins/memory-embedder.ts 消费同一属主类型) A2aServeConfig, A2aServeSkill, RetentionMode, RetentionSweepConfig, MemoryConsolidationDriverConfig, } from "./config-types.js"; export { RETENTION_MODES, MEMORY_CONSOLIDATION_MIN_INTERVAL_SEC } from "./config-types.js"; /** Parse the AUTONOMY env into a validated autonomy mode. Unset/empty → undefined (unmanaged → no extra * tightening). An UNKNOWN value FAILS at startup rather than silently becoming a no-op (a typo'd `AUTONOMY=readonly` * must not silently leave a deployment ungoverned — fail-loud, same discipline as numEnv). Exported so the HOT * config overlay (config-center/facade.ts `applyRuntimeHot`) can re-derive the ENV BASELINE to revert to when center * stops managing `autonomy` (a stale center override must not stick — see applyRuntimeHot). */ export declare function parseAutonomy(raw: string | undefined): Autonomy | undefined; /** [ref]-T1 — parse `USAGE_WINDOWS`(单键 JSON 数组;逐窗散键在窗数可变时组合爆炸,JSON 是唯一 * 封闭形)。unset/空串 → undefined(关)。fail-loud 族(numEnv 同纪律):坏 JSON/非数组/空数组/ * 坏值/未知 anchor 全部 boot throw 点名——「设了但无意义」静默关掉=部署方以为有治理实际没有。 * [ref](core 7.0.0 `UsageWindow.maxCostUsd`,dist/core/usage-window-store.d.ts 亲读): * `maxTokens` 与 `maxCostUsd` 是两只**独立** ceiling——each optional on its own、**at least one * required**(双缺=拒:a window with neither admits nothing it can govern),whichever fills first * 即耗尽;缺席的 ceiling 不参与(非 0、非 ∞)。修前这里的白名单重铸只认三键,$ 键配了也被静默 * 剥掉——正是本函数要消灭的那族病自己。 * server 严于 core(core 允两 ceiling 取 0=恒满锁死窗;这里都要求正数,0 配置显然是笔误不是意图); * `maxCostUsd` 另提前执行 core 的 micro-USD 门(×1e6 仍有限——超界 ceiling 在账本单位里读作 ∞, * 「看起来被治理、实际什么都不拦」,core 在门口拒,这里拒在 boot)。 */ export declare function parseUsageWindows(raw: string | undefined): readonly { windowMs: number; maxTokens?: number; maxCostUsd?: number; anchor: "first-use" | "rolling"; }[] | undefined; /** Numeric env with validation: a non-numeric value FAILS at startup instead of silently becoming `NaN` * (which e.g. slips past the `runStaleSec` liveness guard and silently disables the reaper). */ export declare function numEnv(name: string, fallback: string): number; /** * 数值 env 的核心判据,**接受 raw 值**(而非自己去读 process.env),这样注入式配置也能复用同一条判据。 * * 提出来的理由(2026-08-01 gap-sweep):`leader/wire.ts` 的四个资源旋钮各自手写了 * `Number(x) || default`,因为 numEnv 只会读 process.env、够不到它们注入进来的 env 对象。 * 结果是同一个失败模式在另一处复发,而且**回落方向更坏**:那边落到的是慷慨的默认值, * 症状从「设了没生效」变成「想收紧却被松绑」。判据只有一份,谁都能调,才不会各写各的。 * * `undefined`/空串 ⇒ 交给调用方的 fallback 语义处理(本函数只判「给了值但不是数」)。 */ export declare function parseNumOrFail(name: string, raw: string | undefined): number; /** * `parseNumOrFail` layered with a non-negative floor — the SAME judgment `leader/wire.ts`'s (private, * file-local) `parseNumOrFailNonNegative` already applies to its four resource knobs, exported here so a * second call site (bake-runner's heartbeat/idle-poll/disk-guard knobs, gap-sweep 2026-08-01) can reuse the * one shared primitive instead of re-deriving its own "reject a bad number" scheme. A negative value is * JS-truthy, so a bare `|| default` fallback lets it straight through; for a duration knob that means * `clock.sleep(-N)` fires ~immediately (the same tight-loop failure NaN causes), and for a threshold knob * (`freeGb < minFreeGb`) a negative floor makes the comparison vacuously true/false depending on sign — * either way the guard it configures goes silently slack. `undefined`/unset stays NaN (unchanged) so * existing `|| fallback` chains built on `parseNumOrFail` are untouched by this stricter sibling. */ export declare function parseNumOrFailNonNegative(name: string, raw: string | undefined): number; /** Optional FAIL-SAFE numeric env for an OPTIONAL feature knob: a non-numeric / non-positive value yields `undefined` * (the caller falls back to its own default) rather than NaN OR a hard startup throw. Used for SESSION_SNAPSHOT_TTL_SEC * (finding 20): a typo like "600s" must NOT sign every presigned URL with X-Amz-Expires=NaN (total snapshot outage), * but ALSO must not crash boot of an unrelated subsystem — degrade to the MinioBlobBackend default. */ /** S20 (SILENT-FALLBACK P1): soft-knob parse rejects, collected here because config parsing runs BEFORE the * logger exists. main.ts drains this once after createLogger and warns per entry ("configured X=raw is * invalid, using the default"). Module-level on purpose; drain clears it (repeat loadConfig in tests is fine). */ /** * [ref](黑板 [ref]③):一条配置诊断的**种类**。 * * ⚠️ 为什么是显式 `kind` 而不是「`used` 在不在场」(codex 自审 R2-F1 真修):第一版拿可选字段当判别式, * 于是三件语义完全不同的事挤在两个出口上 —— 空串**保留原值**被报成「越界夹取」、坏值**回默认**因为带了 * 采用值也被报成夹取、而钥集条目**被丢弃**(没有任何采用值、也不该印 raw)却被报成「坏值回默认」。 * 告警规则是按事件名分流的,报错的种类就是报错的事实本身;可选字段的在场性是实现细节,不该被当契约用。 * * `invalid-default` 是**缺省种类**(不写 `kind` 即是它)—— 历史形 `{env,raw}` 的三个存量生产者 * (`optFinitePositiveEnv` / `boolEnv` / `boolEnvWithLegacyNegated`)因此逐字不动,它们的断言也不破。 */ type ConfigWarningKind = "invalid-default" | "clamped" | "empty-preserved" | "entry-rejected" | "set-rejected" | "ineffective"; interface ConfigWarning { env: string; /** 环境原文。`entry-rejected` 上恒为脱敏占位 —— 那两条 env 装的是凭据,原文永不进日志。 */ raw: string; /** 真正生效的值(`clamped` 必带;`invalid-default` / `empty-preserved` 可带)。 */ used?: string; kind?: ConfigWarningKind; /** `entry-rejected` / `set-rejected` 专用:条目序号 + 缺陷字段名,或整份坏的形状原因(都不带值)。 */ detail?: string; } export declare function drainConfigWarnings(): ConfigWarning[]; export declare function drainConfigNotices(): Array<{ event: string; fields: Record; }>; /** F10 (codex audit): drain + log BOTH pre-logger diagnostic collectors (S20 soft-knob warnings + [ref]④ boot * notices) in one place. EVERY entrypoint that calls loadConfig() then builds a logger must call this right * after (main.ts, run-local.ts) — a new entry that forgets it would silently drop operator-facing diagnostics * (run-local historically did exactly that), so the drain lives beside the collectors, not in one entrypoint. */ export declare function logConfigDiagnostics(logger: { warn: (event: string, fields?: Record) => void; info?: (event: string, fields?: Record) => void; debug?: (event: string, fields?: Record) => void; }): void; /** B-089:`Model.cost` 的四项(per-1M USD)。类型从 core 的 `Model` 取,不在本仓重列一份。 */ type ModelCost = NonNullable; /** * 🔴 B-089 —— `Model.cost` 这**一个键**的唯一铸点(可展开片段):有价目表 ⇒ 键在;没配 ⇒ **键不出**。 * 三个铸点(env 主槽 / env cheap 槽 / config-center 车道)共用它,所以「缺席怎么表达」在本仓只有一处答案。 * * `Model.cost` 自 core 7.16.0([ref])起在**类型面**上也是可选的(`dist/engine/llm/types.d.ts` 的 * `cost?:`),与它一直以来的运行期读法([ref] 的 `isModelPriced`:这个键在不在)对齐 —— * 于是本函数不再需要任何类型让步,返回 `{}` 就是「不写这个键」的字面表达。 * * ⚠️ 连带纪律仍在:**读** `Model.cost` 一律先判在场(catalog 行的 `c.model.cost?.input`), * 别写 `model.cost.input`;而「这只模型定价了吗」这一问不许在本仓重写一遍,调 `isModelPriced`。 */ export declare function buildModelCostKey(cost: ModelCost | undefined): Pick; /** * 🔴 B-089 —— 「配了 $ 成本天花板,但目录里一只带价模型都没有」的判据 + 文案(纯函数,零副作用)。 * * 两道天花板(`MAX_TASK_COST_USD` / `MAX_PRINCIPAL_COST_USD`)量的是 `costUsd`,而一只**未定价**的模型 * 算不出成本([ref] 之后它连 `costMicroUsd` 这个键都不发)—— 于是这两根旋钮在这台机器上**永不可能 * 触发**:运营方以为配了 $ 护栏,实际一道都没有,且没有任何一处会响。不拒启([ref] 只要求 loud —— * 这是一台**本来就在服务**的机器的既有形,而且「先配上限、稍后配单价」是正当的上线顺序)。 * * ⚠️ 位置即判据(codex r1 [medium] 真复现):**必须拿生效后的目录与限额来判,不能在 `loadConfig` 里判**。 * `loadConfig` 只见 env 车道,而 `main.ts` 在第一次 effective 拉取**之前**就把 boot 诊断排空了 —— * 在那里判会同时犯两个方向的错:center 名册已配价、env 未配 ⇒ **假警**;env 已配价、center 名册撤了价 * (或天花板只由 center 的 limits 下发)⇒ **漏警**。故本函数只给判据,席位在两只入口各自的 * **采纳之后**(`main.ts` 的 `createConfigCenterRuntime` 之后 / `run-local.ts` 的 `applyEffective` 之后)。 * * 判据 = **目录里没有任何一只带价模型**,问法与 `budget.buildPricing` 的填表判据是**同一只函数** * (S-231:core 7.16.0 导出的 `isModelPriced`;第二参传 `undefined` = 这一问发生在注入表建起来之前, * 谓词落在「模型自报价了吗」那一半)。本模块刻意不 import `budget.ts`(那边挂着 metrics/fleet 一串 * 运行期依赖,config 是纯解析层)—— 单源因此不是「谁 import 谁」,而是两边都问引擎那一只。 */ /** 「$ 天花板 × 价目表」的三位事实 —— 告警判据与清警原因的**同一只**读数(S-232 合并复审:清警要说得出为什么清)。 */ export interface CostCeilingPricingPosture { /** 配了的天花板旋钮名(空 = 一根都没配)。 */ ceilings: string[]; /** 生效目录里的模型 id(空 = 目录还空着)。 */ modelIds: string[]; /** 目录里至少一只模型自报了价(`isModelPriced(m, undefined)`,S-231 单源)。 */ anyPriced: boolean; } export declare function costCeilingPricingPosture(models: Record, limits: { maxTaskCostUsd: number; maxPrincipalCostUsd: number; }): CostCeilingPricingPosture; /** 「天花板告警此刻**不**成立」的原因(闭集三词);`undefined` = 告警成立。序 = 先判护栏在不在(`no_ceiling`), * 再判有没有东西可计价(`empty_catalog`),最后才是「真配回了价」(`priced`)—— 只有最后一词可以说 * 「护栏能再触发」,前两词下这句话是假的。 */ export type UnpricedCeilingClearReason = "no_ceiling" | "empty_catalog" | "priced"; export declare function unpricedCeilingClearReasonOf(p: CostCeilingPricingPosture): UnpricedCeilingClearReason | undefined; export declare function buildUnpricedCeilingNotice(models: Record, limits: { maxTaskCostUsd: number; maxPrincipalCostUsd: number; }): { event: string; fields: Record; } | undefined; /** [ref] N8/N9 — the BOOLEAN member of this module's env-parser family (`env`/`env2`/`numEnv`/ * `numEnvBounded`/`optFinitePositiveEnv`/`enumEnv`/`headerNameEnv`/`firstSetEnv`). Until now booleans were the * one type parsed by a hand-written literal comparison at every single site, and that is exactly why the same * `*_ENABLED` suffix grew FOUR incompatible polarities (opt-in `=== "true"`, opt-out `!== "false"`, the posture * tri-state, and negative `*_DISABLED` names): with no shared helper there was nowhere for the default to be * stated, so it was re-invented per line and the suffix stopped predicting anything. * * Semantics are the two dialects' semantics VERBATIM — an opt-in knob is `boolEnv(x, false)`, an opt-out knob is * `boolEnv(x, true)` — so this migration is value-preserving on every input. The one addition: a value that is * neither literal (`1`, `yes`, `TRUE`) still lands on the knob's default (as before) but now raises a config * warning instead of being swallowed, which is the whole failure mode operators kept hitting (`LSP_ENABLED=1` * reads as "on" to a human and as "off" to `=== "true"`). * * Note the deliberate non-migrations: the posture family (`postureOn`) keeps its tri-state — its unset case is * neither `true` nor `false` but "single-user ∧ infra-ready" — and the sentinel-valued knobs (`MEMORY_ENGINE`, * `SANDBOX_PKG_SOURCE`, `MANUAL_MODE_SHELL_GATE`) are enums wearing a boolean's clothes, not booleans. */ /** 复审 2026-07-29 B4:3.0.0 把负名 env 改成 fail-loud 墓碑之后,legacy-env 这个 source 取值**永不可达**—— * 留着它等于在类型上书面承诺"还有一条 legacy 取值路径"。同批摘掉的还有 `ConfigKnobRecord.legacyName` * 与它在 boot 诊断行里的输出:运维在日志里看到 `legacyName: HOST_BG_DISABLED` 的合理结论是"旧名还能 * 用",而实际设了就拒启——诊断面撒的谎比没有诊断更贵(它会让人先去试那个必然拒启的开关)。 */ type ConfigKnobSource = "env" | "posture" | "default"; export interface ConfigKnobRecord { /** The env name. 3.0.0 起只有这一个名字 —— 负名别名已是墓碑(见 `boolEnvWithLegacyNegated`)。 */ name: string; /** `opt-in` = default OFF, `opt-out` = default ON, `posture` = default derived from REQUIRE_PRINCIPAL ∧ infra. */ polarity: "opt-in" | "opt-out" | "posture"; value: boolean; source: ConfigKnobSource; } /** The registered boolean knobs, sorted by name — the data behind `logConfigDiagnostics`'s polarity table. */ export declare function configKnobTable(): ConfigKnobRecord[]; /** [ref] N8 (D-family) + server 3.0.0 墓碑 —— a knob whose CANONICAL name is positive (`X_ENABLED`, * default ON) and whose retired negative name (`X_DISABLED`) is now a **fail-loud tombstone**. * * Why the flip at all: `X_DISABLED !== "true"` is a double negative at the read site, so the four knobs that * used it (`PROJECT_MEMORY`, `CONFIG_LKG`, `HOST_BG`, `HOST_EXEC_SPOOL`) could not be read for their default * without inverting twice in your head, and `projectMemoryDisabled` was the ONE negative boolean on a * `ServiceConfig` that otherwise carries 16 positive `*Enabled`/`*Allowed` fields. * * 🔴 3.0.0([ref],clay 令「不做兼容」):旧名不再有兼容窗——**设了就拒启**。这个函数保留 legacy 形参 * 不是为了继续读它,而正是为了**认出它在场并拒绝启动**(把墓碑放在唯一知道这对名字关系的地方)。 * * Resolution order: * 1. `X_ENABLED` set to a literal ⇒ that. * 2. `X_ENABLED` set to a non-literal ⇒ default + warning (the tombstone is NOT consulted — a typo in the * new name must not turn into a boot refusal about an env the operator never set). * 3. `X_ENABLED` unset ∧ `X_DISABLED` set(**任何值**,含 `"false"`)⇒ throw,文案指路新名。选拒启不选 * 静默忽略:静默会让升级部署的旧开关名义在、实际归默认——比拒启危险,且下游冒烟测试测不出来。 * 4. neither ⇒ default. */ export declare function boolEnvWithLegacyNegated(name: string, legacyNegatedName: string, def: boolean): boolean; /** LKG (last-known-good) persistence of the remote effective-config fetch. Read LAZILY at both main.ts sites * rather than lifted onto `ServiceConfig`, so this stays a pure env knob; `loadConfig` pre-registers it (below) * so its polarity row and any deprecation notice appear at boot with everything else. */ export declare const configLkgEnabled: () => boolean; /** The `host` exec lane's background-shell capability kill switch (read per capability probe in remote-env-host.ts). */ export declare const hostBackgroundShellEnabled: () => boolean; /** The `host` exec lane's spool-file stdio form; false ⇒ the pre-1.226 pipe fallback (read per exec). */ export declare const hostExecSpoolEnabled: () => boolean; /** [ref]① 1M dual-window (core 1.289 `Model.autoCompactTokens`): an EXPLICIT positive value wins (env * `MODEL_AUTO_COMPACT_TOKENS` / center roster `autoCompactTokens`); else the **model-id table** applies — * sonnet-5-family id ∧ contextWindow>=1e6 ⇒ 967000 (the faithful shape of CC's single-entry per-model * config; a small-window sonnet is NOT derived — a field above the physical window is undefined core * behavior); anything else ⇒ REMOVED (a spread-inherited value from a bigger-window base model would * mis-trigger a small window). ⚠️ [ref] P2-6 rescinded the old `contextWindow>=1e6 ⇒ 967000` derivation: * it handed a sonnet-5-only constant to ANY 1M model with no corpus/upstream backing (the earlier note * here claiming this was "the exact CC alignment core recommends embedders set" mis-read core's per-model * advice as a width rule). Returns a discriminant so the env lane can turn the removed implicit value into * a loud CONFIG_NOTICES line ("removed-1m"); the center lane's roster culture is explicit declaration. * Shared by the env lane (main + cheap models) and the center lane (sema-registry toModel). */ export declare function applyAutoCompactWindow(m: Model, explicit?: number): "explicit" | "sonnet5" | "removed-1m" | "removed"; /** * 一条工具名**永不匹配任何现役工具**的三种拼法,及其修正指引。审批/权限名单是 server 侧 ToolPolicy 的 * RAW 逐字比对(canonicalToolName 折叠面已随 core 5.0.0 [ref] 退役),所以写错的名字不会报错、只会静默 * 不生效——对 deny/ask 名单而言这是**失守方向**(运维/客户端以为加了门,实际什么都没加)。 * * 判据在此**单点成文**,三条腿共用(它们此前各自为政,拼法覆盖面不一致才是真缺口): * ① env 腿(`APPROVAL_REQUIRE`/`DENY`/`NEVER_AUTO`,下方)—— boot 拒启; * ② 中心下发腿(`runtime.approvalRequire`,config-center/apply-effective.ts)—— 域级拒绝+保前值+warn * (常驻进程不 crash); * ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts)—— 该请求 422。 * * 五种拼法: * - CC 形规则条目(`Bash(ps:*)`/`Edit(src/**)`,[ref] [ref]§七 装机实测):CC 的权限规则 DSL 写法。这些 * 名单比的是**整串工具名**,带括号那串不是任何活工具的名字 ⇒ 永不匹配。指引=按命令名走 * `runtime.commandPolicy`,整条 shell 进门走 `MANUAL_MODE_SHELL_GATE=always`。判别式见 * {@link findCcRuleFormNames}(它同时是 `allow` 腿的**唯一**判据,见下方例外说明)。 * - 退役名(`bash`/`Task`/`KillShell`…):core `RETIRED_TOOL_NAMES` 静态表,指引=现役名。 * - pre-prefix 短名(`figma__x`):v4 自动加前缀的折叠面已删,实挂名恒带 `mcp__`,指引=补全前缀。 * - 不完整 MCP 名(`mcp__`、`mcp__figma`):前缀对但缺段——MCP 实挂名恒是 `mcp____` 三段形, * 少一段同样永不匹配。旧判据「含 `__` 且 protocolOf 认不出」放过了这一族(前缀恰好认得出),从另一侧 * 漏出去,失守方向与 `figma__x` 完全相同。 * - **core 的文法编不出来的串**(名字里带反斜杠、单条 >512 字符、空串):判词取自 core 自己的 * `validatePermissionRules`,见 {@link uncompilableRuleGuidance}。[ref]①([ref])补齐 —— * 此前只有 `ask` 腿(经 `createPermissionRulePolicy` 的 throw)拒得出这一族,`deny` 静默 no-op。 * * 已接受的权衡(与退役表墓碑同一条):部署自有工具真叫 `figma__x` 的极端形会被误拒——boot 期无 roster 可 * 豁免,且失败响亮、指引明确,好过静默失守。 * * 🔴 **三条腿的消费者现已全部认 covering**([ref] 孪生修,[ref];此前本段记的「分家」已作废): * core 的每一只 name-keyed policy(`createAllowDenyPolicy` / `createPermissionRulePolicy` / * `createApprovalPolicy`)都解 covering 拼法,而 operator 腿的消费者 `createDurableAskPolicy` * (`src/approval.ts`)自 core 5.60.1 的谓词根导出到货后,deny/neverAuto/require 三集合也改成 * 「精确 ∪ covering」(同一只 `namespacedRuleNameCovers`)。于是本函数放行的 * `____*` / `____` 在**每一条**腿上都是**真判**,不再是 * 「放行进 no-op」—— 而那正是 [ref] 病形(`APPROVAL_DENY=mcp__figma__*` 判 allow)的根。 * * ⚠️ 仍然拒的那一格,理由**换了**(别再照旧引「approval.ts 还不认」):`__` **两段形** * (`mcp__figma`)core 全家解成 covering,本函数判它「缺段」拒。挡它的不再是消费腿的能力,而是 * **一名两义**:同一个 `mcp__figma` 既可能是运维想写的「整台服务器」,也可能是他把三段名写漏了一段 * (`mcp__figma__get_file` 少打一截),两者在 boot 期无 roster 可判——所以三条腿一律响亮拒,并在 * 指引里直接给出**无歧义**的等价写法 `mcp__figma__*`(见下方 guidance)。放宽两段形是一次 * behavior-facing 的放宽(三条腿同时变宽,且方向是「更少拒绝」),要走独立批的三问,不是本函数的补丁。 */ export interface UnmatchableToolName { name: string; guidance: string; } /** * S-177(core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。两个消费者形态: * * · `"tool-name-table"` —— 消费者按**整串工具名**比对(精确 ∪ covering):env `APPROVAL_*` 三键与 * 中心下发 `runtime.approvalRequire`(消费者 `createDurableAskPolicy`),**以及** * `settings.permissions.allow`(消费者 core 的 `createAllowDenyPolicy` 的 allow 位)。带括号的内容形 * 在这里永远匹配不上任何活工具名 —— 与引擎的规则文法认不认得它**无关**,是这条腿的比对方式决定的, * 所以内容形照旧响亮拒 + 指路。 * · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`): * `settings.permissions.deny` 与 `.ask`(S-177 / core 7.9.0 起两张表都走编译器)。引擎自此**真的按内容 * 匹配**它们(`Bash(rm -r:*)` / `Edit(~/.sema/**)`),所以本仓不许再替它拒 —— 判据整条交回 core 的 * `validatePermissionRules`,按这张表自己的 behavior 问。 * * 🔴 **不对称不是特判,是从「消费者是谁」自然导出的**:`allow` 之所以豁免名字形与文法两族(退役名 / * 短名 / 反斜杠 / 超帽),是因为它是**白名单** —— 一个匹配不上的条目让白名单空掉 ⇒ 一切都不在白名单 * ⇒ fail-CLOSED 且当场可见,那是可接受的另一种失败形;而内容形不同,它是一个 CC 用户为了**少弹窗** * 写下的拼法族错,静默代价是整只 Bash 不可用。 */ export type RuleEntryConsumer = "tool-name-table" | "rule-policy"; export declare function findUnmatchableToolNames(names: readonly string[], opts: { consumer: RuleEntryConsumer; behavior: RuleBehavior; pathBases?: PathRuleBases; }): UnmatchableToolName[]; /** {@link findUnmatchableToolNames} 的成句形——三条腿的文案同源(只有前缀/出口不同)。 */ export declare function formatUnmatchableToolNames(source: string, bad: readonly UnmatchableToolName[]): string; /** * [ref] 件B/C/D —— 三条**部署治理声明**的 env 解析(auth 域的一段,fail-loud 全在这里)。 * * 三条的词表真源都在 core(`ComplianceProfile` / `ComplianceCapability` / `LOCKED_KEY_REGISTRY`),本函数 * 只负责「env 文本 → 闭集值」这一步,并且**每一步坏值都拒启**:这三根旋钮全在安全/治理轴上,一个静默 * 不生效的档位或一个拼错的锁键,是「以为在管、其实没管」——比没配更贵([ref] A 档 + [ref] §3.4)。 * * 半配也拒:`COMPLIANCE_ADDITIONAL_DENIES` 没有 profile 可收紧时是一句无处安放的话,静默丢掉它等于 * 让 operator 以为多禁了一条(与 `MEMORY_EMBEDDER_*` 的半配拒启同族)。 */ /** [ref] 车2 —— sweep 节律的上界(秒)。`setInterval` 的 delay 是 32 位有符号毫秒(2^31-1);越界会被 * Node **重置成 1ms**,理由与拒启判据见调用点。导出供判据与文档单点引用。 */ export declare const RETENTION_SWEEP_INTERVAL_MAX_SEC: number; /** 域:limits+http(限额与 HTTP 面)—— 端口、附件/工作区体积与 TTL、成本与速率天花板、排空窗、回收周期。 */ /** * [ref]:limitsHttp 组里**有下限**的三个数值旋钮的下限值 —— env 腿(夹取+点名)与 center 热腿(整批拒) * 共用同一组阈值。判据同源、**处置刻意不同**:env 值是既有部署的存量,夹取不打断服务;center 发布是一次 * 显式且可重发的动作,拒绝才是有意义的反馈([ref] 响亮拒)。 */ export declare const LIMITS_HTTP_FLOORS: { readonly attachmentMaxBytes: 1024; readonly attachmentUnboundTtlMs: 60000; readonly drainGraceMs: 10000; }; /** 组名 → 该组取景的平铺键(introspection 面:测试用它钉「每个平铺键恰好被一组取景」)。 */ export declare const CONFIG_GROUP_KEYS: { readonly [G in keyof ServiceConfigGroups]: readonly (keyof ServiceConfigFlat)[]; }; /** 把九个组视图挂到平铺配置上(**就地**,返回的就是同一个对象——视图必须看着这一个存储处)。 * * 组键是**不可枚举**的:`Object.keys(config)` / `JSON.stringify(config)` / `{...config}` 的平铺面因此 * 逐字节不变(既有消费点、既有 dump/审计面零影响),代价是 spread 克隆不带组视图——克隆后要用组形, * 再过一次本函数(读一个缺席的组是当场 TypeError,不会静默给旧值)。 * * `loadConfig` 是主调用点;测试夹具(test/helpers/make-config.ts)构造 `ServiceConfigFlat` 后同样过它。 */ export declare function attachConfigGroups(flat: ServiceConfigFlat): ServiceConfig; export declare function loadConfig(): ServiceConfig; /** 有效监听绑址([ref])。**显式 `BIND_HOST`/`HOST` 恒生效**(operator 保留在任何形下明示暴露的 * 权利);未显式时:**无鉴权写面 ⇒ 127.0.0.1**,其余 ⇒ undefined(Node 默认全接口,既有部署零影响)。 * * 为什么缺省要自己收窄、而不是只加旋钮:桌面形的安全结论原本写着「safe ONLY because HOST is * pinned to 127.0.0.1」,而那个前提在本仓从未成立(HOST 零消费)——把一个没人实现的假设当防线, * 是「沉默陷阱」。零凭证 + 全接口 + host lane(用户真机非沙箱)= 同网段任意执行,缺省必须安全。 * "无鉴权写面" 的判据与 http 层 503 fail-closed 门同源:allowUnauthedWrites 且**完全没有**凭证 * (半配=凭证必须使用,不算无鉴权)。 */ /** * 缝合审 M4:per-request 配速上限旋钮(TASK_TIMEOUT_MAX_SEC / TASK_MAX_OUTPUT_TOKENS_MAX / * TASK_MAX_TURNS_MAX)的解析 —— 判据提成函数放到 env 解析的家里。 * * 病:旧码是 `Math.floor(Number(process.env[name] ?? "0"))`,非有限或 ≤0 ⇒ 回 undefined ⇒ **该上限 * 不设**。而这三个旋钮的全部用途就是给多租部署封顶 caller 自报的配速,于是打错一个字符(`50s`、 * `5_000`、全角数字)⇒ 封顶静默消失,caller 从此可自报任意 timeout/tokens/turns。fail-open 且零痕迹。 * * 本仓**已经有**为这种情况建的渠道(`CONFIG_WARNINGS` / `drainConfigWarnings`,注释逐字写着 * 「soft-knob parse rejects … main.ts drains this once after createLogger and warns per entry」), * 这三个旋钮没走它。这里只加**可观测性**,不改解析语义:非法值仍不设限(改成拒启会把一个打错字的 * 运营方整个挡在门外,代价与收益不成比例),但它必须出声。 * * @returns `{}` = 缺席(正常的「不设限」);`{ value }` = 生效;`{ invalid }` = 存在但非法(不设限 + 该告警)。 */ export declare function parseCapEnv(name: string, raw: string | undefined): { value?: number; invalid?: string; }; /** * 缝合审 M6:数据根与配置根是否分家 —— 判据提成函数,放在 `localDataRoot` 自己的文件里。 * * 两条解析各写各的:数据根吃 `LOCAL_DATA_ROOT`,配置根(`main.ts` 的 `localRoot` / `run-local.ts` 的 * `root`)不吃。只设 `LOCAL_DATA_ROOT=/data/sema` 的部署因此把 store 数据挪到了 /data/sema,而 * config.d / remote-exec.json 仍从 `~/.ai-agent` 读 —— 静默分家,而设它的人多半以为改的是「本地数据放哪」。 * * 讽刺处:`main.ts` 那段的标题逐字写着「ONE local root for BOTH … avoids a split-brain」——那条论证 * 合并了 config provider 与 remoteExec 两个根,**漏了第三个**。 * * 这里**不改解析**(让配置根跟着 `LOCAL_DATA_ROOT` 走,会让现有部署的 config.d 当场失踪 —— 代价比 * 分家本身大得多)。只回一个「分家了,两个根分别是什么」的事实,由 boot 期打告警,合一旋钮 = `CONFIG_LOCAL_DIR`。 * * @returns 分家时回两个根;一致(或已被 CONFIG_LOCAL_DIR/AGENT_DATA_DIR 统一)时回 undefined。 */ export declare function splitLocalRoots(a: { /** optional:类型上可缺席(config-types 里是 `localDataRoot?`)——缺席时无从比较,判为不分家。 */ localDataRoot: string | undefined; configLocalDir: string | undefined; agentDataDir: string | undefined; home: string; }): { dataRoot: string; configRoot: string; } | undefined; export declare function resolveBindHost(config: { bindHost?: string; /** 缝合审 M5:`bindHost` 的**来源**决定它算不算 operator 明示,见下。 */ bindHostSource?: "BIND_HOST" | "HOST"; allowUnauthedWrites?: boolean; authToken?: string; authTokens?: Record; }): string | undefined; //# sourceMappingURL=config.d.ts.map