@sema-agent/server 7.118.0 → 7.120.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 (139) hide show
  1. package/MIGRATION.md +139 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +26 -13
  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 +5 -5
  20. package/dist/approval.d.ts +51 -5
  21. package/dist/approval.js +76 -13
  22. package/dist/audit.d.ts +12 -1
  23. package/dist/audit.js +62 -2
  24. package/dist/auto-mode-face.d.ts +2 -3
  25. package/dist/bake-runner/main.js +1 -6
  26. package/dist/bake-runner/protocol.d.ts +18 -0
  27. package/dist/bake-runner/protocol.js +22 -2
  28. package/dist/bake-runner/runner.d.ts +12 -8
  29. package/dist/bake-runner/runner.js +5 -9
  30. package/dist/boot/config-center.js +6 -6
  31. package/dist/boot/coordinators.js +4 -1
  32. package/dist/boot/device-lane.d.ts +3 -3
  33. package/dist/boot/device-lane.js +1 -2
  34. package/dist/boot/execution-env.js +3 -2
  35. package/dist/boot/fatal-exit.d.ts +7 -1
  36. package/dist/boot/fatal-exit.js +2 -1
  37. package/dist/boot/leader.d.ts +4 -4
  38. package/dist/boot/leader.js +1 -2
  39. package/dist/boot/parked-revive-gate.d.ts +5 -1
  40. package/dist/boot/parked-revive-gate.js +8 -2
  41. package/dist/boot/resolve-spec.js +7 -4
  42. package/dist/boot/runner-deps.d.ts +4 -3
  43. package/dist/boot/runner-deps.js +7 -3
  44. package/dist/boot/stage-01-config.js +2 -0
  45. package/dist/boot/stage-07-capability-layer.js +5 -1
  46. package/dist/boot/stage-10-http-server.js +3 -2
  47. package/dist/boot/stores.js +12 -7
  48. package/dist/capabilities/builtin-tools.d.ts +31 -8
  49. package/dist/capabilities/builtin-tools.js +22 -1
  50. package/dist/capabilities/center-plugins.d.ts +2 -0
  51. package/dist/capabilities/center-plugins.js +24 -6
  52. package/dist/capabilities/code-review-council.js +8 -11
  53. package/dist/capabilities/scenarios.d.ts +42 -13
  54. package/dist/capabilities/scenarios.js +36 -9
  55. package/dist/capabilities/send-user-file-tool.d.ts +6 -3
  56. package/dist/capabilities/send-user-file-tool.js +1 -1
  57. package/dist/config-catalog.js +5 -5
  58. package/dist/config-lkg.d.ts +33 -5
  59. package/dist/config-lkg.js +55 -11
  60. package/dist/config-types.d.ts +8 -1
  61. package/dist/config.js +11 -11
  62. package/dist/deployment-governance.d.ts +8 -3
  63. package/dist/deployment-governance.js +7 -4
  64. package/dist/device-store.d.ts +7 -0
  65. package/dist/device-store.js +4 -0
  66. package/dist/device-ws-hub.js +17 -9
  67. package/dist/device-ws-protocol.d.ts +15 -0
  68. package/dist/host-decision.d.ts +52 -27
  69. package/dist/host-decision.js +14 -7
  70. package/dist/http/admission.js +6 -6
  71. package/dist/http/resume-legs.js +6 -1
  72. package/dist/http/routes/approvals-assistant.d.ts +25 -3
  73. package/dist/http/routes/approvals-assistant.js +9 -5
  74. package/dist/http/routes/capabilities.js +5 -3
  75. package/dist/http/routes/devices.js +3 -3
  76. package/dist/http/routes/memory-origin.d.ts +1 -1
  77. package/dist/http/routes/rules.js +13 -6
  78. package/dist/http/routes/runs.js +11 -5
  79. package/dist/http/routes/sessions.js +20 -16
  80. package/dist/http/wire-types.d.ts +12 -13
  81. package/dist/images/manifest.d.ts +7 -1
  82. package/dist/images/manifest.js +18 -16
  83. package/dist/list-entry-display.d.ts +14 -0
  84. package/dist/list-entry-display.js +14 -0
  85. package/dist/memory-store-refusal.d.ts +7 -12
  86. package/dist/memory-store-refusal.js +4 -5
  87. package/dist/namespaced-rule-name.d.ts +4 -3
  88. package/dist/namespaced-rule-name.js +2 -6
  89. package/dist/observability/fail-open.d.ts +9 -1
  90. package/dist/observability/fail-open.js +9 -1
  91. package/dist/operator-ask.d.ts +7 -6
  92. package/dist/plugins/approval-exemption-store.d.ts +4 -0
  93. package/dist/plugins/approval-exemption-store.js +1 -0
  94. package/dist/plugins/blob-backend.d.ts +13 -0
  95. package/dist/plugins/blob-backend.js +26 -0
  96. package/dist/plugins/local-session-store.d.ts +78 -47
  97. package/dist/plugins/local-session-store.js +152 -122
  98. package/dist/plugins/one-time-migrations.d.ts +14 -0
  99. package/dist/plugins/one-time-migrations.js +90 -3
  100. package/dist/plugins/permission-rule-store-file.js +2 -6
  101. package/dist/plugins/permission-rule-store-sql.d.ts +64 -6
  102. package/dist/plugins/permission-rule-store-sql.js +166 -59
  103. package/dist/plugins/remote-env-device.d.ts +12 -1
  104. package/dist/plugins/remote-env-device.js +13 -5
  105. package/dist/plugins/scheduler-support.d.ts +8 -4
  106. package/dist/plugins/scheduler-support.js +5 -5
  107. package/dist/plugins/snapshot-bucket-presence.d.ts +36 -0
  108. package/dist/plugins/snapshot-bucket-presence.js +40 -0
  109. package/dist/plugins/store-backend.d.ts +17 -6
  110. package/dist/plugins/store-backend.js +46 -16
  111. package/dist/plugins/store-open-wait.d.ts +33 -0
  112. package/dist/plugins/store-open-wait.js +73 -0
  113. package/dist/rule-entry-list.d.ts +0 -1
  114. package/dist/rule-entry-list.js +5 -10
  115. package/dist/rules-consent.d.ts +12 -3
  116. package/dist/rules-consent.js +5 -4
  117. package/dist/run-local.js +6 -3
  118. package/dist/security.d.ts +5 -3
  119. package/dist/security.js +4 -0
  120. package/dist/sema-config-dir.d.ts +19 -0
  121. package/dist/sema-config-dir.js +33 -0
  122. package/dist/session-policy-wire.js +10 -14
  123. package/dist/spec-fields.d.ts +7 -2
  124. package/dist/store-posture.d.ts +14 -6
  125. package/dist/store-posture.js +7 -4
  126. package/dist/task-settings.d.ts +30 -58
  127. package/dist/task-settings.js +6 -18
  128. package/dist/tool-approval.d.ts +97 -43
  129. package/dist/tool-approval.js +120 -31
  130. package/dist/trace/engine-notice-wire.d.ts +6 -4
  131. package/dist/trace/engine-notice-wire.js +16 -9
  132. package/dist/trace/redact.d.ts +6 -0
  133. package/dist/trace/redact.js +9 -9
  134. package/dist/trace/self-minted-wire.d.ts +15 -1
  135. package/dist/trace/self-minted-wire.js +2 -0
  136. package/dist/user-input-ledger.d.ts +19 -15
  137. package/dist/user-input-ledger.js +12 -20
  138. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +56 -14
  139. package/package.json +2 -2
@@ -8,24 +8,32 @@
8
8
  *
9
9
  * ## 形
10
10
  * `declared` = 运维的声明(`DB_BACKEND` 词,含缺省推导);`backend` = **装配出的**后端 kind(没装出后端 = `memory`);两者不等是合法的
11
- * 形(`DB_BACKEND=pg` + `SESSION_BACKEND=memory` = 声明 pg、刻意不持久;auto 降级;缺省推导 local 起不来)—— 两位都上 wire,
11
+ * 形(`DB_BACKEND=pg` + `SESSION_BACKEND=memory` = 声明 pg、刻意不持久;缺省推导 local 起不来)—— 两位都上 wire,
12
12
  * 由调用方自己读出落差。`degradedReason` present-iff 降级臂走过(按后端族定词,{@link storeDegradedReasonFor})。
13
13
  * 顶层 `sql` 并入 `store.sql`(硬 breaking,零别名;对象逐字同形)。
14
14
  */
15
15
  import type { StoreBackend as StoreBackendWord } from "@sema-agent/settings-schema/execution-lanes";
16
+ import type { ServiceConfig } from "./config-types.js";
16
17
  import type { StoreBackend } from "./plugins/store-backend.js";
17
18
  import type { SqlEngineFacts } from "./plugins/sql-driver.js";
18
19
  import { type SqlEngineCapability } from "./sql-engine-posture.js";
19
20
  /**
20
- * 降级词闭集(**按后端族**,不按失败发生在哪一步 —— SQL 池懒连,连接失败第一次就出现在 `ensureSchema` 里,与 DDL 失败同一个调用,
21
- * 按调用位置分不出「连不上」与「建表失败」):
22
- * · `db_unreachable` —— SQL 族(`mysql` / `pg`)在 `SESSION_BACKEND=auto` 下打不开(连接或建表);
21
+ * 降级词闭集(**按后端族**,不按失败发生在哪一步):
23
22
  * · `local_unwritable` —— **缺省推导**的 local 文件店构造期失败(mkdir / 数据根锁 / 权限 / 只读挂载 / 磁盘满)。
23
+ *
24
+ * 🔴 SQL 族**没有**降级词:配了 DSN 的库当下打不开 ⇒ 窗内重试、到窗拒启(`plugins/store-open-wait.ts` / `openStoreBackendWithFallback`),
25
+ * 从来不退成内存 —— 修前这里有第二个词 `db_unreachable`(`SESSION_BACKEND=auto` 下 SQL 族打不开 ⇒ 内存 + 本位置位),那是 [ref] P 类的静默 fail-open,随降级臂一并退役。
24
26
  */
25
- export declare const STORE_DEGRADED_REASONS: readonly ["db_unreachable", "local_unwritable"];
27
+ export declare const STORE_DEGRADED_REASONS: readonly ["local_unwritable"];
26
28
  export type StoreDegradedReason = (typeof STORE_DEGRADED_REASONS)[number];
27
- /** 降级臂按后端族定词(穷举 switch:词表加员 ⇒ 编译红)。`memory` 从不尝试打开后端,走不到降级臂 —— 到了就是装配缺陷,响亮抛。 */
29
+ /** 降级臂按后端族定词(穷举 switch:词表加员 ⇒ 编译红)。只有缺省推导的 `local` 走得到降级臂;`mysql` / `pg` / `memory` 走到这里 = 装配缺陷,响亮抛。 */
28
30
  export declare function storeDegradedReasonFor(declared: StoreBackendWord): StoreDegradedReason;
31
+ /**
32
+ * `/health.sessionBackend` 与 `listening` 日志 `session` 位的**唯一读法**:装配后的 `config.sessionBackend`(`memory` | `tidb`;`tidb` = 「后端自己的 durable 店」的泛指标签)
33
+ * + 装配出的后端 kind ⇒ 对运维说人话的有效店 —— `durable(pg)` / `durable(mysql)` / `durable(local)` / `memory`。`sessionBackend` 缺 backend 的 `tidb`
34
+ * 不替它编一个后端名(那是装配缺陷,`createSessionStore` 会先拒)。部署侧的 smoke(`deploy/sema-up/lib/health.sh`)按这一形读同一位。
35
+ */
36
+ export declare function sessionStoreLabelOf(sessionBackend: ServiceConfig["sessionBackend"], backendKind: StoreBackend["kind"] | undefined): string;
29
37
  /** 装配值(纯数据;`boot/stores.ts` 铸一次,gauge / `/health` / caps 三面同读)。 */
30
38
  export interface StorePosture {
31
39
  readonly declared: StoreBackendWord;
@@ -1,12 +1,12 @@
1
1
  import { projectSqlEngineCapability } from "./sql-engine-posture.js";
2
- export const STORE_DEGRADED_REASONS = ["db_unreachable", "local_unwritable"];
2
+ export const STORE_DEGRADED_REASONS = ["local_unwritable"];
3
3
  export function storeDegradedReasonFor(declared) {
4
4
  switch (declared) {
5
- case "mysql":
6
- case "pg":
7
- return "db_unreachable";
8
5
  case "local":
9
6
  return "local_unwritable";
7
+ case "mysql":
8
+ case "pg":
9
+ throw new Error(`store posture: DB_BACKEND=${declared} never degrades to memory (a configured SQL store is retried then refused, never replaced) — this is a wiring defect`);
10
10
  case "memory":
11
11
  throw new Error("store posture: DB_BACKEND=memory never opens a backend, so it cannot degrade — this is a wiring defect");
12
12
  default: {
@@ -15,6 +15,9 @@ export function storeDegradedReasonFor(declared) {
15
15
  }
16
16
  }
17
17
  }
18
+ export function sessionStoreLabelOf(sessionBackend, backendKind) {
19
+ return sessionBackend === "tidb" && backendKind !== undefined ? `durable(${backendKind})` : sessionBackend;
20
+ }
18
21
  export function buildStorePosture(a) {
19
22
  return { declared: a.declared, backend: a.assembled ?? "memory", ...(a.degradedReason !== undefined ? { degradedReason: a.degradedReason } : {}) };
20
23
  }
@@ -27,8 +27,8 @@
27
27
  * `APPROVAL_REQUIRE` (an ask rule), `APPROVAL_NEVER_AUTO` (a person, mandated), `MANUAL_MODE_SHELL_GATE=always` (shell).
28
28
  * A request-carried skill's `allowedTools` is the same member's twin (same grammar, same predicate, same admission judge
29
29
  * `rule-entry-list.ts`). Pinned on the real assembly: `test/allow-layer-operator-knobs.test.ts`.
30
- * - permissionMode(轴B #2 注释保鲜,1.254——[ref]/[ref] 后的真实现):`defaultMode` 收**五**模式
31
- * (default | acceptEdits | plan | bypassPermissions | auto),bypass/auto 被 HONOR 为 gate-shape 选择
30
+ * - permissionMode(轴B #2 注释保鲜,1.254——[ref]/[ref] 后的真实现):`defaultMode` 收 core `PERMISSION_MODES` **全集**
31
+ * (default | acceptEdits | plan | auto | bypassPermissions | dontAsk),bypass/auto 被 HONOR 为 gate-shape 选择
32
32
  * ——但深层不变量未变:bypass=「不加 ask 门」,恒不越部署基线(tightenTaskSpec deny-wins,基线 deny/
33
33
  * ask 照压);精化语义见 :2xx 行内注释。旧头注的「NEVER honored/三模式白名单」是 pre-[ref] 拍照。
34
34
  *
@@ -51,60 +51,32 @@ import { type ExecutionEnv, type TaskSpec, type ThinkingLevel, type ToolPolicy,
51
51
  import type { HooksConfig } from "@sema-agent/settings-schema/hooks";
52
52
  import { type KeyClosureIssue } from "./request-key-closure.js";
53
53
  /**
54
- * S-902(7.107.0,车QY 候裁 11 裁 A;[ref])—— core 的**每一个**权限模式词,本仓请求面收不收:`"request"` = 受理(顶层 `permissionMode` /
55
- * `settings.permissions.defaultMode` / `?permissionMode=` 查询串三处同读),`"excluded"` = 显式排除(不上请求面)。
56
- *
57
- * 🔴 这张表是**表态**,不是词表的第二份:词与词序只从 core `PERMISSION_MODES` 来(下面 {@link SETTINGS_PERMISSION_MODES} 按它过滤);
58
- * 表只对每个词说一句「收 / 不收」。`satisfies Record<PermissionMode, …>` 对 core 的词型**穷举**:core 加一个词 ⇒ 本表缺键 **编译红**
59
- * (新词必须被表态一次,不会被静默收进请求面、也不会被静默漏掉);core 删 / 改名一个词 ⇒ 本表多键编译红(残条目不留)。
60
- *
61
- * 今天的表态(与 7.106.0 逐字同行为):五词受理;`dontAsk`(CC 的「自动拒」档,core 7.34.0 起第六词)**排除** —— 它上不上请求面是一件
62
- * 新能力(未裁,本表不替它裁);不收它时提交面 400 `request.field_invalid`,续跑重放腿按 {@link coercePermissionMode} 的既有宽读折 `default`。
63
- * 语义沿革(为什么是这五个、`bypassPermissions` / `auto` 何时进来):[ref] / [ref] / [ref] —— CC 自己的全集是六词,CC 托管 / 远端形收四词
64
- * (= 本仓 − `bypassPermissions`),本仓 = {CC 远端四词} ∪ {`bypassPermissions`},登记过的偏离;生效模式原样过境 `TaskSpec.permissionMode`(S-877)。
54
+ * 请求面 `permissionMode` 的闭集 = core `PERMISSION_MODES` **全集**(顶层 `permissionMode` / `settings.permissions.defaultMode` /
55
+ * `?permissionMode=` 查询串三处同读)。本仓不持有第二份词表,也不持有「收 / 不收」的表态:判据 = core 的 `isPermissionMode`,型 = core 的
56
+ * `PermissionMode`,wire 句子里的词表只从这里取词({@link PERMISSION_MODE_WORDS})。core 加一个词 ⇒ 请求面自动收它,它在派生腿上的去向由
57
+ * {@link deriveSettingsPolicy} 的穷举 `switch` 逼出来(缺一臂 ⇒ 编译红);core 删 / 改名一个词 ⇒ 本文件里按名写出它的那一臂编译红。
58
+ * 生效模式原样过境 `TaskSpec.permissionMode`(S-877):引擎自己解释模式 —— 含 `dontAsk`(CC 的「自动拒」档:凡是要问的一律由模式拒,零卡,
59
+ * 门记录 `deniedBy:"mode"`),本仓不另写判。
65
60
  */
66
- export declare const PERMISSION_MODE_REQUEST_FACE: {
67
- readonly default: "request";
68
- readonly acceptEdits: "request";
69
- readonly plan: "request";
70
- readonly auto: "request";
71
- readonly bypassPermissions: "request";
72
- readonly dontAsk: "excluded";
73
- };
74
- /** 请求面受理的模式词的型形 —— 从表态表派生(`"request"` 那几行的键)。 */
75
- export type SettingsPermissionMode = {
76
- [M in PermissionMode]: (typeof PERMISSION_MODE_REQUEST_FACE)[M] extends "request" ? M : never;
77
- }[PermissionMode];
78
- /** 请求面受理的模式词(core 序)= core `PERMISSION_MODES` 过滤表态 `"request"`。wire 门、coerce 两族、两句 400 文案同读这一份。 */
79
- export declare const SETTINGS_PERMISSION_MODES: readonly SettingsPermissionMode[];
80
- /** 显式排除集(core 序)= core `PERMISSION_MODES` 过滤表态 `"excluded"`;今天恰是 `["dontAsk"]`。只作披露 / 钉读,不参与判定。 */
81
- export declare const SETTINGS_PERMISSION_MODES_EXCLUDED: readonly Exclude<PermissionMode, SettingsPermissionMode>[];
82
- /** 五词表的 wire 文案形(`"a" | "b" | …`)—— 两条 400 的句子从这里取词,不再各抄一份。 */
83
- export declare const SETTINGS_PERMISSION_MODE_WORDS: string;
84
- /** 闭集成员判定(`unknown` 入口用)—— 词表的**唯一**运行期读者:wire 门与 coerce 两族同调。 */
85
- export declare function isSettingsPermissionMode(raw: unknown): raw is SettingsPermissionMode;
61
+ /** 请求面闭集的 wire 文案形(`"a" | "b" | …`,core 序)—— 三处 400(顶层 / bundle / 查询串)的句子从这里取词,不再各抄一份。 */
62
+ export declare const PERMISSION_MODE_WORDS: string;
86
63
  /**
87
- * R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends the
88
- * CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
64
+ * R4: coerce a raw top-level `body.permissionMode` (the LIGHT per-turn wire — the shell sends
65
+ * the CC permission mode RAW, axis-agnostic; the service interprets) to a service-honored mode, or undefined.
89
66
  * 🔴 TIGHTEN-ONLY vs the DEPLOYMENT baseline still holds for every value: the mode only selects the CLIENT-derived
90
67
  * gate ({@link deriveSettingsPolicy}); it can never subtract from the deployment/operator policy (applyTaskSettings
91
- * folds via tightenTaskSpec, deny-wins). Post-[ref] the honored set is the five-word table above (NOT "CC verbatim" —
92
- * see the {@link SettingsPermissionMode} header for the three-set contrast; the earlier "five CC modes" wording here
93
- * was [ref] P2-1's finding: no CC set has five members). `acceptEdits`,
94
- * `bypassPermissions` and `auto` are HONORED as gate-shape choices (they no longer coerce to `default`, which
95
- * would FORCE the manual ask gate onto a caller that explicitly asked for less asking — with the gate live, the old
96
- * coercion would have been a behavior change for them, incl. a headless-deny regression for bypass callers). An
97
- * UNKNOWN string still coerces to `default` (fail-safe: the most-asking mode).
98
- * [ref]-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-five-mode value
99
- * (server.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
68
+ * folds via tightenTaskSpec, deny-wins). The honored set is core's `PERMISSION_MODES` in full — every word passes
69
+ * through verbatim (`dontAsk` included: folding the auto-deny mode to `default`, the asking mode, would turn a refusal
70
+ * into a card). An UNKNOWN string coerces to `default` (fail-safe: the most-asking mode).
71
+ * [ref]-②: fresh submits can no longer REACH the unknown-word arm — the HTTP gate 400s a non-member value
72
+ * (admission.ts, sibling of promptProfile) and the wire type is a closed enum. This lenient fold stays for the one
100
73
  * population the gate deliberately does not cover: RESUME replay of a persisted pre-gate body (outputStyle/model
101
74
  * two-layer posture — bricking a resume over a value that was legal at submit time is worse than the quiet fold).
102
75
  */
103
- export declare function coercePermissionMode(raw: unknown): SettingsPermissionMode | undefined;
104
- /** 五模式闭集的**严格**读法([ref] ④:`/v1/capabilities?permissionMode=` 查询串用它 —— 读面对词表外的值 400,
105
- * 不走上面那条给 resume 重放留的 `default` 宽臂)。上面的宽读只是本函数外再包一层折叠。
106
- * S-496:词表单点已上移到 {@link SETTINGS_PERMISSION_MODES},本函数只是它的 `undefined` 形读者。 */
107
- export declare function coercePermissionModeStrict(raw: unknown): SettingsPermissionMode | undefined;
76
+ export declare function coercePermissionMode(raw: unknown): PermissionMode | undefined;
77
+ /** 闭集的**严格**读法(`/v1/capabilities?permissionMode=` 查询串用它 —— 读面对词表外的值 400,不走上面那条给 resume 重放留的 `default` 宽臂)。
78
+ * 上面的宽读只是本函数外再包一层折叠;词表只有 core 的 `PERMISSION_MODES`(判据 `isPermissionMode`)。 */
79
+ export declare function coercePermissionModeStrict(raw: unknown): PermissionMode | undefined;
108
80
  /**
109
81
  * S-433 —— 一份**被批准的计划**之后,这条会话继续用的 permission mode(`plan_review` 决裁体的
110
82
  * `permissionModeAfter`)。core 的契约句是 `present-plan-tool.js`:「If the plan is approved, the run
@@ -116,8 +88,8 @@ export declare function coercePermissionModeStrict(raw: unknown): SettingsPermis
116
88
  * edits」/「Yes, and manually approve edits」)的对位;`plan` 自己(= 不松开)由 `edit`/`reject` 两个
117
89
  * 决裁表达,`bypassPermissions` / `auto` 不在这张卡上 —— 一个批准动作不该顺带把部署的 ask 面整个关掉。
118
90
  *
119
- * 🔴 **不手抄词表**:`satisfies readonly SettingsPermissionMode[]` 让「本表的成员必须仍是本仓受理的
120
- * 模式词」成为**编译期**事实 —— 五词表某天改名/删词,这里就是红的,而不是静默留一个没人认的词。
91
+ * 🔴 **不手抄词表**:`satisfies readonly PermissionMode[]` 让「本表的成员必须仍是 core 的模式词」成为**编译期**事实 ——
92
+ * core 某天改名/删词,这里就是红的,而不是静默留一个没人认的词。
121
93
  */
122
94
  export declare const PLAN_APPROVE_PERMISSION_MODES_AFTER: readonly ["default", "acceptEdits"];
123
95
  /** {@link PLAN_APPROVE_PERMISSION_MODES_AFTER} 的类型形(值表单点,类型从值派生 —— 两处各写一份就会漂)。 */
@@ -139,7 +111,7 @@ export declare function isPlanApprovePermissionModeAfter(raw: unknown): raw is P
139
111
  export declare function storedBodyPermissionMode(body: {
140
112
  permissionMode?: unknown;
141
113
  settings?: unknown;
142
- }): SettingsPermissionMode | undefined;
114
+ }): PermissionMode | undefined;
143
115
  /** L2 ultracode ([ref]): the effective `thinking` level given the explicit `reasoningEffort` and the ultracode
144
116
  * preset. ultracode FLOORS at the "ultra" tier — core's single source `resolveReasoningProfile("ultra").thinking`
145
117
  * (= "max"), never a hardcoded literal — so an explicit higher tier still wins (none exists above max today;
@@ -148,7 +120,7 @@ export declare function effectiveThinking(reasoningEffort: unknown, ultracode: b
148
120
  /** R4: fold a top-level `body.permissionMode` onto the parsed settings as `permissions.defaultMode` (the top-level
149
121
  * field is the explicit per-turn intent → it WINS over a bundle defaultMode). Creates a minimal settings object when
150
122
  * no `body.settings` bundle was sent. The result flows through {@link applyTaskSettings} (tighten-only). */
151
- export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode: SettingsPermissionMode): ParsedTaskSettings;
123
+ export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode: PermissionMode): ParsedTaskSettings;
152
124
  /**
153
125
  * [ref] §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
154
126
  * 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
@@ -161,7 +133,7 @@ export declare function withPermissionMode(settings: ParsedTaskSettings | undefi
161
133
  */
162
134
  export declare function effectivePermissionMode(body: {
163
135
  permissionMode?: unknown;
164
- }, settings: ParsedTaskSettings | undefined): SettingsPermissionMode | undefined;
136
+ }, settings: ParsedTaskSettings | undefined): PermissionMode | undefined;
165
137
  /**
166
138
  * S-346 —— [ref] 技能清单预算两旋钮的**值域谓词**(一对具名单铸点)。
167
139
  *
@@ -187,7 +159,7 @@ export interface ParsedTaskSettings {
187
159
  deny?: string[];
188
160
  /** Carried for completeness; v1 folds `ask` into the gate via the deny-wins base (see {@link applyTaskSettings}). */
189
161
  ask?: string[];
190
- defaultMode?: SettingsPermissionMode;
162
+ defaultMode?: PermissionMode;
191
163
  /** [ref] 件⑥([ref] §3.4,[ref];PM 裁 [ref]):CC settings 的 kill-switch `permissions.disableAutoMode`,cli 投影
192
164
  * (settingsRulesWire.ts toWireSettings)而此前本白名单丢弃 ⇒ 用户/managed 层关 auto 到不了引擎。语义 = 只能关
193
165
  * 不能开(tighten-only):把生效模式 auto 折 default(座不写 ⇒ 不武装)。折叠单点 = {@link effectivePermissionMode}。
@@ -401,9 +373,9 @@ export interface FsWriteGateWiring {
401
373
  * 存不存在 ⇒ 把哨兵递给它,`deny Edit(src/**)` 会编译成功、指向一个谁也不会写的目录,对真实工作区
402
374
  * 的 `<工作区>/src/secret.ts` 这类目标判 allow —— 一条运维亲手写下的收紧规则**静默失效**,方向与写门那条正相反。
403
375
  *
404
- * ⇒ 本字段**只在真任务根已知时**在场。缺席 ⇒ 规则编译器拿不到 `pathBases.root` ⇒ 需要该基的拼法
405
- * (`Edit(src/**)` / `Edit(/etc/**)`)被 core 以 `unsupported.path_base` **整表拒**(→ 422,fail-loud),
406
- * 而不是编成一条够不着任何东西的规则。绝对形(`//abs`)与 `~/` 形不需要基,两种部署形都照常编得出。
376
+ * ⇒ 本字段**只在真任务根已知时**在场。缺席 ⇒ 不声明根 ⇒ 需要该基的拼法(`Edit(src/**)` / `Edit(/etc/**)`)锚在**装入这只策略的那条腿**
377
+ * 的真根(core 7.46.0 #1383 车 B:锚装一次、在被装入处;`unsupported.path_base` 只剩「声明了一个用不了的基」)—— 哨兵从不被声明成基。
378
+ * 修前(core ≤7.45)缺席 ⇒ 整表拒(→ 422):沙箱车道上相对 / `/x` 形规则恒 422。绝对形(`//abs`)不需要基;`~/` 形见下一字段。
407
379
  */
408
380
  taskRoot?: string;
409
381
  /**
@@ -1,4 +1,4 @@
1
- import { combinePolicies, createFsWriteGatePolicy, parseRuleText, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, PERMISSION_MODES, ruleToolGrammarOf, } from "@sema-agent/core";
1
+ import { combinePolicies, createFsWriteGatePolicy, parseRuleText, resolveReasoningProfile, rankOf, isThinkingLevel, CYBER_RISK, URL_SAFETY, HARNESS_SECTION_ANCHOR, RUN_WORKFLOW_TOOL_NAME, PERMISSION_MODES, isPermissionMode, ruleToolGrammarOf, } from "@sema-agent/core";
2
2
  import { unmatchableToolNamesRefusal } from "./config.js";
3
3
  import { MAX_PERMISSION_RULES_PER_LIST, createRuleListPolicy } from "./permission-rule-list.js";
4
4
  import { allowLayerEntryCountIssue, describeAllowLayerOverCap, describeEmptyToolSegmentEntry, emptyToolSegmentEntryOf, ruleEntryListShapeOk } from "./rule-entry-list.js";
@@ -7,20 +7,7 @@ import { MAX_KEY_ECHO, keyClosureClause, scanKeyClosure } from "./request-key-cl
7
7
  import { parseHooksConfig } from "./hooks/hook-runner.js";
8
8
  import { heldRuleName } from "./tool-rule-names.js";
9
9
  import { policyAskStandingAnswerFacts, standingAnswerDurablyRecordable } from "./tool-approval.js";
10
- export const PERMISSION_MODE_REQUEST_FACE = {
11
- default: "request",
12
- acceptEdits: "request",
13
- plan: "request",
14
- auto: "request",
15
- bypassPermissions: "request",
16
- dontAsk: "excluded",
17
- };
18
- export const SETTINGS_PERMISSION_MODES = PERMISSION_MODES.filter((m) => PERMISSION_MODE_REQUEST_FACE[m] === "request");
19
- export const SETTINGS_PERMISSION_MODES_EXCLUDED = PERMISSION_MODES.filter((m) => PERMISSION_MODE_REQUEST_FACE[m] === "excluded");
20
- export const SETTINGS_PERMISSION_MODE_WORDS = SETTINGS_PERMISSION_MODES.map((m) => `"${m}"`).join(" | ");
21
- export function isSettingsPermissionMode(raw) {
22
- return typeof raw === "string" && SETTINGS_PERMISSION_MODES.includes(raw);
23
- }
10
+ export const PERMISSION_MODE_WORDS = PERMISSION_MODES.map((m) => `"${m}"`).join(" | ");
24
11
  export function coercePermissionMode(raw) {
25
12
  const strict = coercePermissionModeStrict(raw);
26
13
  if (strict !== undefined)
@@ -30,7 +17,7 @@ export function coercePermissionMode(raw) {
30
17
  return undefined;
31
18
  }
32
19
  export function coercePermissionModeStrict(raw) {
33
- return isSettingsPermissionMode(raw) ? raw : undefined;
20
+ return isPermissionMode(raw) ? raw : undefined;
34
21
  }
35
22
  export const PLAN_APPROVE_PERMISSION_MODES_AFTER = ["default", "acceptEdits"];
36
23
  export function isPlanApprovePermissionModeAfter(raw) {
@@ -306,7 +293,7 @@ function createWorkflowAskPolicy(isExempt) {
306
293
  if (heldRuleName(req, isWorkflowName) === undefined)
307
294
  return { action: "allow" };
308
295
  const ask = { action: "ask", message: `dynamic workflow "${req.toolName}" requires approval in this permission mode`, decisionReason: "rule" };
309
- if (isExempt && standingAnswerDurablyRecordable(policyAskStandingAnswerFacts(req, ask))) {
296
+ if (isExempt && standingAnswerDurablyRecordable(policyAskStandingAnswerFacts(req, ask, false))) {
310
297
  try {
311
298
  if (await isExempt(req.toolName))
312
299
  return { action: "allow", decisionReason: "rule" };
@@ -400,7 +387,8 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
400
387
  return p ? { toolPolicy: p } : {};
401
388
  }
402
389
  case "default":
403
- case "auto": {
390
+ case "auto":
391
+ case "dontAsk": {
404
392
  const p = compose(createWorkflowAskPolicy(workflowGate?.isExempt));
405
393
  return p ? { toolPolicy: p } : {};
406
394
  }
@@ -1,4 +1,4 @@
1
- import { type ApprovalDecidedBy, type HostSettledBy } from "./host-decision.js";
1
+ import { type ApprovalDecidedBy, type HostSettledBy, type RetiredDecisionKeyFacts } from "./host-decision.js";
2
2
  import type { IncomingMessage } from "node:http";
3
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";
@@ -9,6 +9,7 @@ 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
11
  import { type GrantBinding, type StandingAnswerGrantCarrier } from "./standing-answer-grant.js";
12
+ import { type ApprovalExemptionStore } from "./plugins/approval-exemption-store.js";
12
13
  /** [ref]:审批 gate kind 闭集(core gateMatch 的 `human`/`irreversible_ask` ↔ outcome.gate "policy_ask")。
13
14
  * 此前 7 份手写副本散在两只 checkpoint store 的数组/SQL 字面与 /decide 守卫——core 加审批味 kind 时
14
15
  * 全部静默漂移。core 无导出词表(全大写导出面零命中,2026-08-09 亲验),属主落此;SQL IN 片段从
@@ -76,24 +77,30 @@ export type ParkedAskRedeem = (req: ParkedAskRedeemRequest) => Promise<{
76
77
  status: number;
77
78
  body: Record<string, unknown>;
78
79
  }>;
80
+ /** 阶梯里**重发**一把 claim 的墙钟上限(ms;首把用 `DURABLE_CALL_TIMEOUT_MS`)。🔴 codex 交叉复审 C5
81
+ * (2026-08-06 真缺陷):只数重试次数是**不够**的 —— 次数只约束「已经返回或已经 reject 的调用」,一次
82
+ * **挂住不返回**的店调用(DB 连接黑洞、池饿死)会让整个退避循环连一格都走不完,`onAsk` 的 promise 于是永久
83
+ * 悬挂 —— 正是这段代码要消灭的那个形。每把都与这个 deadline 显式 race,超时算一次 reject 继续阶梯;全部用尽
84
+ * 走意图投影。[ref] 前它给恢复读口的 `getAsk` 用,取值不变(轮询节奏)。 */
85
+ export declare const DURABLE_CONVERGE_READ_TIMEOUT_MS = 2000;
86
+ /** 这张卡上有没有名单自己的那一问:名单门问这只调用 ∧ 卡上带着名单那一问的类(名单门因豁免行没出问时,卡上的问是引擎自己的 —— 比如服务器工具的 `unstated`)。 */
87
+ export declare function carriesDeploymentListQuestion(deploymentListAsks: boolean, classes: readonly AskClass[] | undefined): boolean;
79
88
  /**
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`)。
89
+ * 本仓**策略层自己交出去的一问**的 {@link StandingAnswerFacts}。读者两个:部署名单门(`approval.ts`,`deploymentListQuestion: true`)与 workflow 问句门
90
+ * (`task-settings.ts`,`false`)—— 它们在交出这一问之前先读按工具名记的耐久豁免店(「本会话不再询问」在本层兑现,继承链上的同一问才够得着;审批席依约不退役
91
+ * 继承问),而**能不能读**与审批席 / 回决写侧是同一只判据({@link standingAnswerDurablyRecordable}):记得下的才代答,记不下的(带授予绑定的问 ……)不代答,
92
+ * 存量行也不例外 —— 一事一读。事实取自**这一问本身**:类集经 core 包根唯一的类派生 `classifyAsk`({@link NO_GATE_FACTS});`requiresRealApproval` /
93
+ * `mandated` 读这一问自报的成员;治理位恒假(本层不是治理层);授予绑定读调用(`grantBindingOf`)。
87
94
  */
88
95
  export declare function policyAskStandingAnswerFacts(req: StandingAnswerGrantCarrier, ask: Extract<PermissionResult, {
89
96
  action: "ask";
90
- }>): StandingAnswerFacts;
97
+ }>, deploymentListQuestion: boolean): StandingAnswerFacts;
91
98
  /**
92
- * 一问身上与「站着的答案」有关的事实位 —— {@link standingAnswerMayRetire} 的全部入参。
99
+ * 一问身上与「站着的答案」有关的事实位 —— {@link standingAnswerHome} 的全部入参。
93
100
  *
94
101
  * 🔴 **全必填**,刻意不做 `?: true` 的「只记真」形:判据哪天多一位,每个读者都要当场编译红,
95
102
  * 而不是漏传一格被静默读成 `false`(那是安全轴上的 fail-open)。读者各自从自己手里的形折成这三格,
96
- * 然后交**同一只** {@link standingAnswerMayRetire}(判据只有一份,折法各归读者):
103
+ * 然后交**同一只** {@link standingAnswerHome}(判据只有一份,折法各归读者):
97
104
  * · live 读侧取 `req` + 本地治理 peek;live 写侧取 pending 条目;
98
105
  * · 耐久 `/decide` 腿(S-663 → NP-1,7.100.0)取 park 行,经 {@link parkRowStandingAnswerFacts}(三格全从行上读)。
99
106
  * 本型**不绑** live 条目。
@@ -112,16 +119,31 @@ export interface StandingAnswerFacts {
112
119
  /** 引擎盖在这一问上的**类集**(`AskRequest.classes` / 停驻行孪生座;经 core 的屏 `screenAskClassMembers` 读)。`undefined` = 卡上没有类
113
120
  * (老引擎 / 不盖类的站)或屏拒 —— 读不出的类不是「无类」。 */
114
121
  readonly classes: readonly AskClass[] | undefined;
122
+ /**
123
+ * **问的属主**:这张卡上有没有部署审批名单(`APPROVAL_REQUIRE`)**自己的**那一问 —— 名单门(铸点)对这只调用出它的问 ∧ 卡上带着那一问的类
124
+ * (`carriesDeploymentListQuestion`;名单门的判词由 `approval.ts` 的 `deploymentListAsks` 读,不另造一张表)。名单的问是 server 铸的,它的站着的
125
+ * 答案记在 server 的会话豁免店、由名单门在出问**之前**读;其余每一问由引擎铸,引擎的类表裁它退不退得掉。
126
+ */
127
+ readonly deploymentListQuestion: boolean;
115
128
  }
116
129
  /**
117
- * 🔴 **S-660 —— 会话级放行的唯一判据:站着的答案能不能退掉这一问。** 纯谓词,三个读者:
118
- * · **读侧**(`askBroadcast` 的会话授权短路):`true` 才可能由既有 grant 代答,否则往下走真出卡;
119
- * · **写侧**(`finishRespond` 的 `grantable`):`true` 才可能把这张卡上的 `allow_session` 铸成 grant,
120
- * 否则只放行这一次、`rememberApplied:false`;
121
- * · **耐久 `/decide` 腿**(NP-1,`http/resume-legs.ts` 的 remember 可记性):入参由 {@link parkRowStandingAnswerFacts}
122
- * 从 park 行铸 —— `false` ⇒ 可记工具名 `null` ⇒ 放行本次、不落豁免、回执 `rememberApplied:false`。
123
- * 两臂读同一份事实、说同一句话:一问若不许被记住的放行答掉,它的应答也不许变成一条记住的放行 —— 否则
124
- * 同一只卡「重跑一遍」就被自己上一次的回答代答(test [ref] U1s-off 的真形)。
130
+ * 站着的答案(「本会话放行」/ 耐久 `remember:"session"`)对一问**记在哪、由谁读**(闭集;读者一律穷举):
131
+ * · `none` —— 退不掉这一问:不许被记住的放行答掉,它的应答也不许变成一条记住的放行(只放行这一次,`rememberApplied:false`);
132
+ * · `seat` —— **引擎铸的问**,引擎的规则车道清得掉它:审批席读进程内授权 / 耐久豁免店代答,`allow_session` 铸进程内授权,
133
+ * `/decide` 的 remember 落耐久豁免店(没有授予绑定时);
134
+ * · `server_store` —— **部署名单(`APPROVAL_REQUIRE`)自己的问**:它的答案只记在 server 的会话豁免店(按会话 × 工具名),由名单门在出问**之前**读;
135
+ * 审批席不代答它(问到了席上 = 店里没有),`allow_session` 不铸进程内授权、只经 {@link ToolApprovalCoordinator.recordDeploymentListAnswer} 落店。
136
+ */
137
+ export type StandingAnswerHome = "none" | "seat" | "server_store";
138
+ /**
139
+ * 🔴 **S-660 —— 会话级放行的唯一判据:站着的答案能不能退掉这一问,能的话它记在哪({@link StandingAnswerHome})。** 纯函数,读者:
140
+ * · **读侧**(`askBroadcast` 的会话授权短路 {@link ToolApprovalCoordinator.standingGrantSettles}):`seat` 才可能由既有授权代答,否则往下走真出卡;
141
+ * · **写侧**(`finishRespond` 的 `grantable` 与 `recordDeploymentListAnswer`):`seat` 把 `allow_session` 铸成进程内授权;`server_store` 落 server 的会话豁免店;
142
+ * `none` 只放行这一次、`rememberApplied:false`;
143
+ * · **卡面提示位**(活卡帧 / 条目 / `livePending` 行的 `rememberRefused`):在场 ⇔ `none`;
144
+ * · **耐久可记性**({@link standingAnswerDurablyRecordable}:`/decide` 的 remember、运维队列行、策略层两只豁免读面)。
145
+ * 各读者读同一份事实、说同一句话:一问若不许被记住的放行答掉,它的应答也不许变成一条记住的放行 —— 否则
146
+ * 同一只卡「重跑一遍」就被自己上一次的回答代答(test [ref] U1s-off 的真形)。读者侧没有任何按「问的属主」另判的臂:属主只在本函数里读一次。
125
147
  *
126
148
  * **为什么三位是同一类**(「站着的答案退不掉的一问」):会话授权 = **上一次**应答记下的一揽子放行,不是对
127
149
  * **这一次**调用的应答 —— 与持久 allow 规则同族,只是更短命(`askBroadcast` 短路处的注早已这么定性)。
@@ -145,19 +167,19 @@ export interface StandingAnswerFacts {
145
167
  * 存着落库那一刻的判定。(那张「行」是本仓的 ask 账本行,不是 core 的 park 行;core 7.31.0 #1079 起 park 行上的
146
168
  * `mandated` 孪生座由**耐久** `/decide` 腿读,见 {@link parkRowStandingAnswerFacts}。)
147
169
  */
148
- export declare function standingAnswerMayRetire(facts: StandingAnswerFacts): boolean;
170
+ export declare function standingAnswerHome(facts: StandingAnswerFacts): StandingAnswerHome;
149
171
  /**
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})。
172
+ * **耐久**豁免店能不能记下这一问的答案 —— {@link standingAnswerHome} 的一个读者(不是第二只判据):`server_store` 记得住(那一臂本就要求没有授予绑定);
173
+ * `seat` 还得没有授予绑定 —— 耐久店按 `(会话, 工具名)` 记,没有绑定这一维,记下一张带绑定的答案就会代答同名工具的每一种绑定
174
+ * (`plugins/approval-exemption-store.ts`);`none` 记不住。读者:耐久 `/decide` 腿的 remember 可记性(`http/resume-legs.ts`)与运维队列行的
175
+ * `rememberRefused`(`http/routes/approvals-assistant.ts`)—— 两处同答;策略层的豁免读面(部署名单门、workflow 问句门)经本函数 + {@link policyAskStandingAnswerFacts}
176
+ * 决定读不读店。读侧同理:耐久店不代答带绑定的问(`standingGrantSettles` 经 `answerGrantsBeyondCall`)。
155
177
  */
156
178
  export declare function standingAnswerDurablyRecordable(facts: StandingAnswerFacts): boolean;
157
179
  /**
158
180
  * 🔴 **S-663 → NP-1(7.100.0 合并复审 [ref] 交叉表 #2)—— 耐久 `/decide` 腿的 {@link StandingAnswerFacts}:从 park 行铸三位。**
159
- * 纯投影,**不判**:判据是同一只 {@link standingAnswerMayRetire}(唯一读点 = `http/resume-legs.ts` 的 remember 可记性,
160
- * `standingAnswerMayRetire(parkRowStandingAnswerFacts(…))`)。修前耐久腿自成一只谓词、只读 `ruleOffersAbsence` 一词,同一行上
181
+ * 纯投影,**不判**:判据是同一只 {@link standingAnswerHome}(唯一读点 = `http/resume-legs.ts` 的 remember 可记性,
182
+ * `standingAnswerDurablyRecordable(parkRowStandingAnswerFacts(…))`)。修前耐久腿自成一只谓词、只读 `ruleOffersAbsence` 一词,同一行上
161
183
  * core 盖好的 `requiresRealApproval` 与治理出身都不读 ⇒ 那两类卡的 `remember:"session"` 照旧铸会话免询问,而 live 腿对同一张卡
162
184
  * 答 `false`(两腿同一个问题、两份事实覆盖;两腿复审同报)。三格各自的来源(全在**这一条**已属主校验的载入行上,不另读):
163
185
  * · `governanceForced` —— 行上的治理标:`gate.riskDescriptor.shellGateDoctrine` × 本部署治理姿态,经 **同一只**
@@ -184,7 +206,9 @@ export declare function standingAnswerDurablyRecordable(facts: StandingAnswerFac
184
206
  */
185
207
  export declare function parkRowStandingAnswerFacts(pending: Extract<PendingAction, {
186
208
  kind: "tool_approval";
187
- }>, gate: CheckpointGate, governance: GovernancePosture | undefined): StandingAnswerFacts;
209
+ }>, gate: CheckpointGate, governance: GovernancePosture | undefined,
210
+ /** 部署名单门对这一行的调用出不出它自己的问(`approval.ts` 的 `deploymentListAsks`,调用方现读活配置;停驻行上只有 wire 名)。 */
211
+ deploymentListAsks: boolean): StandingAnswerFacts;
188
212
  /** A live approval frame delivered to whoever tails this run's stream. `type` IS the SSE event name (named-event
189
213
  * convention, same as question). The shell renders `tool_approval` as the CC three-choice card and dismisses on
190
214
  * `tool_approval_complete`. */
@@ -265,14 +289,14 @@ export interface ToolApprovalFrame {
265
289
  * (`ApprovalCardSchema` 没有这一位,`.strict()`)⇒ 断连重放的 `approval_request` 帧(`buildReplayFrame` 只回放卡)上它恒缺席,
266
290
  * 消费方读队列行 / 活卡帧 / `livePending` 行(8D-6:此前这里写的「`card_json` 只读 `AskRequest.mandated`」是错句,T4② 车OC 改真)。
267
291
  * 🔴 **server 不判「是不是强制」**(判定属主在 core 的闸站),只**读**闸站盖好的这一位 —— 读者恰一个:
268
- * S-660 起的会话级放行判据 {@link standingAnswerMayRetire}(会话授权是**上一次**应答记下的站着的答案,
292
+ * S-660 起的会话级放行判据 {@link standingAnswerHome}(会话授权是**上一次**应答记下的站着的答案,
269
293
  * 不是对这一次调用的应答 ⇒ 强制卡上既不被既有 grant 代答、也不铸 grant)。上一段「一揽子 `onAsk:"allow"`
270
294
  * 席照常答得了」说的是**这一次调用的应答者**,本仓不改它。
271
295
  */
272
296
  mandated?: true;
273
297
  /**
274
298
  * **7.116.0(审计修复批 S4;ADDITIVE,`"tool_approval"` only)—— 这张卡上的「本会话放行」只放行这一次、不会被记住。**
275
- * 值 = 同一只判据 {@link standingAnswerMayRetire} 的否定(四位:治理强制 / 必须真人 / 强制 / 应答还授予别的东西),回决侧写授权
299
+ * 在场 ⇔ 同一只判据 {@link standingAnswerHome} 答 `none`(治理强制 / 必须真人 / 强制 / 应答还授予别的东西 / 类集清不掉),回决侧写授权
276
300
  * (`finishRespond`)读的是同一份事实 ⇒ 帧说不可记 ⇔ 点了 `allow_session` 回执 `rememberApplied:false`。壳读这一位决定渲不渲
277
301
  * 「本会话放行」,不再从 `requiresRealApproval` / `mandated` / `governanceForced` 三位自己推断(那一推断漏掉第四个理由:core 7.41.0
278
302
  * 的 `Skill` 卡批准即授予清单,三位全无)。`card_json` 卡面、`livePending` 行、运维队列行同名同义。
@@ -752,8 +776,8 @@ export declare const READ_ROOT_GRANT_ALLOW_ONLY = "readRootGrant is only meaning
752
776
  /** S-720:`readRootGrant` 的形 —— 卡上 `readRootCandidate.dir` 的原串(非空,≤ core `READ_ROOT_CANDIDATE_DIR_MAX`;超限整只拒,不截:截过的目录是另一个目录)。 */
753
777
  export declare const READ_ROOT_GRANT_SHAPE = "readRootGrant must be the card's readRootCandidate.dir sent back byte for byte \u2014 a non-empty string of at most 1024 characters";
754
778
  export declare const PERSIST_RULE_SCOPE_REFUSED = "this endpoint does not accept a rule scope \u2014 the server mints it from where the approval happened (send neither `scope` nor `persistRule.scope`)";
755
- /** Validate the respond body — the closed three-choice enum (rationale in the module header). */
756
- export declare function parseToolApprovalResponse(body: unknown): {
779
+ /** {@link parseToolApprovalResponse} 的成功分支形(`respond()` 拆分出的私有收尾方法复用,避免裸内联匿名类型重复三处)。 */
780
+ type ParsedToolApproval = {
757
781
  ok: true;
758
782
  value: ToolApprovalDecision;
759
783
  updatedInput?: unknown;
@@ -761,10 +785,18 @@ export declare function parseToolApprovalResponse(body: unknown): {
761
785
  note?: string;
762
786
  decidedBy: ApprovalDecidedBy;
763
787
  readRootGrant?: string;
764
- } | {
788
+ };
789
+ /**
790
+ * Validate the respond body — the closed three-choice enum (rationale in the module header).
791
+ *
792
+ * 失败形的机读位:体里带着退役键 `settledBy` 的任何一次拒(无论这次拒的**句子**说的是它还是体里别的毛病 —— 校验是顺序短路的,先撞上谁谁说话)都另带
793
+ * 机读位 `field`({@link retiredDecisionKeyFacts} 的 `extra`,三条决定口同一只铸点;`respond()` 映射 400 时带出);拒的成因就是退役键时,**句**也取自同一铸点。
794
+ * 铸在**这一层**而不是散在下面每个失败 return 上:一个事实一处读。
795
+ */
796
+ export declare function parseToolApprovalResponse(body: unknown): ParsedToolApproval | ({
765
797
  ok: false;
766
798
  error: string;
767
- };
799
+ } & Partial<RetiredDecisionKeyFacts["extra"]>);
768
800
  /**
769
801
  * [ref] R-13 C —— `UNATTENDED_APPROVAL_POLICY` 的**闭集**词表([ref]Q1「裁2:A+C」/[ref] 裁②)。
770
802
  *
@@ -809,10 +841,9 @@ export declare function buildUnattendedPolicyRefusal(reason: string): Unattended
809
841
  * 铸:park 政策的路由答 `"unavailable"`,deny 政策的 {@link UnattendedPolicyRefusal}。没有布尔臂(S-689:裸 `false`
810
842
  * 是人的拒绝,这个口从不替人说话)。 */
811
843
  export type UnattendedAskOutcome = Extract<AskOutcome, "unavailable"> | UnattendedPolicyRefusal;
812
- /** S-877 候裁 16:耐久会话授权店的读面(`src/plugins/approval-exemption-store.ts` 的 `has`,结构型 —— 协调器不值引店模块)。 */
813
- export interface SessionGrantReader {
814
- has(sessionId: string, canonicalToolName: string): Promise<boolean>;
815
- }
844
+ /** S-877 候裁 16:耐久会话授权店(`src/plugins/approval-exemption-store.ts` 的同一只店):审批席读它(引擎的问),活卡上部署名单那一问的
845
+ * 「本会话放行」写它(名单门在出问之前读同一行)。 */
846
+ export type SessionGrantStore = Pick<ApprovalExemptionStore, "has" | "grant">;
816
847
  /**
817
848
  * [ref] 车3 刀 3b —— 「流内审批协议到底上不上场」的**单一谓词**(设计稿 §2.3 注入 / §2.4 能力面 /
818
849
  * §8.4 park 设施自检,三处同一份判据)。
@@ -1104,9 +1135,14 @@ export declare class ToolApprovalCoordinator {
1104
1135
  * 腿闭(`runWithContext` finally)。**纯观察面**:sink 自己吞写失败并 `recordFailOpen`,任何一次审计
1105
1136
  * 写都不得改变审批终局(店头注成文)。SQL 车道恒缺席(那里有真 ask 店)。 */
1106
1137
  private readonly askAudit;
1107
- /** S-877 候裁 16 → A:耐久会话授权店的**读面**(`/decide` 的 `remember:"session"` 写、本席读)。在场 ⇔ 部署有 store backend
1108
- * (装配点 `boot/coordinators.ts`,与 `/decide` 写的是**同一只**店)。读口唯一 = {@link standingGrantSettles}。 */
1138
+ /** S-877 候裁 16 → A:耐久会话授权店(`/decide` 的 `remember:"session"` 写、本席读)。在场 ⇔ 部署有 store backend
1139
+ * (装配点 `boot/coordinators.ts`,与 `/decide` 写的是**同一只**店)。读口唯一 = {@link standingGrantSettles};写口唯一 = {@link recordDeploymentListAnswer}
1140
+ * (活卡上部署名单那一问的「本会话放行」)。 */
1109
1141
  private readonly sessionGrants;
1142
+ /** **问的属主**的读法:部署名单门对这只调用出不出它自己的问(`approval.ts` 的 `deploymentListAsks` 对活配置现读,装配点 `boot/coordinators.ts`)。
1143
+ * 缺席(没有豁免店的装配)⇒ 没有一张卡读作名单的问 —— 名单那一问的答案没有地方记,它就按引擎的类表读(记不住)。
1144
+ * 活的一问(`AskRequest`)上只有 wire 名(没有调用的别名面)⇒ 名单靠别名命中的调用读不出属主,按引擎的问读(记不住;方向是多问)。 */
1145
+ private readonly deploymentListAsks;
1110
1146
  /** S-929:有序关停已开始(`drainForShutdown` 锁存,不复位 —— 进程正在退出)。之后登记的每一张卡在登记那一刻按关停收场。 */
1111
1147
  private shutdownDraining;
1112
1148
  /** S-929:此刻在 `askBroadcast` 里的 ask 数(进出成对,见那里的 try/finally)—— 关停等待「还没登记进 `pending` 的卡」的计数。 */
@@ -1143,8 +1179,12 @@ export declare class ToolApprovalCoordinator {
1143
1179
  parkTombstonePerPrincipalMax?: number;
1144
1180
  /** [ref] (c):local 车道审计 sink(见 {@link ToolApprovalCoordinator.askAudit})。 */
1145
1181
  askAudit?: ApprovalAskAuditSink;
1146
- /** S-877 候裁 16:耐久会话授权店的读面(见 {@link ToolApprovalCoordinator.sessionGrants})。 */
1147
- sessionGrants?: SessionGrantReader;
1182
+ /** S-877 候裁 16:耐久会话授权店(见 {@link ToolApprovalCoordinator.sessionGrants})。 */
1183
+ sessionGrants?: SessionGrantStore;
1184
+ /** 问的属主的读法(见 {@link ToolApprovalCoordinator.deploymentListAsks})。只能与 `sessionGrants` 同在:名单那一问的答案就记在那只店里。 */
1185
+ deploymentListAsks?: (call: {
1186
+ readonly toolName: string;
1187
+ }) => boolean;
1148
1188
  /** S-929 F1:park 提交的观察面(见 {@link ToolApprovalCoordinator.parkCommits})。 */
1149
1189
  parkCommits?: ParkCommitWatch;
1150
1190
  });
@@ -1385,7 +1425,7 @@ export declare class ToolApprovalCoordinator {
1385
1425
  * 🔴 **S-877 候裁 16 → A —— 站着的答案(会话授权)的唯一兑现口**(主会话裁定 2026-09-29;core 7.34.0 wire 段逐字指定的兑现位:
1386
1426
  * 「a host approval seat that settles asks from a session grant reads it [`authoredBy`] to keep its hands off」)。
1387
1427
  *
1388
- * **一条判据**:一只 ask 由会话授权代答 ⇔ ① 它是站着的答案退得掉的一问({@link standingAnswerMayRetire} —— 治理强制 /
1428
+ * **一条判据**:一只 ask 由会话授权代答 ⇔ ① 判据说它的站着的答案在本席({@link standingAnswerHome} 答 `seat` —— 治理强制 /
1389
1429
  * `requiresRealApproval` / `mandated` 三位的既有排除一字不动;运维「恒问人」的卡(`APPROVAL_NEVER_AUTO` / 耐久预算耗尽)
1390
1430
  * 自 S-877 起盖部署出身 ⇒ core 盖 `mandated`,同落这一臂)∧ ② 有真会话 ∧ ③ 两只授权店任一命中:live `allow_session` 的
1391
1431
  * 进程内表(按能力桶,[ref]② 修2)或耐久 `/decide` `remember:"session"` 的豁免店(按 canonical 工具名,`/decide` 写的同一只店;
@@ -1397,6 +1437,8 @@ export declare class ToolApprovalCoordinator {
1397
1437
  * 盖 `mandated`(`ruleClearanceOf` 的 inheritedUnresolved / ancestorResolved 门),本席依约不退役 —— 只有策略层的「不问」够得着继承链
1398
1438
  * (主会话裁定原想退役那三只探针,施工中被 test/parked-decide-leg.test.ts「[候裁② 修]remember 全链行为」证伪,收车档 ⑩ 16-⑤)。
1399
1439
  * 本席的两个入口(`askBroadcast` 开头、窗 0 / 无活流身份席 {@link boundUnattendedAsk})都只调这一只。
1440
+ * 本席只代答判据说 `seat` 的问({@link standingAnswerHome});部署名单自己的问(`server_store`)的站着的答案由名单门在出问之前读过了
1441
+ * (问到了这里 = 店里没有),本席手里的授权是引擎那些问的答案;本席代答还会被记成一个人的应答。
1400
1442
  */
1401
1443
  private standingGrantSettles;
1402
1444
  /** 耐久豁免店的一次读(fail-closed:读抛 **或超时** ⇒ 不命中 + warn)。只由 {@link standingGrantSettles} 调。
@@ -1640,6 +1682,18 @@ export declare class ToolApprovalCoordinator {
1640
1682
  status: number;
1641
1683
  body: unknown;
1642
1684
  }>;
1685
+ /**
1686
+ * **部署名单(`APPROVAL_REQUIRE`)那一问的「本会话放行」只落 server 的会话豁免店**(谁问的,谁的站着的答案算数):名单的问是 server 铸的,
1687
+ * 名单门在出问之前读这只店(键 = 会话 × wire 工具名,与耐久 `/decide` 的 `remember:"session"` 写下的同形),命中就不出问 —— 引擎看到的
1688
+ * 是「这次调用没出问」。不铸进程内授权(那是审批席替引擎的问记的答案;本席不代答名单的问),不交任何东西给引擎的规则店。
1689
+ *
1690
+ * 只在这次回决**真成了终局、而且终局是本请求写的**之后写(200 那一支 ∧ {@link RulePersistEligibility} 合格;CAS 输 / 条目已被别人结算 ⇒ 不写;
1691
+ * 提交歧义臂 —— 行上只证得出「有一条同向决议」,可能是并发的另一路「只批这一次」写的 —— 撤销资格 ⇒ 不写):一次不是自己的放行不留站着的答案。写在结算之后,
1692
+ * 所以同一条腿紧跟着的下一问若先于这次写落到名单门,会多出一张卡(方向是多问,不是静默放行)。回执 `rememberApplied` 按这次写的真实结果说:
1693
+ * 写成 ⇒ `true`;写失败 / 超时 ⇒ `false` + 一行 warn(这一次照旧放行,与 `/decide` 腿「grant 失败不回滚 approve」同向)。
1694
+ * 不是名单的问、记不住的卡(卡上还带着别人的问、强制、授予绑定 ……)、没有会话 ⇒ 原样交回(那几形的回执已由 {@link finishRespond} 定)。
1695
+ */
1696
+ private recordDeploymentListAnswer;
1643
1697
  /**
1644
1698
  * [ref]:自由文本规则的**裁决前**文本预检 —— 回 `undefined` = 放行(继续正常回决),回一个响应 = 400。
1645
1699
  *