@sema-agent/server 7.104.0 → 7.106.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 (206) hide show
  1. package/MIGRATION.md +114 -0
  2. package/USAGE.md +193 -52
  3. package/deploy/sema-up/chart/templates/server-v4pro.yaml +2 -0
  4. package/deploy/sema-up/cluster-up.sh +15 -2
  5. package/deploy/sema-up/kube-up.sh +8 -3
  6. package/deploy/sema-up/lib/flagshape.sh +29 -0
  7. package/deploy/sema-up/lib/parse-machines.py +4 -1
  8. package/deploy/sema-up/sema-up.sh +11 -9
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +3 -1
  10. package/dist/approval-card.d.ts +1 -1
  11. package/dist/approval.d.ts +6 -1
  12. package/dist/approval.js +4 -4
  13. package/dist/auth-bridge.d.ts +14 -0
  14. package/dist/auth-bridge.js +7 -0
  15. package/dist/auto-mode-face.d.ts +49 -40
  16. package/dist/auto-mode-face.js +6 -68
  17. package/dist/bench/s1/live-deps.d.ts +9 -2
  18. package/dist/bench/s1/live-deps.js +13 -8
  19. package/dist/boot/budget-tracing.js +3 -2
  20. package/dist/boot/config-center.d.ts +4 -0
  21. package/dist/boot/config-center.js +95 -37
  22. package/dist/boot/coordinators.d.ts +1 -0
  23. package/dist/boot/coordinators.js +3 -1
  24. package/dist/boot/device-lane.d.ts +2 -1
  25. package/dist/boot/execution-env.d.ts +11 -0
  26. package/dist/boot/execution-env.js +30 -6
  27. package/dist/boot/execution-lane.js +2 -3
  28. package/dist/boot/leader.d.ts +2 -1
  29. package/dist/boot/leader.js +2 -0
  30. package/dist/boot/parked-revive-gate.d.ts +10 -7
  31. package/dist/boot/parked-revive-gate.js +4 -11
  32. package/dist/boot/resolve-spec.d.ts +1 -4
  33. package/dist/boot/resolve-spec.js +44 -21
  34. package/dist/boot/runner-deps.d.ts +5 -8
  35. package/dist/boot/runner-deps.js +5 -10
  36. package/dist/boot/side-query-lane.d.ts +2 -2
  37. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  38. package/dist/boot/stage-03-store-backend.js +2 -2
  39. package/dist/boot/stage-04-stores.d.ts +1 -1
  40. package/dist/boot/stage-05-execution-env.d.ts +2 -1
  41. package/dist/boot/stage-05-execution-env.js +2 -2
  42. package/dist/boot/stage-06-runners.d.ts +3 -2
  43. package/dist/boot/stage-06-runners.js +2 -5
  44. package/dist/boot/stage-07-capability-layer.d.ts +2 -2
  45. package/dist/boot/stage-07-capability-layer.js +1 -2
  46. package/dist/boot/stage-08-reapers.d.ts +2 -2
  47. package/dist/boot/stage-09-leader.d.ts +2 -2
  48. package/dist/boot/stage-09-leader.js +2 -2
  49. package/dist/boot/stage-10-http-server.d.ts +2 -2
  50. package/dist/boot/stage-10-http-server.js +11 -7
  51. package/dist/boot/stores.d.ts +1 -1
  52. package/dist/boot/stores.js +6 -4
  53. package/dist/brain.d.ts +24 -2
  54. package/dist/brain.js +29 -10
  55. package/dist/budget.js +3 -0
  56. package/dist/capabilities/center-wiring.d.ts +64 -0
  57. package/dist/capabilities/center-wiring.js +24 -0
  58. package/dist/capabilities/composite-members.d.ts +89 -0
  59. package/dist/capabilities/composite-members.js +20 -0
  60. package/dist/capabilities/execution-lane.d.ts +3 -0
  61. package/dist/capabilities/execution-lane.js +5 -2
  62. package/dist/config-catalog.d.ts +30 -5
  63. package/dist/config-catalog.js +75 -26
  64. package/dist/config-center/apply-effective.d.ts +1 -1
  65. package/dist/config-center/apply-effective.js +7 -8
  66. package/dist/config-center/apply-ledger.d.ts +17 -0
  67. package/dist/config-center/apply-ledger.js +5 -0
  68. package/dist/config-center/center-request.d.ts +17 -0
  69. package/dist/config-center/center-request.js +4 -0
  70. package/dist/config-center/pull-state.d.ts +29 -0
  71. package/dist/config-center/pull-state.js +2 -0
  72. package/dist/config-types.d.ts +41 -14
  73. package/dist/config-types.js +1 -0
  74. package/dist/config.d.ts +50 -2
  75. package/dist/config.js +111 -23
  76. package/dist/cross-session-settings.d.ts +1 -1
  77. package/dist/deployment-governance.d.ts +12 -1
  78. package/dist/deployment-governance.js +2 -0
  79. package/dist/execution-lane-caps.d.ts +30 -2
  80. package/dist/execution-lane-caps.js +12 -0
  81. package/dist/file-history-disclosure.d.ts +7 -31
  82. package/dist/file-history-disclosure.js +12 -0
  83. package/dist/fleet/fleet-bus.d.ts +5 -1
  84. package/dist/fleet-client.d.ts +18 -13
  85. package/dist/fleet-client.js +32 -22
  86. package/dist/fleet-lease.d.ts +72 -47
  87. package/dist/fleet-lease.js +66 -14
  88. package/dist/governance-http-status.d.ts +0 -1
  89. package/dist/governance-http-status.js +0 -1
  90. package/dist/hosted-posture.d.ts +6 -17
  91. package/dist/hosted-posture.js +11 -6
  92. package/dist/http/admission.d.ts +1 -1
  93. package/dist/http/admission.js +14 -9
  94. package/dist/http/approval-sweeps.js +13 -1
  95. package/dist/http/dispatch.d.ts +18 -0
  96. package/dist/http/dispatch.js +18 -5
  97. package/dist/http/durable-run-leg.js +2 -0
  98. package/dist/http/request-guards.d.ts +25 -0
  99. package/dist/http/request-guards.js +25 -5
  100. package/dist/http/resume-legs.d.ts +1 -1
  101. package/dist/http/resume-legs.js +19 -11
  102. package/dist/http/route-ctx.d.ts +9 -0
  103. package/dist/http/route-table.d.ts +15 -4
  104. package/dist/http/route-table.js +6 -5
  105. package/dist/http/routes/approvals-assistant.js +0 -1
  106. package/dist/http/routes/capabilities.js +12 -11
  107. package/dist/http/routes/config-catalog.js +2 -1
  108. package/dist/http/routes/diagnostics.js +2 -0
  109. package/dist/http/routes/fleet.d.ts +3 -2
  110. package/dist/http/routes/images.js +0 -1
  111. package/dist/http/routes/runs.js +2 -8
  112. package/dist/http/routes/session-sync.js +6 -1
  113. package/dist/http/routes/sessions.js +5 -2
  114. package/dist/http/routes/side-query.js +8 -1
  115. package/dist/http/routes/tasks.js +4 -1
  116. package/dist/http/routes/trace-usage.d.ts +2 -0
  117. package/dist/http/routes/trace-usage.js +4 -9
  118. package/dist/http/server.d.ts +29 -8
  119. package/dist/http/server.js +4 -1
  120. package/dist/http/session-inherit-env.d.ts +37 -0
  121. package/dist/http/session-inherit-env.js +6 -0
  122. package/dist/http/sse-lifecycle.d.ts +1 -1
  123. package/dist/http/sse-log.d.ts +12 -9
  124. package/dist/http/sse-log.js +1 -8
  125. package/dist/http/wire-types.d.ts +19 -28
  126. package/dist/index.d.ts +2 -1
  127. package/dist/index.js +2 -1
  128. package/dist/key-resolver.d.ts +74 -19
  129. package/dist/key-resolver.js +65 -7
  130. package/dist/leader/leader.d.ts +6 -0
  131. package/dist/leader/leader.js +2 -1
  132. package/dist/leader/repair-wire.js +2 -1
  133. package/dist/leader/wire.d.ts +9 -1
  134. package/dist/leader/wire.js +10 -6
  135. package/dist/observability/fail-open.d.ts +9 -1
  136. package/dist/observability/fail-open.js +9 -1
  137. package/dist/observability/metrics.js +3 -1
  138. package/dist/observability/permission-rule-events.d.ts +1 -1
  139. package/dist/observability/permission-rule-events.js +1 -1
  140. package/dist/observability/run-terminal.d.ts +82 -9
  141. package/dist/observability/run-terminal.js +112 -9
  142. package/dist/observability/secret-env-scrub.js +1 -1
  143. package/dist/observability/tool-trace.d.ts +2 -0
  144. package/dist/operator-ask.d.ts +29 -1
  145. package/dist/operator-ask.js +6 -0
  146. package/dist/parked-decide.d.ts +14 -1
  147. package/dist/parked-decide.js +8 -1
  148. package/dist/plugins/image-bake-store-sql.d.ts +0 -2
  149. package/dist/plugins/image-bake-store-sql.js +0 -5
  150. package/dist/plugins/retention-store-sql.js +1 -0
  151. package/dist/plugins/run-store-sql.d.ts +3 -2
  152. package/dist/plugins/schema-indexes.js +1 -0
  153. package/dist/plugins/session-policy-store-sql.d.ts +38 -7
  154. package/dist/plugins/session-policy-store-sql.js +73 -18
  155. package/dist/plugins/sql-driver.d.ts +1 -1
  156. package/dist/plugins/store-backend.d.ts +17 -6
  157. package/dist/plugins/store-backend.js +16 -5
  158. package/dist/plugins/tidb-pool.js +5 -4
  159. package/dist/read-face-posture.d.ts +25 -4
  160. package/dist/read-face-posture.js +9 -0
  161. package/dist/request-key-closure.js +2 -2
  162. package/dist/request-value-closure.d.ts +17 -5
  163. package/dist/request-value-closure.js +12 -0
  164. package/dist/router/route-orchestration.d.ts +11 -0
  165. package/dist/router/route-orchestration.js +7 -6
  166. package/dist/rules-consent.js +0 -11
  167. package/dist/run-local.js +4 -2
  168. package/dist/runs.d.ts +2 -1
  169. package/dist/runs.js +4 -4
  170. package/dist/runtime-caps-resolver.d.ts +4 -10
  171. package/dist/runtime-governance.d.ts +8 -11
  172. package/dist/runtime-governance.js +10 -41
  173. package/dist/sealed-key.d.ts +0 -40
  174. package/dist/sealed-key.js +0 -20
  175. package/dist/server-secret-env.d.ts +11 -12
  176. package/dist/session-policy-wire.d.ts +100 -12
  177. package/dist/session-policy-wire.js +71 -8
  178. package/dist/session-titler.d.ts +11 -0
  179. package/dist/session-titler.js +6 -2
  180. package/dist/sql-engine-posture.d.ts +2 -2
  181. package/dist/store-posture.d.ts +53 -0
  182. package/dist/store-posture.js +32 -0
  183. package/dist/task-settings.d.ts +34 -45
  184. package/dist/task-settings.js +46 -25
  185. package/dist/tool-approval.d.ts +51 -22
  186. package/dist/tool-approval.js +44 -8
  187. package/dist/tool-declaration-http-status.d.ts +12 -0
  188. package/dist/tool-declaration-http-status.js +12 -0
  189. package/dist/trace/core-keyset-guard.d.ts +7 -7
  190. package/dist/trace/engine-notice-wire.d.ts +1 -1
  191. package/dist/trace/engine-notice-wire.js +5 -0
  192. package/dist/trace/project.d.ts +13 -7
  193. package/dist/trace/project.js +54 -6
  194. package/dist/trace/redact.d.ts +7 -0
  195. package/dist/trace/redact.js +153 -61
  196. package/dist/trace/wire-projection-faces.d.ts +1 -1
  197. package/dist/trace/wire-projection-faces.js +1 -1
  198. package/dist/unattended-permission-mode.d.ts +110 -0
  199. package/dist/unattended-permission-mode.js +65 -0
  200. package/package.json +3 -3
  201. package/dist/boot/session-shell-gate-registry.d.ts +0 -39
  202. package/dist/boot/session-shell-gate-registry.js +0 -21
  203. package/dist/plugins/memory-session-policy-store.d.ts +0 -46
  204. package/dist/plugins/memory-session-policy-store.js +0 -52
  205. package/dist/roster-bound-http-status.d.ts +0 -6
  206. package/dist/roster-bound-http-status.js +0 -6
@@ -0,0 +1,53 @@
1
+ /**
2
+ * S-866 ①(设计稿 v1.1 §2①)—— 「这台副本**真的在持久化吗**」的单一推导点:caps `store`、`/health.storeDegraded` / `durable:false`、
3
+ * gauge `store_backend_degraded` 三面读**同一只装配值**({@link StorePosture},`boot/stores.ts` 铸一次)。
4
+ *
5
+ * ## 病
6
+ * 7.105.0 及以前,店降级的痕迹是 gauge + `/health` 两位 + caps `sql:null` —— 而 `sql:null` 同时是「本来就没配 SQL」「local 文件店」
7
+ * 「降级到内存」「最近一次连接初始化被拒」四件事;调用方分不清「以为在持久化,其实在内存」这最坏的一形。
8
+ *
9
+ * ## 形
10
+ * `declared` = 运维的声明(`DB_BACKEND` 词,含缺省推导);`backend` = **装配出的**后端 kind(没装出后端 = `memory`);两者不等是合法的
11
+ * 形(`DB_BACKEND=pg` + `SESSION_BACKEND=memory` = 声明 pg、刻意不持久;auto 降级;缺省推导 local 起不来)—— 两位都上 wire,
12
+ * 由调用方自己读出落差。`degradedReason` present-iff 降级臂走过(按后端族定词,{@link storeDegradedReasonFor})。
13
+ * 顶层 `sql` 并入 `store.sql`(硬 breaking,零别名;对象逐字同形)。
14
+ */
15
+ import { type DbBackendWord } from "./config-types.js";
16
+ import type { StoreBackend } from "./plugins/store-backend.js";
17
+ import type { SqlEngineFacts } from "./plugins/sql-driver.js";
18
+ import { type SqlEngineCapability } from "./sql-engine-posture.js";
19
+ /**
20
+ * 降级词闭集(**按后端族**,不按失败发生在哪一步 —— SQL 池懒连,连接失败第一次就出现在 `ensureSchema` 里,与 DDL 失败同一个调用,
21
+ * 按调用位置分不出「连不上」与「建表失败」):
22
+ * · `db_unreachable` —— SQL 族(`mysql` / `pg`)在 `SESSION_BACKEND=auto` 下打不开(连接或建表);
23
+ * · `local_unwritable` —— **缺省推导**的 local 文件店构造期失败(mkdir / 数据根锁 / 权限 / 只读挂载 / 磁盘满)。
24
+ */
25
+ export declare const STORE_DEGRADED_REASONS: readonly ["db_unreachable", "local_unwritable"];
26
+ export type StoreDegradedReason = (typeof STORE_DEGRADED_REASONS)[number];
27
+ /** 降级臂按后端族定词(穷举 switch:词表加员 ⇒ 编译红)。`memory` 从不尝试打开后端,走不到降级臂 —— 到了就是装配缺陷,响亮抛。 */
28
+ export declare function storeDegradedReasonFor(declared: DbBackendWord): StoreDegradedReason;
29
+ /** 装配值(纯数据;`boot/stores.ts` 铸一次,gauge / `/health` / caps 三面同读)。 */
30
+ export interface StorePosture {
31
+ readonly declared: DbBackendWord;
32
+ readonly backend: DbBackendWord;
33
+ readonly degradedReason?: StoreDegradedReason;
34
+ }
35
+ /** `build*`(纯数据)。`assembled` = 装配出的 `StoreBackend.kind`;缺席 = 没装出后端 ⇒ `memory`。 */
36
+ export declare function buildStorePosture(a: {
37
+ readonly declared: DbBackendWord;
38
+ readonly assembled: StoreBackend["kind"] | undefined;
39
+ readonly degradedReason: StoreDegradedReason | undefined;
40
+ }): StorePosture;
41
+ /** caps `store` 的形。 */
42
+ export interface StoreCapability {
43
+ readonly declared: DbBackendWord;
44
+ readonly backend: DbBackendWord;
45
+ readonly durable: boolean;
46
+ readonly degradedReason?: StoreDegradedReason;
47
+ /** 7.105.0 及以前的顶层 `sql` 对象,原样搬入(`{engine, isolation, txnMode}`)。present-iff `backend ∈ {mysql, pg}` ∧ 驱动此刻给得出
48
+ * 回读复核过的事实 —— 最近一次连接初始化被拒(S-137 ④)时驱动交 `null`,本键随之缺席(此前是顶层 `sql:null`)。 */
49
+ readonly sql?: SqlEngineCapability;
50
+ }
51
+ /** 装配值 → caps 投影(纯函数)。 */
52
+ export declare function projectStoreCapability(posture: StorePosture, sqlFacts: SqlEngineFacts | null | undefined): StoreCapability;
53
+ //# sourceMappingURL=store-posture.d.ts.map
@@ -0,0 +1,32 @@
1
+ import {} from "./config-types.js";
2
+ import { projectSqlEngineCapability } from "./sql-engine-posture.js";
3
+ export const STORE_DEGRADED_REASONS = ["db_unreachable", "local_unwritable"];
4
+ export function storeDegradedReasonFor(declared) {
5
+ switch (declared) {
6
+ case "mysql":
7
+ case "pg":
8
+ return "db_unreachable";
9
+ case "local":
10
+ return "local_unwritable";
11
+ case "memory":
12
+ throw new Error("store posture: DB_BACKEND=memory never opens a backend, so it cannot degrade — this is a wiring defect");
13
+ default: {
14
+ const unhandled = declared;
15
+ throw new Error(`store posture: unknown DB_BACKEND word ${String(unhandled)}`);
16
+ }
17
+ }
18
+ }
19
+ export function buildStorePosture(a) {
20
+ return { declared: a.declared, backend: a.assembled ?? "memory", ...(a.degradedReason !== undefined ? { degradedReason: a.degradedReason } : {}) };
21
+ }
22
+ export function projectStoreCapability(posture, sqlFacts) {
23
+ const sql = posture.backend === "mysql" || posture.backend === "pg" ? projectSqlEngineCapability(sqlFacts) : null;
24
+ return {
25
+ declared: posture.declared,
26
+ backend: posture.backend,
27
+ durable: posture.backend !== "memory",
28
+ ...(posture.degradedReason !== undefined ? { degradedReason: posture.degradedReason } : {}),
29
+ ...(sql !== null ? { sql } : {}),
30
+ };
31
+ }
32
+ //# sourceMappingURL=store-posture.js.map
@@ -124,6 +124,12 @@ export declare function effectiveThinking(reasoningEffort: unknown, ultracode: b
124
124
  * field is the explicit per-turn intent → it WINS over a bundle defaultMode). Creates a minimal settings object when
125
125
  * no `body.settings` bundle was sent. The result flows through {@link applyTaskSettings} (tighten-only). */
126
126
  export declare function withPermissionMode(settings: ParsedTaskSettings | undefined, mode: SettingsPermissionMode): ParsedTaskSettings;
127
+ /**
128
+ * 7.106.0 合并复审 X8(opus O-6)—— 请求**没有表态**模式时引擎实际跑的那个词。core 7.34.0 [ref]「Absent = `default`」
129
+ * (装树 `dist/core/runner/prepare-task.js` 的 `requested ?? "default"`)。只给**观测面**用(hook 载荷的 `permission_mode`,CC 恒带这个键):
130
+ * spec 过境点照旧不写键(`effectivePermissionMode` 缺席 ⇒ 不替调用方选 —— 那是引擎的默认,不是本仓的表态)。
131
+ */
132
+ export declare const ENGINE_ABSENT_PERMISSION_MODE: "default";
127
133
  /**
128
134
  * [ref] §6-1 —— 本请求的**生效** permission mode,单点。优先序逐字同 {@link withPermissionMode}:
129
135
  * 顶层 `body.permissionMode`(本轮显式表态)赢过 bundle 的 `settings.permissions.defaultMode`;两处
@@ -137,51 +143,6 @@ export declare function withPermissionMode(settings: ParsedTaskSettings | undefi
137
143
  export declare function effectivePermissionMode(body: {
138
144
  permissionMode?: unknown;
139
145
  }, settings: ParsedTaskSettings | undefined): SettingsPermissionMode | undefined;
140
- /**
141
- * [ref] §2 —— 显式 permission mode → `TaskSpec.shellGate` 档位的**翻译表(单一真源)**。
142
- *
143
- * · `bypassPermissions` ⇒ `"off"` —— CC `--dangerously-skip-permissions` 的对位裁定
144
- * (与 fs-write 面的 bypass 臂两面归一,见 {@link deriveSettingsPolicy})。
145
- * · `auto` ⇒ `"classify"` —— **[ref] §8 开口按施工期新披露亲裁改译**(原裁定字面是 off,
146
- * §8 保留「core auto 分类器覆盖面有新披露时可独立再裁,表驱动一行」——新披露见下段):
147
- *
148
- * ⚠️ **`auto` 这一行有一条待裁的开口([ref] §8 明列「core auto 分类器覆盖面有新披露时可独立再裁,
149
- * 表驱动一行」)。施工期交叉复审给出了那条新披露,已亲读安装包核实**:
150
- * · core 只在**已经产生 `ask`** 之后才咨询分类器(`dist/core/hooks.js`:`if (input.autoMode &&
151
- * decision.action === "ask" …)`);
152
- * · 而 `shellGate:"off"` 下 core **不铸任何 shell 面的门**(`dist/core/runner/prepare-task.js` 的
153
- * `shellGate === "off"` 臂反而发一条 `classification:"shell-gate-off"` 的 onError:「真可写 Bash 挂着
154
- * 却没有 shell 安全轴折叠」)。
155
- * 两条合起来:`auto` × off ⇒ Bash 既不产 ask、分类器也就永不被咨询 —— off 档下「筛选权交给分类器」
156
- * 不成立。故 auto 归 classify 组:分类器坐在 classify 产的 ask 之上,语义才真是「交给分类器」
157
- * (良性只读命令由分类器自动放行,其余 ask;这正是 auto 模式的本义)。发车帖向 [ref] 裁定链披露此
158
- * 一行偏离;core auto 分类器若来日在 off 档下也有咨询点,可再裁回(表驱动一行)。
159
- * · `default` / `acceptEdits` / `plan` ⇒ `"classify"` —— 这一档原先由壳无条件注入 `MANUAL_MODE_SHELL_GATE`
160
- * 供给(车道缺省走了 operator 通道),现在归位到表态轴。`plan` 本身 handsReadOnly(core 明写此档下
161
- * shellGate 无效),给它 classify 纯为一致性。
162
- *
163
- * 🔴 **入参非可选**:缺席(无表态)不是这张表的一行 —— 它的语义是「不写这个键」,只能在调用点判。
164
- * 把它折进来会逼出一个 `undefined` 返回值,而那正是「写 off」与「不写」被混同的入口(core 的
165
- * 缺席默认是 off,但**写**一个 off 会成为 governance 的 base,与缺席不是同一件事)。
166
- *
167
- * 🔴 **闭集穷举表,无 default 臂**:五个模式词是封闭词表,新增一个模式而不更新本表
168
- * 是**编译错误**([ref] 的安全轴纪律:词表的未知项不许有静默兜底臂)。
169
- */
170
- export declare function shellGateForMode(effMode: SettingsPermissionMode): "off" | "classify";
171
- /**
172
- * [ref] —— 生效模式 → `TaskSpec.writeFace`(**写**面的 roots 容纳判断)。`"open"` = 工作区根之外也能写;
173
- * `undefined` = **不写这个键**(core 缺省 `"roots"`,与 7.77.0 及以前逐字节相同)。表逐字在
174
- * {@link MODE_ENGINE_FACES},裁定与硬边界写在 `resolve-spec.ts` 的写点上。
175
- *
176
- * 🔴 与 {@link shellGateForMode} 同律:入参非可选 —— 「表态缺席」不是这张表的一行(那是调用点判的
177
- * 「整个键都不写」),把它折进来就会逼出第二个 `undefined` 语义。
178
- */
179
- export declare function writeFaceForMode(effMode: SettingsPermissionMode): "open" | undefined;
180
- /**
181
- * [ref] ① —— 生效模式 → `TaskSpec.autoModeRequested`(auto **意图**座,CC 250「用户开」半场)。
182
- * `true` 只有 auto 一行;`undefined` = 不写键。表逐字在 {@link MODE_ENGINE_FACES}。
183
- */
184
- export declare function autoModeRequestedForMode(effMode: SettingsPermissionMode): true | undefined;
185
146
  /**
186
147
  * S-346 —— [ref] 技能清单预算两旋钮的**值域谓词**(一对具名单铸点)。
187
148
  *
@@ -262,6 +223,34 @@ export declare const MAX_SETTINGS_PERMISSION_RULES = 1000;
262
223
  export declare const MAX_SETTINGS_ENV_VARS = 256;
263
224
  export declare const MAX_SETTINGS_ENV_VALUE_CHARS = 32768;
264
225
  export declare const MAX_SETTINGS_ENV_KEY_CHARS = 256;
226
+ /** 三桶(闭集;判据按此序逐桶判)。 */
227
+ export declare const SETTINGS_PERMISSION_BUCKETS: readonly ["allow", "deny", "ask"];
228
+ export type SettingsPermissionBucket = (typeof SETTINGS_PERMISSION_BUCKETS)[number];
229
+ /** 拒因(闭集;wire 字段 `reason` 的全部词)。 */
230
+ export declare const SETTINGS_PERMISSION_BUCKET_REFUSALS: readonly ["over_cap", "bad_entry"];
231
+ export type SettingsPermissionBucketRefusal = (typeof SETTINGS_PERMISSION_BUCKET_REFUSALS)[number];
232
+ /** 拒体的机读字段(按 `reason` 定形;原样进 wire 错误体 / `HttpError.extra`)。 */
233
+ export type SettingsPermissionBucketIssueFields = {
234
+ readonly field: `settings.permissions.${SettingsPermissionBucket}`;
235
+ readonly reason: "over_cap";
236
+ readonly count: number;
237
+ readonly cap: number;
238
+ } | {
239
+ readonly field: `settings.permissions.${SettingsPermissionBucket}`;
240
+ readonly reason: "bad_entry";
241
+ readonly index?: number;
242
+ };
243
+ /** 一次裁决的产物:wire 文案 + 机读字段。 */
244
+ export interface SettingsPermissionBucketIssue {
245
+ readonly message: string;
246
+ readonly fields: SettingsPermissionBucketIssueFields;
247
+ }
248
+ /**
249
+ * 三桶的**唯一**判据(纯函数)。`perms` 不是对象 ⇒ 不判(那一形归「settings.permissions must be an object」那道门);桶缺席 / `null`
250
+ * ⇒ 不判(与 `permissions: null` 的「容器缺席」同一约定)。逐桶按 {@link SETTINGS_PERMISSION_BUCKETS} 的序:不是数组 ⇒ `bad_entry`(无
251
+ * `index`);超帽 ⇒ `over_cap`(先判,拒体有界);首个非串 / 空串项 ⇒ `bad_entry` + 它的下标。全合法 ⇒ `null`。
252
+ */
253
+ export declare function settingsPermissionBucketIssue(perms: unknown): SettingsPermissionBucketIssue | null;
265
254
  /** 本 service 真正**消费**的 `settings` 顶层键 —— 闭集。每一个都有消费点:
266
255
  * `env`/`hooks`/`model`/`outputStyle`/`permissions`/`ultracode` 与两个 `skillListing*` 在
267
256
  * {@link parseTaskSettings};`webSearch` 在 `capabilities/scenarios.ts`(判决 `judgeRequestWebSearch`,单用户闸;形错在受理面 400,S-199)。
@@ -37,6 +37,7 @@ export function withPermissionMode(settings, mode) {
37
37
  const base = settings ?? {};
38
38
  return { ...base, permissions: { ...base.permissions, defaultMode: mode } };
39
39
  }
40
+ export const ENGINE_ABSENT_PERMISSION_MODE = "default";
40
41
  export function effectivePermissionMode(body, settings) {
41
42
  const requested = coercePermissionMode(body.permissionMode) ?? settings?.permissions?.defaultMode;
42
43
  const kill = settings?.permissions?.disableAutoMode;
@@ -44,22 +45,6 @@ export function effectivePermissionMode(body, settings) {
44
45
  return "default";
45
46
  return requested;
46
47
  }
47
- export function shellGateForMode(effMode) {
48
- return MODE_ENGINE_FACES[effMode].shellGate;
49
- }
50
- const MODE_ENGINE_FACES = {
51
- bypassPermissions: { shellGate: "off", writeFace: "open", autoModeRequested: undefined },
52
- auto: { shellGate: "classify", writeFace: undefined, autoModeRequested: true },
53
- default: { shellGate: "classify", writeFace: undefined, autoModeRequested: undefined },
54
- acceptEdits: { shellGate: "classify", writeFace: undefined, autoModeRequested: undefined },
55
- plan: { shellGate: "classify", writeFace: undefined, autoModeRequested: undefined },
56
- };
57
- export function writeFaceForMode(effMode) {
58
- return MODE_ENGINE_FACES[effMode].writeFace;
59
- }
60
- export function autoModeRequestedForMode(effMode) {
61
- return MODE_ENGINE_FACES[effMode].autoModeRequested;
62
- }
63
48
  export function skillListingBudgetFractionOk(v) {
64
49
  return typeof v === "number" && Number.isFinite(v) && v > 0 && v <= 1;
65
50
  }
@@ -88,11 +73,44 @@ function cleanEnvMap(v) {
88
73
  }
89
74
  return n > 0 ? out : undefined;
90
75
  }
91
- function cleanStringArray(v) {
92
- if (!Array.isArray(v))
93
- return undefined;
94
- const out = v.filter((x) => typeof x === "string" && x.length > 0).slice(0, MAX_SETTINGS_PERMISSION_RULES);
95
- return out.length > 0 ? out : undefined;
76
+ export const SETTINGS_PERMISSION_BUCKETS = ["allow", "deny", "ask"];
77
+ export const SETTINGS_PERMISSION_BUCKET_REFUSALS = ["over_cap", "bad_entry"];
78
+ function bucketIssueMessage(f) {
79
+ switch (f.reason) {
80
+ case "over_cap":
81
+ return (`${f.field} has ${f.count} entries — at most ${f.cap} are accepted. Refused rather than truncated: a truncated list silently drops the rules past the cap, ` +
82
+ `and a dropped deny / ask rule is a tightening that never happened. Merge rules into broader patterns or trim the list`);
83
+ case "bad_entry":
84
+ return f.index === undefined
85
+ ? `${f.field} must be an array of non-empty rule strings — refused rather than ignored, because an ignored list reads as "in effect" to the caller`
86
+ : `${f.field}[${f.index}] is not a non-empty rule string — every entry must be one. Refused rather than dropped, because a dropped entry reads as "in effect" to the caller`;
87
+ }
88
+ }
89
+ export function settingsPermissionBucketIssue(perms) {
90
+ if (perms === null || typeof perms !== "object" || Array.isArray(perms))
91
+ return null;
92
+ for (const bucket of SETTINGS_PERMISSION_BUCKETS) {
93
+ const v = perms[bucket];
94
+ if (v === undefined || v === null)
95
+ continue;
96
+ const field = `settings.permissions.${bucket}`;
97
+ let fields;
98
+ if (!Array.isArray(v))
99
+ fields = { field, reason: "bad_entry" };
100
+ else if (v.length > MAX_SETTINGS_PERMISSION_RULES)
101
+ fields = { field, reason: "over_cap", count: v.length, cap: MAX_SETTINGS_PERMISSION_RULES };
102
+ else {
103
+ const index = v.findIndex((x) => typeof x !== "string" || x.length === 0);
104
+ if (index >= 0)
105
+ fields = { field, reason: "bad_entry", index };
106
+ }
107
+ if (fields !== undefined)
108
+ return { message: bucketIssueMessage(fields), fields };
109
+ }
110
+ return null;
111
+ }
112
+ function acceptedRuleList(v) {
113
+ return Array.isArray(v) && v.length > 0 ? v.slice() : undefined;
96
114
  }
97
115
  export const TASK_SETTINGS_KEYS = ["env", "hooks", "model", "outputStyle", "permissions", "skillListingBudgetFraction", "skillListingMaxDescChars", "ultracode", "webSearch"];
98
116
  export const TASK_SETTINGS_PERMISSION_KEYS = ["allow", "ask", "defaultMode", "deny", "disableAutoMode"];
@@ -154,9 +172,12 @@ export function parseTaskSettings(raw) {
154
172
  }
155
173
  if (perms !== null && typeof perms === "object" && !Array.isArray(perms)) {
156
174
  const p = perms;
157
- const allow = cleanStringArray(p.allow);
158
- const deny = cleanStringArray(p.deny);
159
- const ask = cleanStringArray(p.ask);
175
+ const bucketsOk = settingsPermissionBucketIssue(p) === null;
176
+ if (!bucketsOk)
177
+ deferred.push("permissions");
178
+ const allow = bucketsOk ? acceptedRuleList(p.allow) : undefined;
179
+ const deny = bucketsOk ? acceptedRuleList(p.deny) : undefined;
180
+ const ask = bucketsOk ? acceptedRuleList(p.ask) : undefined;
160
181
  const defaultMode = coercePermissionModeStrict(p.defaultMode);
161
182
  const rawKill = p.disableAutoMode;
162
183
  const disableAutoMode = rawKill === "disable" || rawKill === true || rawKill === false ? rawKill : undefined;
@@ -348,7 +369,7 @@ export function deriveSettingsPolicy(settings, gate, workflowGate) {
348
369
  }
349
370
  case "default":
350
371
  case "auto": {
351
- const p = compose(fsWriteGate(false), createWorkflowAskPolicy(workflowGate?.isExempt));
372
+ const p = compose(createWorkflowAskPolicy(workflowGate?.isExempt));
352
373
  return p ? { toolPolicy: p } : {};
353
374
  }
354
375
  case "bypassPermissions":
@@ -284,27 +284,6 @@ export interface ToolApprovalFrame {
284
284
  * ask」,治理策略产的与普通策略产的在这一个词上同形 ⇒ 顶替不了(逐字论证见 `governance-ask-marks.ts` 顶注)。
285
285
  */
286
286
  origin?: string;
287
- /**
288
- * **[ref] C3**(core 7.14.0;**ADDITIVE**,`"tool_approval"` only)—— `origin: "rule_store_unavailable"`
289
- * 的**机制判别位**:持久规则车道这一次**读不下来的是哪一半**。core 闭二词
290
- * (`RULE_STORE_UNREADABLE_KINDS`):`"store"` = 接上的规则店读不了(读失败 / 超时);`"call"` = 店读到了,
291
- * 但**这条命令**按人的 deny/ask 行读不出来(收紧 lexer 的 `unreadable`:展开里含规则词、引号没闭、语法
292
- * 错——具体那句在 `message` 里)。`AskRequest.ruleStoreUnreadable` 逐字透传。
293
- *
294
- * 🔴 **在场 ⇔ `origin === "rule_store_unavailable"`**(core 顶注逐字)。两词对审批人是两件事:`store`
295
- * 指向「去看规则店 / 网络」,`call` 指向「这条命令的写法让规则判不了」——一个 origin 词对这两件事同形。
296
- * 缺席 = 车道把店和这条命令都读通了,**或**本部署没接规则车道;消费端禁把缺席读成对规则店健康的断言。
297
- *
298
- * 🔴 **不枚举词表**(值形是 `string` 而不是本仓自铸的联合):词表单一属主在 core
299
- * (`isRuleStoreUnreadable` 是它的成员判),抄一份会把 core 加的新词吞成缺席 —— 与 {@link origin} 逐字同规。
300
- *
301
- * 🔴 **echo-only,零消费**:策略自声明的值在 core 的**唯一**盖章点就被抹掉了,server 不重算、不再筛第二遍,
302
- * 更不许据本键自铸第二张车道资格表 —— 同 `origin` / `denialLimitFallback` 两条顶注的同一条禁令。
303
- *
304
- * ⚰️ 本键的前任 `classifierUnavailable`(S-185 / [ref])随 core 7.14.0 [ref] **整族退役**:引擎那份事实改骑
305
- * `GateDisposition.denied.cause`(`tool_end.gate` / `permissionDenied.gate`),ask 面自此没有这一位。
306
- */
307
- ruleStoreUnreadable?: string;
308
287
  /**
309
288
  * **[ref]**(core 7.19.0;**ADDITIVE**,`"tool_approval"` only)—— 这只**根外读** ask 的**清障目录**:
310
289
  * 把 `dir` 加进本会话的读目录(壳侧既有的 `/add-dir` 口),同一条调用就不会再问。
@@ -319,7 +298,7 @@ export interface ToolApprovalFrame {
319
298
  *
320
299
  * 🔴 **echo-only,零判读**:server 不从命令文本重推目录,也不复核它真不真能清掉这只 ask —— core 已经
321
300
  * 把提议的根放回读边界又走了一遍才递出来,下游再判一遍就是同一事实的第二个判官(与 `origin` /
322
- * `ruleStoreUnreadable` / `denialLimitFallback` 三条顶注的同一条禁令)。
301
+ * `denialLimitFallback` 两条顶注的同一条禁令)。
323
302
  *
324
303
  * 🔴 **只此一面**:core 的负控逐字写着「the durable park row has no twin of this seat in this version」——
325
304
  * 一只 PARKED 的根外读 ask 渲的仍是单独那张确认卡。所以 `card_json` / 耐久面**没有**这一座,本仓也不
@@ -849,6 +828,10 @@ export type UnattendedPolicyRefusal = Extract<AskOutcome, {
849
828
  * 铸:park 政策的路由答 `"unavailable"`,deny 政策的 {@link UnattendedPolicyRefusal}。没有布尔臂(S-689:裸 `false`
850
829
  * 是人的拒绝,这个口从不替人说话)。 */
851
830
  export type UnattendedAskOutcome = Extract<AskOutcome, "unavailable"> | UnattendedPolicyRefusal;
831
+ /** S-877 候裁 16:耐久会话授权店的读面(`src/plugins/approval-exemption-store.ts` 的 `has`,结构型 —— 协调器不值引店模块)。 */
832
+ export interface SessionGrantReader {
833
+ has(sessionId: string, canonicalToolName: string): Promise<boolean>;
834
+ }
852
835
  /**
853
836
  * [ref] 车3 刀 3b —— 「流内审批协议到底上不上场」的**单一谓词**(设计稿 §2.3 注入 / §2.4 能力面 /
854
837
  * §8.4 park 设施自检,三处同一份判据)。
@@ -1140,6 +1123,9 @@ export declare class ToolApprovalCoordinator {
1140
1123
  * 腿闭(`runWithContext` finally)。**纯观察面**:sink 自己吞写失败并 `recordFailOpen`,任何一次审计
1141
1124
  * 写都不得改变审批终局(店头注成文)。SQL 车道恒缺席(那里有真 ask 店)。 */
1142
1125
  private readonly askAudit;
1126
+ /** S-877 候裁 16 → A:耐久会话授权店的**读面**(`/decide` 的 `remember:"session"` 写、本席读)。在场 ⇔ 部署有 store backend
1127
+ * (装配点 `boot/coordinators.ts`,与 `/decide` 写的是**同一只**店)。读口唯一 = {@link standingGrantSettles}。 */
1128
+ private readonly sessionGrants;
1143
1129
  constructor(opts?: {
1144
1130
  ttlMs?: number;
1145
1131
  /** [ref](案A):零活流铸造的 ask 的窗(见 {@link unreachedTtlMs})。装配点在协议**上场**时才传
@@ -1167,6 +1153,8 @@ export declare class ToolApprovalCoordinator {
1167
1153
  parkTombstonePerPrincipalMax?: number;
1168
1154
  /** [ref] (c):local 车道审计 sink(见 {@link ToolApprovalCoordinator.askAudit})。 */
1169
1155
  askAudit?: ApprovalAskAuditSink;
1156
+ /** S-877 候裁 16:耐久会话授权店的读面(见 {@link ToolApprovalCoordinator.sessionGrants})。 */
1157
+ sessionGrants?: SessionGrantReader;
1170
1158
  });
1171
1159
  /**
1172
1160
  * [ref] R-13 C —— 「**无人可答**」这一类终局的**唯一**成形口(park 路由 vs deny 政策)。
@@ -1350,6 +1338,47 @@ export declare class ToolApprovalCoordinator {
1350
1338
  * 面)—— 把它混进本名单会让「同 askId 的重复本地注册」拿到裸 deny,而赢家拿的是 park 路由:同一条
1351
1339
  * 持久行、两种 live 终局。那一支改走 {@link settleSameAskIdParkRoute}。 */
1352
1340
  private settleVoidedSiblings;
1341
+ /** [ref]/[ref]:本只 ask 的治理来源标(一次 peek,非消费式)。**出处决定判定**:按 `origin` 算 broker key(与写侧同一把),
1342
+ * 投递到哪条连接不参与 —— 见 {@link AskOriginIdentity} 顶注。 */
1343
+ private governanceForcedOf;
1344
+ /**
1345
+ * 🔴 **S-877 候裁 16 → A —— 站着的答案(会话授权)的唯一兑现口**(主会话裁定 2026-09-29;core 7.34.0 wire 段逐字指定的兑现位:
1346
+ * 「a host approval seat that settles asks from a session grant reads it [`authoredBy`] to keep its hands off」)。
1347
+ *
1348
+ * **一条判据**:一只 ask 由会话授权代答 ⇔ ① 它是站着的答案退得掉的一问({@link standingAnswerMayRetire} —— 治理强制 /
1349
+ * `requiresRealApproval` / `mandated` 三位的既有排除一字不动;运维「恒问人」的卡(`APPROVAL_NEVER_AUTO` / 耐久预算耗尽)
1350
+ * 自 S-877 起盖部署出身 ⇒ core 盖 `mandated`,同落这一臂)∧ ② 有真会话 ∧ ③ 两只授权店任一命中:live `allow_session` 的
1351
+ * 进程内表(按能力桶,[ref]② 修2)或耐久 `/decide` `remember:"session"` 的豁免店(按 canonical 工具名,`/decide` 写的同一只店;
1352
+ * 读失败 ⇒ 不命中 = fail-closed,人多看一张卡而不是被静默代答)∧ ④ 行上没有与本地 peek 矛盾的出处({@link sessionGrantUncontestedByRow})。
1353
+ *
1354
+ * **两个读位、一只店、按问句的作者分**(收车档 ⑩ 16):本席兑现**引擎自装的问**(模式写门 / 形状层 —— core 7.34.0 起它们是引擎
1355
+ * 自己的层、不读任何豁免,折叠 ask 赢 ⇒ 策略层探针对它们恒无效,codex F2);**本仓自己的问**(durable 名单门、workflow 问句门、
1356
+ * acceptEdits 的 cwd 域写门)照旧在各自的层上读同一只店「不问」。为什么两处都要:子代经父约束链继承到的问到达祖先审批席时 core
1357
+ * 盖 `mandated`(`ruleClearanceOf` 的 inheritedUnresolved / ancestorResolved 门),本席依约不退役 —— 只有策略层的「不问」够得着继承链
1358
+ * (主会话裁定原想退役那三只探针,施工中被 test/parked-decide-leg.test.ts「[候裁② 修]remember 全链行为」证伪,收车档 ⑩ 16-⑤)。
1359
+ * 本席的两个入口(`askBroadcast` 开头、窗 0 / 无活流身份席 {@link boundUnattendedAsk})都只调这一只。
1360
+ */
1361
+ private standingGrantSettles;
1362
+ /** 耐久豁免店的一次读(fail-closed:读抛 **或超时** ⇒ 不命中 + warn)。只由 {@link standingGrantSettles} 调。
1363
+ * 🔴 **有界**(codex 续车轮 F2):这一读排在呈卡 / 待决登记 / 审批计时器**之前**,一次黑洞化的店读会让整只 ask 连一个兜底都没有地
1364
+ * 永久悬挂(与 R2-5 抓过的 `ensureAsk` 同病)。期限取 {@link DURABLE_CONVERGE_READ_TIMEOUT_MS}(只读复核,不是做事的调用);超时 ⇒
1365
+ * 按未命中继续既有的呈卡 / 无人可答路径 —— 方向是人多看一张卡,不是被静默代答。迟到的真答案不追认(授权是「这一问要不要问」,
1366
+ * 问已经发出去了)。 */
1367
+ private durableGrantHas;
1368
+ /**
1369
+ * 🔴 **S-877 候裁 16 —— 窗 0 席**:本席**不呈卡**,但在报「无人可答」之前先问站着的答案。装配点 = 三条腿的 `windowZero` 席
1370
+ * (sync `http/routes/tasks.ts`、bg `runs.ts`、resume `http/resume-legs.ts`)。修前三处是装配层的就地闭包 `() => unattendedAskOutcome(...)`
1371
+ * —— 不读授权,引擎那一问(模式写门 / 形状层)在窗 0 部署上 remember 恒无效。改形后:授权命中 ⇒ `true`;否则逐字走
1372
+ * {@link unattendedAskOutcome} 这**一个**政策口(五臂一处施加,[ref] R-13 C 不变)。
1373
+ * parked 赎回腿的继承条目席**不**改:那条腿上到席的问全是继承问(core 盖 `mandated`),本席依约不退役,改了也兑现不了任何东西 ——
1374
+ * 赎回腿上那类问的授权照旧由重建链里 durable 名单门的策略层探针兑现(锚 = 行的 root 会话)。复活子代**自己的**引擎问(模式写门 /
1375
+ * 形状层)则经赎回腿铸的 execute ctx `onAsk` 落本席(身份 = 行的 root 会话,S-877 四续 ⑪,`parked-decide.ts` `approverSeatFor`)。
1376
+ */
1377
+ boundUnattendedAsk: (identity: {
1378
+ owner: string | null;
1379
+ taskId: string;
1380
+ sessionId?: string;
1381
+ }) => ((req: AskRequest) => Promise<AskOutcome>);
1353
1382
  /**
1354
1383
  * 🔴 session grant 短路前的**持久出处复核**([ref] 的 codex R1-[high] 补丁;[ref] 换判据)。
1355
1384
  *
@@ -363,6 +363,7 @@ export class ToolApprovalCoordinator {
363
363
  parkTombstonePerPrincipalMax;
364
364
  parkedRedeem;
365
365
  askAudit;
366
+ sessionGrants;
366
367
  constructor(opts) {
367
368
  this.ruleConsent = opts?.ruleConsent;
368
369
  this.ruleScopeRootFor = opts?.ruleScopeRootFor;
@@ -379,6 +380,7 @@ export class ToolApprovalCoordinator {
379
380
  this.parkTombstoneTtlMs = opts?.parkTombstoneTtlMs ?? DEFAULT_PARK_TOMBSTONE_TTL_MS;
380
381
  this.parkTombstonePerPrincipalMax = opts?.parkTombstonePerPrincipalMax ?? DEFAULT_PARK_TOMBSTONE_PER_PRINCIPAL_MAX;
381
382
  this.askAudit = opts?.askAudit;
383
+ this.sessionGrants = opts?.sessionGrants;
382
384
  }
383
385
  unattendedOutcome(site) {
384
386
  let outcome;
@@ -696,6 +698,45 @@ export class ToolApprovalCoordinator {
696
698
  entry.settle(false, "expired");
697
699
  }
698
700
  }
701
+ governanceForcedOf(origin, req) {
702
+ return (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
703
+ }
704
+ async standingGrantSettles(origin, req, governanceForced) {
705
+ if (!standingAnswerMayRetire({ governanceForced, requiresRealApproval: req.requiresRealApproval === true, mandated: req.mandated === true }))
706
+ return false;
707
+ const sessionId = origin.sessionId;
708
+ if (!sessionId)
709
+ return false;
710
+ const live = this.allowAllSessions.has(sessionAllowKey(origin.owner, sessionId, sessionAllowCategory(req.toolName)));
711
+ const durable = !live && (await this.durableGrantHas(sessionId, req.toolName));
712
+ if (!live && !durable)
713
+ return false;
714
+ if (!(await this.sessionGrantUncontestedByRow(origin, req)))
715
+ return false;
716
+ if (durable)
717
+ defaultLogger.info("approval_exempted", { sessionId, toolName: req.toolName, seat: "approval_seat" });
718
+ return true;
719
+ }
720
+ async durableGrantHas(sessionId, canonicalToolName) {
721
+ if (!this.sessionGrants)
722
+ return false;
723
+ try {
724
+ const hit = await this.withStoreDeadline(this.sessionGrants.has(sessionId, canonicalToolName), "sessionGrants.has(standing-grant)", DURABLE_CONVERGE_READ_TIMEOUT_MS);
725
+ return hit === true;
726
+ }
727
+ catch (err) {
728
+ defaultLogger.warn("session_grant_read_failed", { sessionId, toolName: canonicalToolName, error: String(err).slice(0, 200) });
729
+ return false;
730
+ }
731
+ }
732
+ boundUnattendedAsk = (identity) => {
733
+ return async (req) => {
734
+ const origin = { owner: identity.owner, taskId: identity.taskId, ...(identity.sessionId !== undefined ? { sessionId: identity.sessionId } : {}), legKey: "" };
735
+ if (await this.standingGrantSettles(origin, req, this.governanceForcedOf(origin, req)))
736
+ return true;
737
+ return this.unattendedAskOutcome({ taskId: identity.taskId, owner: identity.owner, toolName: req.toolName });
738
+ };
739
+ };
699
740
  async sessionGrantUncontestedByRow(origin, req) {
700
741
  if (!this.askStore)
701
742
  return true;
@@ -944,6 +985,9 @@ export class ToolApprovalCoordinator {
944
985
  async askBroadcast(origin, ctxs, req, signal) {
945
986
  if (signal?.aborted)
946
987
  return false;
988
+ const governanceForced = this.governanceForcedOf(origin, req);
989
+ if (await this.standingGrantSettles(origin, req, governanceForced))
990
+ return true;
947
991
  const aliveCtxs = ctxs.filter((c) => !c.abortSignal?.aborted);
948
992
  const reachability = aliveCtxs.length === 0 ? "unreached" : "reachable";
949
993
  if (reachability === "unreached") {
@@ -952,13 +996,6 @@ export class ToolApprovalCoordinator {
952
996
  }
953
997
  }
954
998
  const primary = aliveCtxs[0];
955
- const governanceForced = (this.governanceAskMarks ?? governanceAskMarksFor(this.streamKey(origin.owner, origin.sessionId, origin.taskId))).isMarked(req.toolCallId);
956
- if (standingAnswerMayRetire({ governanceForced, requiresRealApproval: req.requiresRealApproval === true, mandated: req.mandated === true }) &&
957
- origin.sessionId &&
958
- this.allowAllSessions.has(sessionAllowKey(origin.owner, origin.sessionId, sessionAllowCategory(req.toolName))) &&
959
- (await this.sessionGrantUncontestedByRow(origin, req))) {
960
- return true;
961
- }
962
999
  const releaseAdmission = this.admit(origin.taskId, origin.owner);
963
1000
  if (!releaseAdmission) {
964
1001
  defaultLogger.warn("approval admission gate: too many pending asks — routing to park", {
@@ -1009,7 +1046,6 @@ export class ToolApprovalCoordinator {
1009
1046
  ...(ruleLaneMaterial !== undefined ? { ruleOffers: ruleLaneMaterial.offers } : {}),
1010
1047
  ...((a) => (a !== undefined ? { ruleOffersAbsence: a } : {}))(readRuleOffersAbsence(req)),
1011
1048
  ...((f) => (f !== undefined ? { denialLimitFallback: f } : {}))(readDenialLimitFallback(req)),
1012
- ...(typeof req.ruleStoreUnreadable === "string" && req.ruleStoreUnreadable.length > 0 ? { ruleStoreUnreadable: req.ruleStoreUnreadable } : {}),
1013
1049
  ...((g) => (g !== undefined ? { readRootCandidate: g } : {}))(readReadRootCandidate(req)),
1014
1050
  ...((c) => (c !== undefined ? { probeCause: c } : {}))(readProbeCause(req)),
1015
1051
  ...((c) => (c !== undefined ? { ruleEvidence: c } : {}))(readRuleEvidence(req)),
@@ -0,0 +1,12 @@
1
+ export declare const TOOL_DECLARATION_HTTP_STATUS: {
2
+ readonly "config.tool_mount_denied": 400;
3
+ readonly "config.tool_roster_bound": 503;
4
+ readonly "config.tool_name_too_long": 503;
5
+ readonly "config.tool_contract_too_long": 503;
6
+ readonly "config.tool_schema_bound": 503;
7
+ readonly "config.tool_face_invalid": null;
8
+ readonly "config.tool_name_invalid": null;
9
+ readonly "config.egress_requires_write_effect": null;
10
+ readonly "config.reserved_tool_name": null;
11
+ };
12
+ //# sourceMappingURL=tool-declaration-http-status.d.ts.map
@@ -0,0 +1,12 @@
1
+ export const TOOL_DECLARATION_HTTP_STATUS = {
2
+ "config.tool_mount_denied": 400,
3
+ "config.tool_roster_bound": 503,
4
+ "config.tool_name_too_long": 503,
5
+ "config.tool_contract_too_long": 503,
6
+ "config.tool_schema_bound": 503,
7
+ "config.tool_face_invalid": null,
8
+ "config.tool_name_invalid": null,
9
+ "config.egress_requires_write_effect": null,
10
+ "config.reserved_tool_name": null,
11
+ };
12
+ //# sourceMappingURL=tool-declaration-http-status.js.map
@@ -43,8 +43,8 @@ type _GuardBgNotifReverse = AssertAllKeysHandled<Exclude<BgNotifProjected | BgNo
43
43
  type RosterProjected = "name" | "agentId" | "sessionId" | "toolUseId" | "owner" | "scope" | "sessionScoped" | "rootSessionId" | "model" | "modelFallback" | "createdAt";
44
44
  type _GuardRoster = AssertAllKeysHandled<Exclude<keyof RosterEntry, RosterProjected>>;
45
45
  type _GuardRosterReverse = AssertAllKeysHandled<Exclude<RosterProjected, keyof RosterEntry>>;
46
- type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "readRootCandidate" | "mandated" | "mandate";
47
- type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd";
46
+ type AskProjected = "toolName" | "toolCallId" | "args" | "message" | "sourceTaskId" | "fromSubagent" | "sourceAgentName" | "delegation" | "ruleOffers" | "ruleOffersAbsence" | "persistedRuleShadowed" | "probeCause" | "ruleEvidence" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "readRootCandidate" | "mandated" | "mandate";
47
+ type AskExcluded = "preview" | "principal" | "riskAxes" | "boundInputHash" | "isDelegatedChild" | "probeReason" | "hasBidiControls" | "previewWithheld" | "execCwd" | "authoredBy";
48
48
  type _GuardAsk = AssertAllKeysHandled<Exclude<keyof AskRequest, AskProjected | AskExcluded>>;
49
49
  type _GuardAskReverse = AssertAllKeysHandled<Exclude<AskProjected | AskExcluded, keyof AskRequest>>;
50
50
  type AskFrameServerMinted = "type" | "approvalId" | "argsOmitted" | "outcome" | "governanceForced" | "expiresAtMs" | "expiresInMs" | "serverNowMs" | "parked" | "inputHasBidi";
@@ -133,19 +133,19 @@ type _GuardPermissionTraceReverse = AssertAllKeysHandled<Exclude<PermissionTrace
133
133
  /** `T` 非 `never` 时收敛到 `never`(空集 ⇒ 残留 `"EMPTY"` ⇒ tsc 红)。 */
134
134
  type AssertNonEmpty<T> = [T] extends [never] ? "EMPTY" : never;
135
135
  type _GuardPermissionTraceNonEmpty = AssertAllKeysHandled<AssertNonEmpty<PermissionTraceKind>>;
136
- type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "ruleStoreUnreadable" | "mandated" | "mandate";
137
- type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld";
136
+ type SummaryProjected = "sessionId" | "scope" | "createdAt" | "gateKind" | "severity" | "spentMicroUsd" | "deadline" | "hasBidiControls" | "requiresRealApproval" | "denialLimitFallback" | "origin" | "mandated" | "mandate";
137
+ type SummaryExcluded = "token" | "toolInput" | "toolName" | "toolCallId" | "preview" | "principal" | "sourceTaskId" | "contentKind" | "restoreMode" | "checkpointId" | "previewWithheld" | "authoredBy";
138
138
  type _GuardInboxSummary = AssertAllKeysHandled<Exclude<keyof CheckpointSummary, SummaryProjected | SummaryExcluded>>;
139
139
  type _GuardInboxSummaryReverse = AssertAllKeysHandled<Exclude<SummaryProjected | SummaryExcluded, keyof CheckpointSummary>>;
140
140
  type ParkArmKeys = keyof Extract<PendingAction, {
141
141
  kind: "tool_approval";
142
142
  }>;
143
143
  type ParkArmQueueProjected = "toolName" | "toolCallId" | "boundInputHash" | "ruleOffers" | "hasBidiControls" | keyof import("../approval-card.js").ParkRowFacts;
144
- type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "ruleStoreUnreadable";
144
+ type ParkArmQueueExcluded = "kind" | "args" | "batchToolCallIds" | "completedCallIds" | "capabilityId" | "contractId" | "shapeDigest" | "implementationRevision" | "rosterDigest" | "family" | "pathTarget" | "names" | "preview" | "previewWithheld" | "execCwd" | "denialLimitFallback" | "authoredBy";
145
145
  type _GuardParkArmQueue = AssertAllKeysHandled<Exclude<ParkArmKeys, ParkArmQueueProjected | ParkArmQueueExcluded>>;
146
146
  type _GuardParkArmQueueReverse = AssertAllKeysHandled<Exclude<ParkArmQueueProjected | ParkArmQueueExcluded, ParkArmKeys>>;
147
147
  type _NoDoubleBookParkArmQueue = AssertAllKeysHandled<Extract<ParkArmQueueProjected, ParkArmQueueExcluded>>;
148
- type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "approverPosture" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "autoModeRequested" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory";
148
+ type TaskSpecWired = "objective" | "principal" | "images" | "sessionId" | "requireExistingSession" | "oneShot" | "clientContext" | "resumeAt" | "resumeAtMode" | "model" | "compactionModel" | "thinking" | "systemPrompt" | "appendSystemPrompt" | "promptProvider" | "memory" | "suggestNextPrompts" | "restoreFiles" | "acceptPartialRestore" | "rewindFilesTo" | "tools" | "excludeTools" | "deferTools" | "toolMaterializeStrategy" | "promptProfile" | "toolPolicy" | "onAsk" | "onQuestion" | "hooks" | "durableApproval" | "resourceSuspend" | "resilience" | "finalVerification" | "checkpointStore" | "handsReadOnly" | "memoryPersistenceCapable" | "additionalDirectories" | "additionalReadDirectories" | "enablePlanMode" | "interactiveTools" | "enableFork" | "shellGate" | "writeFace" | "mcp" | "a2a" | "skills" | "backgroundScope" | "envFacts" | "limits" | "configOverrides" | "forwardSubagentEvents" | "retainSubagentSessions" | "retainBackgroundProcesses" | "outputSchema" | "outputRetries" | "compaction" | "attachments" | "selfOrchestration" | "getApiKeyAndHeaders" | "interactionPosture" | "agents" | "permissionMode" | "taskId" | "signal" | "preemptSignal" | "modelRole" | "enableBlockedReport" | "maxSuspends" | "skillListingBudgetFraction" | "skillListingMaxDescChars" | "excludeAllTools" | "fileHistory";
149
149
  type TaskSpecUnwired = "actor" | "roles" | "lspManager" | "tracer" | "readFace" | "readDenyPatterns" | "interactiveQuestionFallback" | "alwaysLoadTools" | "restoreGatedTools" | "deferSelfResolve" | "basePolicyForResumeEdit" | "lspDiagnostics" | "streamingToolExecution" | "toolResultThresholdChars" | "peerPresence";
150
150
  type _GuardTaskSpec = AssertAllKeysHandled<Exclude<keyof TaskSpec, TaskSpecWired | TaskSpecUnwired>>;
151
151
  type _GuardTaskSpecReverse = AssertAllKeysHandled<Exclude<TaskSpecWired | TaskSpecUnwired, keyof TaskSpec>>;
@@ -163,7 +163,7 @@ type GateOutcomeProjected = "disposition" | "settlement" | "origin";
163
163
  type _GuardGateOutcome = AssertAllKeysHandled<Exclude<keyof GateOutcome, GateOutcomeProjected>>;
164
164
  type _GuardGateOutcomeReverse = AssertAllKeysHandled<Exclude<GateOutcomeProjected, keyof GateOutcome>>;
165
165
  type GateDispositionKeys = GateDisposition extends infer D ? (D extends object ? keyof D : never) : never;
166
- type GateDispositionProjected = "kind" | "deniedBy" | "cause" | "classifier";
166
+ type GateDispositionProjected = "kind" | "deniedBy" | "cause" | "classifier" | "released" | "mode" | "askClass" | "classes";
167
167
  type _GuardGateDisposition = AssertAllKeysHandled<Exclude<GateDispositionKeys, GateDispositionProjected>>;
168
168
  type _GuardGateDispositionReverse = AssertAllKeysHandled<Exclude<GateDispositionProjected, GateDispositionKeys>>;
169
169
  type SettlementProjected = "kind" | "who" | "when" | "note";
@@ -16,7 +16,7 @@
16
16
  * 同文件 G1 e2e 的全码扇出夹具、以及契约文档附录 D.3 那张表(含**小节标题里的码数** —— 那道门本批
17
17
  * 刚立,立完就在下一次加码时自己咬住了)。
18
18
  */
19
- export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped"];
19
+ export declare const ENGINE_NOTICE_WIRE_CODES: readonly ["memory.session_polluted", "memory.harvest_quarantined", "memory.delegation_static_mark_waived", "memory.hold_opened", "memory.hold_released", "memory.hold_disposed", "task.turn_interrupted", "steering.parked_input_blocked", "task.user_steer_undrained", "task.user_followup_undrained", "memory.capture_opted_out", "memory.capture_optout_unpersisted", "task.halt_unconsumed", "task.late_approval", "config.durable_gate_unavailable", "peer.inbound_disposition", "peer.held_settled", "peer.idle_subscription", "classifier.denial_limit", "mcp.injection_dropped", "delegation.ask_unresolvable", "task.interrupt_unconsumed", "approval.read_root_granted", "approval.read_root_grant_rejected", "instructions.source_changed", "session.records_skipped", "classifier.unavailable", "config.permission_mode_unavailable", "permission.ask_released_by_mode", "permission.read_deny_unapplied", "permission.rule_store_unavailable"];
20
20
  export type EngineNoticeWireCode = (typeof ENGINE_NOTICE_WIRE_CODES)[number];
21
21
  /**
22
22
  * S-690 —— **本仓自铸**的通告码与它们的受众(码 / 铸点 / 受众三者的属主都是本仓)。
@@ -27,6 +27,11 @@ export const ENGINE_NOTICE_WIRE_CODES = [
27
27
  "approval.read_root_grant_rejected",
28
28
  "instructions.source_changed",
29
29
  "session.records_skipped",
30
+ "classifier.unavailable",
31
+ "config.permission_mode_unavailable",
32
+ "permission.ask_released_by_mode",
33
+ "permission.read_deny_unapplied",
34
+ "permission.rule_store_unavailable",
30
35
  ];
31
36
  export const SERVER_NOTICE_AUDIENCE = {
32
37
  "instructions.source_changed": "user",