@sema-agent/server 7.117.0 → 7.119.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 (159) hide show
  1. package/MIGRATION.md +182 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +58 -28
  5. package/deploy/sema-up/README.md +2 -0
  6. package/deploy/sema-up/chart/templates/minio.yaml +2 -1
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +1 -0
  8. package/deploy/sema-up/chart/templates/server.yaml +1 -0
  9. package/deploy/sema-up/chart/values.yaml +7 -1
  10. package/deploy/sema-up/cluster-up.sh +20 -8
  11. package/deploy/sema-up/kube-up.sh +29 -16
  12. package/deploy/sema-up/lib/buckets.sh +21 -0
  13. package/deploy/sema-up/lib/health.sh +33 -0
  14. package/deploy/sema-up/sema-up.sh +14 -9
  15. package/deploy/sema-up/smoke.sh +16 -6
  16. package/deploy/sema-up/stack/compose.yaml +1 -1
  17. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -1
  18. package/dist/approval-ask-audit-store.js +2 -7
  19. package/dist/approval-card.d.ts +40 -10
  20. package/dist/approval-card.js +11 -1
  21. package/dist/approval.d.ts +55 -17
  22. package/dist/approval.js +74 -33
  23. package/dist/audit.d.ts +12 -1
  24. package/dist/audit.js +62 -2
  25. package/dist/auto-mode-face.d.ts +2 -3
  26. package/dist/bake-runner/main.js +1 -6
  27. package/dist/bake-runner/protocol.d.ts +18 -0
  28. package/dist/bake-runner/protocol.js +22 -2
  29. package/dist/bake-runner/runner.d.ts +12 -8
  30. package/dist/bake-runner/runner.js +5 -9
  31. package/dist/boot/config-center.js +1 -1
  32. package/dist/boot/coordinators.js +4 -1
  33. package/dist/boot/device-lane.d.ts +3 -3
  34. package/dist/boot/device-lane.js +1 -2
  35. package/dist/boot/leader.d.ts +4 -4
  36. package/dist/boot/leader.js +1 -2
  37. package/dist/boot/parked-revive-gate.d.ts +4 -6
  38. package/dist/boot/parked-revive-gate.js +2 -6
  39. package/dist/boot/resolve-spec.js +9 -11
  40. package/dist/boot/runner-deps.js +2 -2
  41. package/dist/boot/stage-07-capability-layer.js +7 -3
  42. package/dist/boot/stage-10-http-server.js +3 -2
  43. package/dist/boot/stores.js +12 -7
  44. package/dist/capabilities/center-plugins.d.ts +2 -0
  45. package/dist/capabilities/center-plugins.js +24 -6
  46. package/dist/capabilities/code-review-council.js +8 -11
  47. package/dist/capabilities/hands-lane.d.ts +25 -0
  48. package/dist/capabilities/hands-lane.js +1 -1
  49. package/dist/capabilities/mcp-probe.d.ts +2 -2
  50. package/dist/capabilities/scenarios.d.ts +34 -7
  51. package/dist/capabilities/scenarios.js +33 -6
  52. package/dist/config-catalog.js +3 -3
  53. package/dist/config.d.ts +14 -18
  54. package/dist/config.js +15 -26
  55. package/dist/deployment-governance.d.ts +5 -7
  56. package/dist/deployment-governance.js +4 -5
  57. package/dist/host-decision.d.ts +114 -31
  58. package/dist/host-decision.js +39 -2
  59. package/dist/http/admission.js +12 -6
  60. package/dist/http/approval-sweeps.js +1 -1
  61. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  62. package/dist/http/mcp-dial-deadline.js +21 -0
  63. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  64. package/dist/http/memory-compliance-failure-reply.js +17 -8
  65. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  66. package/dist/http/memory-store-refusal-reply.js +16 -5
  67. package/dist/http/resume-legs.js +8 -3
  68. package/dist/http/routes/approvals-assistant.d.ts +25 -3
  69. package/dist/http/routes/approvals-assistant.js +27 -26
  70. package/dist/http/routes/capabilities.js +6 -3
  71. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  72. package/dist/http/routes/mcp-reconnect.js +6 -1
  73. package/dist/http/routes/memory-bundle.js +1 -1
  74. package/dist/http/routes/memory-compliance.js +1 -1
  75. package/dist/http/routes/memory-origin.d.ts +1 -1
  76. package/dist/http/routes/memory-origin.js +1 -1
  77. package/dist/http/routes/runs.js +47 -33
  78. package/dist/http/routes/sessions.d.ts +1 -1
  79. package/dist/http/routes/sessions.js +23 -22
  80. package/dist/http/routes/workflows.js +2 -0
  81. package/dist/http/wire-types.d.ts +17 -13
  82. package/dist/images/manifest.d.ts +7 -1
  83. package/dist/images/manifest.js +18 -16
  84. package/dist/memory-store-refusal.d.ts +38 -18
  85. package/dist/memory-store-refusal.js +25 -7
  86. package/dist/model-unreachable-notice.d.ts +1 -1
  87. package/dist/namespaced-rule-name.d.ts +46 -41
  88. package/dist/namespaced-rule-name.js +36 -22
  89. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  90. package/dist/observability/corrupt-read-seat.js +1 -0
  91. package/dist/observability/fail-open.d.ts +19 -3
  92. package/dist/observability/fail-open.js +19 -3
  93. package/dist/operator-ask.d.ts +6 -2
  94. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  95. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  96. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  97. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  98. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  99. package/dist/orchestration/workflow-notify-journal.js +7 -2
  100. package/dist/outbound-dispatcher.d.ts +29 -20
  101. package/dist/outbound-dispatcher.js +105 -16
  102. package/dist/parked-decide.d.ts +43 -48
  103. package/dist/parked-decide.js +54 -16
  104. package/dist/permission-rule-vocab.d.ts +4 -3
  105. package/dist/permission-rule-vocab.js +1 -1
  106. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  107. package/dist/plugins/approval-ask-store-sql.js +0 -3
  108. package/dist/plugins/approval-exemption-store.d.ts +4 -0
  109. package/dist/plugins/approval-exemption-store.js +1 -0
  110. package/dist/plugins/blob-backend.d.ts +13 -0
  111. package/dist/plugins/blob-backend.js +26 -0
  112. package/dist/plugins/local-session-store.d.ts +78 -47
  113. package/dist/plugins/local-session-store.js +152 -122
  114. package/dist/plugins/one-time-migrations.d.ts +11 -0
  115. package/dist/plugins/one-time-migrations.js +128 -4
  116. package/dist/plugins/pg-pool.js +1 -1
  117. package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
  118. package/dist/plugins/snapshot-bucket-presence.js +40 -0
  119. package/dist/plugins/store-backend.d.ts +17 -6
  120. package/dist/plugins/store-backend.js +46 -16
  121. package/dist/plugins/store-open-wait.d.ts +33 -0
  122. package/dist/plugins/store-open-wait.js +73 -0
  123. package/dist/plugins/tidb-pool.js +1 -1
  124. package/dist/plugins/web-search.d.ts +20 -1
  125. package/dist/plugins/web-search.js +39 -10
  126. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  127. package/dist/plugins/workflow-run-store-sql.js +5 -7
  128. package/dist/request-key-closure.js +1 -1
  129. package/dist/request-value-closure.d.ts +1 -1
  130. package/dist/rule-entry-list.d.ts +35 -46
  131. package/dist/rule-entry-list.js +18 -26
  132. package/dist/rules-consent.js +3 -0
  133. package/dist/run-local.js +5 -3
  134. package/dist/runs.d.ts +1 -1
  135. package/dist/security.d.ts +5 -3
  136. package/dist/security.js +4 -0
  137. package/dist/session-policy-wire.d.ts +13 -12
  138. package/dist/session-policy-wire.js +11 -11
  139. package/dist/spec-fields.d.ts +13 -11
  140. package/dist/spec-fields.js +4 -7
  141. package/dist/standing-answer-grant.d.ts +14 -2
  142. package/dist/standing-answer-grant.js +8 -1
  143. package/dist/store-posture.d.ts +14 -6
  144. package/dist/store-posture.js +7 -4
  145. package/dist/task-settings.d.ts +27 -55
  146. package/dist/task-settings.js +13 -21
  147. package/dist/tool-approval.d.ts +133 -138
  148. package/dist/tool-approval.js +175 -66
  149. package/dist/trace/core-keyset-guard.d.ts +5 -5
  150. package/dist/trace/redact.d.ts +6 -0
  151. package/dist/trace/redact.js +9 -9
  152. package/dist/trace/self-minted-wire.d.ts +16 -2
  153. package/dist/trace/self-minted-wire.js +7 -0
  154. package/dist/user-input-ledger.d.ts +30 -14
  155. package/dist/user-input-ledger.js +25 -14
  156. package/package.json +2 -2
  157. package/scripts/drain-check-local.mjs +74 -3
  158. package/scripts/drain-check.mysql.sql +43 -17
  159. package/scripts/drain-check.pg.sql +46 -20
@@ -35,8 +35,10 @@ export function createCouncilTool(subRunner, repoTools, objective, opts = {}) {
35
35
  parameters: Type.Object({}),
36
36
  execute: async (_args, ctx) => {
37
37
  const active = pickLenses(opts.lensCount);
38
- const lenses = await runLensReviews(subRunner, repoTools, objective, ctx.signal, active);
39
- const findings = lenses.map((l) => `### ${l.lens} reviewer\n${l.text}`).join("\n\n");
38
+ const outcomes = await runLensReviews(subRunner, repoTools, objective, ctx.signal, active);
39
+ const lenses = outcomes.filter((o) => o.ok);
40
+ const failedLenses = outcomes.flatMap((o) => (o.ok ? [] : [{ lens: o.lens, reason: o.reason }]));
41
+ const findings = outcomes.map((o) => `### ${o.lens} reviewer\n${o.ok ? o.text : `(lens errored: ${o.reason})`}`).join("\n\n");
40
42
  const converge = await runConvergence(subRunner, repoTools, objective, findings, opts, ctx.signal, active);
41
43
  const totalTokens = lenses.reduce((s, l) => s + l.tokens, 0) + converge.tokens;
42
44
  const totalTurns = lenses.reduce((s, l) => s + l.turns, 0) + converge.turns;
@@ -46,10 +48,10 @@ export function createCouncilTool(subRunner, repoTools, objective, opts = {}) {
46
48
  opts.metrics?.inc("council_runs_total", { mode });
47
49
  opts.metrics?.inc("task_tokens_total", {}, totalTokens);
48
50
  opts.metrics?.observe("council_tokens", totalTokens);
49
- opts.logger?.info("council_done", { mode, lenses: lenses.length, totalTokens, totalTurns });
51
+ opts.logger?.info("council_done", { mode, lenses: lenses.length, failedLenses, totalTokens, totalTurns });
50
52
  return {
51
53
  content: converge.review,
52
- details: { council: { mode, totalTokens, totalTurns, perLens: lenses.map((l) => ({ lens: l.lens, tokens: l.tokens })) } },
54
+ details: { council: { mode, totalTokens, totalTurns, perLens: lenses.map((l) => ({ lens: l.lens, tokens: l.tokens })), failedLenses } },
53
55
  };
54
56
  },
55
57
  };
@@ -65,16 +67,11 @@ async function runLensReviews(subRunner, repoTools, objective, signal, lenses) {
65
67
  enableBlockedReport: false,
66
68
  signal,
67
69
  });
68
- return { lens: lens.name, text: r.result?.trim() || "(no findings)", tokens: r.stats.tokens, turns: r.stats.turns };
70
+ return { ok: true, lens: lens.name, text: r.result?.trim() || "(no findings)", tokens: r.stats.tokens, turns: r.stats.turns };
69
71
  }));
70
72
  return settled.map((s, i) => s.status === "fulfilled"
71
73
  ? s.value
72
- : {
73
- lens: lenses[i].name,
74
- text: `(lens errored: ${s.reason instanceof Error ? s.reason.message : String(s.reason)})`,
75
- tokens: 0,
76
- turns: 0,
77
- });
74
+ : { ok: false, lens: lenses[i].name, reason: s.reason instanceof Error ? s.reason.message : String(s.reason) });
78
75
  }
79
76
  async function runConvergence(subRunner, repoTools, objective, findings, opts, signal, lenses) {
80
77
  if (opts.debate) {
@@ -33,6 +33,31 @@ export interface HandsRunnerPair {
33
33
  }
34
34
  /** 按场景表态取 Runner。**穷举 switch**:`ScenarioHands` 加词而此处不改 = 编译错(无 default 臂)。 */
35
35
  export declare function pickHandsRunner(hands: ScenarioHands, pair: HandsRunnerPair): Runner;
36
+ /**
37
+ * core 手带 band 的工具名**全集** —— 「挂 executionEnvFactory 才出现、不挂就消失」的那一批。
38
+ *
39
+ * 两段来源,都不是拍脑袋:
40
+ * · `handBandEffects()`(core 7.8.0 B1 起的导出面;此前是手抄表常量 `HAND_TOOL_EFFECTS`)—— band 的
41
+ * effect 表,由**静态工具目录**按 `mountedBy.includes("hands")` 派生。名册单源之后这张表不再是一张
42
+ * 手抄名表,加/退工具由目录一处说了算。
43
+ * · 下方锚定补集 —— `createHandsToolkit` / 手带块还会铸但**不在** effect 表里的 5 名(实测差集,见
44
+ * test/scenario-hands-lane.test.ts 的对表格:它用真 Runner 跑「有手 roster ∖ 无手 roster」,与本常量逐名对账,
45
+ * core 加/改 band 成员时红,防漂移)。
46
+ *
47
+ * ⚠️ 本全集是**观测口径**(用于防漂移对账),**不是** deny 名单 —— deny 用下面的
48
+ * {@link HANDS_EXCLUSIVE_TOOL_NAMES},理由见那里。
49
+ *
50
+ * 🚨 [ref] P0(2026-08-09):派生时必须过滤 core 的 `RETIRED_TOOL_NAMES` —— core 5.21.0 的
51
+ * `HAND_TOOL_EFFECTS` 里残留已退役的 `MultiEdit`,而 core `prepare-task` 的策略名审计见退役名
52
+ * **无条件 throw**(`config.legacy_tool_name`);不过滤,deny 名单一进 `spec.toolPolicy`,
53
+ * `hands: none` 三场景(scan/code-review/team)在**所有部署**上零 turn 全灭。core 5.21.1 已删
54
+ * 那条死条目([ref]),这道 filter 是结构性防御:将来 core 再退役任何 band 名都不会重演
55
+ * (test/scenario-hands-lane.test.ts 格7/格8 双钉:真 Runner 过审计 + 零交集门)。
56
+ * filter 打在**整表**(effect 表 + 锚定补集)之后 —— caret floor 下装置会解析到更新的 core,
57
+ * 补集名哪天被退役,只滤前半就是同一次故障换个名字(codex 复审 finding,2026-08-09)。
58
+ * S-1047(core 7.41.0 #1156):判据换成 core 导出的**那一只** `judgeRuleToolName`(策略名审计、会话规则店、本仓写口与配置表同调)——
59
+ * 修前这里直读 `RETIRED_TOOL_NAMES` 表,是本仓第三份「这是不是旧名」的手写判据。名册 = 空集(这张 deny 名单不挂在任何一跑的名册上判)。
60
+ */
36
61
  export declare const HANDS_BAND_TOOL_NAMES: readonly string[];
37
62
  /**
38
63
  * band 里**只有手才可能出现**的那批(= deny 名单)。
@@ -1,4 +1,5 @@
1
1
  import { handBandEffects, judgeRuleToolName, combinePolicies, createAllowDenyPolicy } from "@sema-agent/core";
2
+ import { NO_DECLARED_TOOL_NAMES } from "../namespaced-rule-name.js";
2
3
  export function withoutExecutionEnv(deps) {
3
4
  const { executionEnvFactory: _handsDropped, ...rest } = deps;
4
5
  return rest;
@@ -11,7 +12,6 @@ export function pickHandsRunner(hands, pair) {
11
12
  return pair.handsless;
12
13
  }
13
14
  }
14
- const NO_DECLARED_TOOL_NAMES = new Set();
15
15
  export const HANDS_BAND_TOOL_NAMES = Object.freeze([
16
16
  ...handBandEffects().keys(),
17
17
  "SendMessage",
@@ -79,7 +79,7 @@ export declare const MCP_PROBE_MAX_INFLIGHT = 32;
79
79
  *
80
80
  * 🔴 **不是新旋钮**:没有 env、不进 config-catalog、部署方改不了它 —— 「不新铸旋钮」这条要求管的是
81
81
  * **旋钮**,不是常量。数值与既有 `GET /v1/sessions/:id/mcp` 面板的物化上界同阶(那边 10s),而**名字刻意
82
- * 独立**:本仓 `sessions.ts` 的四枚同族时限逐字写着理由 ——「两条腿的物化成本不同族,将来任一侧调窗时不该
82
+ * 独立**:本仓 `sessions.ts` 的同族时限(MCP / A2A / memory-status 各一枚)逐字写着理由 ——「两条腿的物化成本不同族,将来任一侧调窗时不该
83
83
  * 被另一侧的名字绑架」。那边界的是**整次物化**,这边界的是**每台的裁决**,连语义都不是一回事。
84
84
  *
85
85
  * ⚠️ core 的硬条款(d.ts 逐字):`timeoutMs` 界**裁决**不界**拨号** —— 被时钟放弃的那次拨号仍在跑,
@@ -99,7 +99,7 @@ export declare const MCP_PROBE_VERDICT_TIMEOUT_MS = 10000;
99
99
  * 与 core 自己成文的走查时长公式**逐字同形**(d.ts:"its own duration is those per-dial times summed over
100
100
  * `ceil(n / concurrency)` rounds"),所以在一台**对端会答或会失败**的部署上这口钟结构上摸不到 —— 摸到它
101
101
  * 就是真有一台对端在卡发送。读数:1 台 ⇒ 14s;32 台 ⇒ 44s。
102
- * 兄弟先例:`GET /v1/sessions/:id/mcp` 面板的 `MCP_MATERIALIZE_TIMEOUT_MS`(那边界的也是整次物化,但它是
102
+ * 兄弟先例:`GET /v1/sessions/:id/mcp` 面板的 `MCP_DIAL_DEADLINE_MS`(`http/mcp-dial-deadline.ts`;那边界的也是整次物化,但它是
103
103
  * 一个定值;本面的申报条数由调用方决定,所以定值在这里就是那个「与所问无关的宽限」)。
104
104
  * **纯数据** ⇒ `build*`。
105
105
  */
@@ -174,7 +174,12 @@ export declare function semaIdentityProvider(): PromptProvider;
174
174
  */
175
175
  export declare function codePromptProvider(brand: boolean): PromptProvider;
176
176
  export declare function buildScenarios(deps: ScenarioDeps): Record<string, Scenario>;
177
- /** Resolve the scenario for a request (falls back to `default`). */
177
+ /**
178
+ * Resolve the scenario bundle for a name. `undefined` = no preference ⇒ the built-in `default`; any OTHER name must be
179
+ * registered — an unregistered one is a caller bug and THROWS (it never falls back to `default`: a restricted principal's
180
+ * bundle must not silently widen). Request paths get their name from {@link gateScenarioRequest}, whose result is always
181
+ * registered, so the throw is unreachable from them; it is the loud arm for a direct caller that skipped the gate.
182
+ */
178
183
  export declare function selectScenario(scenarios: Record<string, Scenario>, name: string | undefined): Scenario;
179
184
  /** web 渲染契约(定稿形状;knobs 现在没有=整字段省略)。 */
180
185
  export interface ScenarioDetail {
@@ -259,16 +264,38 @@ export interface ScenarioRuling {
259
264
  /** 纯场景名集合(principal/组名不上 wire);空 = 该 principal 无任何指派 = 无治理。 */
260
265
  allowlist: string[];
261
266
  }
267
+ /**
268
+ * 一个场景名的**来源**(闭集:新增一词 ⇒ {@link danglingScenarioRefusal} 的穷尽 switch 编译红)。
269
+ * · `ruling` —— 中心按主体指派的场景(`ScenarioRuling.scenario`);
270
+ * · `deployment` —— 部署自己写的缺省(`DEFAULT_SCENARIO` / config `defaultScenario`)。
271
+ * 请求体显式给的名字不在此列:那是调用方的话,不在册 ⇒ 400 `scenario_unknown`(见 {@link gateScenarioRequest})。
272
+ */
273
+ export type ScenarioNameSource = "ruling" | "deployment";
274
+ /**
275
+ * 🔴 **悬空场景名的判据与拒句 —— 全仓唯一属主。**
276
+ *
277
+ * 指派 / 部署缺省指向不在册的名字,此前一路被折成 `default`(工具 / 提示 / skills / mcp 整包跟着换,零 warn):受限场景的主体被
278
+ * 悄悄放进更宽的 default。本函数把那条回退链换成两条出路,**没有第三条**:
279
+ * · `deployment` 悬空 ⇒ 起服拒启(`boot/stage-07-capability-layer.ts` 在 center 场景 overlay 之后调它、`throw new Error`);
280
+ * · `ruling` 悬空 ⇒ 该 run 503 `state.scenario_assignment_unknown`({@link gateScenarioRequest} 调它);
281
+ * · `deployment` 悬空到得了 run(只有自己装配 `resolveSpec`、没经过起服的嵌入方)⇒ 同码同判据同一句话 —— 起服拒启与这一条是同一个答案。
282
+ *
283
+ * 返回 `undefined` = 在册;否则是**拒句**(点名名字与来源、给在册词表、说怎么修)。判据按**自有键**(`known` 是场景表的
284
+ * `Object.keys`,`constructor` / `__proto__` 不是场景名)。
285
+ */
286
+ export declare function danglingScenarioRefusal(name: string, source: ScenarioNameSource, known: readonly string[]): string | undefined;
262
287
  /**
263
288
  * resolveSpec 的执法入口(指派为默认、body.scenario 可换但限 allowlist)。返回本请求的
264
- * 生效场景名(未做 unknown→default 回退——那是 resolveSpec 既有职责,保持单点)。
289
+ * **在册**生效场景名 —— 返回值一定在 `known` 里:下游直接按它取场景,没有「未知名 → default」的回退臂。
265
290
  *
266
- * - body.scenario 显式给出:allowlist 非空且集外 ⇒ typed 拒绝(400 `scenario_not_allowed`,附 allowlist
267
- * ——机器码归 wire、人话归壳/web);集内 ⇒ 用户主动切换赢。allowlist 为空(无 ruling / 该 principal
268
- * 三层皆无指派 / caps 面故障)⇒ 无治理约束,现状放行(与「null 落兜底链」同一语义:分配面未覆盖 =
291
+ * - body.scenario 显式给出:不在册 ⇒ typed 400 `scenario_unknown`(调用方的错);allowlist 非空且集外 ⇒ typed 拒绝
292
+ * (400 `scenario_not_allowed`,附 allowlist ——机器码归 wire、人话归壳/web);集内 ⇒ 用户主动切换赢。allowlist 为空
293
+ * (无 ruling / 该 principal 三层皆无指派 / caps 面故障)⇒ 无治理约束,现状放行(与「null 落兜底链」同一语义:分配面未覆盖 =
269
294
  * 不收紧,恒不比没有治理时更宽;caps 故障不把用户锁死在 default——场景切换不是权限放大面,fail-open
270
295
  * 到现状,与 workflows 面的 fail-closed 是刻意的 posture 差异,见 runtime-caps-resolver)。
271
- * - body.scenario 未给:ruling 胜者 ⇒ 指派为默认;无胜者 ⇒ defaultScenario 现状链。
296
+ * - body.scenario 未给:ruling 胜者 ⇒ 指派为默认;无胜者 ⇒ defaultScenario 现状链。链上的名字不在册 ⇒ 503
297
+ * `state.scenario_assignment_unknown`(部署的错 / 中心的错,不是调用方的错),**不**折 default
298
+ * ({@link danglingScenarioRefusal})。`principal` 只进那一行运维日志,不参与判定。
272
299
  */
273
- export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[], logger?: Logger): string;
300
+ export declare function gateScenarioRequest(ruling: ScenarioRuling | undefined, bodyScenario: unknown, defaultScenario: string, known: readonly string[], logger?: Logger, principal?: string): string;
274
301
  //# sourceMappingURL=scenarios.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { CODE_AGENT_PROMPT, CODE_SYSTEM_PROMPT, DEFAULT_SYSTEM_PROMPT, WEB_FETCH_TOOL_NAME, assembleCodeTools, defaultTaskRegistry } from "@sema-agent/core";
2
- import { HttpError } from "../security.js";
2
+ import { HttpError, createCodedHttpError } from "../security.js";
3
3
  import { createSubagentExtraTools, nowTool } from "./builtin-tools.js";
4
4
  import { parseRepo, repoToolsFor } from "./repo-tools.js";
5
5
  import { createCouncilTool, clampRounds } from "./code-review-council.js";
@@ -120,8 +120,12 @@ export function buildScenarios(deps) {
120
120
  };
121
121
  }
122
122
  export function selectScenario(scenarios, name) {
123
- const resolved = name;
124
- return (resolved && Object.hasOwn(scenarios, resolved) && scenarios[resolved]) || scenarios.default;
123
+ if (name === undefined)
124
+ return scenarios.default;
125
+ const picked = Object.hasOwn(scenarios, name) ? scenarios[name] : undefined;
126
+ if (picked === undefined)
127
+ throw new Error(`scenario "${name}" is not registered — resolve a request's scenario through gateScenarioRequest, which only returns registered names`);
128
+ return picked;
125
129
  }
126
130
  function availabilityFields(deps, toolset) {
127
131
  const verdict = scenarioAvailability(deps, toolset);
@@ -295,11 +299,28 @@ export function mergeUserSkills(scenarioSkills, userSkills, logger) {
295
299
  logger?.warn("user_skills_dropped", { reason: "name reserved by scenario (or duplicate)", names: dropped });
296
300
  return out;
297
301
  }
298
- export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known, logger) {
302
+ function registeredScenariosClause(known) {
303
+ return `this deployment's scenarios are: ${known.join(", ")}`;
304
+ }
305
+ export function danglingScenarioRefusal(name, source, known) {
306
+ if (known.includes(name))
307
+ return undefined;
308
+ switch (source) {
309
+ case "ruling":
310
+ return `the scenario assigned to this principal, "${name}", is not a scenario of this deployment — ${registeredScenariosClause(known)}; nothing ran — re-point the assignment (or add that scenario to this deployment) and retry`;
311
+ case "deployment":
312
+ return `DEFAULT_SCENARIO "${name}" does not name a scenario of this deployment — ${registeredScenariosClause(known)}; set DEFAULT_SCENARIO to one of them`;
313
+ default: {
314
+ const unhandled = source;
315
+ return unhandled;
316
+ }
317
+ }
318
+ }
319
+ export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known, logger, principal) {
299
320
  if (typeof bodyScenario === "string") {
300
321
  const requested = bodyScenario;
301
322
  if (!known.includes(requested)) {
302
- throw new HttpError(400, `unknown scenario "${bodyScenario}" — this deployment's scenarios are: ${known.join(", ")}`, {
323
+ throw new HttpError(400, `unknown scenario "${bodyScenario}" — ${registeredScenariosClause(known)}`, {
303
324
  code: "scenario_unknown",
304
325
  extra: { known: [...known] },
305
326
  });
@@ -313,6 +334,12 @@ export function gateScenarioRequest(ruling, bodyScenario, defaultScenario, known
313
334
  }
314
335
  return requested;
315
336
  }
316
- return ruling?.scenario ?? defaultScenario;
337
+ const [name, source] = ruling?.scenario != null ? [ruling.scenario, "ruling"] : [defaultScenario, "deployment"];
338
+ const refusal = danglingScenarioRefusal(name, source, known);
339
+ if (refusal !== undefined) {
340
+ logger?.warn("scenario_assignment_unknown", { scenario: name, source, ...(principal !== undefined ? { principal } : {}) });
341
+ throw createCodedHttpError(503, "state.scenario_assignment_unknown", refusal, { detail: { scenario: name, source } });
342
+ }
343
+ return name;
317
344
  }
318
345
  //# sourceMappingURL=scenarios.js.map
@@ -104,7 +104,7 @@ export const CONFIG_CATALOG = [
104
104
  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 }),
105
105
  r("DB_BACKEND", "store", "enum", "store 后端引擎选择(mysql 兼容/pg/本机文件店/纯内存)", { enumValues: STORE_BACKENDS, danger: DATA, derivedDefaultNote: "裸 boot ⇒ local(多租户裸 boot ⇒ memory + notice);有 SQL 信号 ⇒ mysql", configKey: "dbBackend", resolve: (c) => c.dbBackend }),
106
106
  r("DB_QUERY_TIMEOUT_MS", "store", "number", "per-query DB 超时(ms;坏值回默认姿态+warn,绝不 NaN 进 driver)", { derivedDefaultNote: "缺席 ⇒ 池级不设/计数族 30s(按引擎)", configKey: "dbQueryTimeoutMs" }),
107
- r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回)", { staticDefault: "code", configKey: "defaultScenario" }),
107
+ r("DEFAULT_SCENARIO", "integrations", "string", "出厂缺省场景(编码产品出厂态;通用中性 persona 用 default 显式选回);必须是本部署在册的场景名(内建 ∪ 配置中心声明的),不在册 ⇒ 拒启", { staticDefault: "code", configKey: "defaultScenario" }),
108
108
  r("DELEGATION_MAX_CONCURRENT", "orchestration", "number", "委派入口并发帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 20)" }),
109
109
  r("DELEGATION_MAX_PER_SESSION", "orchestration", "number", "委派入口每会话累计帽", { derivedDefaultNote: "缺席 ⇒ 不铸键,core 自缺省(CC parity 200)" }),
110
110
  r("DEVICE_DELIVERY_TIMEOUT_MS", "orchestration", "number", "device 车道等在途槽上限(ms;到期 device.busy)", { derivedDefaultNote: "缺席 ⇒ adapter 默认 10s" }),
@@ -411,7 +411,7 @@ export const CONFIG_CATALOG = [
411
411
  r("SERVICE_AUTH_TOKEN", "auth", "secret", "共享 service token(写面门;缺席且未开 ALLOW_UNAUTHED_WRITES ⇒ 拒提交)", { danger: SEC }),
412
412
  r("SERVICE_AUTH_TOKENS", "auth", "secret", "per-system 凭据表(tok=system CSV;source 从凭据派生,绝不自报)", { danger: SEC }),
413
413
  r("SESSION_AUTO_TITLE", "orchestration", "boolean", "首次提交自动铸 session 标题(一次 cheap 调用)", { staticDefault: "true", danger: BILL, configKey: "sessionAutoTitle" }),
414
- r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=探到 DB 用 DB,探不到回内存;回显=boot 解析后的真引擎)", { enumValues: SESSION_BACKENDS, danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => (c.sessionBackend === "tidb" ? "mysql" : c.sessionBackend) }),
414
+ r("SESSION_BACKEND", "store", "enum", "session 店引擎(auto=配了该引擎的坐标就用那只库、没配就是内存;配了而此刻连不上 ⇒ 在 STORE_LOCK_WAIT_MS 窗内重试、到窗拒启,绝不落内存;回显=boot 解析后的真引擎)", { enumValues: SESSION_BACKENDS, danger: DATA, derivedDefaultNote: "缺席:显式 DB_BACKEND=mysql|pg ⇒ mysql,否则 memory", configKey: "sessionBackend", resolve: (c) => (c.sessionBackend === "tidb" ? "mysql" : c.sessionBackend) }),
415
415
  r("SESSION_CACHE_TTL_SEC", "store", "number", "session 读缓存 TTL(秒;0=关)", { staticDefault: "300", configKey: "sessionCacheTtlSec" }),
416
416
  r("SESSION_EVENTS_HOT_MS", "limitsHttp", "number", "session SSE 订阅热探针间隔(ms)", { derivedDefaultNote: "缺席 ⇒ 200" }),
417
417
  r("SESSION_EVENTS_MAX_CONNS", "limitsHttp", "number", "session SSE 并发连接帽", { derivedDefaultNote: "缺席 ⇒ 256" }),
@@ -431,7 +431,7 @@ export const CONFIG_CATALOG = [
431
431
  r("SSH_PORT", "orchestration", "number", "ssh 端口", { derivedDefaultNote: "缺席 ⇒ 22" }),
432
432
  r("SSH_PRIVATE_KEY", "orchestration", "secret", "ssh 私钥(adapter 持有,绝不进 workspace)", { danger: SEC }),
433
433
  r("SSH_USER", "orchestration", "string", "ssh 用户(必需件)"),
434
- r("STORE_LOCK_WAIT_MS", "store", "number", "local 数据根 LOCK 撞上活持锁人时的有界等待窗(ms;0=当场拒;到窗仍拿不到 ⇒ 原码拒启,永不放行第二个写者)", { staticDefault: "60000", danger: DATA, configKey: "storeLockWaitMs" }),
434
+ r("STORE_LOCK_WAIT_MS", "store", "number", "store 启动期的有界等待窗(ms):local 数据根 LOCK 撞上活持锁人,或已配置的 SQL 库此刻连不上 / 建表失败;0=当场拒;到窗仍开不了 ⇒ 拒启(local 保持原码,永不放行第二个写者;SQL 点名后端与 host:port,永不退成内存)", { staticDefault: "60000", danger: DATA, configKey: "storeLockWaitMs" }),
435
435
  r("STORE_PROBE_INTERVAL_MS", "store", "number", "/health 活体 store 后台探针间隔(ms;0=显式关;仅 SQL 后端挂环)", { staticDefault: "15000", configKey: "storeProbeIntervalMs" }),
436
436
  r("STREAM_APPROVAL_ADHOC_GRACE_MS", "approval", "number", "流内审批 adhoc 腿窗后宽限(ms;须 window+grace < orphanTtl,破坏形拒启)", { staticDefault: "60000" }),
437
437
  r("STREAM_APPROVAL_ADMIT_MAX_PER_OWNER", "approval", "number", "流内审批 per-owner 未决帽", { staticDefault: "256" }),
package/dist/config.d.ts CHANGED
@@ -297,10 +297,10 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
297
297
  * - 退役名(`bash`/`Task`/`KillShell`…)与 pre-prefix 短名(`figma__x`):S-1047(core 7.41.0 #1156)起判据是 core 导出的
298
298
  * **那一只** `judgeRuleToolName`(准备阶段、两只会话规则店、本仓会话规则写口同调),本函数零手写臂;指引 = core 表给的现役指引 /
299
299
  * 补全协议前缀。
300
- * - 够不着挂载工具的协议名:缺段(`mcp__`、`mcp__srv__`、`mcp____x`)与**对端段铸不出**(`mcp__github.com`、`mcp__github.com__*`、
301
- * 超长 / 归一后带连串下划线的服务器段;服务器段带通配的 `mcp__*` 单列判词 `wildcard_peer`)—— 前缀对,但引擎铸注册名时归一对端段,而规则对对端段逐字比,永不匹配。
302
- * 判官与句子是 `namespaced-rule-name.ts` 的那**一只**(判据 = core `mintNamespacePrefix` 的自铸对照;会话规则写口两腿与 SQL 店写口同调),
303
- * 本文件零手写臂。修前(7.116.0 之前)这里只有缺段一臂外加一条只排 `*` 的特例(`coversAServer`),对端段带点号 / 超长的覆盖形在每个面都照收。
300
+ * - 够不着挂载工具的协议名:**对端段铸不出**(`mcp__github.com`、`mcp__github.com__*`、`mcp__*`、超长 / 归一后带连串下划线的服务器段、对端段为空的
301
+ * `mcp__` / `mcp____x`)—— 前缀对,但引擎铸注册名时归一对端段,而规则对对端段逐字比,永不匹配。判官是引擎的 `judgeRuleToolName`(core 7.43.0 #1355 (b)
302
+ * 的判词 `unmintable_segment`,句子带它的 guidance 与 1:1 现名);本仓 7.116.0–7.117.x 那只自比 `mintNamespacePrefix` 的对照随之退役。
303
+ * 引擎判官放行、却一只工具都够不着的一形 —— **工具段为空**(`mcp__srv__`)—— 由本仓判(`namespaced-rule-name.ts`,会话规则写口两腿与 SQL 店写口同调)。
304
304
  * ⚠️ 两段覆盖形 `mcp__<server>`(服务器段是自铸形)够得着(S-1047 收掉的分叉,见下)。
305
305
  * - **core 的文法编不出来的串**(名字里带反斜杠、单条 >512 字符、空串):判词取自 core 自己的
306
306
  * `validatePermissionRules`,见 {@link uncompilableRuleGuidance}。[ref]①([ref])补齐 ——
@@ -338,23 +338,19 @@ export interface UnmatchableToolName {
338
338
  * · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):`settings.permissions.deny` / `.ask`
339
339
  * (S-177 / core 7.9.0 起)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` / `Edit(~/.sema/**)`),所以本仓不许再替它拒 ——
340
340
  * 判据整条交回 core 的 `validatePermissionRules`,按这张表自己的 behavior 问。core 7.42.0 起策略只说 deny / ask(allow 表不再是本消费者)。
341
- * · `"allow-layer"` —— 消费者是引擎的 **allow 层**(`TaskSpec.allowedTools`):`settings.permissions.allow`(7.117.0 起)。本仓**只判名字**
342
- * (退役名 / 无前缀短名 / 协议名够不着挂载工具 —— 与另两张桶同一只判、同一句),**不判内容**:条目的文法、匹配、读成空都归引擎
343
- * (读成空的引擎在开跑时通告)。没有 behavior 一问(引擎的 allow 层不经规则编译器)。
344
- * 翻转条件:引擎的 allow 层对非现行名也出通告(core 7.43.0 起在条目编译拿到工具名册时给「不是工具名 / 近似名」这类原因)—— 提到那一版、
345
- * 并把名册交给条目编译之后,本消费者的名字判退役(allow 侧整只交引擎)。
341
+ * (7.117.0 有过第三种 `"allow-layer"` —— 请求的 `settings.permissions.allow` 只判名字。7.118.0 / core 7.43.0 #1358 起引擎的 allow 层自己按名册读条目名并通告
342
+ * (`permission.allowed_tool_not_granted`,`reason: "tool_name"` / `near_miss`)⇒ 本仓的名字判退役,allow 侧整只交引擎,只剩引擎读不到的那一形在
343
+ * `task-settings.ts` 经 `rule-entry-list.ts` 拒。)
346
344
  */
347
- export type RuleEntryConsumer = "tool-name-table" | "rule-policy" | "allow-layer";
345
+ export type RuleEntryConsumer = "tool-name-table" | "rule-policy";
348
346
  /**
349
347
  * {@link unmatchableToolNamesRefusal} 的判据入参:这张表喂给谁(名表 / 规则编译器 / allow 层);前两种按哪种态度问 core 的文法、路径基 ——
350
348
  * allow 层没有文法一问(见 {@link RuleEntryConsumer}),型上就不带这两位。
351
349
  */
352
350
  export type UnmatchableToolNameJudgment = {
353
- readonly consumer: "tool-name-table" | "rule-policy";
351
+ readonly consumer: RuleEntryConsumer;
354
352
  readonly behavior: PermissionRule["behavior"];
355
353
  readonly pathBases?: PathRuleBases;
356
- } | {
357
- readonly consumer: "allow-layer";
358
354
  };
359
355
  /**
360
356
  * 「这张表里有永远不会生效的条目」的**判据与拒因句,同一只函数**(返回 `undefined` = 通过;拒 ⇒ 句子 + 被点名的条目)。
@@ -372,13 +368,13 @@ export declare function unmatchableToolNamesRefusal(source: string, names: reado
372
368
  } | undefined;
373
369
  /**
374
370
  * 运维 allow 表 `APPROVAL_GRANT` 的**拒启判据与拒因句**(同一只函数;`config-invariants.ts` 的 `…Refusal` 形:返回句子 = 拒,`undefined` = 通过)。
375
- * 7.117.0(core 7.42.0)起这张表整份投 `RunnerDeps.allowedTools`(引擎 allow 层的部署成员,构造时读一次),判据改成**引擎的读法**,与请求的两张
371
+ * 7.117.0(core 7.42.0)起这张表整份投 `RunnerDeps.allowedTools`(引擎 allow 层的部署成员,构造时读一次),判据是**引擎的读法**,与请求的两张
376
372
  * allow 表同一份(`rule-entry-list.ts`):
377
373
  * ① 值不是字符串表 ⇒ 拒(env 的 CSV 恒给字符串表;判在这里是为了「值一旦换了来源,第一道判仍在」);
378
- * ② 协议名对端段铸不出 / 缺段 / 对端段写通配 ⇒ 拒(与技能清单、请求 allow 桶同一只函数);
379
- * ③ 引擎读成空的条目(`compileAllowList(names, "allow").inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字)⇒ 拒,
380
- * 句子点名条目、引擎给的原因词、该怎么写。请求的 allow 侧对 ③ 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,
381
- * 而引擎在 Runner 构造期对读成空的部署条目只发一条无会话的通告。
374
+ * ② 引擎读不到的那一形(工具段为空的协议名)⇒ 拒(与技能清单、请求 allow 桶同一只函数);
375
+ * ③ 引擎读成空的条目(`compileAllowList(names, "allow", 名册 = 空集).inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字,以及
376
+ * core 7.43.0 起条目名过引擎判官读成 `tool_name` 的退役名 / 无前缀短名 / 对端段铸不出)⇒ 拒,句子点名条目、引擎给的原因词(名字层带判词与现名)、
377
+ * 该怎么写。请求的 allow 侧对 ③ 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,而引擎对读成空的部署条目只在每一跑发一条通告。
382
378
  * 文法随引擎:工具名、协议名覆盖形、`Bash(...)` 命令规则、`Read(//dir/**)` 读目录都收(内容形里带逗号的写不进 CSV —— 这张表的分隔符就是逗号)。
383
379
  */
384
380
  export declare function approvalGrantRefusal(names: unknown): string | undefined;
package/dist/config.js CHANGED
@@ -18,12 +18,12 @@ import { buildReadFacePostureNotice, readFaceForPosture } from "./read-face-post
18
18
  import { THINKING_LEVELS, THINKING_FORMATS, buildModelCompat, isThinkingFormat, isThinkingTier } from "./model-compat.js";
19
19
  import {} from "./posture-source.js";
20
20
  import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
21
- import { describeUnreachableNamespacedRuleName, ruleEntryToolName, unreachableNamespacedRuleNameOf } from "./namespaced-rule-name.js";
21
+ import { describeUnmatchableRuleName, ruleEntryToolName, unmatchableRuleNameOf } from "./namespaced-rule-name.js";
22
22
  import { bakeOperatorPrincipalsRefusal, bakeRunnerCredentialRefusal, halfOpenDirectDoorRefusal, leaderDurableStoreRefusal, operatorPrincipalsRequiredRefusal } from "./config-invariants.js";
23
23
  import { REMOTE_EXEC_PROVIDER_WORDS, executionLaneOf, executionLaneRequiredRefusal, isSingleMachineDefaultLane, laneRequiredEnv } from "./execution-lane-caps.js";
24
24
  import { MEMORY_ENGINE_BACKENDS, SESSION_BACKENDS, STORE_BACKENDS } from "@sema-agent/settings-schema/execution-lanes";
25
25
  import { AUTONOMY_MODES } from "./runtime-governance.js";
26
- import { allowLayerEntryCountIssue, describeInertRuleEntries, describeRetiredRuleEntry, describeUnreachableRuleEntry, inertRuleEntriesOf, retiredRuleEntryOf, ruleEntryListShapeOk, unreachableRuleEntryOf } from "./rule-entry-list.js";
26
+ import { allowLayerEntryCountIssue, describeEmptyToolSegmentEntry, describeInertRuleEntries, emptyToolSegmentEntryOf, inertRuleEntriesOf, ruleEntryListShapeOk } from "./rule-entry-list.js";
27
27
  import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
28
28
  import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
29
29
  import { modelProviderRefusal } from "./model-provider.js";
@@ -842,13 +842,13 @@ function buildUnusedCoordinatesRefusal(keys, auto) {
842
842
  const engine = keys.length === 1 ? (keys[0] === "PG_HOST" ? "pg" : "mysql") : "mysql|pg";
843
843
  const probedHostKey = auto?.autoProbes === "pg" ? "PG_HOST" : "MYSQL_HOST";
844
844
  const why = auto !== undefined
845
- ? `SESSION_BACKEND=auto probes the ${auto.autoProbes} engine (DB_BACKEND ${process.env.DB_BACKEND ? "names it" : "unset ⇒ mysql"}), whose host key ${probedHostKey} is not set`
845
+ ? `SESSION_BACKEND=auto follows the ${auto.autoProbes} engine (DB_BACKEND ${process.env.DB_BACKEND ? "names it" : "unset ⇒ mysql"}), whose host key ${probedHostKey} is not set`
846
846
  : "no durable session store is selected (neither DB_BACKEND=mysql|pg nor SESSION_BACKEND=mysql|auto), so the store would run IN MEMORY";
847
847
  return (`${keys.join(" and ")} ${keys.length === 1 ? "is" : "are"} set but the current configuration never uses ${keys.length === 1 ? "it" : "them"}: ${why}. ` +
848
848
  `Refusing to boot rather than running in memory silently. ` +
849
849
  `To persist: DB_BACKEND=${engine} (or SESSION_BACKEND=mysql for the MySQL-protocol engine); ` +
850
850
  `to run in memory: remove the coordinates, or set DB_BACKEND=memory; ` +
851
- `for best effort (persist while the store is reachable, memory otherwise): SESSION_BACKEND=auto with the engine pointed right (DB_BACKEND=pg with PG_HOST, DB_BACKEND=mysql with MYSQL_HOST).`);
851
+ `SESSION_BACKEND=auto persists exactly when the engine's coordinates are configured (DB_BACKEND=pg with PG_HOST, DB_BACKEND=mysql with MYSQL_HOST) — and then refuses to boot, rather than falling back to memory, while the store is unreachable.`);
852
852
  }
853
853
  function parseStoreDomain(ctx) {
854
854
  const { requirePrincipal } = ctx;
@@ -1243,29 +1243,21 @@ function findUnmatchableToolNames(names, opts) {
1243
1243
  }
1244
1244
  }
1245
1245
  const subject = opts.consumer === "tool-name-table" ? name : ruleEntryToolName(name);
1246
- const verdict = judgeRuleToolName(subject, NO_DECLARED_TOOL_NAMES);
1247
- if (verdict.kind === "retired") {
1248
- out.push({ name, guidance: verdict.guidance });
1246
+ const verdict = unmatchableRuleNameOf(subject);
1247
+ if (verdict?.kind === "retired") {
1248
+ out.push({ name, guidance: describeUnmatchableRuleName(verdict, subject) });
1249
1249
  continue;
1250
1250
  }
1251
- if (opts.consumer !== "allow-layer") {
1252
- const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
1253
- if (uncompilable !== undefined) {
1254
- out.push({ name, guidance: uncompilable });
1255
- continue;
1256
- }
1257
- }
1258
- if (verdict.kind === "unprefixed_namespace") {
1259
- out.push({ name, guidance: `pre-prefix short form — the live name carries a protocol prefix: ${PROTOCOL_TABLE.map((e) => `${e.prefix}${presentToolName(name)}`).join(" or ")}` });
1251
+ const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
1252
+ if (uncompilable !== undefined) {
1253
+ out.push({ name, guidance: uncompilable });
1260
1254
  continue;
1261
1255
  }
1262
- const unreachable = unreachableNamespacedRuleNameOf(subject);
1263
- if (unreachable !== undefined)
1264
- out.push({ name, guidance: describeUnreachableNamespacedRuleName(unreachable) });
1256
+ if (verdict !== undefined)
1257
+ out.push({ name, guidance: describeUnmatchableRuleName(verdict, subject) });
1265
1258
  }
1266
1259
  return out;
1267
1260
  }
1268
- const NO_DECLARED_TOOL_NAMES = new Set();
1269
1261
  export function unmatchableToolNamesRefusal(source, names, judgment) {
1270
1262
  const bad = findUnmatchableToolNames(names, judgment);
1271
1263
  if (bad.length === 0)
@@ -1282,12 +1274,9 @@ export function approvalGrantRefusal(names) {
1282
1274
  const over = allowLayerEntryCountIssue(APPROVAL_GRANT_ENV, names);
1283
1275
  if (over !== undefined)
1284
1276
  return `${over.message} — refusing to start rather than running with a truncated list`;
1285
- const retired = retiredRuleEntryOf(names);
1286
- if (retired !== undefined)
1287
- return `${describeRetiredRuleEntry(APPROVAL_GRANT_ENV, retired)} — refusing to start rather than running without it`;
1288
- const unreachable = unreachableRuleEntryOf(names);
1289
- if (unreachable !== undefined)
1290
- return `${describeUnreachableRuleEntry(APPROVAL_GRANT_ENV, unreachable)} — refusing to start rather than running without it`;
1277
+ const empty = emptyToolSegmentEntryOf(names);
1278
+ if (empty !== undefined)
1279
+ return `${describeEmptyToolSegmentEntry(APPROVAL_GRANT_ENV, empty)} — refusing to start rather than running without it`;
1291
1280
  const inert = inertRuleEntriesOf(names, "allow");
1292
1281
  if (inert.length > 0)
1293
1282
  return `${describeInertRuleEntries(APPROVAL_GRANT_ENV, inert)} — the engine would grant nothing for ${inert.length === 1 ? "it" : "them"}; refusing to start rather than running with an allow rule that never takes effect`;
@@ -1,5 +1,5 @@
1
1
  import { FileError, StubExecutionEnv, type ExecutionEnv, type Result, type ToolPolicy } from "@sema-agent/core";
2
- import { type DurableAskOptions } from "./approval.js";
2
+ import { type DeploymentListExemption } from "./approval.js";
3
3
  import type { ServiceConfig } from "./config.js";
4
4
  import type { applyRuntimeGovernance } from "./runtime-governance.js";
5
5
  /** `applyRuntimeGovernance` 的 governance 实参 —— 从折叠属主的签名位**推导**,不复制形状
@@ -92,12 +92,10 @@ export interface DurableApprovalSeat {
92
92
  readonly kind: "durable";
93
93
  /** 活体问答面;`undefined` ⇒ 恒 durable park 的原形门。 */
94
94
  readonly question: LiveQuestionFace | undefined;
95
- /** 每会话「本会话不再询问」探针(canonical toolName 键空间)。 */
96
- readonly exempt?: DurableAskOptions["exempt"];
97
- /** 豁免短路时的审计钩子。 */
98
- readonly onExempted?: DurableAskOptions["onExempted"];
95
+ /** 本会话的「本会话放行」读面(部署名单那一问的站着的答案;铸点 `approval.ts` 的 `createDeploymentListExemption`)。缺席 = 这条腿没有会话 / 没有豁免店 ⇒ 名单照问。 */
96
+ readonly exemption?: DeploymentListExemption;
99
97
  }
100
- /** live 席:Runner 的活 onAsk 当场服 ask(无 park、无会话豁免店)。 */
98
+ /** live 席:Runner 的活 onAsk 当场服 ask(无 park、无会话豁免店:run-local 一次性腿)。 */
101
99
  export interface LiveApprovalSeat {
102
100
  readonly kind: "live";
103
101
  }
@@ -268,7 +266,7 @@ export declare function buildApprovalGrantNotice(config: {
268
266
  * `undefined`**:治理层是 tighten-only 的叠加层,没有基线可叠时它自己也产不出「门在场」这件事。
269
267
  *
270
268
  * 三形,按席穷举(S-694,席的定义见 {@link ApprovalSeat}):
271
- * · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto/预算/会话豁免全在
269
+ * · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto/预算/本会话的豁免行全在
272
270
  * `createDurableAskPolicy` 里)。gated `ask` 由 core 变成 durable checkpoint suspend。
273
271
  * · `live` ⇒ **同一只**三张名单审批门,不叠问答门。gated `ask` 由 core 交给 Runner 的活 onAsk 席
274
272
  * (本口**不**做 ask→deny 映射:无人可问时的拒由那只席说,判据与文案都归它)。
@@ -126,9 +126,9 @@ export function buildApprovalGrantNotice(config) {
126
126
  export function createApprovalBaselinePolicy(config, seat) {
127
127
  switch (seat.kind) {
128
128
  case "durable":
129
- return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config, seat));
129
+ return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config, seat.exemption));
130
130
  case "live":
131
- return createApprovalListPolicy(config, {});
131
+ return createApprovalListPolicy(config, undefined);
132
132
  case "absent":
133
133
  if (hasApprovalListIntent(config)) {
134
134
  throw new Error("approval baseline: this leg has no approval seat (nobody can answer an ask), yet an approval list is in force " +
@@ -142,14 +142,13 @@ export function createApprovalBaselinePolicy(config, seat) {
142
142
  }
143
143
  }
144
144
  }
145
- function createApprovalListPolicy(config, memory) {
145
+ function createApprovalListPolicy(config, exemption) {
146
146
  return createDurableAskPolicy({
147
147
  requireApproval: config.approvalRequire,
148
148
  deny: config.approvalDeny,
149
149
  autoBudget: config.approvalAutoBudget,
150
150
  neverAuto: config.approvalNeverAuto,
151
- ...(memory.exempt ? { exempt: memory.exempt } : {}),
152
- ...(memory.onExempted ? { onExempted: memory.onExempted } : {}),
151
+ ...(exemption !== undefined ? { exemption } : {}),
153
152
  });
154
153
  }
155
154
  //# sourceMappingURL=deployment-governance.js.map