@sema-agent/server 7.99.0 → 7.101.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/MIGRATION.md +35 -1
  2. package/README.md +12 -1
  3. package/README.zh-CN.md +9 -1
  4. package/USAGE.md +150 -56
  5. package/dist/approval-ask-machine.d.ts +10 -0
  6. package/dist/approval-ask-machine.js +3 -0
  7. package/dist/approval-card.d.ts +86 -1
  8. package/dist/approval-card.js +42 -8
  9. package/dist/approval-policy-settlement.d.ts +31 -0
  10. package/dist/approval-policy-settlement.js +16 -0
  11. package/dist/approval-reconciler.d.ts +8 -1
  12. package/dist/approval-reconciler.js +5 -4
  13. package/dist/approval.d.ts +44 -20
  14. package/dist/approval.js +14 -6
  15. package/dist/boot/config-center.d.ts +16 -8
  16. package/dist/boot/config-center.js +114 -90
  17. package/dist/boot/execution-env.d.ts +1 -2
  18. package/dist/boot/execution-env.js +21 -3
  19. package/dist/boot/leader.js +12 -9
  20. package/dist/boot/memory-consolidation.d.ts +38 -0
  21. package/dist/boot/memory-consolidation.js +35 -0
  22. package/dist/boot/parked-revive-gate.js +1 -0
  23. package/dist/boot/reapers.js +1 -0
  24. package/dist/boot/resolve-spec.d.ts +0 -1
  25. package/dist/boot/resolve-spec.js +28 -10
  26. package/dist/boot/runner-deps.d.ts +2 -2
  27. package/dist/boot/runner-deps.js +9 -3
  28. package/dist/boot/stage-02-registry-pull.js +9 -0
  29. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  30. package/dist/boot/stage-06-runners.d.ts +2 -2
  31. package/dist/boot/stage-06-runners.js +3 -3
  32. package/dist/boot/stage-07-capability-layer.d.ts +2 -3
  33. package/dist/boot/stage-07-capability-layer.js +6 -6
  34. package/dist/boot/stage-08-reapers.d.ts +3 -4
  35. package/dist/boot/stage-08-reapers.js +2 -2
  36. package/dist/boot/stage-09-leader.d.ts +2 -3
  37. package/dist/boot/stage-10-http-server.d.ts +2 -3
  38. package/dist/boot/stage-10-http-server.js +2 -2
  39. package/dist/boot/stores.js +4 -0
  40. package/dist/capabilities/center-plugins.d.ts +14 -2
  41. package/dist/capabilities/center-plugins.js +13 -5
  42. package/dist/capabilities/hands-lane.js +2 -1
  43. package/dist/capabilities/scenarios.js +18 -5
  44. package/dist/capabilities/tool-defer.d.ts +2 -2
  45. package/dist/config-catalog.d.ts +2 -2
  46. package/dist/config-catalog.js +16 -15
  47. package/dist/config-center/apply-effective.d.ts +59 -39
  48. package/dist/config-center/apply-effective.js +231 -120
  49. package/dist/config-center/apply-ledger.d.ts +45 -16
  50. package/dist/config-center/apply-ledger.js +28 -18
  51. package/dist/config-center/facade.d.ts +12 -6
  52. package/dist/config-center/facade.js +2 -2
  53. package/dist/config-center/hot-keys-registry.d.ts +11 -4
  54. package/dist/config-center/hot-keys-registry.js +11 -7
  55. package/dist/config-center/http-client.d.ts +4 -1
  56. package/dist/config-center/http-client.js +15 -10
  57. package/dist/config-center/read-warnings.d.ts +115 -6
  58. package/dist/config-center/read-warnings.js +115 -5
  59. package/dist/config-center/restart-signal.d.ts +21 -14
  60. package/dist/config-center/restart-signal.js +16 -22
  61. package/dist/config-center/types.d.ts +9 -3
  62. package/dist/config-invariants.d.ts +2 -2
  63. package/dist/config-invariants.js +15 -0
  64. package/dist/config-provider.d.ts +4 -0
  65. package/dist/config-provider.js +64 -9
  66. package/dist/config-types.d.ts +57 -13
  67. package/dist/config.js +87 -22
  68. package/dist/cross-session-settings.js +1 -1
  69. package/dist/deployment-governance.d.ts +45 -12
  70. package/dist/deployment-governance.js +37 -9
  71. package/dist/env-name-allowlist-knobs.d.ts +37 -0
  72. package/dist/env-name-allowlist-knobs.js +24 -0
  73. package/dist/fleet-client.d.ts +2 -1
  74. package/dist/hooks/hook-llm.js +13 -4
  75. package/dist/host-lsp-manager.d.ts +41 -0
  76. package/dist/host-lsp-manager.js +15 -0
  77. package/dist/http/active-run-conflict.d.ts +2 -12
  78. package/dist/http/active-run-conflict.js +1 -6
  79. package/dist/http/admission.js +30 -7
  80. package/dist/http/memory-store-refusal-reply.d.ts +24 -0
  81. package/dist/http/memory-store-refusal-reply.js +10 -0
  82. package/dist/http/resume-legs.d.ts +1 -1
  83. package/dist/http/resume-legs.js +14 -10
  84. package/dist/http/route-ctx.d.ts +2 -2
  85. package/dist/http/routes/approvals-assistant.d.ts +1 -1
  86. package/dist/http/routes/approvals-assistant.js +3 -2
  87. package/dist/http/routes/capabilities.js +3 -3
  88. package/dist/http/routes/memory-bundle.js +3 -0
  89. package/dist/http/routes/memory-compliance.js +3 -0
  90. package/dist/http/routes/memory-origin.js +3 -0
  91. package/dist/http/routes/sessions.js +7 -0
  92. package/dist/http/routes/side-query.js +10 -4
  93. package/dist/http/routes/tasks.js +7 -6
  94. package/dist/http/server.d.ts +6 -5
  95. package/dist/http/wire-types.d.ts +6 -3
  96. package/dist/instruction-source-notice.d.ts +97 -0
  97. package/dist/instruction-source-notice.js +78 -0
  98. package/dist/leader/wire.d.ts +20 -12
  99. package/dist/leader/wire.js +7 -6
  100. package/dist/memory-layer-legacy-lock.d.ts +14 -0
  101. package/dist/memory-layer-legacy-lock.js +74 -0
  102. package/dist/memory-store-refusal.d.ts +37 -0
  103. package/dist/memory-store-refusal.js +14 -0
  104. package/dist/model-entry-refusal.d.ts +130 -0
  105. package/dist/model-entry-refusal.js +157 -0
  106. package/dist/model-provider.d.ts +9 -3
  107. package/dist/model-route-endpoint.d.ts +1 -1
  108. package/dist/model-select.d.ts +49 -4
  109. package/dist/model-select.js +48 -17
  110. package/dist/observability/corrupt-read-seat.d.ts +21 -4
  111. package/dist/observability/corrupt-read-seat.js +31 -0
  112. package/dist/observability/fail-open.d.ts +16 -4
  113. package/dist/observability/fail-open.js +16 -4
  114. package/dist/observability/metrics.js +1 -0
  115. package/dist/observability/run-terminal.js +1 -0
  116. package/dist/observability/secret-env-scrub.d.ts +27 -3
  117. package/dist/observability/secret-env-scrub.js +22 -6
  118. package/dist/orchestration/workflow-completion-inbox.d.ts +31 -23
  119. package/dist/orchestration/workflow-completion-inbox.js +8 -3
  120. package/dist/plugins/approval-ask-store-file.d.ts +20 -2
  121. package/dist/plugins/approval-ask-store-file.js +21 -3
  122. package/dist/plugins/approval-ask-store-memory.d.ts +2 -0
  123. package/dist/plugins/approval-ask-store-memory.js +39 -13
  124. package/dist/plugins/approval-ask-store-sql.d.ts +24 -5
  125. package/dist/plugins/approval-ask-store-sql.js +27 -3
  126. package/dist/plugins/caching-session-store.d.ts +6 -0
  127. package/dist/plugins/caching-session-store.js +6 -0
  128. package/dist/plugins/checkpoint-store-sql.d.ts +9 -3
  129. package/dist/plugins/checkpoint-store-sql.js +19 -2
  130. package/dist/plugins/local-checkpoint-store.js +2 -0
  131. package/dist/plugins/local-session-store.d.ts +20 -25
  132. package/dist/plugins/local-session-store.js +111 -64
  133. package/dist/plugins/mailbox-store-sql.d.ts +7 -5
  134. package/dist/plugins/mailbox-store-sql.js +6 -4
  135. package/dist/plugins/permission-rule-store-file.js +3 -2
  136. package/dist/plugins/pg-session-storage.d.ts +7 -0
  137. package/dist/plugins/pg-session-storage.js +19 -0
  138. package/dist/plugins/remote-env-host.d.ts +3 -1
  139. package/dist/plugins/remote-env-host.js +3 -6
  140. package/dist/plugins/sql-json-column.d.ts +16 -3
  141. package/dist/plugins/sql-json-column.js +15 -4
  142. package/dist/plugins/tidb-pool.js +5 -0
  143. package/dist/plugins/tidb-session-store.d.ts +13 -0
  144. package/dist/plugins/tidb-session-store.js +14 -0
  145. package/dist/plugins/web-search.d.ts +44 -23
  146. package/dist/plugins/web-search.js +161 -50
  147. package/dist/project-memory.d.ts +10 -0
  148. package/dist/project-memory.js +28 -14
  149. package/dist/run-local.d.ts +17 -15
  150. package/dist/run-local.js +119 -18
  151. package/dist/runs.js +6 -5
  152. package/dist/runtime-governance.d.ts +50 -3
  153. package/dist/runtime-governance.js +5 -0
  154. package/dist/sealed-key.d.ts +5 -2
  155. package/dist/server-secret-env.d.ts +26 -2
  156. package/dist/server-secret-env.js +9 -0
  157. package/dist/task-settings.d.ts +1 -1
  158. package/dist/tool-approval.d.ts +150 -28
  159. package/dist/tool-approval.js +150 -54
  160. package/dist/trace/core-keyset-guard.d.ts +30 -8
  161. package/dist/trace/engine-notice-wire.d.ts +27 -1
  162. package/dist/trace/engine-notice-wire.js +8 -0
  163. package/dist/trace/ledger-sink.js +2 -2
  164. package/dist/trace/project.d.ts +13 -0
  165. package/dist/trace/project.js +22 -5
  166. package/dist/trace/projection-drop.d.ts +2 -0
  167. package/dist/trace/projection-drop.js +6 -0
  168. package/dist/trace/sema-provenance.d.ts +4 -1
  169. package/dist/trace/sema-provenance.js +1 -1
  170. package/dist/trace/task-notification-facets.d.ts +65 -0
  171. package/dist/trace/task-notification-facets.js +31 -0
  172. package/dist/trace/wire-projection-faces.d.ts +3 -3
  173. package/dist/trace/wire-projection-faces.js +3 -3
  174. package/package.json +3 -3
@@ -18,8 +18,9 @@ export const HANDS_BAND_TOOL_NAMES = Object.freeze([
18
18
  "Monitor",
19
19
  "EnterWorktree",
20
20
  "ExitWorktree",
21
+ "ListAgents",
21
22
  ].filter((n, i, all) => !RETIRED_TOOL_NAMES.has(n) && all.indexOf(n) === i));
22
- export const HANDS_DUAL_USE_TOOL_NAMES = Object.freeze(["TaskOutput", "TaskStop", "SendMessage", "AgentTranscript"]);
23
+ export const HANDS_DUAL_USE_TOOL_NAMES = Object.freeze(["TaskOutput", "TaskStop", "SendMessage", "AgentTranscript", "ListAgents"]);
23
24
  export const HANDS_EXCLUSIVE_TOOL_NAMES = Object.freeze(HANDS_BAND_TOOL_NAMES.filter((n) => !HANDS_DUAL_USE_TOOL_NAMES.includes(n)));
24
25
  export function handsDenyPolicy() {
25
26
  return createAllowDenyPolicy({ deny: [...HANDS_EXCLUSIVE_TOOL_NAMES] });
@@ -5,7 +5,7 @@ import { parseRepo, repoToolsFor } from "./repo-tools.js";
5
5
  import { createCouncilTool, clampRounds } from "./code-review-council.js";
6
6
  import { stablePrompt } from "./prompt.js";
7
7
  import { createDiscussionTools, discussionCoordinatorPrompt, getTeam, teamNames } from "./team.js";
8
- import { createWebSearchBackend, webSearchConfigFromSettings } from "../plugins/web-search.js";
8
+ import { createWebSearchBackend, judgeRequestWebSearch } from "../plugins/web-search.js";
9
9
  import { skillsForScenario } from "./skills.js";
10
10
  import { pickHandsRunner } from "./hands-lane.js";
11
11
  export { SEMA_IDENTITY } from "./prompts/identity.js";
@@ -31,12 +31,25 @@ export function codePromptProvider(brand) {
31
31
  function subRunnerFor(deps, hands) {
32
32
  return pickHandsRunner(hands, { full: deps.subRunner, handsless: deps.handslessSubRunner });
33
33
  }
34
+ function requestWebSearchBackend(verdict, deployment, logger) {
35
+ switch (verdict.kind) {
36
+ case "absent":
37
+ return deployment;
38
+ case "honored":
39
+ return createWebSearchBackend(verdict.config);
40
+ case "malformed":
41
+ logger?.warn("web_search_settings_unusable", {
42
+ issue: verdict.issue,
43
+ note: "this request carries a settings.webSearch block that is not well-formed (stored before the submit-time check, or assembled without it); WebSearch is not mounted for this call rather than sending the searches to a different backend",
44
+ });
45
+ return undefined;
46
+ }
47
+ }
34
48
  export function buildScenarios(deps) {
35
49
  const defaultScenario = (req, _principal, ctx) => {
36
- const reqWebSearch = deps.requirePrincipal !== true
37
- ? webSearchConfigFromSettings(req?.settings?.webSearch)
38
- : undefined;
39
- const webSearch = reqWebSearch ? createWebSearchBackend(reqWebSearch) : deps.webSearch;
50
+ const webSearch = requestWebSearchBackend(deps.requirePrincipal !== true
51
+ ? judgeRequestWebSearch(req?.settings?.webSearch)
52
+ : { kind: "absent" }, deps.webSearch, deps.logger);
40
53
  return {
41
54
  tools: [
42
55
  nowTool(),
@@ -18,8 +18,8 @@
18
18
  * 在本仓缺省场景下那会连 Agent 与 Task 四件一起卷进候选(与 CC 姿态相反),所以我们**刻意不开**;
19
19
  * 🔴 该判断在 core 5.40.0 / 7.15.0 上**逐字仍成立**(亲验):两次收窄动的都是「候选集是谁」,而
20
20
  * Agent / Task 四件是 **caller spec 工具**,收窄后照样在候选池里 —— 不开 auto 的理由没有被消解。
21
- * 我方**从不接线**这个座:全树零 `RunnerDeps.deferMode` 赋值(`deferModelPlane` 是 config-center 的
22
- * 同形近名,与本轴无关)。机器钉见 test/tool-defer.test.ts 的 [ref] 件⑤ 格 —— 这条「零接线」正是本仓
21
+ * 我方**从不接线**这个座:全树零 `RunnerDeps.deferMode` 赋值(config-center 曾有同形近名 `deferModelPlane`,
22
+ * 与本轴无关,已于 S-668 波 0 退役)。机器钉见 test/tool-defer.test.ts 的 [ref] 件⑤ 格 —— 这条「零接线」正是本仓
23
23
  * 在两个 core 世代之间行为逐字不变的**全部**依据,所以它值一根钉而不只是一句注。
24
24
  * - per-tool `ToolSpec.defer:true` 是本 helper 施加的那一位。**7.15.0 起它是一次「进候选集」的声明,
25
25
  * 不再是「一定占位」的保证** —— 长尾集单独一只(下面实测 ≈842 字符)远在阈值之下,于是这根旋钮在
@@ -10,14 +10,14 @@ export type CatalogDangerAxis = (typeof CATALOG_DANGER_AXES)[number];
10
10
  /** 域 = 配置九组(类型锁步:组集变了这里跟着红)。 */
11
11
  export type CatalogDomain = keyof ServiceConfigGroups;
12
12
  export declare const CATALOG_RESTART_SLICES: readonly RestartSlice[];
13
- /** center 联接座(spec 层)。`models-plane` 是内部时机词:build 时按 tier 表在场性实算成 hot/restart。 */
13
+ /** center 联接座(spec 层)。 */
14
14
  export interface CatalogCenterSeatSpec {
15
15
  readonly domain: string;
16
16
  readonly path: string;
17
17
  /** `centerManagedKeys`(config-center 在管键集)里查「在管」用的键;models 面统一 "models"。 */
18
18
  readonly centerKey: string;
19
19
  readonly precedence: "center-wins" | "env-wins";
20
- readonly timing: "hot" | "restart" | "inert" | "models-plane";
20
+ readonly timing: "hot" | "restart" | "inert";
21
21
  readonly restartSlice?: RestartSlice;
22
22
  readonly restartReason?: string;
23
23
  }
@@ -1,12 +1,13 @@
1
1
  import { CROSS_SESSION_INBOUND_SETTINGS, CROSS_SESSION_DIALOG_EXPIRY_VALUES } from "@sema-agent/core";
2
2
  import { THINKING_FORMATS } from "./model-compat.js";
3
3
  import { RESILIENCE_DEFAULTS, TASK_WRITE_FACE_ESCALATIONS } from "./config-types.js";
4
- import { webSearchConfigFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
4
+ import { webSearchProviderFromEnv, WEB_SEARCH_PROVIDERS } from "./plugins/web-search.js";
5
5
  import { AUTONOMY_MODES } from "./runtime-governance.js";
6
6
  import { REMOTE_EXEC_PROVIDER_WORDS } from "./execution-lane-caps.js";
7
7
  import { CENTER_READ_FACE_LIVE_KEYS } from "./config-center/read-face.js";
8
8
  import { MODEL_PROVIDER_WORDS } from "./model-provider.js";
9
9
  import { buildDisplaySafeUrl } from "./trace/redact.js";
10
+ import { describeAllowlistPostures } from "./env-name-allowlist-knobs.js";
10
11
  export const CATALOG_VALUE_TYPES = ["boolean", "number", "string", "enum", "csv", "json", "path", "url", "secret", "opaque"];
11
12
  export const CATALOG_TIMINGS = ["hot", "restart", "deploy", "inert"];
12
13
  export const CATALOG_DANGER_AXES = ["security", "billing", "data"];
@@ -15,10 +16,9 @@ const RESTART_SLICE_SET = {
15
16
  mcp: true,
16
17
  a2a: true,
17
18
  scenarios: true,
18
- "runtime-gates": true,
19
- "models-tiers": true,
20
19
  "degrade-route": true,
21
20
  "read-face": true,
21
+ "memory-consolidation": true,
22
22
  };
23
23
  export const CATALOG_RESTART_SLICES = Object.keys(RESTART_SLICE_SET);
24
24
  const r = (name, domain, type, semantics, opts = {}) => ({ name, domain, type, semantics, ...opts });
@@ -31,7 +31,7 @@ const cLimits = (path, over = {}) => ({
31
31
  ...over,
32
32
  });
33
33
  const cGov = (path, over = {}) => ({ domain: "governance", path, centerKey: path, precedence: "center-wins", timing: "hot", ...over });
34
- const cModels = (path) => ({ domain: "models", path, centerKey: "models", precedence: "center-wins", timing: "models-plane" });
34
+ const cModels = (path) => ({ domain: "models", path, centerKey: "models", precedence: "center-wins", timing: "hot" });
35
35
  const cRead = (path, centerKey, over = {}) => ({
36
36
  domain: "readFace",
37
37
  path,
@@ -65,7 +65,7 @@ export const CONFIG_CATALOG = [
65
65
  r("APPROVAL_DENY", "approval", "csv", "始终拒绝的工具名单(装配期拒不可匹配名)", { danger: SEC, configKey: "approvalDeny" }),
66
66
  r("APPROVAL_HMAC_KEYS", "approval", "secret", "直连门(D-G)HMAC 完整性密钥集(空=直连门不激活)", { danger: SEC }),
67
67
  r("APPROVAL_NEVER_AUTO", "approval", "csv", "永不被 AUTO_BUDGET 自动放行的工具集(不可逆动作恒要人)", { danger: SEC, configKey: "approvalNeverAuto" }),
68
- r("APPROVAL_REQUIRE", "approval", "csv", "必须人审的工具名单(F4 门;center 腿走 runtime-gates 重启片)", { danger: SEC, configKey: "approvalRequire", center: cGov("approvalRequire", { timing: "restart", restartSlice: "runtime-gates" }) }),
68
+ r("APPROVAL_REQUIRE", "approval", "csv", "必须人审的工具名单(F4 门;center 腿热:refresh 拍整表换代,无 checkpoint 店的部署发非空名单 ⇒ 审批组整组拒)", { danger: SEC, configKey: "approvalRequire", center: cGov("approvalRequire") }),
69
69
  r("APPROVAL_TERMINAL_BACKSTOP_MS", "approval", "number", "SQL checkpoint 店 pending 行的 crash-safe 绝对 GC 上限(ms;下限 60s,非有限值落默认)", { staticDefault: String(30 * 86_400_000), danger: DATA }),
70
70
  r("APPROVAL_TIMEOUT_SEC", "approval", "number", "durable HITL 等人上限(秒);0=无限期等,时间型 suspended 回收不跑", { staticDefault: "0", configKey: "approvalTimeoutSec" }),
71
71
  r("ASK_QUESTION_ENABLED", "approval", "boolean", "AskUserQuestion 活流 HITL 装配开关", { derivedDefaultNote: "posture 三态:显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)⇒ on / 多租户 ⇒ off", configKey: "askQuestionEnabled" }),
@@ -102,7 +102,7 @@ export const CONFIG_CATALOG = [
102
102
  r("CORS_ORIGIN", "auth", "csv", "CORS 白名单(单值 v1 兼容回显;多值按请求 Origin 匹配 + Vary;未设=无 CORS 面)", { danger: SEC, configKey: "corsOrigins" }),
103
103
  r("COST_QUOTA_WINDOW_SEC", "limitsHttp", "number", "per-principal 成本配额窗长(秒);SQL 腿窗长进桶主键 ⇒ center 腿显式 deferred(重启生效)", { staticDefault: "86400", danger: BILL, configKey: "costQuotaWindowSec", center: cLimits("costQuotaWindowSec", { timing: "restart", restartReason: "cost-quota-window" }) }),
104
104
  r("CROSS_SESSION_DIALOG_EXPIRY", "approval", "enum", "held 跨会话消息等待人审的期限(CC settings 键名 dialogExpiry;未设=不铸键,引擎自落缺省 5m;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_DIALOG_EXPIRY_VALUES], configKey: "crossSessionDialogExpiry", derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省 5m", resolve: (c) => c.crossSessionDialogExpiry ?? null }),
105
- r("CROSS_SESSION_INBOUND", "approval", "enum", "入站跨会话消息的部署/组织层治理(CC settings 键名 crossSessionInbound 的 managed 层;未设=本层不表态≠accept;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_INBOUND_SETTINGS], configKey: "crossSessionInbound", derivedDefaultNote: "缺席 ⇒ managed 层不设,由其余层与 mode-parity 决定", resolve: (c) => c.crossSessionInbound ?? null }),
105
+ r("CROSS_SESSION_INBOUND", "approval", "enum", "入站跨会话消息的部署/组织层治理(CC settings 键名 crossSessionInbound 的 managed 层;未设=本层不表态≠ next-turn;词表外拒启)", { danger: SEC, enumValues: [...CROSS_SESSION_INBOUND_SETTINGS], configKey: "crossSessionInbound", derivedDefaultNote: "缺席 ⇒ managed 层不设,由其余层与 mode-parity 决定", resolve: (c) => c.crossSessionInbound ?? null }),
106
106
  r("DB_BACKEND", "store", "enum", "store 后端引擎选择(mysql 兼容/pg/本机文件店/纯内存)", { enumValues: ["mysql", "pg", "local", "memory"], danger: DATA, derivedDefaultNote: "裸 boot ⇒ local(多租户裸 boot ⇒ memory + notice);有 SQL 信号 ⇒ mysql", configKey: "dbBackend", resolve: (c) => c.dbBackend }),
107
107
  r("DB_QUERY_TIMEOUT_MS", "store", "number", "per-query DB 超时(ms;坏值回默认姿态+warn,绝不 NaN 进 driver)", { derivedDefaultNote: "缺席 ⇒ 池级不设/计数族 30s(按引擎)", configKey: "dbQueryTimeoutMs" }),
108
108
  r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回)", { staticDefault: "code", configKey: "defaultScenario" }),
@@ -126,7 +126,7 @@ export const CONFIG_CATALOG = [
126
126
  r("DOCKER_NETWORK", "orchestration", "enum", "local-docker 容器网络模式", { enumValues: ["none", "bridge", "host"], staticDefault: "bridge", danger: SEC }),
127
127
  r("DOCKER_PATH", "orchestration", "path", "docker 二进制路径"),
128
128
  r("DOCKER_PIDS_LIMIT", "orchestration", "number", "local-docker fork-bomb 背栓(--pids-limit;非法值回 adapter 默认 512+warn)", { danger: SEC, derivedDefaultNote: "缺席 ⇒ adapter 默认 512" }),
129
- r("DOCKER_SANDBOX_ENV", "orchestration", "csv", "转发进容器的本进程 env **名字**表(值只在 worker env,不进 spec/center)", { danger: SEC }),
129
+ r("DOCKER_SANDBOX_ENV", "orchestration", "secret", "local-docker 车道每条 docker exec 以 -e 注入的本进程 env 点名表(NAME,NAME;点到的键从 worker env 原样取,值不进 spec/center/日志;与 LSP_ENV_ALLOW / E2B_SANDBOX_ENV 同一只解析器与「点名放行」谓词:形错 / 点名名单型旋钮拒启且不回显值;本进程环境里没有的名按序号 warn、不转发;只在本车道被选中时求值;原文按凭据类对待:只回在场位、不进任何子进程)", { danger: SEC }),
130
130
  r("DRAIN_GRACE_MS", "limitsHttp", "number", "SIGTERM 优雅排空窗(ms;期间新计费提交 503+Retry-After)。出厂默认是**服务器部署**的取值;壳自 spawn 的引擎会被壳注入更短的窗 ⇒ 排壳形关机预算时以本行的 effectiveValue(= 本进程实际生效值)为准,不照抄 staticDefault", { staticDefault: String(10 * 60 * 1000), configKey: "drainGraceMs", center: cLimits("drainGraceMs") }),
131
131
  r("DURABLE_APPROVAL", "approval", "boolean", "durable suspend/resume HITL(需 SQL 店;决定 checkpointStore 与 /v1/approvals 面的存在性)", { danger: SEC, derivedDefaultNote: "posture 三态(S-178):显式词恒赢,缺席 = 单用户(REQUIRE_PRINCIPAL≠true)且有耐久店 ⇒ on / 多租户或无耐久店 ⇒ off;生效来源见 GET /v1/diagnostics/wiring 的 serverGates.durableApproval.source", configKey: "durableApproval" }),
132
132
  r("E2B_ALLOW_NET", "orchestration", "boolean", "e2b 沙箱出网许可", { danger: SEC, derivedDefaultNote: "缺席 ⇒ 不铸键(adapter 默认允许)" }),
@@ -137,7 +137,7 @@ export const CONFIG_CATALOG = [
137
137
  r("E2B_LIVENESS_MS", "orchestration", "number", "e2b 单命令静默活性上限(长静默构建请调大;后台+轮询仍是首选)", { derivedDefaultNote: "缺席 ⇒ adapter 默认 120s" }),
138
138
  r("E2B_MOUNT_PATH", "orchestration", "path", "e2b 工作区根(resume 用当前值,改前先 drain 挂起任务)"),
139
139
  r("E2B_ORPHAN_RECLAIM", "orchestration", "boolean", "崩溃遗留沙盒回收腿(boot 一次+周期;判据保守,拿不准恒不杀)", { staticDefault: "true", danger: BILL }),
140
- r("E2B_SANDBOX_ENV", "orchestration", "csv", "转发进 e2b 沙箱的本进程 env 名字表(值不进 spec/center/事件日志)", { danger: SEC }),
140
+ r("E2B_SANDBOX_ENV", "orchestration", "secret", "转发进 e2b 沙箱的本进程 env 点名表(NAME,NAME;点到的键从 worker env 原样取,值不进 spec/center/事件日志;与 LSP_ENV_ALLOW / DOCKER_SANDBOX_ENV 同一只解析器与「点名放行」谓词:形错 / 点名名单型旋钮拒启且不回显值;本进程环境里没有的名按序号 warn、不转发;只在本车道被选中时求值;原文按凭据类对待:只回在场位、不进任何子进程)", { danger: SEC }),
141
141
  r("E2B_SANDBOX_MAX_MS", "orchestration", "number", "e2b 沙箱最长存活(ms)", { staticDefault: "3600000", danger: BILL }),
142
142
  r("E2B_TEMPLATE", "orchestration", "string", "e2b 模板名(缺省 adapter 基础模板)"),
143
143
  r("E2B_TIMEOUT_MS", "orchestration", "number", "e2b 单命令总超时(ms)"),
@@ -209,6 +209,7 @@ export const CONFIG_CATALOG = [
209
209
  r("LOCKED_CONFIG_KEYS", "auth", "csv", "管理员锁定层声明(词表=core 闭集注册表,未知键拒启)", { danger: SEC, configKey: "lockedConfigKeys" }),
210
210
  r("LOG_LEVEL", "observability", "string", "日志档位", { staticDefault: "info", configKey: "logLevel" }),
211
211
  r("LSP_ENABLED", "orchestration", "boolean", "LSP 沙箱腿 opt-in(需烤好的 LSP 模板;host 腿另有 LSP_HOST_ENABLED)", { staticDefault: "false", configKey: "lspEnabled" }),
212
+ r("LSP_ENV_ALLOW", "orchestration", "secret", `host 腿语言服务器子进程 env 的点名放行名单(点到的键从服务 env 原样放回、部署方显式承担;没点到的照剥;与 host shell inheritEnv:[names] 同一只谓词;形错 / 点名名单型旋钮(自己或两只沙箱名单)拒启且不回显值;本进程环境里没有的名按序号 warn、不放行、不回显;多租户姿态(忽略时起服 warn 一次):${describeAllowlistPostures()};原文按凭据类对待:只回在场位、不进任何子进程,生效名单见起服披露行 allowed)`, { danger: SEC, derivedDefaultNote: "缺席 / 空串 ⇒ 空名单 = 全剥(7.100.0 行为)" }),
212
213
  r("LSP_HOST_ENABLED", "orchestration", "boolean", "LSP host 腿开关(本机 language server,优雅降级)", { staticDefault: "true", configKey: "lspHostEnabled" }),
213
214
  r("MANUAL_MODE_SHELL_GATE", "approval", "enum", "手动模式 shell 门收紧腿(off=no-op 非放松 —— 本键只有收紧半场;拼错拒启)", { danger: SEC, enumValues: ["always", "classify", "off"], echoWords: ["always", "classify"], resolve: (c) => c.manualModeShellGate ?? null, derivedDefaultNote: "off/未设 ⇒ 本键不贡献任何东西(归一成键缺席)⇒ 回显 null;真姿态只有 always/classify 两个" }),
214
215
  r("MAX_PRINCIPAL_COST_USD", "limitsHttp", "number", "per-principal 窗内成本天花板(0=关)", { staticDefault: "0", danger: BILL, configKey: "maxPrincipalCostUsd", center: cLimits("maxPrincipalCostUsd") }),
@@ -454,12 +455,12 @@ export const CONFIG_CATALOG = [
454
455
  r("UNREACHED_ASK_TTL_MS", "approval", "number", "零活流铸造的 ask 悬挂窗(ms;[60s,24h] 拒启带界)", { staticDefault: "3600000" }),
455
456
  r("USAGE_WINDOWS", "orchestration", "opaque", "部署级治理窗(JSON 数组;滑动窗内跨任务 token/$ 预算,key=principal)", { danger: BILL }),
456
457
  r("WEB_SEARCH_API_KEY", "orchestration", "secret", "web search 后端 API 凭据(brave/tavily)", { danger: [...SEC, ...BILL] }),
457
- r("WEB_SEARCH_ENDPOINT", "orchestration", "url", "web search 后端 endpoint 覆写(searxng 自建面)"),
458
- r("WEB_SEARCH_MAX_RESULTS", "orchestration", "number", "web search 单次结果数上限", { derivedDefaultNote: "缺席/非正 ⇒ backend 默认" }),
458
+ r("WEB_SEARCH_ENDPOINT", "orchestration", "url", "web search 后端 endpoint 覆写(searxng 实例地址;brave 下可指向 Brave 兼容网关的完整搜索 URL,如 Nólë;空 = 未设;不是 scheme://host 形的绝对 URL、或带 userinfo(user[:password]@host —— 运行时 fetch 本就拒这一形)⇒ 拒启)"),
459
+ r("WEB_SEARCH_MAX_RESULTS", "orchestration", "number", "web search 单次结果数上限(非数 / < 1 ⇒ 拒启;> 20 夹到 20)", { derivedDefaultNote: "缺席/空 ⇒ backend 默认 10" }),
459
460
  r("WEB_SEARCH_PROBE_ON_BOOT", "orchestration", "boolean", "boot 期 web search 连通性探测(只认 true/1;含糊值当没开——猜错方向是凭空出网)", { staticDefault: "false" }),
460
- r("WEB_SEARCH_PROVIDER", "orchestration", "enum", "web search 工具后端(设合法词=装配 WebSearch;其它/缺席=不装配)", { enumValues: WEB_SEARCH_PROVIDERS, danger: SEC, resolve: (_c, env) => webSearchConfigFromEnv(env)?.provider ?? null }),
461
- r("WEB_SEARCH_SEARXNG_PARAMS", "orchestration", "string", "searxng 额外查询参数(k=v 对;解析不出任何一对 ⇒ 键不铸)"),
462
- r("WEB_SEARCH_TIMEOUT_MS", "orchestration", "number", "web search 单次超时(ms)", { derivedDefaultNote: "缺席/非正 ⇒ backend 默认" }),
461
+ r("WEB_SEARCH_PROVIDER", "orchestration", "enum", "web search 工具后端(设合法词=装配 WebSearch;其它/缺席=不装配)", { enumValues: WEB_SEARCH_PROVIDERS, danger: SEC, resolve: (_c, env) => webSearchProviderFromEnv(env) ?? null }),
462
+ r("WEB_SEARCH_SEARXNG_PARAMS", "orchestration", "string", "searxng 额外查询参数(name=value;name=value;空 ⇒ 不铸;任一段不成形 / 名不是参数名 / 同名两次 ⇒ 拒启)"),
463
+ r("WEB_SEARCH_TIMEOUT_MS", "orchestration", "number", "web search 单次超时(ms;非数 / < 1 ⇒ 拒启;< 1000 抬到 1000)", { derivedDefaultNote: "缺席/空 ⇒ backend 默认 10000" }),
463
464
  r("WORKFLOW_AGENTS_READONLY", "orchestration", "boolean", "workflow 子 agent 只读钳(TOB 保守旋钮;默认同权 CC parity)", { staticDefault: "false", danger: SEC, configKey: "workflowAgentsReadOnly" }),
464
465
  r("WORKFLOW_JOURNAL_RETENTION_MS", "orchestration", "number", "workflow_journal GC 保留期(ms)", { staticDefault: String(7 * 24 * 60 * 60 * 1000), danger: DATA, configKey: "workflowJournalRetentionMs" }),
465
466
  r("WORKFLOW_ORPHAN_GRACE_MS", "orchestration", "number", "running workflow 判崩溃孤儿的年龄阈(ms)", { staticDefault: String(24 * 60 * 60 * 1000), configKey: "workflowOrphanGraceMs" }),
@@ -537,8 +538,8 @@ export function buildConfigCatalog(inputs) {
537
538
  const seat = spec.center;
538
539
  const domainExists = seat !== undefined && centerDomains.includes(seat.domain);
539
540
  const managedNow = seat !== undefined && (centerManagedKeys?.has(seat.centerKey) ?? false);
540
- const centerTiming = seat === undefined ? undefined : seat.timing === "models-plane" ? "hot" : seat.timing;
541
- const centerRestartSlice = seat === undefined || seat.timing === "models-plane" ? undefined : seat.restartSlice;
541
+ const centerTiming = seat?.timing;
542
+ const centerRestartSlice = seat?.restartSlice;
542
543
  const centerEffective = seat !== undefined && managedNow && centerTiming === "hot" && (seat.precedence === "center-wins" || !envHeld);
543
544
  const effectiveLane = centerEffective ? "center" : envHeld ? "env" : "default";
544
545
  let effectiveValue;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * EffectiveConfig application — mutate the boot `ServiceConfig` OVER with a pulled `EffectiveConfig`
3
- * (models/roles/tiers/projects `applyEffective`, the boot-only approval gate + runtime limit residue
3
+ * (models/roles/tiers/projects `applyEffective`, the approval group + runtime limit residue
4
4
  * `applyRuntimeGates`, the per-request-hot governance baton `applyRuntimeHot`) plus their shared helpers
5
5
  * (`toModel`, `mutateInPlace`, `resolveDefaultModelName`, `logEffectiveDiff`). Split out of `facade.ts`
6
6
  * ([ref] A13, internal-lossless — the facade re-exports every symbol below unchanged).
@@ -10,6 +10,7 @@ import type { ServiceConfig } from "../config-types.js";
10
10
  import { type SealedKeyOpener } from "../sealed-key.js";
11
11
  import type { Logger } from "../observability/logger.js";
12
12
  import type { EffectiveConfig } from "./types.js";
13
+ import { type ConfigApplyReport } from "./apply-ledger.js";
13
14
  /** Apply center config OVER the env-derived config (mutates `config`). Empty domains keep env defaults. */
14
15
  /** Replace a record's contents IN PLACE (clear keys, copy source) so holders of the SAME reference see the
15
16
  * change. This is what makes models/roles/pricing HOT-apply: core's Runner reads
@@ -34,41 +35,63 @@ export declare const UNPARSABLE_AT_MODEL_ALLOWLIST_ENTRY = "\0<unparsable-at-mod
34
35
  * commit 之前,活配置零触碰)。caller 收到 false 必须把**同一世代的旁路消费**(prompts 采用/pricing/
35
36
  * keyResolver/etag 推进/LKG 落盘)一并跳过——否则 applyEffective 拒了主面、旁路却半应用同一个被拒
36
37
  * 世代,混合世代从侧门回来。首次 apply(live 未登记)恒 true。 */
37
- /** [ref]:一次 apply 的**世代账载荷**(post-commit 发,[ref]② —— commit 段仍是零回调零发射)。 */
38
- export interface ApplyReport {
39
- readonly version: number;
40
- /** [ref]:本候选的收货序数(= opts.generation 原样带回;世代账按它记拒收,身份标仍是 version)。 */
41
- readonly ordinal?: number;
42
- readonly limits: {
43
- /** 本世代真正换了值的键。 */
44
- readonly applied: readonly string[];
45
- /** 值合法但**不热**的键(逐键 defer:变更只能重启生效)。 */
46
- readonly deferred?: ReadonlyArray<{
47
- readonly key: string;
48
- readonly reason: string;
49
- }>;
50
- /** 整批被拒时的成因(消毒:键名 + 判据文案,绝不带值)。 */
51
- readonly rejected?: {
52
- readonly keys: string[];
53
- readonly reason: string;
54
- readonly restartReason?: string;
55
- };
56
- };
38
+ /** [ref]:一次 apply 的**世代账载荷**(post-commit 发,[ref]② —— commit 段仍是零回调零发射)。形 = 世代账的
39
+ * `ConfigApplyReport`(**每一组都报**,组闭集 `CONFIG_APPLY_GROUPS` 在 apply-ledger.ts)+ 整代判决。 */
40
+ export interface ApplyReport extends ConfigApplyReport {
57
41
  /** 🔴 codex R2-F1:本候选是否**完整**应用 —— 有任何一组被拒 ⇒ false。调用方据此决定要不要把它发布成
58
42
  * LKG / 推进 etag(一个「其余面提交了但某组被拒」的候选覆盖掉好 LKG,重启后就再也回不去了)。 */
59
43
  readonly fullyApplied: boolean;
60
44
  }
61
- export declare function applyEffective(config: ServiceConfig, eff: EffectiveConfig, logger?: Logger, opts?: {
62
- teamsOnly?: boolean;
63
- sealedKeys?: SealedKeyOpener;
64
- deferModelPlane?: boolean;
65
- generation?: number;
66
- report?: (r: ApplyReport) => void;
67
- swapPlane?: (plane: {
45
+ /**
46
+ * S-668 波 2 —— 一次 apply 在**哪条腿**上跑,以及这条腿知道的**部署事实**。必填(没有缺省腿):安全轴上「忘了说是哪条
47
+ * 腿」不许静默落成某一种语义([ref])。
48
+ * · `boot` —— 装配期(stage-02 的 boot 拉取 / LKG 起服;run-local 的一次性装配)。此刻 checkpoint 店**还没建**,
49
+ * 审批组的「门意图可服务」由装配完之后的 stage-07 对**整份** config 判(拒启)—— 与热腿是同一只
50
+ * `gateIntentUnserviceableReason`,不是两份判据。
51
+ * · `hot` —— 进程已起(refresh 拍 / 迟到 boot 到货)。`checkpointStorePresent` = 本进程建没建出 checkpoint 店
52
+ * (stage-06 的产物,**不在 ServiceConfig 里**),审批组 stage 当场对候选判,不可服务 ⇒ 整组拒。
53
+ */
54
+ export type ApplyLeg = {
55
+ readonly leg: "boot";
56
+ } | {
57
+ readonly leg: "hot";
58
+ readonly checkpointStorePresent: boolean;
59
+ };
60
+ export type ApplyEffectiveOpts = ApplyLeg & {
61
+ readonly sealedKeys?: SealedKeyOpener;
62
+ readonly generation?: number;
63
+ readonly report?: (r: ApplyReport) => void;
64
+ readonly swapPlane?: (plane: {
68
65
  models: Record<string, Model>;
69
66
  tiers: Record<string, string>;
70
67
  }) => void;
71
- }): boolean;
68
+ };
69
+ export declare function applyEffective(config: ServiceConfig, eff: EffectiveConfig, logger: Logger | undefined, opts: ApplyEffectiveOpts): boolean;
70
+ /**
71
+ * 🔴 **模型面组由哪些 effective 域组成 —— 唯一定义**(7.101.0 验修轮 V1 / codex V-1;OA 验修轮 V2 的以点改面)。
72
+ *
73
+ * collab 在组里:模板带模型引用,新模板配旧目录(或旧模板配新目录)就是一次混代发布。此前「模型面」散在三处各写一份 ——
74
+ * 世代账的键表只列 models 域的落点、stage 的 collab 闸按「模型面没被拒」ad hoc 门住、boot 按组回落 LKG 的合成只换
75
+ * `models` 域 —— 最后那份漏了 collab,于是 boot 回落时 LKG 的目录配上了被拒候选的模板(与 refresh 腿同态不同答)。
76
+ * 读者一律从这里派生,禁手抄:
77
+ * · stage:{@link stageModelPlane} 产出组内**每个**域的产物({@link ModelPlaneStages} 按本表映射),组被拒 ⇒ 一个域都没有;
78
+ * · 世代账:整组拒的 `keys` 与落地的 `applied` 都由 {@link MODEL_PLANE_SURFACES} 按本表派生({@link modelPlaneGroupReport});
79
+ * · boot 按组回落 LKG 的合成文档与 restart 基线:{@link withModelPlaneOf}(`boot/config-center.ts`)。
80
+ * (与 `config.ts` 的配置组视图 `MODEL_PLANE_GROUP_KEYS` 不是一回事:那是 `ServiceConfig` 的分节视图;这里是「中心热应用的
81
+ * 模型面这一组包含哪些 effective 域」。)
82
+ */
83
+ export declare const MODEL_PLANE_DOMAINS: readonly ["models", "collab"];
84
+ export type ModelPlaneDomain = (typeof MODEL_PLANE_DOMAINS)[number];
85
+ /** 模型面组在 effective 上的那一切片(组内各域;`models` 必在、`collab` 可缺席 —— 与 `EffectiveConfig` 同形)。 */
86
+ export type ModelPlaneOf = Pick<EffectiveConfig, ModelPlaneDomain>;
87
+ /**
88
+ * `target` 换上 `source` 的**整个模型面**(组内每个域都取 `source` 的;`source` 缺席的域在结果里同样缺席,不留 `target` 的),
89
+ * 其余域原样是 `target` 的。boot 按组回落 LKG 的两处(回落 apply 的合成文档、restart 对照基线)都走这一只 —— 禁在调用点
90
+ * 手写 `{ ...x, models, collab }`(漏一个域正是 V1 的病形)。纯数据:不改两个入参。
91
+ */
92
+ export declare function withModelPlaneOf<E extends EffectiveConfig>(target: E, source: ModelPlaneOf): E;
93
+ /** 「没有中心模型面」—— 组拒且无 LKG 时进程真正服务的那一代(env 表:没有目录、没有中心 collab 模板)。纯数据,每次新铸。 */
94
+ export declare function buildEnvOnlyModelPlane(): ModelPlaneOf;
72
95
  /**
73
96
  * UNIFORM presence semantics across the center gates (center `f95438a` adopted this from our sentinel flag —
74
97
  * SERVICE-INTEGRATION「Runtime 治理/限额闸」): every gate field is center `.optional()`, so
@@ -87,14 +110,9 @@ export declare function applyEffective(config: ServiceConfig, eff: EffectiveConf
87
110
  /** `runtime` 域的限额五闸(= settings-schema `RuntimeConfig` 的全部键,[ref] 件3 对账门钉集合等式),ONE place:
88
111
  * legacy 位的 presence 判据({@link runtimeGatePresent} → stage-limits 的 `legacyGates`)、config-catalog 的
89
112
  * center 在管键、dry-run 现值快照都从这一张表派生。[ref] 批1 起五键**全热**(属主 stage-limits,boot+refresh
90
- * 双腿),所以本表与 restart-to-apply 再无关系——审批闸不在这里:它住 governance 域({@link BOOT_GATE_KEYS})。 */
113
+ * 双腿),所以本表与 restart-to-apply 再无关系——审批闸不在这里:它住 governance 域({@link stageApprovalGate})。 */
91
114
  export declare const RUNTIME_GATE_KEYS: readonly ["rateLimitPerMin", "maxTaskCostUsd", "maxTaskTokens", "maxPrincipalCostUsd", "costQuotaWindowSec"];
92
115
  export type RuntimeGateKey = (typeof RUNTIME_GATE_KEYS)[number];
93
- /** boot-only(restart-to-apply)的治理闸:今天只有 `governance.approvalRequire` 一键——它被 main.ts 在本函数之后
94
- * 用 `config` 构造进 approval gate,refresh 腿(teamsOnly)不写,变更由 restart-signal 的 `runtime-gates` 片通告。
95
- * ([ref] 批2 若把它转热,这张表清空、那个片随之退役——一处改动。) */
96
- export declare const BOOT_GATE_KEYS: readonly ["approvalRequire"];
97
- export type BootGateKey = (typeof BOOT_GATE_KEYS)[number];
98
116
  /** Uniform presence rule (center f95438a) for the `runtime` limit gates: "managed" when the field is present —
99
117
  * any number (incl. 0). Sole guard: costQuotaWindowSec must be > 0 (a 0 window is nonsensical — core divides by
100
118
  * it; center's `.positive()` already enforces, belt-and-suspenders here). The approval gate's presence rule
@@ -106,7 +124,8 @@ export type BootGateKey = (typeof BOOT_GATE_KEYS)[number];
106
124
  * 判据与各消费腿**同源**,不自铸第二份:
107
125
  * · limits 域 = `hasOwnProperty`(stage-limits 的 published 判据逐字——present 含显式 0);
108
126
  * · 六闸 legacy 位 = {@link runtimeGatePresent}(旧位 presence 规则的唯一属主);
109
- * · governance/runtime 热二件 + approvalRequire = 键在场(applyRuntimeHot 的 non-sticky 判据同门);
127
+ * · governance 三件(autonomy / commandPolicy / approvalRequire)= 键在场(applyRuntimeHot 与 stageApprovalGate
128
+ * 的 non-sticky presence 判据同门:`!== undefined`);
110
129
  * · readFace 域成员 = 键在场(read-face.ts 的逐键 env-wins 合成看的就是成员在场性);
111
130
  * · models 面 = 有启用行(markRosterLanded 同判据);atModelAllowlist 单列(整表重算键)。
112
131
  * 纯投影零副作用;`undefined` eff(还没拉到 / 纯 env 形)⇒ 空集。
@@ -114,11 +133,12 @@ export type BootGateKey = (typeof BOOT_GATE_KEYS)[number];
114
133
  export declare function centerManagedConfigKeys(eff: EffectiveConfig | undefined): ReadonlySet<string>;
115
134
  export declare function runtimeGatePresent(rt: NonNullable<EffectiveConfig["runtime"]>, key: RuntimeGateKey): boolean;
116
135
  /** 独立调用面的兼容壳(测试/外部):stage → 就地赋值 → 通知。applyEffective 不走这里——它把
117
- * assignments 并进自己的 commit 段以保住整世代原子性([ref]②)。入参是 eff 的两个域([ref] 件3:审批闸
136
+ * 赋值并进自己的 commit 段以保住整世代原子性([ref]②)。入参是 eff 的两个域([ref] 件3:审批闸
118
137
  * 住 governance,限额五闸住 runtime——两位各自的属主判据,这里不再合成视图)。
119
138
  * [ref]:限额五闸的属主已移交热腿,所以本壳**同样**要过 stage-limits —— 否则一个仍叫 applyRuntimeGates
120
- * 的函数会静默漏掉五个限额键(比改名更坏的那种沉默)。 */
121
- export declare function applyRuntimeGates(config: ServiceConfig, eff: Pick<EffectiveConfig, "runtime" | "governance"> | undefined, logger?: Logger): void;
139
+ * 的函数会静默漏掉五个限额键(比改名更坏的那种沉默)。S-668 波 2:审批组的判据依赖腿(④ 只在热腿判),
140
+ * 所以本壳与 applyEffective 一样**必须**说是哪条腿。 */
141
+ export declare function applyRuntimeGates(config: ServiceConfig, eff: Pick<EffectiveConfig, "runtime" | "governance"> | undefined, leg: ApplyLeg, logger?: Logger): void;
122
142
  export declare function applyRuntimeHot(config: ServiceConfig, gov: EffectiveConfig["governance"], logger?: Logger): void;
123
143
  /**
124
144
  * [ref]① 显式默认解析——applyEffective 与 dry-run(logEffectiveDiff.wouldDefaultModel)共用的单源。优先级