@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
@@ -1,13 +1,14 @@
1
- import type { HostDecidedBy, HostSettledBy } from "./host-decision.js";
1
+ import { type ApprovalDecidedBy, type HostSettledBy } from "./host-decision.js";
2
2
  import type { IncomingMessage } from "node:http";
3
- import { type AskClass, type AskRequest, type AskOutcome, type CheckpointGate, type PendingAction, type PersistedRuleMandate, type ReadRootGrantCandidate, type RuleOffer } from "@sema-agent/core";
3
+ import { type AskClass, type AskRequest, type AskOutcome, type PermissionResult, type CheckpointGate, type PendingAction, type PersistedRuleMandate, type ReadRootGrantCandidate, type RuleOffer, type AutoModeFallbackCause, type BoundSkillGrants } from "@sema-agent/core";
4
4
  import { type CardRulePersisted, type RuleConsentLane } from "./rules-consent.js";
5
- import { type DenialLimitFallback, type RuleOffersAbsence, type ApprovalRequestFrame, type ApprovalRevokeFrame, type RuleEvidenceProjection } from "./approval-card.js";
5
+ import { type AllowRuleShadowProjection, type DenialLimitFallback, type RuleOffersAbsence, type ApprovalRequestFrame, type ApprovalRevokeFrame, type RuleEvidenceProjection } from "./approval-card.js";
6
6
  import { type ApprovalAskStore, type AskRow } from "./plugins/approval-ask-store-sql.js";
7
7
  import type { ApprovalAskAuditSink } from "./approval-ask-audit-store.js";
8
8
  import type { ParkCommitWatch } from "./plugins/park-commit-watch.js";
9
9
  import { type GovernanceAskMarks } from "./governance-ask-marks.js";
10
10
  import { type GovernancePosture } from "./runtime-governance.js";
11
+ import { type GrantBinding, type StandingAnswerGrantCarrier } from "./standing-answer-grant.js";
11
12
  /** [ref]:审批 gate kind 闭集(core gateMatch 的 `human`/`irreversible_ask` ↔ outcome.gate "policy_ask")。
12
13
  * 此前 7 份手写副本散在两只 checkpoint store 的数组/SQL 字面与 /decide 守卫——core 加审批味 kind 时
13
14
  * 全部静默漂移。core 无导出词表(全大写导出面零命中,2026-08-09 亲验),属主落此;SQL IN 片段从
@@ -56,13 +57,10 @@ export interface ParkedAskRedeemRequest {
56
57
  decision: "approve" | "deny";
57
58
  /** [ref] 同源的人写理由(deny 时 core 把它交给模型;allow 只进审计面)。 */
58
59
  note?: string;
59
- /** #1016:回决体自报的**结算来源**,原样过境到耐久腿的 `hostDecision.decidedBy`(缺席 ⇒ `"person"`)。
60
- * live 命中臂与本 PARKED 臂读**同一格** —— 同一份体落在哪条臂上只取决于副本与时序。
61
- * 型 = `string`(本层证得出的全部);判词的是耐久座的属主 core,cast 在 `hostDecisionFacts` 那一处。
62
- * ⚠️ 两座的**词表并不相同**(live = `human`/`timeout`/`policy`,耐久 = `person`/`sla_timeout`/`policy`,
63
- * 只在 `policy` 上相交)—— 这正是这条 wire 键只开 `"policy"` 一个词的原因,也是它不能声明成其中任何
64
- * 一只闭集的原因:同一个串要落到两个不同词表的座上。 */
65
- settledBy?: string;
60
+ /** S-1154:决定体的**出处位**(已过 `readDecidedBy` 的 wire 词,缺席已落成 `"person"`),经 `hostDecisionFacts` 落到停驻座的
61
+ * `hostDecision.decidedBy`。live 命中臂与本 PARKED 臂读**同一格** —— 同一份体落在哪条臂上只取决于副本与时序;两臂各走自己座的
62
+ * 那一只映射(live = `liveSettledBy`,停驻 = 恒等),词表只有一份。 */
63
+ decidedBy: ApprovalDecidedBy;
66
64
  /** [ref] ctrl+g 编辑放行的实参整体替换(approve 才有意义)。 */
67
65
  updatedInput?: unknown;
68
66
  /** 发起本次 respond 的**已验证**调用方(live 腿 `gatedPrincipal` 的那一把)。席在赎回之前拿它与
@@ -79,9 +77,21 @@ export type ParkedAskRedeem = (req: ParkedAskRedeemRequest) => Promise<{
79
77
  body: Record<string, unknown>;
80
78
  }>;
81
79
  /**
82
- * 一问身上与「站着的答案」有关的三个事实位 —— {@link standingAnswerMayRetire} 的全部入参。
80
+ * 本仓**策略层自己交出去的一问**的 {@link StandingAnswerFacts}。读者一个:workflow 问句门(`task-settings.ts`)—— 它在交出这一问之前先读按工具名记的
81
+ * 耐久豁免店(「本会话不再询问」在本层兑现,继承链上的同一问才够得着;审批席依约不退役继承问),而**能不能读**与审批席 / 回决写侧是同一只判据
82
+ * ({@link standingAnswerDurablyRecordable}):记得下的才代答,记不下的(规则车道清不掉的类、带授予绑定的问 ……)不代答,存量行也不例外 —— 一事一读。
83
+ * 部署名单门不读店(它的问带 `matchedAskRule` ⇒ 类 `ask_rule` ⇒ 规则车道清不掉,本函数对它也答这一类 —— `test/approval-exemption.test.ts` 的同判钉)。
84
+ * 事实取自**这一问本身**:类集经 core 包根唯一的类派生 `classifyAsk`;它的第二参是闸内的事实,本层看不到 ⇒ 给诚实的空(core 对「不持席位的站」的同一句;
85
+ * 只读决定半场的 `decisionAskClassesOf` 不在包根导出,所以空闸事实不是手造的第二种读法,而是同一只派生在本层能交出的全部输入)。
86
+ * `requiresRealApproval` / `mandated` 读这一问自报的成员;治理位恒假(本层不是治理层);授予绑定读调用(`grantBindingOf`)。
87
+ */
88
+ export declare function policyAskStandingAnswerFacts(req: StandingAnswerGrantCarrier, ask: Extract<PermissionResult, {
89
+ action: "ask";
90
+ }>): StandingAnswerFacts;
91
+ /**
92
+ * 一问身上与「站着的答案」有关的事实位 —— {@link standingAnswerMayRetire} 的全部入参。
83
93
  *
84
- * 🔴 **三格全必填布尔**,刻意不做 `?: true` 的「只记真」形:判据哪天多一位,每个读者都要当场编译红,
94
+ * 🔴 **全必填**,刻意不做 `?: true` 的「只记真」形:判据哪天多一位,每个读者都要当场编译红,
85
95
  * 而不是漏传一格被静默读成 `false`(那是安全轴上的 fail-open)。读者各自从自己手里的形折成这三格,
86
96
  * 然后交**同一只** {@link standingAnswerMayRetire}(判据只有一份,折法各归读者):
87
97
  * · live 读侧取 `req` + 本地治理 peek;live 写侧取 pending 条目;
@@ -95,10 +105,13 @@ export interface StandingAnswerFacts {
95
105
  readonly requiresRealApproval: boolean;
96
106
  /** core 闸站盖的强制位([ref]):规则清不掉、站着的答案退不掉,只有对**这一次调用**的应答能答。 */
97
107
  readonly mandated: boolean;
98
- /** 7.116.0(审计修复批 S4,core 7.41.0 `Skill` 卡):这一问的应答**还授予**这一次调用之外的东西(批准 `Skill` = 激活技能 + 本 run 内
99
- * 清单免问)。站着的答案要代答它必须读得出授予绑定、且绑定相同;core 7.41.0 在活的问上不给绑定 ⇒ 两侧都读不出 ⇒ 不可记、不被代答。
100
- * 读者一律经 `standing-answer-grant.ts` 的 `answerGrantsBeyondCall`(唯一读口)折这一位。 */
101
- readonly grantsBeyondCall: boolean;
108
+ /** 这一问的应答**授不授予**这一次调用之外的东西、授予的话绑定是什么(批准引擎的 `Skill` = 激活技能 + 本 run 内清单免问)。
109
+ * `bound` ⇒ 站着的答案按「工具 + 绑定」记、只代答绑定相同的问(core 7.43.0 #1348 / #1357:活的问与停驻行同带 `boundGrantDigest`);
110
+ * `unreadable` ⇒ 不可记、不被代答(fail-closed)。读者一律经 `standing-answer-grant.ts` 的 `grantBindingOf`(唯一读口)折这一位。 */
111
+ readonly grantBinding: GrantBinding;
112
+ /** 引擎盖在这一问上的**类集**(`AskRequest.classes` / 停驻行孪生座;经 core 的屏 `screenAskClassMembers` 读)。`undefined` = 卡上没有类
113
+ * (老引擎 / 不盖类的站)或屏拒 —— 读不出的类不是「无类」。 */
114
+ readonly classes: readonly AskClass[] | undefined;
102
115
  }
103
116
  /**
104
117
  * 🔴 **S-660 —— 会话级放行的唯一判据:站着的答案能不能退掉这一问。** 纯谓词,三个读者:
@@ -133,6 +146,14 @@ export interface StandingAnswerFacts {
133
146
  * `mandated` 孪生座由**耐久** `/decide` 腿读,见 {@link parkRowStandingAnswerFacts}。)
134
147
  */
135
148
  export declare function standingAnswerMayRetire(facts: StandingAnswerFacts): boolean;
149
+ /**
150
+ * **耐久**豁免店能不能记下这一问的答案:先得退得掉({@link standingAnswerMayRetire}),再得没有授予绑定 —— 耐久店按 `(会话, 工具名)` 记,
151
+ * 没有绑定这一维,记下一张带绑定的答案就会代答同名工具的每一种绑定(`plugins/approval-exemption-store.ts`)。读者两处:耐久 `/decide` 腿的
152
+ * remember 可记性(`http/resume-legs.ts`)与运维队列行的 `rememberRefused`(`http/routes/approvals-assistant.ts`)—— 两处同答。
153
+ * 读侧同理:耐久店不代答带绑定的问(`standingGrantSettles` 经 `answerGrantsBeyondCall`;策略层的豁免探针 —— workflow 问句门 ——
154
+ * 经本判据 + {@link policyAskStandingAnswerFacts})。
155
+ */
156
+ export declare function standingAnswerDurablyRecordable(facts: StandingAnswerFacts): boolean;
136
157
  /**
137
158
  * 🔴 **S-663 → NP-1(7.100.0 合并复审 [ref] 交叉表 #2)—— 耐久 `/decide` 腿的 {@link StandingAnswerFacts}:从 park 行铸三位。**
138
159
  * 纯投影,**不判**:判据是同一只 {@link standingAnswerMayRetire}(唯一读点 = `http/resume-legs.ts` 的 remember 可记性,
@@ -311,6 +332,18 @@ export interface ToolApprovalFrame {
311
332
  /** S-934 ②:这一问属于的**全部**类(core `ASK_CLASSES` 序,`classes[0] === askClass`,非空)—— 读整只集合,别只读 {@link askClass}(一问可同属
312
333
  * 几类)。与 `askClass` 同在同缺、同一只窄读;逐字同序过境,不排序不去重。 */
313
334
  classes?: AskClass[];
335
+ /**
336
+ * core 7.43.0 #1348 / #1357(**ADDITIVE**,`"tool_approval"` only)—— 对引擎 `Skill` 工具的调用,批准它**还授予**的东西的摘要
337
+ * (`AskRequest.boundGrantDigest`,技能名 + 两只清单规范拼写的哈希);别的卡缺席。与停驻行 / inbox 行的同名位同值(core:同一只工厂铸,
338
+ * 活卡与它的停驻孪生按 `(toolCallId, boundGrantDigest)` 对账)。逐字过境,本仓不重算;站着的答案按「工具 + 它」记(S-1115)。
339
+ */
340
+ boundGrantDigest?: string;
341
+ /** 上一位哈希的那一份值:`{ skill, allowedTools, disallowedTools }`(`skill: null` = 调用没点名任何已载技能)。与摘要同在同缺;**部署文本**,
342
+ * 壳按上下文转义渲染(core:display metadata)。逐字过境。 */
343
+ boundGrants?: BoundSkillGrants;
344
+ /** core 7.43.0 #1353:分类器没给判词的成因词(`error` / `timeout` / `parse_error`;只在类 `classifier_no_ruling` 的卡上)。闭集经 core 的
345
+ * `readClosedWordSeat(req, "classifierNoRuling")` 窄读(筛 = core `isAutoModeFallbackCause`),集外 ⇒ 缺席 + 计一次。display:本仓不按它裁决。 */
346
+ classifierNoRuling?: AutoModeFallbackCause;
314
347
  /**
315
348
  * **[ref]**(core 7.19.0;**ADDITIVE**,`"tool_approval"` only)—— 这只**根外读** ask 的**清障目录**:
316
349
  * 把 `dir` 加进本会话的读目录(壳侧既有的 `/add-dir` 口),同一条调用就不会再问。
@@ -372,37 +405,28 @@ export interface ToolApprovalFrame {
372
405
  expiresInMs?: number;
373
406
  serverNowMs?: number;
374
407
  /**
375
- * [ref](core 5.25.0,[ref] 接力契约 / [ref] 主件;**ADDITIVE**,`"tool_approval"` only)——
376
- * 这只 ask **命中了**调用方的一条持久 allow 规则,而那条规则**没能清掉它**。值 = 被越级的那条规则
377
- * **原文**(core `AskRequest.persistedRuleShadowed`,四个 mint 点全带;core 侧已过 `inlineUntrusted`)。
378
- *
379
- * 🔴 **它存在的理由是一次真实的用户面回归**:core 5.25.0 收窄了消音边界(裁定逐字:「Allow rules
380
- * silence the CLASSIFIER's questions, never a MANDATED one」)—— `shellGate:"always"` 的部署、工具自带
381
- * egress/irreversible mark、以及既有的 governance 三类门下,用户此前被规则消掉的 ask **重新出现**。
382
- * 没有这个键,人看到的是「我明明点过『不再询问』,它怎么又问」,唯一合理的结论是「我的规则坏了/没存上」。
383
- * 有了它,壳能渲「你的规则仍在,只是这次调用被(运维令 / 工具本性)要求逐次确认」。
384
- *
385
- * 🔴 **缺席 ≠「你没有规则」**:本键只在「有规则命中 ∧ 规则清不掉这只 ask」时在场。绝大多数 ask
386
- * 压根没有规则命中(缺席),而**命中且清掉了**的那些根本不会变成 ask(它们被消音了,没有卡)。
387
- * 消费端禁把缺席读成「你在这条命令上没有规则」。
388
- *
389
- * 与 {@link ToolApprovalFrame.governanceForced} **刻意分列、不合并**:那个键回答「门是谁下的」
390
- * (运维治理层),本键回答「你那条规则怎么了」。governance 只是不可消音的三个来源之一,另两个
391
- * (doctrine `always` / 工具自带 mark)不打 governance 标 —— 合并会让后两类的 ask 要么谎报治理出身、
392
- * 要么丢掉规则解释。出身的完整推法见 [ref]:`gate.safetyAxis` + `riskDescriptor.shellGateDoctrine` +
393
- * `realApproval.origin` 组合读,server 不新铸出身键。
394
- *
395
- * 值是**用户内容族**(规则原文是人写的文本)⇒ 与 `message`/`sourceAgentName` 同待遇:`redactSecrets`
396
- * 后上帧,UNTRUSTED-for-display。空串**不铸键**(core 只在真有命中时带它,空串是坏值不是「空规则」)。
397
- *
398
- * 耐久路(park 行)的对偶是 `gate.riskDescriptor.shadowedRule` —— 我方对 `riskDescriptor` 是**整体透传**
399
- * (SQL 双生落整只 JSON 列、`listPending` 整只回读、两条 durable 读面整行上 wire),所以那一路**键集**零施工;
400
- * 唯一的施工是**内容**:那两条读面(`GET /v1/approvals` + `/v1/approvals/stream`)在读边界对这一格补
401
- * `redactSecrets`(core 只中和不脱敏,而运维队列是跨租户可见的那一条 —— 见
402
- * `http/routes/approvals-assistant.ts` 的 `redactPendingDisclosures` 顶注)。
403
- * 两路的钉见 `test/approval-shadowed-rule-wire.test.ts` 与 db-integration 的「[ref] 件4」格。
408
+ * core 7.42.0 #1327(**硬破坏换名**:取代 7.41.x 的 `persistedRuleShadowed`,零别名;`"tool_approval"` only)——
409
+ * allow 层里**命中了这次调用、却没能清掉这只 ask** 的成员,每个成员一条 `{ member, entries, omitted? }`:`member` ∈ core `ALLOW_RULE_MEMBERS`
410
+ * (`hook` 部署 PreToolUse hook 的 allow / `deployment` 运维的 `APPROVAL_GRANT` / `persisted` 人存的 allow 规则 / `skill` 被激活技能的清单 /
411
+ * `request` 请求的 `settings.permissions.allow`);`entries` = 那个成员命中的条目原文(hook 恒空表);`omitted` = 没列出的条目数。
412
+ *
413
+ * 🔴 **它存在的理由**:allow 层只清「普通问 / 没人表态」两类问;运维的强制类(`APPROVAL_REQUIRE` / `APPROVAL_NEVER_AUTO` / shell 地板 /
414
+ * 写保护 / 治理)、hook 的问、plan 模式的问照样到人。没有这个键,人看到的是「我明明写了 allow,它怎么又问」,唯一合理的结论是「我的规则坏了」;
415
+ * 有了它,壳能渲「你的规则在、命中了,只是这次调用必须逐次确认」。卡上那句话引擎已经写进 `message`(每个呈卡站的 `cardDisclosureOf`),
416
+ * 本键是同一事实的结构化一面,本仓不另写句子。
417
+ *
418
+ * 🔴 **缺席 ≠「你没有规则」**:本键只在「有成员命中 ∧ 清不掉这只 ask」时在场;命中且清掉了的根本不会变成 ask。
419
+ *
420
+ * 与 {@link ToolApprovalFrame.governanceForced} **刻意分列**:那个键回答「门是谁下的」,本键回答「你那条规则怎么了」。
421
+ *
422
+ * 值是**内容族**(人 / 运维写的条目)⇒ 唯一窄读点 `approval-card.ts` 的 `readAllowRuleShadowed`(卡也用它 ⇒ 帧与 `card_json` 同值;
423
+ * `redactSecrets` 后截长一次算定)。形不合 / 空表 ⇒ 不铸键。
424
+ *
425
+ * 耐久路(park 行)的对偶是 `gate.riskDescriptor.allowRuleShadowed`(整体透传,两条 durable 读面在读边界对条目补 `redactSecrets`,
426
+ * 叶表属主 `observability/run-terminal.ts` 的 `redactRiskDescriptor`)。升级前停驻的行带旧键 `shadowedRule`,起服一次性迁移改写
427
+ * (`plugins/one-time-migrations.ts` 登记表)。
404
428
  */
405
- persistedRuleShadowed?: string;
429
+ allowRuleShadowed?: AllowRuleShadowProjection[];
406
430
  /**
407
431
  * [ref] 车二(core 5.18.0 [ref]):`"tool_approval"` only —— 引擎为这次 ask 铸的**规则候选**
408
432
  * (`AskRequest.ruleOffers`,core 5.58.0 判别联合逐字透传:闭词表 `kind`/`match` + 引擎铸的文本,
@@ -506,9 +530,9 @@ export interface ToolApprovalFrame {
506
530
  * 🔴 **展示/对账元数据,永不是裁决输入**(core 契约逐字:removing it would leave every decision
507
531
  * byte-identical);server 逐字转录**引擎盖章**的那一份,**永不自铸缺席词**(server 铸词 = 谎报
508
532
  * 「某一层没报」)。窄读走 `approval-card.ts` 的 `readRuleEvidence`(卡也用它 ⇒ 帧与 `card_json`
509
- * 同值;redact+截长在函数内一次算定,`persistedRuleShadowed` R2-[medium] 同款)。
533
+ * 同值;redact+截长在函数内一次算定,allow 层披露 R2-[medium] 同款)。
510
534
  *
511
- * 与 `persistedRuleShadowed` **刻意分列**:那是**人读的**越级规则原文(display 值,非身份通道),
535
+ * 与 `allowRuleShadowed` **刻意分列**:那是**人读的**命中条目原文(display 值,非身份通道),
512
536
  * 本键的 dots 是那条规则的**机器身份**(a rule is a set of adds;数组即身份,消费端禁 join 成标量)。
513
537
  */
514
538
  ruleEvidence?: RuleEvidenceProjection;
@@ -722,83 +746,6 @@ export declare const PERSIST_RULE_BATCH_EXCLUSIVE_ERROR = "persistRule.batchOffe
722
746
  * 指定 scope = 让一次「在项目 A 里点的同意」写成一条覆盖项目 B(或全局)的常驻放行 —— 那是这条轴上
723
747
  * 最贵的一次扩权。静默忽略同样不行:客户端会以为自己设上了。响亮拒,一句话说清谁拥有这一格。
724
748
  */
725
- /**
726
- * #1016 —— `settledBy` 与 allow 族**互斥**的那一句(两条回决腿共用一份文本:同一个体在两条口上必须
727
- * 拿到同一句话)。判的是**键在不在**,不是它是什么词。
728
- */
729
- /**
730
- * #1016 —— `settledBy` 的**形**拒句(两条回决腿共用一份文本,与 {@link SETTLED_BY_DENY_ONLY} 同姿势)。
731
- *
732
- * 🔴 **判形 ≠ 判词**:本层证得出的只有「它是个串」;「它是不是词表里的那个串」由**引擎**判(两条腿各有
733
- * 一处响亮拒,读数在收车档)。加这道形门的理由是**类型必须说真话**(wire 边界强类型宪法):
734
- * 一个非串值往下会落进 `AskOutcome.settledBy`(core 的串闭集)与本仓审计账本的 `settledBy?: string` 席 ——
735
- * 两处都声明成串,而运行期塞进去一个数字。引擎对它照样响亮拒(实测三形一致:`"bogus"` / `1` / `null`
736
- * 都是 `approver_contract` 的 fail-closed deny),所以这不是在补一个 fail-open,而是**不让本层的类型撒谎**;
737
- * 顺带把「一个非串永远不可能是闭集成员」这件必然的事拒在最早的地方,与同一只 parser 里 `note` /
738
- * `persistRule.rule` / `persistRule.edited` 三道既有形门**同族**(那三道在,只有本格没有 = 本格才是特例)。
739
- */
740
- export declare const SETTLED_BY_MUST_BE_STRING = "settledBy must be a string \u2014 the settlement source is one word from the engine's closed set, and this layer judges only the SHAPE (which word it is, the engine decides)";
741
- /**
742
- * #1016 / S-558 —— **live 座的那一次边界 cast,全仓只此一处**(耐久座的那一次在 `hostDecisionFacts` 里,
743
- * 一座一次)。它说的是「我把一个**未判词**的串放到只收词的座上」:判词的是 core —— `resolveAsk` 对词表外
744
- * 是 `approver_contract` 的响亮 fail-closed deny,不是静默当人拒(实测三形同判,特征钉在
745
- * `test/tool-approval.test.ts` 的 #1016 组)。本层判过的只有**形**(是个串、只与 deny 同行,见
746
- * {@link SETTLED_BY_MUST_BE_STRING} / {@link SETTLED_BY_DENY_ONLY}),而形不是词。
747
- *
748
- * 两个座位读它:这次回决的**即时结算**(`entry.settle` 的自报参)与**持久行**(`decideAsk` 的
749
- * `settledBy` 列)。S-558 之前只有前者 —— 于是同一个事实在「当次」与「重放」上是两个答案。
750
- * S-843:耐久 ask 回决口(`POST /v1/tasks/:taskId/asks/:askId/decision`)写的是**同一个座**(同一列,经
751
- * `outcomeOfDecidedRow` 交给同一个 `AskOutcome.settledBy`),所以它也走这一只 —— cast 仍是一座一次。
752
- */
753
- export declare function respondSettledBy(parsed: Pick<ParsedApprovalResponse, "settledBy">): HostSettledBy | undefined;
754
- export declare const SETTLED_BY_DENY_ONLY = "settledBy is only meaningful with decision \"deny\" \u2014 only a person approves an action that EXECUTES, so a self-reported settlement source on an allow is a contradiction the engine refuses; the refusal would arrive too late to undo this door's own side effects (a session allow-all grant, a persisted rule, a DECIDED row that replays as a plain approval), so it is refused here, before the decision";
755
- /**
756
- * S-843 / 车QK F7 —— `settledBy` 的**宽**拒句(三条回决腿共用一份文本,与 {@link SETTLED_BY_DENY_ONLY} 同姿势)。句子逐字是 S-843 在
757
- * 耐久 ask 回决口路由里写下的那一句(车QK F7 起搬到判官旁,路由不再自己写),尺 = 店的同一个常量({@link MAX_SETTLED_BY_CHARS})。
758
- */
759
- export declare const SETTLED_BY_TOO_WIDE = "settledBy must be 1-16 characters \u2014 the settlement source is one word from the engine's closed set and lands in a fixed-width column on the decision row";
760
- /**
761
- * S-843 —— 决议体上 `settledBy` 的判据,**一处成形**。三条 HTTP 回决腿共读:
762
- * `POST /v1/approvals/:sessionId/decide`(耐久 park 腿)、`POST /v1/tasks/:taskId/asks/:askId/decision`(耐久 ask 腿)与
763
- * live `POST /v1/tool-approvals/:id/respond`(经 {@link parseToolApprovalResponse};车QK F7 起 —— 此前那只 parser 手写了 ②③ 两臂、
764
- * 没有 ④,是这条判据的第二个写者)。
765
- *
766
- * 次序即契约(①–③ = decide 腿修前那三道 if 的原序,逐字搬来,不是新判据):
767
- * ① 直连门在场 ⇒ `not_in_proof` —— 键不在决策证明的签名载荷里(§6.3 同族第四条,理由在 approvals-assistant 的
768
- * `DECIDE_NOT_IN_PROOF_KEYS` 注);
769
- * ② 不是 deny ⇒ `deny_only` —— {@link SETTLED_BY_DENY_ONLY};
770
- * ③ 不是串 ⇒ `not_string` —— {@link SETTLED_BY_MUST_BE_STRING};
771
- * ④ 长度不在 1..{@link MAX_SETTLED_BY_CHARS} ⇒ `too_wide` —— {@link SETTLED_BY_TOO_WIDE}(车QK F7 / A3,7.104.0 合并复审)。
772
- * 尺与店门口的形门同一只谓词({@link settledByFitsColumn}):`/respond` 与耐久 ask 口写的是**同一列**
773
- * (`approval_ask.settled_by`,`VARCHAR(16)`),列宽是列的事实,不是某一条腿的事实。修前这一道只长在耐久 ask 口的路由里:
774
- * `/respond` 上一只超宽 / 空串的来源位要到 CAS 里才被店门抛,落进 D5「店抖动不挡真实回决」臂 —— 行没决,决议照样交给了引擎,200。
775
- * `/decide` 的来源位不落这一列(交给 core 的 checkpoint,core 在 CAS 之前按词表拒),这一道对它同样成立:core 的闭集词
776
- * (`human` / `timeout` / `policy`)都远短于这把尺,尺外的串不可能是一个词 ⇒ 三条口对同一个体同判同句,不为一条腿开特例。
777
- * 判的是**键在不在 / 配不配这个决议 / 形**,**从不判词**(词表属主是 core;三条腿的词都由引擎响亮拒)。
778
- *
779
- * 返回判决而不是直接回 400:各条腿在调用点按自己的码约定发出(错误文案冻结门按字面站点采集,码是 wire 契约);
780
- * 调用点对判决做穷举 `switch` + `never` 兜底(加一臂 = 编译红,[ref])。`accepted` 臂带着已证形的串 —— 调用点拿它往下传,
781
- * 不再对 `body.settledBy` 做第二次窄化。第三参给字面 `false` 的调用点(live `/respond`:那条口没有直连门证明)拿到的判决型里
782
- * **没有** `not_in_proof` 臂(重载),不必为一条结构上不可达的臂写兜底。
783
- */
784
- export type SettledByBodyVerdict = {
785
- kind: "absent";
786
- } | {
787
- kind: "accepted";
788
- settledBy: string;
789
- } | {
790
- kind: "not_in_proof";
791
- } | {
792
- kind: "deny_only";
793
- } | {
794
- kind: "not_string";
795
- } | {
796
- kind: "too_wide";
797
- };
798
- export declare function judgeSettledByBody(settledBy: unknown, decision: string, directDoorActive: false): Exclude<SettledByBodyVerdict, {
799
- kind: "not_in_proof";
800
- }>;
801
- export declare function judgeSettledByBody(settledBy: unknown, decision: string, directDoorActive: boolean): SettledByBodyVerdict;
802
749
  /** S-720(core 7.32.0 #1093)—— `readRootGrant` 只随 `decision:"allow"`(CC 卡的第三选项「Yes, and add directory」= 放行这一次
803
750
  * + 加目录;`allow_session` 不是那一个选项,`deny` 上它没有意义)。裁决之前响亮拒,不静默丢。 */
804
751
  export declare const READ_ROOT_GRANT_ALLOW_ONLY = "readRootGrant is only meaningful with decision \"allow\" \u2014 it is the card's \"Yes, and add directory\" answer (allow this call and add the card's readRootCandidate.dir to this run's read directories)";
@@ -812,27 +759,12 @@ export declare function parseToolApprovalResponse(body: unknown): {
812
759
  updatedInput?: unknown;
813
760
  persistRule?: ParsedPersistRule;
814
761
  note?: string;
815
- settledBy?: string;
762
+ decidedBy: ApprovalDecidedBy;
816
763
  readRootGrant?: string;
817
764
  } | {
818
765
  ok: false;
819
766
  error: string;
820
767
  };
821
- /** [ref] 车2:`parseToolApprovalResponse` 的成功分支形——`respond()` 拆分出的私有收尾方法(`finishRespond`/
822
- * `respondWithCas`)复用,避免裸内联匿名类型重复三处。 */
823
- type ParsedApprovalResponse = Extract<ReturnType<typeof parseToolApprovalResponse>, {
824
- ok: true;
825
- }>;
826
- /**
827
- * #1016 —— wire 上**开给调用方**的那一个词,从 core 的两只闭集里取**交集**。
828
- *
829
- * 🔴 它是一条**文档钉**,不是过境值的型(过境值是 `string`,理由在 `parseToolApprovalResponse` 那处注):
830
- * `"human"` 与缺席同义、`"timeout"` 是本仓自己观察到的窗(客户端说出来就是替服务端伪证一件只有服务端
831
- * 看得见的事),而耐久座那边的 `"person"` / `"sla_timeout"` 同理。两只词表**只在 `"policy"` 上相交** ——
832
- * 这条 wire 键因此恰好只开那一个词。core 哪天退役它,这里**编译期先红**,而不是在 wire 上静默留一个
833
- * 两个座都已经不收的词;`test/tool-approval.test.ts` 的 #1016 那组按它写判据。
834
- */
835
- export type RespondSettledBy = Extract<HostSettledBy, HostDecidedBy>;
836
768
  /**
837
769
  * [ref] R-13 C —— `UNATTENDED_APPROVAL_POLICY` 的**闭集**词表([ref]Q1「裁2:A+C」/[ref] 裁②)。
838
770
  *
@@ -1047,9 +979,9 @@ type DecidedRowFacts = Pick<AskRow, "decision" | "settledBy"> & {
1047
979
  * · **结算来源**(S-558)—— 行上非 null 即宿主自报的那个词(`null` = 人决,与「不铸这个键」同义;
1048
980
  * 语义属主 = `AskRow.settledBy` 顶注)。
1049
981
  *
1050
- * 🔴 approve 臂**不读**来源位,而且那不是「静默丢」:store 的入店形门(`assertDecideSettledBy`)让
1051
- * 「approve 行带来源位」在**写面不可表示** —— core 对 `{allow:true, settledBy}` 是 fail-closed 拒,
1052
- * 一条那样的行没有任何读回路径能诚实地交还给引擎,所以它在落库那一刻就被拒掉了,而不是在这里被吞掉。
982
+ * 🔴 S-1154:来源位**两向都读**。决定体的出处位 `decidedBy:"policy"` 在 allow 与 deny 上都会落列(引擎 7.39.0 起铸
983
+ * `policy_allowed`),所以 approve 行带来源位是一条合法的行,它的每条读回路径交还 `{allow:true, settledBy:"policy", updatedInput?}`
984
+ * —— 与当次回决交给引擎的那一只逐字同形。
1053
985
  */
1054
986
  export declare function outcomeOfDecidedRow(row: DecidedRowFacts): DecidedAskOutcome;
1055
987
  /**
@@ -1094,7 +1026,7 @@ export declare class ToolApprovalCoordinator {
1094
1026
  * askId 下全部本地条目(自己 + 兄弟 + 重复注册),直接调用它们各自的 `settle`,而不是被动等一个永远
1095
1027
  * 不会来的本地信号。 */
1096
1028
  private readonly pendingByAskId;
1097
- /** Per-capability session grants — keys = sessionAllowKey(owner, sessionId, category) (修2; bounded). */
1029
+ /** Per-capability session grants — keys = sessionAllowKey(owner, sessionId, category, grant binding) (修2; bounded). */
1098
1030
  private readonly allowAllSessions;
1099
1031
  /** [ref] HIGH-1(broker)→[ref]四 core 产品裁定「多活集合+广播+首决胜出」:per-(owner, host-session)
1100
1032
  * 的**当前活跃连接集合**——boundAsk 闭包只携身份,emit 时广播给该 key 下**全部**活连接(宿主重连/