@sema-agent/server 7.116.1 → 7.118.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 (131) hide show
  1. package/MIGRATION.md +195 -0
  2. package/USAGE.md +65 -41
  3. package/dist/approval-card.d.ts +82 -18
  4. package/dist/approval-card.js +46 -4
  5. package/dist/approval.d.ts +4 -12
  6. package/dist/approval.js +4 -26
  7. package/dist/boot/budget-tracing.js +2 -3
  8. package/dist/boot/config-center.d.ts +2 -2
  9. package/dist/boot/config-center.js +5 -7
  10. package/dist/boot/parked-revive-gate.d.ts +1 -7
  11. package/dist/boot/parked-revive-gate.js +2 -12
  12. package/dist/boot/resolve-spec.js +18 -16
  13. package/dist/boot/runner-deps.d.ts +5 -3
  14. package/dist/boot/runner-deps.js +6 -4
  15. package/dist/boot/stage-07-capability-layer.js +2 -2
  16. package/dist/brain.d.ts +17 -8
  17. package/dist/brain.js +10 -7
  18. package/dist/capabilities/hands-lane.d.ts +25 -0
  19. package/dist/capabilities/hands-lane.js +1 -1
  20. package/dist/capabilities/mcp-probe.d.ts +2 -2
  21. package/dist/config-catalog.js +3 -2
  22. package/dist/config-center/apply-effective.d.ts +6 -16
  23. package/dist/config-center/apply-effective.js +0 -6
  24. package/dist/config-center/facade.d.ts +1 -1
  25. package/dist/config-center/facade.js +1 -1
  26. package/dist/config-center/restart-signal.d.ts +1 -1
  27. package/dist/config-types.d.ts +12 -16
  28. package/dist/config.d.ts +32 -29
  29. package/dist/config.js +20 -27
  30. package/dist/deployment-governance.d.ts +13 -19
  31. package/dist/deployment-governance.js +7 -14
  32. package/dist/governance-overrides.d.ts +20 -0
  33. package/dist/governance-overrides.js +8 -0
  34. package/dist/host-decision.d.ts +83 -25
  35. package/dist/host-decision.js +31 -1
  36. package/dist/http/admission.js +13 -7
  37. package/dist/http/approval-sweeps.js +16 -13
  38. package/dist/http/checkpoint-ctx.d.ts +83 -24
  39. package/dist/http/checkpoint-ctx.js +68 -8
  40. package/dist/http/mcp-dial-deadline.d.ts +35 -0
  41. package/dist/http/mcp-dial-deadline.js +21 -0
  42. package/dist/http/memory-compliance-failure-reply.d.ts +11 -5
  43. package/dist/http/memory-compliance-failure-reply.js +17 -8
  44. package/dist/http/memory-store-refusal-reply.d.ts +14 -11
  45. package/dist/http/memory-store-refusal-reply.js +16 -5
  46. package/dist/http/resume-legs.js +18 -18
  47. package/dist/http/retired-skill-manifest.d.ts +1 -1
  48. package/dist/http/route-ctx.d.ts +3 -3
  49. package/dist/http/routes/approvals-assistant.js +21 -24
  50. package/dist/http/routes/capabilities.js +5 -3
  51. package/dist/http/routes/mcp-reconnect.d.ts +17 -0
  52. package/dist/http/routes/mcp-reconnect.js +6 -1
  53. package/dist/http/routes/memory-bundle.js +1 -1
  54. package/dist/http/routes/memory-compliance.js +1 -1
  55. package/dist/http/routes/memory-origin.js +1 -1
  56. package/dist/http/routes/runs.js +53 -33
  57. package/dist/http/routes/sessions.d.ts +1 -1
  58. package/dist/http/routes/sessions.js +3 -6
  59. package/dist/http/routes/workflows.js +2 -0
  60. package/dist/http/wire-types.d.ts +6 -1
  61. package/dist/memory-store-refusal.d.ts +42 -17
  62. package/dist/memory-store-refusal.js +25 -6
  63. package/dist/model-select.d.ts +16 -5
  64. package/dist/model-select.js +7 -3
  65. package/dist/model-unreachable-notice.d.ts +1 -1
  66. package/dist/namespaced-rule-name.d.ts +45 -39
  67. package/dist/namespaced-rule-name.js +36 -22
  68. package/dist/observability/corrupt-read-seat.d.ts +4 -0
  69. package/dist/observability/corrupt-read-seat.js +1 -0
  70. package/dist/observability/fail-open.d.ts +15 -3
  71. package/dist/observability/fail-open.js +15 -3
  72. package/dist/observability/permission-rule-events.d.ts +3 -0
  73. package/dist/observability/permission-rule-events.js +4 -0
  74. package/dist/observability/run-terminal.js +21 -1
  75. package/dist/operator-ask.d.ts +4 -1
  76. package/dist/orchestration/workflow-agent-session-index.d.ts +82 -59
  77. package/dist/orchestration/workflow-agent-session-index.js +80 -14
  78. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -0
  79. package/dist/orchestration/workflow-completion-inbox.js +39 -17
  80. package/dist/orchestration/workflow-notify-journal.d.ts +1 -1
  81. package/dist/orchestration/workflow-notify-journal.js +7 -2
  82. package/dist/outbound-dispatcher.d.ts +47 -20
  83. package/dist/outbound-dispatcher.js +198 -23
  84. package/dist/parked-decide.d.ts +43 -48
  85. package/dist/parked-decide.js +54 -16
  86. package/dist/permission-rule-list.d.ts +20 -31
  87. package/dist/permission-rule-vocab.d.ts +4 -3
  88. package/dist/permission-rule-vocab.js +1 -1
  89. package/dist/plugins/approval-ask-store-sql.d.ts +17 -20
  90. package/dist/plugins/approval-ask-store-sql.js +0 -3
  91. package/dist/plugins/one-time-migrations.d.ts +25 -0
  92. package/dist/plugins/one-time-migrations.js +192 -6
  93. package/dist/plugins/pg-pool.js +1 -1
  94. package/dist/plugins/tidb-pool.js +1 -1
  95. package/dist/plugins/web-search.d.ts +20 -1
  96. package/dist/plugins/web-search.js +39 -10
  97. package/dist/plugins/workflow-run-store-sql.d.ts +7 -6
  98. package/dist/plugins/workflow-run-store-sql.js +5 -7
  99. package/dist/request-key-closure.js +1 -1
  100. package/dist/request-value-closure.d.ts +1 -1
  101. package/dist/router/route-orchestration.d.ts +2 -23
  102. package/dist/rule-entry-list.d.ts +88 -0
  103. package/dist/rule-entry-list.js +102 -0
  104. package/dist/rules-consent.js +3 -0
  105. package/dist/run-local.d.ts +4 -2
  106. package/dist/run-local.js +4 -3
  107. package/dist/runs.d.ts +1 -1
  108. package/dist/runs.js +1 -0
  109. package/dist/runtime-governance.d.ts +5 -21
  110. package/dist/runtime-governance.js +4 -8
  111. package/dist/session-policy-wire.d.ts +13 -12
  112. package/dist/session-policy-wire.js +11 -6
  113. package/dist/spec-fields.d.ts +13 -24
  114. package/dist/spec-fields.js +13 -17
  115. package/dist/standing-answer-grant.d.ts +14 -2
  116. package/dist/standing-answer-grant.js +8 -1
  117. package/dist/task-settings.d.ts +18 -18
  118. package/dist/task-settings.js +63 -32
  119. package/dist/tool-approval.d.ts +77 -145
  120. package/dist/tool-approval.js +80 -62
  121. package/dist/trace/core-keyset-guard.d.ts +6 -6
  122. package/dist/trace/engine-notice-wire.d.ts +4 -6
  123. package/dist/trace/engine-notice-wire.js +14 -1
  124. package/dist/trace/self-minted-wire.d.ts +16 -2
  125. package/dist/trace/self-minted-wire.js +7 -0
  126. package/dist/user-input-ledger.d.ts +24 -12
  127. package/dist/user-input-ledger.js +33 -14
  128. package/package.json +2 -2
  129. package/scripts/drain-check-local.mjs +103 -7
  130. package/scripts/drain-check.mysql.sql +59 -8
  131. package/scripts/drain-check.pg.sql +66 -10
package/dist/config.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type Model, type PathRuleBases, type RuleBehavior } from "@sema-agent/core";
1
+ import { type Model, type PathRuleBases, type PermissionRule } from "@sema-agent/core";
2
2
  import { type Autonomy } from "./runtime-governance.js";
3
3
  import { type RunnerEnvNameAllowlistKnob, type ServerEnvNameAllowlistKnob } from "./env-name-allowlist-knobs.js";
4
4
  import type { ServiceConfig, ServiceConfigFlat, ServiceConfigGroups } from "./config-types.js";
@@ -285,8 +285,9 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
285
285
  * ① env 腿(`APPROVAL_REQUIRE`/`DENY`/`NEVER_AUTO`,下方)—— boot 拒启;
286
286
  * ② 中心下发腿(`runtime.approvalRequire`,config-center/apply-effective.ts)—— 域级拒绝+保前值+warn
287
287
  * (常驻进程不 crash);
288
- * ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts;S-654 (c) 起 `allow` 同判)—— 该请求 422;
289
- * ④ env 腿 `APPROVAL_GRANT`(7.115.0,运维的 allow 规则,与 ③ 的 allow 桶同一个消费者 `rule-policy`)—— boot 拒启({@link approvalGrantRefusal})。
288
+ * ③ per-request 腿(`settings.permissions.{ask,deny}`,task-settings.ts)—— 该请求 422。
289
+ * (两张 allow 表 —— 请求的 `settings.permissions.allow` 与运维的 `APPROVAL_GRANT` —— 自 7.117.0 / core 7.42.0 起不经这里:它们整份交给引擎的
290
+ * allow 层,本仓只判形与协议名对端段,判据在 `rule-entry-list.ts`,与技能两只清单同一份。)
290
291
  *
291
292
  * 五种拼法:
292
293
  * - CC 形规则条目(`Bash(ps:*)`/`Edit(src/**)`,[ref] [ref]§七 装机实测):CC 的权限规则 DSL 写法。这些
@@ -296,10 +297,10 @@ export declare function isRosterGateArmed(configCenter: ServiceConfig["configCen
296
297
  * - 退役名(`bash`/`Task`/`KillShell`…)与 pre-prefix 短名(`figma__x`):S-1047(core 7.41.0 #1156)起判据是 core 导出的
297
298
  * **那一只** `judgeRuleToolName`(准备阶段、两只会话规则店、本仓会话规则写口同调),本函数零手写臂;指引 = core 表给的现役指引 /
298
299
  * 补全协议前缀。
299
- * - 够不着挂载工具的协议名:缺段(`mcp__`、`mcp__srv__`、`mcp____x`)与**对端段铸不出**(`mcp__github.com`、`mcp__github.com__*`、
300
- * 超长 / 归一后带连串下划线的服务器段;服务器段带通配的 `mcp__*` 单列判词 `wildcard_peer`)—— 前缀对,但引擎铸注册名时归一对端段,而规则对对端段逐字比,永不匹配。
301
- * 判官与句子是 `namespaced-rule-name.ts` 的那**一只**(判据 = core `mintNamespacePrefix` 的自铸对照;会话规则写口两腿与 SQL 店写口同调),
302
- * 本文件零手写臂。修前(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 店写口同调)。
303
304
  * ⚠️ 两段覆盖形 `mcp__<server>`(服务器段是自铸形)够得着(S-1047 收掉的分叉,见下)。
304
305
  * - **core 的文法编不出来的串**(名字里带反斜杠、单条 >512 字符、空串):判词取自 core 自己的
305
306
  * `validatePermissionRules`,见 {@link uncompilableRuleGuidance}。[ref]①([ref])补齐 ——
@@ -328,28 +329,29 @@ export interface UnmatchableToolName {
328
329
  guidance: string;
329
330
  }
330
331
  /**
331
- * S-177(core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。两个消费者形态:
332
+ * S-177(core 7.9.0 [ref])—— **这条名单条目喂给谁**,决定「永不匹配」这句话怎么判。三个消费者形态:
332
333
  *
333
334
  * · `"tool-name-table"` —— 消费者按**整串工具名**比对(精确 ∪ covering):env `APPROVAL_*` 三键与
334
335
  * 中心下发 `runtime.approvalRequire`(消费者 `createDurableAskPolicy`)。带括号的内容形
335
336
  * 在这里永远匹配不上任何活工具名 —— 与引擎的规则文法认不认得它**无关**,是这条腿的比对方式决定的,
336
337
  * 所以内容形照旧响亮拒 + 指路。
337
- * · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):
338
- * `settings.permissions.deny` / `.ask`(S-177 / core 7.9.0 起)与 `.allow`(S-654 (c) / 7.115.0 起;修前是
339
- * `createAllowDenyPolicy` 的名单位)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` /
340
- * `Edit(~/.sema/**)`),所以本仓不许再替它拒 —— 判据整条交回 core 的 `validatePermissionRules`,按这张表
341
- * 自己的 behavior 问(allow 侧 core 只收工具名形,内容形由 core 判拒)。
342
- *
343
- * S-654 (c) 前这里还有一条「`allow` 豁免名字形与文法两族」的不对称,前提是 allow 为白名单(匹配不上 ⇒ 全拒,
344
- * fail-closed 且当场可见);allow 改成规则后那条前提不在了(匹配不上 ⇒ 什么也不放行、照常问,写的人收不到信号),豁免随之删除。
338
+ * · `"rule-policy"` —— 消费者是 core 的**规则编译器**(`createPermissionRulePolicy`):`settings.permissions.deny` / `.ask`
339
+ * (S-177 / core 7.9.0 起)。引擎**真的按内容匹配** deny / ask 的内容形(`Bash(rm -r:*)` / `Edit(~/.sema/**)`),所以本仓不许再替它拒 ——
340
+ * 判据整条交回 core 的 `validatePermissionRules`,按这张表自己的 behavior 问。core 7.42.0 起策略只说 deny / ask(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` 拒。)
345
344
  */
346
345
  export type RuleEntryConsumer = "tool-name-table" | "rule-policy";
347
- /** {@link unmatchableToolNamesRefusal} 的判据入参:这张表喂给谁(名表 / 规则编译器)、按哪种态度问 core 的文法、路径基。 */
348
- export interface UnmatchableToolNameJudgment {
346
+ /**
347
+ * {@link unmatchableToolNamesRefusal} 的判据入参:这张表喂给谁(名表 / 规则编译器 / allow 层);前两种按哪种态度问 core 的文法、路径基 ——
348
+ * allow 层没有文法一问(见 {@link RuleEntryConsumer}),型上就不带这两位。
349
+ */
350
+ export type UnmatchableToolNameJudgment = {
349
351
  readonly consumer: RuleEntryConsumer;
350
- readonly behavior: RuleBehavior;
352
+ readonly behavior: PermissionRule["behavior"];
351
353
  readonly pathBases?: PathRuleBases;
352
- }
354
+ };
353
355
  /**
354
356
  * 「这张表里有永远不会生效的条目」的**判据与拒因句,同一只函数**(返回 `undefined` = 通过;拒 ⇒ 句子 + 被点名的条目)。
355
357
  * 五条腿全调它:env `APPROVAL_REQUIRE` / `APPROVAL_DENY` / `APPROVAL_NEVER_AUTO`(拒启)、中心下发 `governance.approvalRequire`(整组拒)、
@@ -365,16 +367,17 @@ export declare function unmatchableToolNamesRefusal(source: string, names: reado
365
367
  readonly names: readonly string[];
366
368
  } | undefined;
367
369
  /**
368
- * 7.115.0 —— 运维 allow 规则 `APPROVAL_GRANT` 的**拒启判据与拒因句**(同一只函数;`config-invariants.ts` 的 `…Refusal` 形:
369
- * 返回句子 = 拒,`undefined` = 通过)。判据与请求腿的 `settings.permissions.allow` 是**同一只**:
370
- * ① 名字形与 core 文法 —— {@link unmatchableToolNamesRefusal},消费者 `rule-policy`、behavior `allow`(退役名 / 无前缀短名 / 缺段或对端段铸不出的协议名 /
371
- * 反斜杠 / 超长 / 内容形与参数级的 allow;句子点名哪一条、为什么、怎么改);
372
- * ② 整表试编一次 —— 编的就是治理折叠里那一只(`runtime-governance.ts` {@link compileApprovalGrantLayers}),于是 core 的整表帽这类
373
- * 只在编译时才抛的判据也在启动时响亮,而不是每个任务一次 500。
374
- * 没有 `pathBases`:运维的规则不挂在任何一条任务的根上,而 allow 文法本来只收工具名形(路径形的 allow core 拒编,在 ① 里就拒了)。
375
- * 坏值**不许**静默丢掉那一条后照常起:运维以为放行了,无人值守腿照旧拒 / 停驻,没有任何信号指回这里。
370
+ * 运维 allow 表 `APPROVAL_GRANT` 的**拒启判据与拒因句**(同一只函数;`config-invariants.ts` 的 `…Refusal` 形:返回句子 = 拒,`undefined` = 通过)。
371
+ * 7.117.0(core 7.42.0)起这张表整份投 `RunnerDeps.allowedTools`(引擎 allow 层的部署成员,构造时读一次),判据是**引擎的读法**,与请求的两张
372
+ * allow 表同一份(`rule-entry-list.ts`):
373
+ * ① 值不是字符串表 ⇒ 拒(env 的 CSV 恒给字符串表;判在这里是为了「值一旦换了来源,第一道判仍在」);
374
+ * ② 引擎读不到的那一形(工具段为空的协议名)⇒ 拒(与技能清单、请求 allow 桶同一只函数);
375
+ * ③ 引擎读成空的条目(`compileAllowList(names, "allow", 名册 = 空集).inert` 非空 —— `*`、`mcp__*`、写路径 / 域名形、带空白或畸形的名字,以及
376
+ * core 7.43.0 起条目名过引擎判官读成 `tool_name` 的退役名 / 无前缀短名 / 对端段铸不出)⇒ 拒,句子点名条目、引擎给的原因词(名字层带判词与现名)、
377
+ * 该怎么写。请求的 allow 侧对 ③ 只收通告;运维这一侧拒启:这是运维自己的配置,写错了当场改,而引擎对读成空的部署条目只在每一跑发一条通告。
378
+ * 文法随引擎:工具名、协议名覆盖形、`Bash(...)` 命令规则、`Read(//dir/**)` 读目录都收(内容形里带逗号的写不进 CSV —— 这张表的分隔符就是逗号)。
376
379
  */
377
- export declare function approvalGrantRefusal(names: readonly string[]): string | undefined;
380
+ export declare function approvalGrantRefusal(names: unknown): string | undefined;
378
381
  /**
379
382
  * [ref] 件B/C/D —— 三条**部署治理声明**的 env 解析(auth 域的一段,fail-loud 全在这里)。
380
383
  *
package/dist/config.js CHANGED
@@ -18,11 +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
- import { APPROVAL_GRANT_SOURCE, AUTONOMY_MODES, compileApprovalGrantLayers } from "./runtime-governance.js";
25
+ import { AUTONOMY_MODES } from "./runtime-governance.js";
26
+ import { allowLayerEntryCountIssue, describeEmptyToolSegmentEntry, describeInertRuleEntries, emptyToolSegmentEntryOf, inertRuleEntriesOf, ruleEntryListShapeOk } from "./rule-entry-list.js";
26
27
  import { createCenterCredentialSource, readCenterCredentialDeclaration } from "./center-credential.js";
27
28
  import { parseCenterCredentialRenewLeadMs } from "./center-credential-renewer.js";
28
29
  import { modelProviderRefusal } from "./model-provider.js";
@@ -1227,11 +1228,6 @@ function uncompilableRuleGuidance(name, behavior, pathBases) {
1227
1228
  const issue = issues[0];
1228
1229
  if (issue === undefined)
1229
1230
  return undefined;
1230
- if (behavior === "allow" && (issue.code === "unsupported.bash_prefix" || issue.code === "unsupported.file_glob" || issue.code === "invalid.allow_param_rule")) {
1231
- return (`${issue.code} — ${issue.message}. An allow rule list compiles tool-name rules only (an exact name, or mcp__<server>__* / ` +
1232
- `a2a__<peer>__* for every tool on that peer); a content-form standing approval lives in the persisted-rule store ` +
1233
- `(/v1/rules/cc-import, or keep the rule when you answer the call's card)`);
1234
- }
1235
1231
  return (`${issue.code} — core's rule grammar cannot compile this name (${issue.message}), so a table keyed by it matches nothing. ` +
1236
1232
  `Tool names carry no backslashes and are far shorter than the 512-character rule cap; check the spelling, and use ` +
1237
1233
  `mcp__<server>__* / a2a__<peer>__* for "every tool on that peer"`);
@@ -1247,9 +1243,9 @@ function findUnmatchableToolNames(names, opts) {
1247
1243
  }
1248
1244
  }
1249
1245
  const subject = opts.consumer === "tool-name-table" ? name : ruleEntryToolName(name);
1250
- const verdict = judgeRuleToolName(subject, NO_DECLARED_TOOL_NAMES);
1251
- if (verdict.kind === "retired") {
1252
- out.push({ name, guidance: verdict.guidance });
1246
+ const verdict = unmatchableRuleNameOf(subject);
1247
+ if (verdict?.kind === "retired") {
1248
+ out.push({ name, guidance: describeUnmatchableRuleName(verdict, subject) });
1253
1249
  continue;
1254
1250
  }
1255
1251
  const uncompilable = uncompilableRuleGuidance(name, opts.behavior, opts.pathBases);
@@ -1257,17 +1253,11 @@ function findUnmatchableToolNames(names, opts) {
1257
1253
  out.push({ name, guidance: uncompilable });
1258
1254
  continue;
1259
1255
  }
1260
- if (verdict.kind === "unprefixed_namespace") {
1261
- 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 ")}` });
1262
- continue;
1263
- }
1264
- const unreachable = unreachableNamespacedRuleNameOf(subject);
1265
- if (unreachable !== undefined)
1266
- out.push({ name, guidance: describeUnreachableNamespacedRuleName(unreachable) });
1256
+ if (verdict !== undefined)
1257
+ out.push({ name, guidance: describeUnmatchableRuleName(verdict, subject) });
1267
1258
  }
1268
1259
  return out;
1269
1260
  }
1270
- const NO_DECLARED_TOOL_NAMES = new Set();
1271
1261
  export function unmatchableToolNamesRefusal(source, names, judgment) {
1272
1262
  const bad = findUnmatchableToolNames(names, judgment);
1273
1263
  if (bad.length === 0)
@@ -1277,16 +1267,19 @@ export function unmatchableToolNamesRefusal(source, names, judgment) {
1277
1267
  bad.map((b) => `${presentToolName(b.name)} → ${b.guidance}`).join("; ")).replace(CONTROL_AND_BIDI_STRIP_RE, "");
1278
1268
  return { message, names: bad.map((b) => b.name) };
1279
1269
  }
1270
+ const APPROVAL_GRANT_ENV = "APPROVAL_GRANT";
1280
1271
  export function approvalGrantRefusal(names) {
1281
- const unmatchable = unmatchableToolNamesRefusal(APPROVAL_GRANT_SOURCE, names, { consumer: "rule-policy", behavior: "allow" });
1282
- if (unmatchable !== undefined)
1283
- return unmatchable.message;
1284
- try {
1285
- compileApprovalGrantLayers(names);
1286
- }
1287
- catch (e) {
1288
- return e instanceof Error ? e.message : String(e);
1289
- }
1272
+ if (!ruleEntryListShapeOk(names))
1273
+ return `${APPROVAL_GRANT_ENV} must be a list of permission-rule strings — refusing to start rather than running without it`;
1274
+ const over = allowLayerEntryCountIssue(APPROVAL_GRANT_ENV, names);
1275
+ if (over !== undefined)
1276
+ return `${over.message} — refusing to start rather than running with a truncated list`;
1277
+ const empty = emptyToolSegmentEntryOf(names);
1278
+ if (empty !== undefined)
1279
+ return `${describeEmptyToolSegmentEntry(APPROVAL_GRANT_ENV, empty)} — refusing to start rather than running without it`;
1280
+ const inert = inertRuleEntriesOf(names, "allow");
1281
+ if (inert.length > 0)
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`;
1290
1283
  return undefined;
1291
1284
  }
1292
1285
  function streamApprovalConfig(gates) {
@@ -1,5 +1,4 @@
1
1
  import { FileError, StubExecutionEnv, type ExecutionEnv, type Result, type ToolPolicy } from "@sema-agent/core";
2
- import { type DurableAskOptions } from "./approval.js";
3
2
  import type { ServiceConfig } from "./config.js";
4
3
  import type { applyRuntimeGovernance } from "./runtime-governance.js";
5
4
  /** `applyRuntimeGovernance` 的 governance 实参 —— 从折叠属主的签名位**推导**,不复制形状
@@ -11,9 +10,6 @@ export interface DeploymentGovernanceConfigView {
11
10
  readonly commandPolicy?: ServiceConfig["commandPolicy"];
12
11
  readonly manualModeShellGate?: ServiceConfig["manualModeShellGate"];
13
12
  readonly sensitiveWritePatterns: ServiceConfig["sensitiveWritePatterns"];
14
- /** 7.115.0:运维的 allow 规则(`APPROVAL_GRANT`)—— 原串交给折叠属主编成叶(`runtime-governance.ts` `compileApprovalGrantLayers`)。
15
- * 必填:三条消费腿的切面都要点名它,漏了是编译错,不是一条腿上静默没有这只旋钮。 */
16
- readonly approvalGrant: ServiceConfig["approvalGrant"];
17
13
  /**
18
14
  * 本部署的**本地店根**(`LOCAL_DATA_ROOT → CONFIG_LOCAL_DIR → AGENT_DATA_DIR → ~/.ai-agent`)—— 在本口只喂
19
15
  * **誊本门**(`<localDataRoot>/sessions`)。S-133 起它**不再**当守卫集的 `dataRoot`(那是引擎记忆根,见
@@ -95,12 +91,8 @@ export interface DurableApprovalSeat {
95
91
  readonly kind: "durable";
96
92
  /** 活体问答面;`undefined` ⇒ 恒 durable park 的原形门。 */
97
93
  readonly question: LiveQuestionFace | undefined;
98
- /** 每会话「本会话不再询问」探针(canonical toolName 键空间)。 */
99
- readonly exempt?: DurableAskOptions["exempt"];
100
- /** 豁免短路时的审计钩子。 */
101
- readonly onExempted?: DurableAskOptions["onExempted"];
102
94
  }
103
- /** live 席:Runner 的活 onAsk 当场服 ask(无 park、无会话豁免店)。 */
95
+ /** live 席:Runner 的活 onAsk 当场服 ask(无 park)。 */
104
96
  export interface LiveApprovalSeat {
105
97
  readonly kind: "live";
106
98
  }
@@ -233,17 +225,16 @@ export interface GateSeatView {
233
225
  * 此前是「这个部署一个门都没接」这个 misconfig 的**唯一**提示,而守卫集只挡那二十来个路径段、其余写
234
226
  * 目标照旧无裁决 —— 信号不能因为我们铺了基线就静默消失,所以由我们自己按同一判据说一次。
235
227
  *
236
- * 判据(与信号消失的条件逐字互补):**非门的部署叶在场**(守卫集,或 7.115.0 起运维的 allow 规则 `APPROVAL_GRANT` —— 两者都让
237
- * `spec.toolPolicy` 在场、又都不是门:一个只拒守卫段,一个只表态 allow)∧ 这条腿的审批基线**没有席**(`absent`)∧ 单用户
238
- * auto-accept 基线不适用。事件名 `tool_policy_only_sensitive_baseline` 是allow 规则之前定的,保留不改(运维的日志告警按它匹配),
239
- * 哪一片叶在场写在 `detail` 与 `guardedPatterns` / `grantRules` 两个计数里。三个量都是部署常量 ⇒ 消费腿在 boot 期说一次,不是每任务一次。
228
+ * 判据(与信号消失的条件逐字互补):**守卫集在场**(它让 `spec.toolPolicy` 在场、又不是门:只拒守卫段)∧ 这条腿的审批基线**没有席**
229
+ * (`absent`)∧ 单用户 auto-accept 基线不适用。三个量都是部署常量 ⇒ 消费腿在 boot 期说一次,不是每任务一次。
230
+ * (7.115.0–7.116.x 运维的 allow 表 `APPROVAL_GRANT` 也是这里的一片非门叶;7.117.0 起它住在 Runner 的部署席、不再是策略,随之出列。)
240
231
  * S-694:席读的是喂审批基线的**同一只**({@link ApprovalSeat})—— `durable` / `live` 两形都有人服 ask、名单
241
232
  * 进策略,「守卫集是唯一的策略」这句话在它们上面不成立。今天真发的只剩 HTTP 腿「durable 关 ∧ 多租户」那一形。
242
233
  *
243
234
  * ⚠️ 单一属主([ref] 件三):HTTP 腿与 run-local 腿共用本判据与文案。两处各写一份 = 一处改了另一处
244
235
  * 没改,而两份都长得像对的 —— 那正是本文件存在的理由。
245
236
  */
246
- export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns" | "approvalGrant">, seat: GateSeatView): OperatorWarning | undefined;
237
+ export declare function buildOnlySensitiveBaselineWarning(config: Pick<DeploymentGovernanceConfigView, "sensitiveWritePatterns">, seat: GateSeatView): OperatorWarning | undefined;
247
238
  /**
248
239
  * `applyRuntimeGovernance` 的 governance 实参预铸([ref] 件一)。
249
240
  *
@@ -259,17 +250,20 @@ export declare function buildOnlySensitiveBaselineWarning(config: Pick<Deploymen
259
250
  */
260
251
  export declare function createDeploymentGovernanceInputs(config: DeploymentGovernanceConfigView, pathAdjudication: PathAdjudication, roots: DeploymentGovernanceRoots): DeploymentGovernanceInputs;
261
252
  /**
262
- * 7.115.0 —— 运维 allow 规则的**起服通告**载荷(纯数据 ⇒ `build*`;HTTP 腿与 run-local 腿各在装配时说一次,用 info 级)。
263
- * 规则名不是秘密(它们是工具名),所以原样列出;空表 ⇒ `undefined`(不打)。运维只读面上同一个事实另有一处:
264
- * `GET /v1/diagnostics/wiring` 的 `serverGates.approvalGrant`。
253
+ * 运维 allow 表 `APPROVAL_GRANT` 的**起服通告**载荷(纯数据 ⇒ `build*`;HTTP 腿与 run-local 腿各在装配时说一次,用 info 级)。规则不是秘密
254
+ * (它们是工具名 / 命令规则),所以原样列出;空表 ⇒ `undefined`(不打)。运维只读面上同一个事实另有一处:`GET /v1/diagnostics/wiring` 的
255
+ * `serverGates.approvalGrant`。`note` 是这只旋钮**本版的效果**的真话(7.117.0 起语义变宽,三问在 CHANGELOG / MIGRATION):清什么、永远清不掉什么。
256
+ * 不在本口读席位:表经部署姿态座席族(`boot/runner-deps.ts` `buildDeploymentPostureSeats`)交给每只 Runner,本口只说它在场。
265
257
  */
266
- export declare function buildApprovalGrantNotice(config: Pick<DeploymentGovernanceConfigView, "approvalGrant">): OperatorWarning | undefined;
258
+ export declare function buildApprovalGrantNotice(config: {
259
+ readonly approvalGrant: ServiceConfig["approvalGrant"];
260
+ }): OperatorWarning | undefined;
267
261
  /**
268
262
  * 审批基线 —— `applyRuntimeGovernance` 那个 base 的 `toolPolicy` 座([ref] 件一)。**恒非
269
263
  * `undefined`**:治理层是 tighten-only 的叠加层,没有基线可叠时它自己也产不出「门在场」这件事。
270
264
  *
271
265
  * 三形,按席穷举(S-694,席的定义见 {@link ApprovalSeat}):
272
- * · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto/预算/会话豁免全在
266
+ * · `durable` ⇒ AskUserQuestion 判决门 + 三张名单的审批门(deny/neverAuto/预算全在
273
267
  * `createDurableAskPolicy` 里)。gated `ask` 由 core 变成 durable checkpoint suspend。
274
268
  * · `live` ⇒ **同一只**三张名单审批门,不叠问答门。gated `ask` 由 core 交给 Runner 的活 onAsk 席
275
269
  * (本口**不**做 ask→deny 映射:无人可问时的拒由那只席说,判据与文案都归它)。
@@ -61,7 +61,7 @@ export function assertGuardPatternsUsable(config) {
61
61
  }
62
62
  }
63
63
  export function buildOnlySensitiveBaselineWarning(config, seat) {
64
- if ((config.sensitiveWritePatterns.length === 0 && config.approvalGrant.length === 0) || seat.singleUserAutoAcceptBaseline)
64
+ if (config.sensitiveWritePatterns.length === 0 || seat.singleUserAutoAcceptBaseline)
65
65
  return undefined;
66
66
  switch (seat.approvalSeat) {
67
67
  case "durable":
@@ -74,17 +74,12 @@ export function buildOnlySensitiveBaselineWarning(config, seat) {
74
74
  throw new Error(`approval seat "${String(unhandled)}" has no arm in the only-sensitive-baseline warning`);
75
75
  }
76
76
  }
77
- const nonGateLayers = [
78
- ...(config.sensitiveWritePatterns.length > 0 ? ["the sensitive-path DENY baseline (SENSITIVE_WRITE_PATTERNS)"] : []),
79
- ...(config.approvalGrant.length > 0 ? ["the APPROVAL_GRANT allow rules"] : []),
80
- ];
81
77
  return {
82
78
  event: "tool_policy_only_sensitive_baseline",
83
79
  fields: {
84
- detail: `${nonGateLayers.join(" and ")} ${nonGateLayers.length > 1 ? "are" : "is"} this deployment's ONLY tool policy and not a gate — writes outside the guarded segments are unadjudicated, and core's own UNGATED warning no longer fires because a policy is now always present`,
80
+ detail: "the sensitive-path DENY baseline (SENSITIVE_WRITE_PATTERNS) is this deployment's ONLY tool policy and not a gate — writes outside the guarded segments are unadjudicated, and core's own UNGATED warning no longer fires because a policy is now always present",
85
81
  remedy: "wire an approval face (DURABLE_APPROVAL / APPROVAL_REQUIRE with a store) or accept the single-user auto-accept baseline",
86
82
  guardedPatterns: config.sensitiveWritePatterns.length,
87
- grantRules: config.approvalGrant.length,
88
83
  },
89
84
  };
90
85
  }
@@ -113,7 +108,6 @@ export function createDeploymentGovernanceInputs(config, pathAdjudication, roots
113
108
  commandPolicy: config.commandPolicy,
114
109
  ...(config.manualModeShellGate ? { manualModeShellGate: config.manualModeShellGate } : {}),
115
110
  ...(sensitivePathLayers ? { sensitivePathLayers } : {}),
116
- ...(config.approvalGrant.length > 0 ? { approvalGrant: config.approvalGrant } : {}),
117
111
  };
118
112
  }
119
113
  export function buildApprovalGrantNotice(config) {
@@ -124,16 +118,17 @@ export function buildApprovalGrantNotice(config) {
124
118
  fields: {
125
119
  rules: config.approvalGrant.length,
126
120
  names: [...config.approvalGrant],
127
- note: "APPROVAL_GRANT states the allow for the tools these rules name on every leg it is installed on (HTTP submit, durable resume, cross-replica revival of parked background agents, run-local) — those calls are no longer asked for want of a statement; any layer's own deny or ask (approval lists, request settings, session rules, the engine's mode gates) still wins",
121
+ note: "APPROVAL_GRANT is the deployment's member of the engine's allow layer (RunnerDeps.allowedTools on every Runner this process builds): a call these rules cover is no longer asked for an ordinary question (a file write, a shell command, a server tool nobody stated) — built-in tools included. " +
122
+ "It never clears a deny from any layer, an APPROVAL_REQUIRE / APPROVAL_NEVER_AUTO / request ask rule, the MANUAL_MODE_SHELL_GATE=always shell floor, a write-protected path, a sensitive-path deny, governance, a PreToolUse hook's ask, or the plan mode's question; a rule that matches and cannot clear is disclosed on the card (allowRuleShadowed member \"deployment\")",
128
123
  },
129
124
  };
130
125
  }
131
126
  export function createApprovalBaselinePolicy(config, seat) {
132
127
  switch (seat.kind) {
133
128
  case "durable":
134
- return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config, seat));
129
+ return combinePolicies(createDurableQuestionGate(seat.question), createApprovalListPolicy(config));
135
130
  case "live":
136
- return createApprovalListPolicy(config, {});
131
+ return createApprovalListPolicy(config);
137
132
  case "absent":
138
133
  if (hasApprovalListIntent(config)) {
139
134
  throw new Error("approval baseline: this leg has no approval seat (nobody can answer an ask), yet an approval list is in force " +
@@ -147,14 +142,12 @@ export function createApprovalBaselinePolicy(config, seat) {
147
142
  }
148
143
  }
149
144
  }
150
- function createApprovalListPolicy(config, memory) {
145
+ function createApprovalListPolicy(config) {
151
146
  return createDurableAskPolicy({
152
147
  requireApproval: config.approvalRequire,
153
148
  deny: config.approvalDeny,
154
149
  autoBudget: config.approvalAutoBudget,
155
150
  neverAuto: config.approvalNeverAuto,
156
- ...(memory.exempt ? { exempt: memory.exempt } : {}),
157
- ...(memory.onExempted ? { onExempted: memory.onExempted } : {}),
158
151
  });
159
152
  }
160
153
  //# sourceMappingURL=deployment-governance.js.map
@@ -0,0 +1,20 @@
1
+ /**
2
+ * **收紧折叠的入参型与唯一折叠口** —— 本仓三处收紧(治理层 `applyRuntimeGovernance`、请求 settings 的 `applyTaskSettings`、SUP 姿态)都经
3
+ * {@link tightenGoverned} 调 core 的 `tightenTaskSpec`,入参型是 {@link GovernanceOverrides}。
4
+ *
5
+ * 🔴 为什么入参型上**没有** `allowedTools`:core 的 `tightenTaskSpec(base, overrides)` 对 `allowedTools` 不做「收紧」,而是**用覆盖值替换**
6
+ * base 上的表(本仓探针亲证)。请求的 allow 表有且只有一个写点(`task-settings.ts` 的 `applyTaskSettings`,在折叠之外);一只往收紧入参里
7
+ * 塞 `allowedTools` 的调用点会静默改写请求的表。型上禁掉它(`allowedTools?: never`),写出来就是编译红 —— 比文本扫描的静态门(方括号写法、
8
+ * 展开形会漏)更硬。
9
+ *
10
+ * 🔴 型禁有一个洞([ref] 合并复审 opus 腿):本仓没开 `exactOptionalPropertyTypes`,`{ allowedTools: undefined }` 过型检,而 core 的替换会把 base 上的表
11
+ * 抹成 `undefined`。所以折叠口再加一道**运行期**守卫:入参对象自带 `allowedTools` 键(任何值,含 `undefined`)⇒ 抛(fail-closed:宁多问一次)。
12
+ */
13
+ import { type TaskSpec } from "@sema-agent/core";
14
+ /** 收紧折叠的入参:`TaskSpec` 的字段,`allowedTools` 除外(型上禁)。 */
15
+ export type GovernanceOverrides = Partial<Omit<TaskSpec, "allowedTools">> & {
16
+ readonly allowedTools?: never;
17
+ };
18
+ /** 本仓唯一的收紧折叠口(deny-wins 合并;core 在放宽时抛)。 */
19
+ export declare function tightenGoverned(base: TaskSpec, overrides: GovernanceOverrides): TaskSpec;
20
+ //# sourceMappingURL=governance-overrides.d.ts.map
@@ -0,0 +1,8 @@
1
+ import { tightenTaskSpec } from "@sema-agent/core";
2
+ export function tightenGoverned(base, overrides) {
3
+ if (Object.hasOwn(overrides, "allowedTools")) {
4
+ throw new Error("tightenGoverned: `allowedTools` is not a tightening input — the engine replaces the base list with the override instead of narrowing it; the request's allow list has exactly one write point (applyTaskSettings)");
5
+ }
6
+ return tightenTaskSpec(base, overrides);
7
+ }
8
+ //# sourceMappingURL=governance-overrides.js.map
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * **宿主的决策事实** —— 一条耐久 park 被结算时,本服务能如实陈述的那半件事:谁结束了这次等待,以及
3
- * 审批通道自报的归属。一个叶子模块(只依赖 `@sema-agent/core`),四个消费点共用一个定义。
3
+ * 审批通道自报的归属,以及决定体上陈述「谁决定的」那一只键(`decidedBy`)的闭集、读法与映射。一个叶子模块
4
+ * (运行期只依赖 `@sema-agent/core`;另一处 import 是纯类型的编译期门),全部决定口共用一个定义。
4
5
  *
5
6
  * ## 为什么是「事实」而不是「结算词」(core 7.6.0 / [ref] S6-A,BREAKING)
6
7
  *
@@ -17,14 +18,12 @@
17
18
  import { type AskOutcome, type ResumeOutcome } from "@sema-agent/core";
18
19
  /**
19
20
  * 谁结束了这次等待,以及通道自报的归属。
20
- * · `decidedBy: "person"` —— 运维经审批通道做的决定(两条 HTTP `/decide` 腿);
21
+ * · `decidedBy: "person"` —— 一个人经审批通道做的决定(决定体缺席 `decidedBy` 即此);
21
22
  * · `decidedBy: "sla_timeout"` —— 宿主自己的 SLA 扫在行的 deadline 上判的(FACET A;**只有** sweep 能这么说,
22
- * 而 sweep 结构上不能 `allow`)。
23
- * · `decidedBy: "policy"`(core 7.26.2 / #1016)—— **这台部署自己的策略**拒的:答话的是机器不是人。调用方
24
- * 经决裁体的 `settledBy` 自报;core 据它给模型铸一句**策略句**(而不是「用户不同意,停下来等他」),
25
- * 并把结算记成 `policy_refused`(`who.party:"policy"`,core 7.39.0 #1265 起;此前 `"none"`)。`allow` 侧:core 7.39.0 起引擎收策略的
26
- * 放行(`policy_allowed`),但本仓的两条 decide 口**不开**这一形(`tool-approval.ts` 的 `SETTLED_BY_ONLY_ON_DENY` 与店列的同一条拒仍在 ——
27
- * 耐久车道上的机器放行是一件没裁过的新能力,不在 7.113.0 提货批内,候登)。
23
+ * 而 sweep 结构上不能 `allow`;wire 上永不受理,见 {@link APPROVAL_DECIDED_BY})。
24
+ * · `decidedBy: "policy"` —— **这台部署自己的策略**答的:答话的是机器不是人,`allow` 与 `deny` 两向都可以。
25
+ * 调用方经决定体的 `decidedBy` 陈述;core 据它铸 `policy_allowed` / `policy_refused`(`who.party:"policy"`),
26
+ * 拒的那一向给模型一句**策略句**(而不是「用户不同意,停下来等他」)。
28
27
  * · `approver` —— 通道自报的结算方标识串。**缺席永不读成**「没人批」或「有人批」,只意味着通道没报名字。
29
28
  */
30
29
  /**
@@ -33,9 +32,9 @@ import { type AskOutcome, type ResumeOutcome } from "@sema-agent/core";
33
32
  * · **铸**(本仓自己观察到的事实):只有 `"timeout"` —— 同步 `onAsk` 腿的等待窗归**宿主**所有(引擎不为
34
33
  * 一个它没排程的回调起表),所以「本仓的 TTL 窗走完了、没人答」这件事引擎结构上观察不到,不自报就
35
34
  * 永远被记成「一个人拒绝了」;
36
- * · **转**(调用方在回决体里自报的事实,core 7.26.2 / #1016 起):`settledBy:"policy"` = 答这张卡的是
37
- * **这台部署自己的策略**,不是人。本仓**零判据、原样过境**:词的拒绝逻辑归引擎(`resolveAsk` 对
38
- * 自报词表外的值是响亮 fail-closed deny),本仓抄一份词表的那天 core 加词就会被静默 400 掉。
35
+ * · **转**(调用方在决定体里陈述的事实):决定体的 `decidedBy:"policy"` 经 {@link liveSettledBy} 落成这一格的
36
+ * `"policy"` = 答这张卡的是**这台部署自己的策略**,不是人。wire 上的词只有 {@link APPROVAL_DECIDED_BY} 两个,
37
+ * 映射只有那一只函数 —— 这一格的「转」不再是一个原样过境的串。
39
38
  * 逐词(词表属主恒是 core):
40
39
  * · `"human"` —— 与「不铸这个键」逐字同义(core 缺席即按人处理);
41
40
  * · `"aborted"` —— core **明令拒收**宿主自报(那是引擎自家 fail-closed 端的词,宿主自报等于把自己的
@@ -62,13 +61,78 @@ export type HostDecision = Extract<ResumeOutcome, {
62
61
  * 编译期先红)。今天 = `"person"` | `"sla_timeout"` | `"policy"`。 */
63
62
  export type HostDecidedBy = HostDecision["decidedBy"];
64
63
  /**
65
- * 一次 decide 的事实,**一处成形**(两条 HTTP decide 腿 + 活卡 PARKED 赎回席共用)。
64
+ * ══ 决定体的出处位 `decidedBy`:一只闭集、一只读法、一只映射 ═══════════════════════════════════════════════
66
65
  *
67
- * `decidedBy` = 调用方**陈述**的决策者(缺席 ⇒ `"person"`,= 本面自古以来的形:一条不说话的 decide 就是
68
- * 一个人按的)。🔴 **本仓零判据**:词表的属主是 core,不认识的词由它 pre-CAS 响亮拒
69
- * (`checkpoint.invalid_outcome`,`hostDecision.decidedBy … is outside the closed set`)。在这里再判一次
70
- * 就是同一语义面的第二个判官(源头修复纪律 [ref]),而且本仓抄一份词表的那天 core 加第四个词就会被
71
- * 静默 400 掉 —— 那正是 #1016 的 `"policy"` 加进来时本可以发生的事。
66
+ * 规则一句话:**一个审批决定用一只键说是谁做的** —— `decidedBy ∈ {"person","policy"}`,缺席 = `"person"`;读它的只有
67
+ * {@link readDecidedBy}(三条工具审批决定口共读:live `POST /v1/tool-approvals/:id/respond`、耐久 ask 账
68
+ * `POST /v1/tasks/:id/asks/:askId/decision`、耐久 `POST /v1/approvals/:sessionId/decide`),把它交给引擎的只有
69
+ * {@link liveSettledBy}(同步座 `AskOutcome.settledBy`)与 {@link hostDecisionFacts}(停驻座 `hostDecision.decidedBy`)。
70
+ * · 词与决定动词**正交**:策略可以放行也可以拒(core 7.39.0 起引擎铸 `policy_allowed` / `policy_refused`)。唯一的例外是
71
+ * 引擎自己的那一条 —— 一张要人的卡(`requiresRealApproval`)不归策略放行,见 {@link policyCannotAllow}。
72
+ * · `"sla_timeout"` **永不从 wire 收**:那是「等待到点、没人答」,只有本服务自己的截止扫说得出来(`approval-sweeps.ts`
73
+ * 直接铸 `{decidedBy:"sla_timeout"}`,不经本读法)。调用方说它 = 替服务端伪证一件只有服务端看得见的事。
74
+ * · `plan_review` 不读这只键:引擎的 plan_review 结局形没有决定者位(core 的结局规整会把别的键整只重建掉),收下它就是
75
+ * 一句「已记」的空话。那一门照兄弟门字段那一族拒(`approvals-assistant.ts` 的回决门族派生),候 core 给座。
76
+ *
77
+ * 词表的来源:core 包根**没有**导出这只词表的运行期常量(只有类型 `ResumeOutcome[...]["hostDecision"]["decidedBy"]`),所以
78
+ * 本仓一只闭集常量 + 两道编译期门**双向**咬着 core 的型:core 加 / 退一个词 ⇒ 这里先红,不会在 wire 上静默留一个两个座都已经
79
+ * 不收的词,也不会把 core 新开的词静默 400 掉。core 导出词表之日,本常量改为直接镜像那一只(请托在案)。
80
+ */
81
+ export declare const APPROVAL_DECIDED_BY: readonly ["person", "policy"];
82
+ /** wire 上的出处词(决定体 `decidedBy` 的值域)。 */
83
+ export type ApprovalDecidedBy = (typeof APPROVAL_DECIDED_BY)[number];
84
+ /** 闭集外的拒句(三条决定口同一句;码按各口既有约定,见 {@link readDecidedBy})。 */
85
+ export declare const DECIDED_BY_OUTSIDE_CLOSED_SET = "decidedBy must be \"person\" or \"policy\" when present \u2014 who made this decision: a person, or this deployment's own policy (absent = \"person\"); \"sla_timeout\" is never accepted from a caller (only this server's own deadline sweep says a wait ended with nobody answering)";
86
+ /** 直连门上的拒句(与 `remember` / `updatedInput` / `checkpointToken` 同族:键不在决策证明的签名载荷里)。 */
87
+ export declare const DECIDED_BY_NOT_IN_PROOF = "decidedBy is not supported on a direct-door worker (not covered by the decision proof)";
88
+ /** 一张要人的卡上策略的放行(三条决定口同一句;判据 {@link policyCannotAllow})。 */
89
+ export declare const POLICY_CANNOT_ALLOW_PERSON_ASK = "decidedBy \"policy\" cannot allow this ask \u2014 it demands a person's approval (risk.requiresRealApproval), and a policy's answer is not a person's; answer it as a person, or refuse it as the policy";
90
+ /**
91
+ * 退役键 `settledBy`(7.93.3–7.117.x 的「只配 deny」那一位)的拒句。耐久两口由各自的键闭集 / strict 体拒;live `/respond` 的解析不是
92
+ * 键闭集的(未知键一律忽略),而这只键不是随便一个未知键:它是调用方在**明确陈述**「这是策略答的」—— 忽略它就是把缺席律(person)
93
+ * 套在一个说了话的体上(旧壳的策略拒记成人拒;一张要人的卡上旧形的放行被读成人的放行,绕过 {@link policyCannotAllow})。所以那条口
94
+ * 在裁决之前响亮拒它,与同一只 parser 里 `scope` 的拒同一条先例;那只 parser 收成键闭集之日,这一臂并进闭集门。
95
+ */
96
+ export declare const SETTLED_BY_RETIRED = "settledBy is retired \u2014 send decidedBy: \"person\" | \"policy\" (who made this decision; absent = \"person\"); ignoring it would record a policy's answer as a person's";
97
+ /** {@link readDecidedBy} 的判决(调用点做穷举 `switch` + `never` 兜底,各按自己的码约定发出)。 */
98
+ export type DecidedByReading = {
99
+ kind: "accepted";
100
+ decidedBy: ApprovalDecidedBy;
101
+ } | {
102
+ kind: "not_in_proof";
103
+ } | {
104
+ kind: "outside_closed_set";
105
+ };
106
+ /**
107
+ * 决定体 `decidedBy` 的**唯一读法**。次序即契约:
108
+ * ① 缺席 ⇒ `"person"`(缺席律:一条不说话的决定就是一个人按的 —— 与修前逐字同形);
109
+ * ② 直连门在场且键在场 ⇒ `not_in_proof`(不论值:它不在签名载荷里,能改线上字节的一方可以把人翻成策略;
110
+ * 第二参给字面 `false` 的调用点 = 那条口没有决策证明,live `/respond` 即此);
111
+ * ③ 闭集内 ⇒ 那个词;其余一切(`"sla_timeout"`、结算词、别的串、非串、`null`)⇒ `outside_closed_set`。
112
+ */
113
+ export declare function readDecidedBy(raw: unknown, directDoorActive: false): Exclude<DecidedByReading, {
114
+ kind: "not_in_proof";
115
+ }>;
116
+ export declare function readDecidedBy(raw: unknown, directDoorActive: boolean): DecidedByReading;
117
+ /**
118
+ * **同步座的映射**(`AskOutcome.settledBy`;ask 账行的 `settled_by` 列存的也是这一格):`person` ⇒ 不铸键(引擎缺席即按人读,
119
+ * 与修前逐字同形 —— 人的放行仍是裸 `true`),`policy` ⇒ `"policy"`。总函数,无 cast。
120
+ */
121
+ export declare function liveSettledBy(decidedBy: ApprovalDecidedBy): Extract<HostSettledBy, "policy"> | undefined;
122
+ /**
123
+ * 引擎自己的那一条例外:**一张要人的卡不归策略放行**(core `policyCannotAnswer` / `unjudgedAllowRefused` —— 同步座上
124
+ * `req.requiresRealApproval === true` 时策略的 allow 被拒成 `blanket_allow_refused`)。本仓**只在自己的副作用先于引擎落地的
125
+ * 两条口**上于裁决之前说这一句(live `/respond`:会话放行 / 常驻规则 / 一条按 approve 回放的 DECIDED 行;耐久 ask 账:一条
126
+ * 宣称 approve 的 DECIDED 行),读的是引擎读的同一个事实(卡上的 `requiresRealApproval`)。耐久 `/decide` 不判:引擎在 CAS 之前
127
+ * 就拒(`checkpoint.invalid_outcome`,卡留 pending),本口的副作用全在引擎受理之后 —— 在那里再判一次就是第二个判官。
128
+ * core 包根没有导出这只谓词(请托在案);导出之日本函数改为直接调它。
129
+ */
130
+ export declare function policyCannotAllow(decidedBy: ApprovalDecidedBy, allow: boolean, demandsAPerson: boolean): boolean;
131
+ /**
132
+ * 一次 decide 的**停驻座事实**,一处成形(耐久 `/decide` 与活卡 PARKED 赎回席共用;截止扫直接铸 `{decidedBy:"sla_timeout"}`)。
133
+ *
134
+ * `decidedBy` = 已经过 {@link readDecidedBy} 的 wire 词:两个词在停驻座上**就是它自己**(`person` / `policy`,
135
+ * 两只词表的交集由上面的编译期门证),所以这里没有 cast,也没有缺席律(缺席已在读法里落成 `"person"`)。
72
136
  *
73
137
  * `principal` = 那条腿已经验过身份的决策者(缺席 = 匿名档的部署,通道确实没有名字可报)。
74
138
  * 归属串走 core 的 `screenApproverAttribution` **先筛后带**:core 对形不合的归属是 pre-CAS 响亮拒,
@@ -76,14 +140,8 @@ export type HostDecidedBy = HostDecision["decidedBy"];
76
140
  * 被改造过的串):归属是转录,不是可以「修一修」的东西 —— 修出来的名字既不是通道报的那个,也不是
77
141
  * 「通道没报」,两头都不真。
78
142
  *
79
- * 🔴 归属**不按词分岔**:core 7.38.x 的 `policy_refused` 结算记录上没有归属席(`who.party:"none"`),7.39.0 #1265 起有了
80
- * (`who.party:"policy"`,`approver?`)—— 无论 core 的记录怎么写,**本服务观察到的事实**不变:送出这次请求的那把已验证身份是真的,本仓的
143
+ * 🔴 归属**不按词分岔**:无论 core 的记录怎么写,**本服务观察到的事实**不变:送出这次请求的那把已验证身份是真的,本仓的
81
144
  * 迟到结算账本(`approval-ask-audit-store.ts`)记的就是它。按词删一格 = 给账本挖一个只在某个词上出现的洞。
82
- *
83
- * 🔴 **入参是 `string`,不是 `HostDecidedBy` —— 这一格刻意让类型说真话**(wire 边界强类型宪法):两条
84
- * decide 腿能证得出的只有「调用方给了个串」(形门在各自的口上),**证不出**它是词表成员;把参数声明成
85
- * 闭集就是让类型替一件本层从没做过的检查背书。⇒ 收 `string`,在**这一处**(本座的边界)做唯一一次 cast,
86
- * 而那次 cast 说的是「我把一个未判词的串放到只收词的座上,判词的是座的属主(core,pre-CAS 响亮拒)」。
87
145
  */
88
- export declare function hostDecisionFacts(decidedBy: string | undefined, principal: string | undefined): HostDecision;
146
+ export declare function hostDecisionFacts(decidedBy: ApprovalDecidedBy, principal: string | undefined): HostDecision;
89
147
  //# sourceMappingURL=host-decision.d.ts.map
@@ -1,6 +1,36 @@
1
1
  import { screenApproverAttribution } from "@sema-agent/core";
2
+ export const APPROVAL_DECIDED_BY = ["person", "policy"];
3
+ export const DECIDED_BY_OUTSIDE_CLOSED_SET = 'decidedBy must be "person" or "policy" when present — who made this decision: a person, or this deployment\'s own policy (absent = "person"); "sla_timeout" is never accepted from a caller (only this server\'s own deadline sweep says a wait ended with nobody answering)';
4
+ export const DECIDED_BY_NOT_IN_PROOF = "decidedBy is not supported on a direct-door worker (not covered by the decision proof)";
5
+ export const POLICY_CANNOT_ALLOW_PERSON_ASK = 'decidedBy "policy" cannot allow this ask — it demands a person\'s approval (risk.requiresRealApproval), and a policy\'s answer is not a person\'s; answer it as a person, or refuse it as the policy';
6
+ export const SETTLED_BY_RETIRED = 'settledBy is retired — send decidedBy: "person" | "policy" (who made this decision; absent = "person"); ignoring it would record a policy\'s answer as a person\'s';
7
+ function isApprovalDecidedBy(v) {
8
+ return APPROVAL_DECIDED_BY.includes(v);
9
+ }
10
+ export function readDecidedBy(raw, directDoorActive) {
11
+ if (raw === undefined)
12
+ return { kind: "accepted", decidedBy: "person" };
13
+ if (directDoorActive)
14
+ return { kind: "not_in_proof" };
15
+ return isApprovalDecidedBy(raw) ? { kind: "accepted", decidedBy: raw } : { kind: "outside_closed_set" };
16
+ }
17
+ export function liveSettledBy(decidedBy) {
18
+ switch (decidedBy) {
19
+ case "person":
20
+ return undefined;
21
+ case "policy":
22
+ return "policy";
23
+ default: {
24
+ const unreachable = decidedBy;
25
+ throw new Error(`liveSettledBy: unknown decidedBy ${String(unreachable)}`);
26
+ }
27
+ }
28
+ }
29
+ export function policyCannotAllow(decidedBy, allow, demandsAPerson) {
30
+ return decidedBy === "policy" && allow && demandsAPerson;
31
+ }
2
32
  export function hostDecisionFacts(decidedBy, principal) {
3
33
  const screened = screenApproverAttribution(principal);
4
- return { decidedBy: (decidedBy ?? "person"), ...(screened.approver !== undefined ? { approver: screened.approver } : {}) };
34
+ return { decidedBy, ...(screened.approver !== undefined ? { approver: screened.approver } : {}) };
5
35
  }
6
36
  //# sourceMappingURL=host-decision.js.map