@sema-agent/server 7.140.0 → 7.142.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 (296) hide show
  1. package/MIGRATION.md +505 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +142 -25
  5. package/dist/approval-ask-audit-store.d.ts +54 -38
  6. package/dist/approval-ask-audit-store.js +58 -39
  7. package/dist/approval-batch-census.d.ts +27 -0
  8. package/dist/approval-batch-census.js +27 -0
  9. package/dist/approval-card.d.ts +39 -24
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval-crash-convergence.d.ts +17 -7
  12. package/dist/approval-crash-convergence.js +94 -22
  13. package/dist/approval-deny-reasons.d.ts +34 -30
  14. package/dist/approval-deny-reasons.js +21 -6
  15. package/dist/approval-policy-settlement.d.ts +1 -1
  16. package/dist/approval-reconciler.d.ts +33 -1
  17. package/dist/approval-reconciler.js +43 -40
  18. package/dist/approval.d.ts +15 -0
  19. package/dist/approval.js +10 -0
  20. package/dist/audit.d.ts +4 -2
  21. package/dist/audit.js +2 -2
  22. package/dist/auth-bridge.d.ts +9 -6
  23. package/dist/auth-bridge.js +9 -3
  24. package/dist/boot/adoption.js +2 -1
  25. package/dist/boot/budget-tracing.d.ts +16 -0
  26. package/dist/boot/budget-tracing.js +10 -6
  27. package/dist/boot/device-lane.d.ts +11 -0
  28. package/dist/boot/device-lane.js +16 -0
  29. package/dist/boot/engine-lease.d.ts +24 -3
  30. package/dist/boot/engine-lease.js +9 -2
  31. package/dist/boot/execution-env.d.ts +24 -4
  32. package/dist/boot/execution-env.js +34 -27
  33. package/dist/boot/execution-lane.js +2 -1
  34. package/dist/boot/exit-boundary.js +5 -4
  35. package/dist/boot/memory-boundary.d.ts +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +1 -1
  37. package/dist/boot/memory-consolidation.js +3 -2
  38. package/dist/boot/parked-revive-gate.js +2 -1
  39. package/dist/boot/permission-rules-audit.d.ts +0 -28
  40. package/dist/boot/permission-rules-audit.js +2 -1
  41. package/dist/boot/read-deny-anchor-audit.js +2 -1
  42. package/dist/boot/reapers.d.ts +2 -2
  43. package/dist/boot/reapers.js +4 -3
  44. package/dist/boot/resolve-spec.js +18 -6
  45. package/dist/boot/retention-lane.js +5 -3
  46. package/dist/boot/runner-deps.js +16 -13
  47. package/dist/boot/runtime-caps.js +3 -2
  48. package/dist/boot/session-faces.d.ts +1 -1
  49. package/dist/boot/session-faces.js +8 -6
  50. package/dist/boot/shutdown.js +8 -2
  51. package/dist/boot/stage-04-stores.js +3 -2
  52. package/dist/boot/stage-05-execution-env.d.ts +1 -1
  53. package/dist/boot/stage-05-execution-env.js +11 -7
  54. package/dist/boot/stage-06-runners.d.ts +1 -3
  55. package/dist/boot/stage-06-runners.js +3 -2
  56. package/dist/boot/stage-07-capability-layer.d.ts +1 -2
  57. package/dist/boot/stage-07-capability-layer.js +10 -11
  58. package/dist/boot/stage-08-reapers.d.ts +1 -2
  59. package/dist/boot/stage-08-reapers.js +3 -2
  60. package/dist/boot/stage-09-leader.d.ts +1 -2
  61. package/dist/boot/stage-10-http-server.d.ts +1 -2
  62. package/dist/boot/stage-10-http-server.js +6 -4
  63. package/dist/boot/stores.js +13 -6
  64. package/dist/boot/web-admin.js +18 -17
  65. package/dist/boot/webfetch-summarize-lane.js +2 -1
  66. package/dist/boot/workflow-orchestration.js +3 -2
  67. package/dist/capabilities/builtin-tools.d.ts +2 -1
  68. package/dist/capabilities/builtin-tools.js +2 -4
  69. package/dist/capabilities/composite-members.d.ts +2 -1
  70. package/dist/capabilities/composite-members.js +2 -1
  71. package/dist/capabilities/scenarios.d.ts +1 -1
  72. package/dist/capability-facts.d.ts +33 -0
  73. package/dist/capability-facts.js +32 -0
  74. package/dist/card-void.d.ts +38 -0
  75. package/dist/card-void.js +23 -0
  76. package/dist/config-catalog.js +12 -8
  77. package/dist/config-provider.d.ts +13 -13
  78. package/dist/config-provider.js +19 -33
  79. package/dist/config-types.d.ts +39 -10
  80. package/dist/config.d.ts +6 -0
  81. package/dist/config.js +72 -49
  82. package/dist/delegated-session-owner.d.ts +21 -19
  83. package/dist/delegated-session-owner.js +1 -1
  84. package/dist/device-store.d.ts +7 -0
  85. package/dist/device-store.js +3 -0
  86. package/dist/execution-config-root.d.ts +22 -0
  87. package/dist/execution-config-root.js +35 -0
  88. package/dist/execution-lane-caps.d.ts +50 -2
  89. package/dist/execution-lane-caps.js +90 -13
  90. package/dist/execution-placement.d.ts +3 -0
  91. package/dist/execution-placement.js +7 -0
  92. package/dist/fleet/drain-entry.d.ts +3 -0
  93. package/dist/fleet/fleet-bus.d.ts +6 -3
  94. package/dist/hooks/hook-runner.d.ts +6 -0
  95. package/dist/hooks/hook-runner.js +30 -1
  96. package/dist/hosted-posture.d.ts +8 -0
  97. package/dist/hosted-posture.js +4 -0
  98. package/dist/http/admission.js +3 -3
  99. package/dist/http/caller-trust.d.ts +3 -2
  100. package/dist/http/caller-trust.js +2 -1
  101. package/dist/http/dispatch.js +8 -2
  102. package/dist/http/execution-context.d.ts +2 -2
  103. package/dist/http/idempotency.d.ts +1 -1
  104. package/dist/http/live-frame.d.ts +24 -0
  105. package/dist/http/live-frame.js +16 -0
  106. package/dist/http/principal-gate.d.ts +3 -1
  107. package/dist/http/principal-gate.js +17 -8
  108. package/dist/http/resume-legs.js +42 -29
  109. package/dist/http/route-table.js +4 -0
  110. package/dist/http/routes/approvals-assistant.js +1 -0
  111. package/dist/http/routes/attachments.js +18 -0
  112. package/dist/http/routes/capabilities.js +10 -6
  113. package/dist/http/routes/diagnostics.d.ts +0 -19
  114. package/dist/http/routes/diagnostics.js +16 -2
  115. package/dist/http/routes/memory-bundle.d.ts +2 -2
  116. package/dist/http/routes/memory-bundle.js +1 -1
  117. package/dist/http/routes/memory-compliance.js +1 -1
  118. package/dist/http/routes/memory-policy.js +27 -7
  119. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  120. package/dist/http/routes/run-permission-mode.js +170 -0
  121. package/dist/http/routes/runs.js +3 -3
  122. package/dist/http/routes/session-memory-sync.d.ts +63 -0
  123. package/dist/http/routes/session-memory-sync.js +157 -0
  124. package/dist/http/routes/session-sync.d.ts +3 -6
  125. package/dist/http/routes/session-sync.js +7 -2
  126. package/dist/http/routes/sessions-list.d.ts +2 -1
  127. package/dist/http/routes/sessions.js +5 -4
  128. package/dist/http/routes/tasks.js +70 -71
  129. package/dist/http/run-read-face.d.ts +7 -0
  130. package/dist/http/run-read-face.js +10 -4
  131. package/dist/http/send.d.ts +6 -1
  132. package/dist/http/send.js +10 -3
  133. package/dist/http/server.d.ts +20 -7
  134. package/dist/http/server.js +2 -4
  135. package/dist/http/sse-log.d.ts +3 -8
  136. package/dist/http/stored-body-replay.d.ts +3 -3
  137. package/dist/http/stored-body-replay.js +3 -1
  138. package/dist/http/submit-replay.d.ts +4 -3
  139. package/dist/http/submit-replay.js +2 -2
  140. package/dist/http/sync-submit-body.d.ts +21 -4
  141. package/dist/http/sync-submit-body.js +13 -0
  142. package/dist/identity-source.d.ts +21 -2
  143. package/dist/identity-source.js +6 -0
  144. package/dist/instruction-source-notice.d.ts +7 -0
  145. package/dist/instruction-source-notice.js +6 -6
  146. package/dist/mcp-assertion-header.d.ts +4 -0
  147. package/dist/mcp-assertion-header.js +5 -0
  148. package/dist/memory-incident-sink.d.ts +19 -6
  149. package/dist/memory-incident-sink.js +8 -4
  150. package/dist/memory-posture.d.ts +1 -1
  151. package/dist/memory-scope.d.ts +4 -4
  152. package/dist/memory-scope.js +1 -1
  153. package/dist/memory-sync-client.d.ts +35 -23
  154. package/dist/memory-sync-client.js +122 -52
  155. package/dist/memory-sync-identity.d.ts +24 -0
  156. package/dist/memory-sync-identity.js +57 -0
  157. package/dist/memory-sync.d.ts +7 -1
  158. package/dist/memory-sync.js +6 -3
  159. package/dist/observability/fail-open.d.ts +57 -12
  160. package/dist/observability/fail-open.js +50 -9
  161. package/dist/observability/metrics.js +0 -1
  162. package/dist/observability/run-terminal.d.ts +93 -31
  163. package/dist/observability/run-terminal.js +175 -171
  164. package/dist/observability/secret-env-scrub.d.ts +20 -1
  165. package/dist/observability/secret-env-scrub.js +14 -0
  166. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  167. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  168. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  169. package/dist/orchestration/workflow-notify-journal.js +2 -1
  170. package/dist/permission-mode-admission.d.ts +68 -0
  171. package/dist/permission-mode-admission.js +23 -0
  172. package/dist/permission-rule-list.d.ts +55 -6
  173. package/dist/permission-rule-list.js +52 -5
  174. package/dist/plugins/approval-ask-store-file.d.ts +14 -4
  175. package/dist/plugins/approval-ask-store-file.js +48 -7
  176. package/dist/plugins/approval-ask-store-memory.d.ts +2 -1
  177. package/dist/plugins/approval-ask-store-memory.js +9 -8
  178. package/dist/plugins/approval-ask-store-sql.d.ts +42 -10
  179. package/dist/plugins/approval-ask-store-sql.js +49 -19
  180. package/dist/plugins/caching-session-store.d.ts +6 -0
  181. package/dist/plugins/caching-session-store.js +3 -0
  182. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  183. package/dist/plugins/checkpoint-store-sql.js +74 -3
  184. package/dist/plugins/device-store-sql.d.ts +5 -2
  185. package/dist/plugins/device-store-sql.js +4 -1
  186. package/dist/plugins/execution-placement-sql.d.ts +11 -1
  187. package/dist/plugins/execution-placement-sql.js +12 -1
  188. package/dist/plugins/file-run-store.d.ts +7 -2
  189. package/dist/plugins/file-run-store.js +12 -2
  190. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  191. package/dist/plugins/fork-routing-session-store.js +2 -61
  192. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  193. package/dist/plugins/k8s-exec-protocol.js +6 -1
  194. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  195. package/dist/plugins/local-checkpoint-store.js +22 -0
  196. package/dist/plugins/local-session-store.d.ts +260 -39
  197. package/dist/plugins/local-session-store.js +582 -57
  198. package/dist/plugins/local-session-window.d.ts +17 -3
  199. package/dist/plugins/local-session-window.js +7 -13
  200. package/dist/plugins/local-task-attachment-store.d.ts +12 -2
  201. package/dist/plugins/local-task-attachment-store.js +67 -43
  202. package/dist/plugins/memory-key-guards.d.ts +3 -3
  203. package/dist/plugins/memory-run-store.d.ts +7 -2
  204. package/dist/plugins/memory-run-store.js +12 -2
  205. package/dist/plugins/one-time-migrations.d.ts +68 -2
  206. package/dist/plugins/one-time-migrations.js +120 -4
  207. package/dist/plugins/pg-pool.js +3 -2
  208. package/dist/plugins/pg-session-storage.d.ts +2 -0
  209. package/dist/plugins/pg-session-storage.js +3 -2
  210. package/dist/plugins/remote-env-e2b.d.ts +47 -7
  211. package/dist/plugins/remote-env-e2b.js +242 -46
  212. package/dist/plugins/remote-env-k8s.d.ts +51 -6
  213. package/dist/plugins/remote-env-k8s.js +234 -44
  214. package/dist/plugins/remote-env-local-docker.d.ts +40 -14
  215. package/dist/plugins/remote-env-local-docker.js +127 -33
  216. package/dist/plugins/run-store-sql.d.ts +11 -3
  217. package/dist/plugins/run-store-sql.js +13 -4
  218. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  219. package/dist/plugins/sandbox-read-floor.js +608 -0
  220. package/dist/plugins/session-ownership.d.ts +2 -1
  221. package/dist/plugins/session-placement.d.ts +17 -4
  222. package/dist/plugins/session-placement.js +2 -1
  223. package/dist/plugins/session-store.d.ts +18 -20
  224. package/dist/plugins/session-store.js +4 -12
  225. package/dist/plugins/store-backend.d.ts +41 -7
  226. package/dist/plugins/store-backend.js +22 -4
  227. package/dist/plugins/store-contracts.d.ts +35 -3
  228. package/dist/plugins/store-contracts.js +3 -0
  229. package/dist/plugins/task-attachment-store.d.ts +112 -14
  230. package/dist/plugins/task-attachment-store.js +221 -28
  231. package/dist/plugins/tidb-pool.js +2 -1
  232. package/dist/plugins/tidb-session-store.d.ts +4 -1
  233. package/dist/plugins/tidb-session-store.js +3 -2
  234. package/dist/plugins/workspace-fill.d.ts +60 -0
  235. package/dist/plugins/workspace-fill.js +106 -0
  236. package/dist/project-bundle.d.ts +38 -0
  237. package/dist/project-bundle.js +609 -0
  238. package/dist/run-cancel-context.d.ts +34 -0
  239. package/dist/run-cancel-context.js +7 -0
  240. package/dist/run-local.js +2 -8
  241. package/dist/runs.d.ts +39 -12
  242. package/dist/runs.js +69 -55
  243. package/dist/sandbox-egress-policy.d.ts +4 -1
  244. package/dist/sandbox-egress-policy.js +3 -1
  245. package/dist/sandbox-git-plane.d.ts +31 -12
  246. package/dist/sandbox-git-plane.js +24 -4
  247. package/dist/security.d.ts +11 -2
  248. package/dist/security.js +8 -0
  249. package/dist/server-secret-env.d.ts +2 -1
  250. package/dist/server-secret-env.js +2 -4
  251. package/dist/session-error-wire.d.ts +46 -4
  252. package/dist/session-error-wire.js +23 -0
  253. package/dist/session-incarnation.d.ts +9 -3
  254. package/dist/session-sync-kernel.d.ts +42 -1
  255. package/dist/session-sync-kernel.js +17 -0
  256. package/dist/session-sync.js +16 -4
  257. package/dist/sql-engine-coordinates.d.ts +49 -0
  258. package/dist/sql-engine-coordinates.js +27 -0
  259. package/dist/standing-answer-grant.d.ts +31 -2
  260. package/dist/standing-answer-grant.js +14 -2
  261. package/dist/store-posture.d.ts +1 -1
  262. package/dist/task-a2a.d.ts +1 -1
  263. package/dist/task-mcp.d.ts +1 -1
  264. package/dist/task-settings.d.ts +62 -27
  265. package/dist/task-settings.js +105 -29
  266. package/dist/terminal.d.ts +97 -20
  267. package/dist/terminal.js +107 -18
  268. package/dist/tool-approval.d.ts +4 -3
  269. package/dist/tool-approval.js +12 -14
  270. package/dist/trace/core-keyset-guard.d.ts +196 -2
  271. package/dist/trace/engine-notice-wire.d.ts +8 -2
  272. package/dist/trace/engine-notice-wire.js +4 -2
  273. package/dist/trace/exit-seat.d.ts +9 -1
  274. package/dist/trace/exit-seat.js +1 -0
  275. package/dist/trace/ledger-sink.d.ts +8 -4
  276. package/dist/trace/ledger-sink.js +2 -2
  277. package/dist/trace/model-segment.d.ts +2 -2
  278. package/dist/trace/project.d.ts +18 -1
  279. package/dist/trace/project.js +16 -0
  280. package/dist/trace/redact.js +323 -90
  281. package/dist/trace/self-minted-wire.d.ts +419 -4
  282. package/dist/trace/self-minted-wire.js +145 -0
  283. package/dist/trace/wire-projection-faces.d.ts +1 -1
  284. package/dist/trace/wire-projection-faces.js +1 -1
  285. package/dist/verifier-pause-card.d.ts +8 -0
  286. package/dist/verifier-pause-card.js +19 -0
  287. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  288. package/dist/web-admin-client/apply-effective.js +22 -2
  289. package/dist/web-admin-client/candidate-gate.d.ts +1 -1
  290. package/dist/web-admin-client/effective-keys.js +2 -0
  291. package/dist/web-admin-client/read-warnings.d.ts +2 -2
  292. package/dist/web-admin-client/types.d.ts +4 -0
  293. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  294. package/package.json +2 -2
  295. package/wire-changes/server@7.141.0.json +367 -0
  296. package/wire-changes/server@7.142.0.json +639 -0
@@ -1,5 +1,6 @@
1
1
  import { summarizeRedactions } from "@sema-agent/core";
2
2
  import { describeAllowlistPostures } from "../env-name-allowlist-knobs.js";
3
+ import { createLogger } from "./logger.js";
3
4
  export const SECRET_ENV_SCRUB_SITES = {
4
5
  "server.remote-env-host.spawn-env": {
5
6
  note: "模型可驱动的 host shell 继承 env(`inheritEnv` 三词任一,缺省 `\"scrub\"`;`\"all\"` 词下扣的只有本服务自己的凭据名)——这条腿上的一次泄漏等于把编排器的脑钥/仓库令牌交给模型选的命令",
@@ -43,6 +44,7 @@ const SCRUB_COUNTER_HELP = "secret env entries not handed on as written (dropped
43
44
  const DISCLOSED_CAP = 512;
44
45
  const SEP = "=";
45
46
  let sink = {};
47
+ let preInstallLogger;
46
48
  let counts = new Map();
47
49
  let pushed = new Map();
48
50
  let disclosed = new Set();
@@ -101,6 +103,18 @@ export function recordSecretEnvScrub(site, findings, allowed) {
101
103
  catch {
102
104
  }
103
105
  }
106
+ const OWN_CREDENTIAL_VALUE_NOT_WITHHELD = "own_credential_value_not_withheld";
107
+ function emitValueRefusal(logger, r) {
108
+ logger.warn(OWN_CREDENTIAL_VALUE_NOT_WITHHELD, { name: r.name, reason: r.reason });
109
+ }
110
+ export function recordOwnCredentialValueNotWithheld(refusal) {
111
+ try {
112
+ const logger = sink.logger ?? (preInstallLogger ??= createLogger("warn"));
113
+ emitValueRefusal(logger, refusal);
114
+ }
115
+ catch {
116
+ }
117
+ }
104
118
  export function installSecretEnvScrubRecorder(deps) {
105
119
  sink = { ...deps };
106
120
  deps.metrics?.counter(SCRUB_COUNTER, SCRUB_COUNTER_HELP);
@@ -18,8 +18,8 @@ export interface WorkflowCompletionInboxEntry {
18
18
  * is the deployment's job). `runId` then holds the core task_id (disjoint id spaces). */
19
19
  kind?: "workflow_complete" | "task_notification";
20
20
  /** The per-kind extras as a JSON text — bounded + redacted AT ENQUEUE (the store carries only egress-safe bytes).
21
- * `task_notification`: {task_type, toolUseId?, result?, output_file?, …}. `workflow_complete`: at most `{"completionId": …}`
22
- * (see {@link buildWorkflowCompleteInboxEntry}; absent when the engine minted none). The SQL stores' existing `payload`
21
+ * `task_notification`: {task_type, toolUseId?, result?, output_file?, …}. `workflow_complete`: at most `{"completionId": …, "diagnostics": …}`
22
+ * (see {@link buildWorkflowCompleteInboxEntry}; each key absent when the engine minted none). The SQL stores' existing `payload`
23
23
  * column carries it verbatim for both kinds — no DDL. */
24
24
  payload?: string;
25
25
  }
@@ -203,6 +203,8 @@ export declare class FileWorkflowCompletionInbox extends InMemoryWorkflowComplet
203
203
  /**
204
204
  * 一次 workflow 完成 → 入箱条目(`workflow_complete` 家族;启动期投递闭包的**唯一**构造点)。`summary` 在 core 的 notifier seam 上已脱敏 + 限长,这里原样入箱;
205
205
  * `completionId`(引擎铸,`WorkflowCompletionPayload` 上在场 iff 引擎给了)逐字落进 `payload` 列 —— 同一次完成的补投帧与 live 帧同一个相关 id,本服务零自铸。
206
+ * `diagnostics`(引擎铸的诊断教学文本,在场 iff 引擎给了)是自由文本:**这里**过出口座(`inbox-diagnostics-text` 行 —— 与 live `task_notification.diagnostics` 同一个脱敏 scope,
207
+ * 加一条界)再落 `payload` 列,drain 逐字带出;店里不放未洗的字节。
206
208
  */
207
209
  export declare function buildWorkflowCompleteInboxEntry(route: {
208
210
  sessionId: string;
@@ -212,6 +214,7 @@ export declare function buildWorkflowCompleteInboxEntry(route: {
212
214
  status: "completed" | "failed";
213
215
  summary: string;
214
216
  completionId?: string;
217
+ diagnostics?: string;
215
218
  }, enqueuedAt: number): WorkflowCompletionInboxEntry;
216
219
  /**
217
220
  * P1 ①② delivery half, TWO-PHASE since 1.80: read the session's pending completions, emit
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, openSync, writeSync, fsyncSync, closeSync, renameSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { redactSecrets } from "../trace/redact.js";
4
+ import { seatText } from "../trace/exit-seat.js";
4
5
  import { replayJsonlSkipping } from "../observability/corrupt-read-seat.js";
5
6
  import { injectionTierOrAbsent } from "../trace/injection-tier.js";
6
7
  import { semaProvenanceOrAbsent } from "../trace/sema-provenance.js";
@@ -273,12 +274,19 @@ function readEntryExtras(payload) {
273
274
  }
274
275
  return typeof extras === "object" && extras !== null && !Array.isArray(extras) ? extras : {};
275
276
  }
276
- function encodeWorkflowCompleteExtras(completionId) {
277
- return completionId ? { payload: JSON.stringify({ completionId }) } : {};
277
+ function encodeWorkflowCompleteExtras(extras) {
278
+ const body = {
279
+ ...(extras.completionId ? { completionId: extras.completionId } : {}),
280
+ ...(extras.diagnostics ? { diagnostics: extras.diagnostics } : {}),
281
+ };
282
+ return Object.keys(body).length > 0 ? { payload: JSON.stringify(body) } : {};
278
283
  }
279
284
  function workflowCompleteFrameExtras(payload) {
280
- const id = readEntryExtras(payload).completionId;
281
- return typeof id === "string" && id !== "" ? { completionId: id } : {};
285
+ const { completionId, diagnostics } = readEntryExtras(payload);
286
+ return {
287
+ ...(typeof completionId === "string" && completionId !== "" ? { completionId } : {}),
288
+ ...(typeof diagnostics === "string" && diagnostics !== "" ? { diagnostics } : {}),
289
+ };
282
290
  }
283
291
  export function buildWorkflowCompleteInboxEntry(route, p, enqueuedAt) {
284
292
  return {
@@ -288,7 +296,7 @@ export function buildWorkflowCompleteInboxEntry(route, p, enqueuedAt) {
288
296
  status: p.status,
289
297
  summary: p.summary,
290
298
  enqueuedAt,
291
- ...encodeWorkflowCompleteExtras(p.completionId),
299
+ ...encodeWorkflowCompleteExtras({ completionId: p.completionId, diagnostics: p.diagnostics ? seatText("inbox-diagnostics-text", p.diagnostics) : undefined }),
292
300
  };
293
301
  }
294
302
  export async function emitPendingWorkflowCompletions(inbox, sessionId, callerPrincipal, emit, diag) {
@@ -6,8 +6,10 @@ import { type WorkflowAgentSessionIndex } from "./workflow-agent-session-index.j
6
6
  * core 给 `WorkflowCompletionNotifier.notify` 的入参加一个键 ⇒ 这里编译红(缺一行),逼人对「它上不上这条链」表态;core 删 / 改名一个键 ⇒ 同样红(多一行)。
7
7
  * · `carry`:搬运到投递(journal 门 → 收件箱 → `workflow_complete` 帧 / 账本行)。`runId` / `status` / `summary` 必带;其余可选键引擎没给(或空串)⇒ 载荷上缺席。
8
8
  * `completionId` = 引擎的一次完成的相关 id(fill-once,终态转换上铸):逐字、不脱敏、本服务零自铸。
9
- * · `drop`:这条持久链只搬运一句受限摘要 + 路由键 —— `task_id` / `task_type` / `toolUseId` 是 live `task_notification` 帧的键(那条路径自带),`result` / `usage` /
10
- * `diagnostics` 是完整结果 / 计量 / 诊断路由(终态轮询 `TaskOutput` / `WorkflowStatus` 读得到),不进补投帧。
9
+ * `diagnostics` = 引擎在完成通知上铸的诊断教学文本(未完成 agent 的摘要行、完整记录的读取手柄、journal 坐标、续跑句):补投路径是它在原腿已不在(断线重连 / 重启)时唯一的去处,
10
+ * 所以搬运;它是自由文本,出门前在入箱处过出口座(见 `buildWorkflowCompleteInboxEntry`),载荷上仍是引擎值。**崩溃恢复臂不带**:`WorkflowRun` 行上没有这一位,本服务不编。
11
+ * · `drop`:这条持久链只搬运一句受限摘要 + 诊断文本 + 路由键 —— `task_id` / `task_type` / `toolUseId` 是 live `task_notification` 帧的键(那条路径自带),`result` / `usage` 是完整结果 / 计量
12
+ * (`WorkflowStatus` 读得到),不进补投帧。
11
13
  * 键名直呼 core 的入参型(型文本里就有 core 名)⇒ 读表的 literals 段收得到这些键。
12
14
  */
13
15
  export declare const WORKFLOW_NOTIFY_KEY_DISPOSITION: {
@@ -18,12 +20,12 @@ export declare const WORKFLOW_NOTIFY_KEY_DISPOSITION: {
18
20
  readonly principal: "carry";
19
21
  readonly originatingSessionId: "carry";
20
22
  readonly completionId: "carry";
23
+ readonly diagnostics: "carry";
21
24
  readonly task_id: "drop";
22
25
  readonly task_type: "drop";
23
26
  readonly toolUseId: "drop";
24
27
  readonly result: "drop";
25
28
  readonly usage: "drop";
26
- readonly diagnostics: "drop";
27
29
  };
28
30
  type NotifyKeyDisposition = typeof WORKFLOW_NOTIFY_KEY_DISPOSITION;
29
31
  /** 处置表里 `carry` 的键。 */
@@ -13,12 +13,12 @@ export const WORKFLOW_NOTIFY_KEY_DISPOSITION = {
13
13
  principal: "carry",
14
14
  originatingSessionId: "carry",
15
15
  completionId: "carry",
16
+ diagnostics: "carry",
16
17
  task_id: "drop",
17
18
  task_type: "drop",
18
19
  toolUseId: "drop",
19
20
  result: "drop",
20
21
  usage: "drop",
21
- diagnostics: "drop",
22
22
  };
23
23
  const SUMMARY_MAX = 500;
24
24
  export function recoverySummary(status, error) {
@@ -81,6 +81,7 @@ export class WorkflowNotifyGate {
81
81
  ...(input.principal ? { principal: input.principal } : {}),
82
82
  ...(input.originatingSessionId ? { originatingSessionId: input.originatingSessionId } : {}),
83
83
  ...(input.completionId ? { completionId: input.completionId } : {}),
84
+ ...(input.diagnostics ? { diagnostics: input.diagnostics } : {}),
84
85
  }, input.scope ?? input.principal ?? "default");
85
86
  },
86
87
  };
@@ -0,0 +1,68 @@
1
+ /**
2
+ * **一个调用方交来的权限模式词,本部署收不收、收成什么** —— 唯一的受理判官(规则一处、三只属主各一处、序一处)。
3
+ *
4
+ * ## 一条规则,三只属主,本模块只排序
5
+ * ① 本任务 settings 的 kill-switch 折叠(`task-settings.ts` `foldDisableAutoMode`:`disableAutoMode` 在 ⇒ `auto` 折 `default`);
6
+ * ② 运维 `AUTONOMY` 的入口地板钳(`runtime-governance.ts` `entryPermissionMode` × `autonomyModeFloor`:`plan` 地板下比它松的词折成 `plan`);
7
+ * ③ 托管姿态的写围栏拒(`hosted-posture.ts` `permissionModeWriteFaceRefusal`:开写面的词在托管形上 422 `request.host_posture_refused`)。
8
+ * ①② 是**入口一步**({@link entryStep}:改词、不拒),③ 是**托管一步**(只拒、不改词)。顺序承重的只有「② 在 ③ 之前」(地板把 bypass 钳成 plan 之后就不开写面,不拒);
9
+ * ① 只动 `auto`、③ 只看开写面的词,两者互不相交。
10
+ *
11
+ * ## 两口的调用形(同一组函数、同一个序;同一个词 × 同一份部署事实 × 同一份任务 settings ⇒ 同一个结果,钉 `test/run-permission-mode-admission.test.ts` 的两口表)
12
+ * · 在飞 run 的切档口(`http/routes/run-permission-mode.ts`:`POST /v1/runs/:id/permission-mode` 的 `mode`,本任务 settings 读存储体)调整只判官 {@link admitPermissionModeWord}。
13
+ * · 提交口(`POST /v1/tasks|/v1/tasks/stream|/v1/runs` 的词 = 顶层 `permissionMode` ?? bundle `settings.permissions.defaultMode`)把两步落在**上一版就在的两处**:
14
+ * 入口一步在受理门盖章({@link stampSubmissionEntryMode},`http/admission.ts`:值形门之后、装配之前 —— 首跑装配与 `buildCheckpointCtx` 读的都是盖过章的体);
15
+ * 托管一步是装配期那一处判(`boot/resolve-spec.ts`,对盖过章的词调同一只 ③;提交腿与续跑重放腿同经)。不把整只判官提前到受理门:那会让托管拒抢在
16
+ * 属主 / 会话 / 执行上下文与装配期其余值判之前答(拒序变了 —— 钉同一文件的拒序组)。续跑重放不再过 ②:地板是**入口**事实,热改只影响之后提交的任务。
17
+ *
18
+ * ## 为什么切档口必须经这只判官(而不是靠引擎的 deny 位)
19
+ * 引擎在根腿上只认两只模式拒位(`RuntimeCaps.bypassPermissions` / `RuntimeCaps.autoMode`,本仓由 center 与本地 kill-switch 写);上面三问
20
+ * 都不是引擎位 —— 根腿没有天花板座,托管姿态与任务 settings 都是逐提交的事实。切档口若不经这只判官,提交口在受理时拒过 / 折过的词
21
+ * 就能在 run 跑起来之后原样切进去。
22
+ *
23
+ * ## 答
24
+ * · `refused` —— ③ 拒:状态 422、码 `request.host_posture_refused`、句子是属主的那一句;两口同码同句。
25
+ * · `admitted` —— `requested`(调用方说的词,缺席 = 没说)、`admitted`(交给引擎的词)、`entry`(② 那一次求值的读数,盖章与地板通告只读它)、
26
+ * `foldedBy`(本判官把词改了的那一站;没改 ⇒ 缺席。① ② 都改了 ⇒ `entry_floor`:落定的词是地板给的)。
27
+ */
28
+ import type { PermissionMode } from "@sema-agent/core";
29
+ import { type Autonomy, type EntryPermissionMode } from "./runtime-governance.js";
30
+ import { HOST_POSTURE_REFUSED_CODE, type TaskWriteFaceInput } from "./hosted-posture.js";
31
+ import { type ParsedTaskSettings } from "./task-settings.js";
32
+ /** 本判官改词的站(闭集;wire 上切档回执的 `admission.foldedBy`)。引擎自己的折因词(`operator_deny` / `face_absent` / `inherited_ceiling`)在引擎回执里,不进这张表。 */
33
+ export declare const PERMISSION_MODE_ADMISSION_FOLDS: readonly ["entry_floor", "disable_auto_mode"];
34
+ export type PermissionModeAdmissionFold = (typeof PERMISSION_MODE_ADMISSION_FOLDS)[number];
35
+ /** 判官读的事实:部署(活 config:`AUTONOMY` 地板 + 托管射程与天花板旋钮)与本任务的 settings(提交口 = 提交体的,切档口 = 存储体的)。 */
36
+ export interface PermissionModeAdmissionFacts {
37
+ readonly deployment: TaskWriteFaceInput & {
38
+ readonly autonomy?: Autonomy | undefined;
39
+ };
40
+ readonly settings: ParsedTaskSettings | undefined;
41
+ }
42
+ export type PermissionModeAdmission = {
43
+ readonly kind: "refused";
44
+ readonly status: 422;
45
+ readonly code: typeof HOST_POSTURE_REFUSED_CODE;
46
+ readonly message: string;
47
+ } | {
48
+ readonly kind: "admitted";
49
+ readonly requested: PermissionMode | undefined;
50
+ readonly admitted: PermissionMode | undefined;
51
+ readonly entry: EntryPermissionMode;
52
+ readonly foldedBy?: PermissionModeAdmissionFold;
53
+ };
54
+ /** 整只判官(入口一步 → 托管一步)—— 在飞 run 的切档口调它。 */
55
+ export declare function admitPermissionModeWord(word: PermissionMode | undefined, facts: PermissionModeAdmissionFacts): PermissionModeAdmission;
56
+ /**
57
+ * 提交口的入口一步(`http/admission.ts` 唯一调用;直调装配腿的测试夹具也调它 —— 少了这一步的体是部署上到不了的体):
58
+ * 体上的词(顶层 `permissionMode` ?? bundle `settings.permissions.defaultMode`)过 {@link entryStep},本任务的 settings = 体上那一份;
59
+ * 入口词盖进体(`task-settings.ts` `stampEntryPermissionMode`:地板在场时写顶层 `permissionMode`,缺席原物)。不拒 —— 托管一步在装配期判(见顶注「两口的调用形」)。
60
+ */
61
+ export declare function stampSubmissionEntryMode<B extends {
62
+ permissionMode?: unknown;
63
+ settings?: unknown;
64
+ }>(body: B, deployment: PermissionModeAdmissionFacts["deployment"]): {
65
+ readonly body: B;
66
+ readonly entry: EntryPermissionMode;
67
+ };
68
+ //# sourceMappingURL=permission-mode-admission.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { autonomyModeFloor, entryPermissionMode } from "./runtime-governance.js";
2
+ import { HOST_POSTURE_REFUSED_CODE, permissionModeWriteFaceRefusal } from "./hosted-posture.js";
3
+ import { foldDisableAutoMode, parseTaskSettings, requestedPermissionMode, stampEntryPermissionMode } from "./task-settings.js";
4
+ export const PERMISSION_MODE_ADMISSION_FOLDS = ["entry_floor", "disable_auto_mode"];
5
+ function entryStep(word, facts) {
6
+ const folded = foldDisableAutoMode(word, facts.settings);
7
+ const entry = entryPermissionMode(folded, autonomyModeFloor(facts.deployment.autonomy));
8
+ const foldedBy = entry.clamped ? "entry_floor" : folded !== word ? "disable_auto_mode" : undefined;
9
+ return { entry, ...(foldedBy !== undefined ? { foldedBy } : {}) };
10
+ }
11
+ export function admitPermissionModeWord(word, facts) {
12
+ const { entry, foldedBy } = entryStep(word, facts);
13
+ const why = permissionModeWriteFaceRefusal(entry.effective, facts.deployment);
14
+ if (why !== undefined)
15
+ return { kind: "refused", status: 422, code: HOST_POSTURE_REFUSED_CODE, message: why };
16
+ return { kind: "admitted", requested: word, admitted: entry.effective, entry, ...(foldedBy !== undefined ? { foldedBy } : {}) };
17
+ }
18
+ export function stampSubmissionEntryMode(body, deployment) {
19
+ const settings = parseTaskSettings(body.settings).settings;
20
+ const { entry } = entryStep(requestedPermissionMode(body, settings), { deployment, settings });
21
+ return { body: stampEntryPermissionMode(body, entry), entry };
22
+ }
23
+ //# sourceMappingURL=permission-mode-admission.js.map
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * **规则表的唯一编译口** —— 一张工具名规则表(`ask` / `deny` 之一)编成一只 `ToolPolicy`。读者:请求腿的 `settings.permissions.{ask,deny}`
3
- * 两桶(`task-settings.ts` 的 `deriveSettingsPolicy`)。
3
+ * 两桶(`task-settings.ts` 的 `deriveSettingsPolicy`,每桶按根至多编两集,见文件尾 {@link ruleRootOf})。设置层条目的形与层 → 根表也住这里(文件尾一节)。
4
4
  *
5
5
  * 🔴 core 7.42.0 起策略只说 deny / ask:allow 规则有且只有引擎 allow 层的两个席位 —— 请求的 `settings.permissions.allow` 整份投
6
6
  * `TaskSpec.allowedTools`(`task-settings.ts` 的 `applyTaskSettings`),运维的 `APPROVAL_GRANT` 投 `RunnerDeps.allowedTools`
@@ -9,6 +9,7 @@
9
9
  * 为什么住在一只叶模块里(不留在 `task-settings.ts`):本模块只 import core 与契约包 settings-schema(纯叶),读者引它不成环。
10
10
  */
11
11
  import { type PathRuleBases, type PermissionRule, type PermissionRulePolicyOptions, type ToolPolicy } from "@sema-agent/core";
12
+ import { type SettingsSource } from "@sema-agent/settings-schema";
12
13
  /** 一张表里规则条数的上限 —— 请求腿 wire 门(`task-settings.ts` 的 deny / ask 两桶判形)读的也是这一个名字:core 的缺省帽是 256,
13
14
  * 不对齐的话第 257 条起编译当场抛,等于服务端自己收下的东西自己反悔。 */
14
15
  export declare const MAX_PERMISSION_RULES_PER_LIST = 1000;
@@ -21,13 +22,13 @@ export declare const MAX_PERMISSION_RULES_PER_LIST = 1000;
21
22
  * 交一只名册解析器会多出一种读法:目录没有的名字成 `unsupported.tool_unknown` —— 本仓自铸的工具(`repo_read_file(path:…)`)上一条编得出、真判的规则会在受理口被拒。
22
23
  */
23
24
  export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | undefined): Pick<PermissionRulePolicyOptions, "caps" | "pathBases">;
24
- /** A name-keyed rule-list ToolPolicy: a tool named by `names` gets `behavior` (`ask` tightens to an approval gate;
25
+ /** A name-keyed rule-list ToolPolicy: a tool named by `rules` gets `behavior` (`ask` tightens to an approval gate;
25
26
  * `deny` hard-blocks); every other tool is out of scope (`defaultAction: "allow"` = this policy has no opinion about
26
27
  * anything it does not name — core 7.42.0: it states nothing and clears nothing). Composed deny-wins with the rest of
27
28
  * the fold, so both only ever tighten.
28
29
  *
29
- * `source` = 这张表**写在哪**(请求腿 `settings.permissions.<桶>`)。它盖进每条规则(core 的 deny / ask
30
- * 句里点名它:`flagged by permission rule "mcp__figma__*" (settings.permissions.ask)`),编不出来时的错误句也点名它;只是出处,
30
+ * `source` = 这条规则**写在哪**(请求腿 `settings.permissions.<桶>`,带层条目再加层:`settings.permissions.ask, layer userSettings`)。它盖进每条规则(core 的 deny / ask
31
+ * 句里点名它:`flagged by permission rule "mcp__figma__*" (settings.permissions.ask)`),编不出来时的错误句点名桶;只是出处,
31
32
  * 不带任何优先级(层序在各自的折叠里)。
32
33
  *
33
34
  * 🔴 [ref] / core [ref] —— 本函数曾是 `new Set(askNames).has(req.toolName)` 的**逐字**比对,那是
@@ -74,6 +75,54 @@ export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | und
74
75
  * 排除在外),dist 亲读在案。审计归因改由 `matchedAskRule` + `source` 承担,后者比前者强:它还让 core 的 auto-mode 分类器
75
76
  * **拒绝**替人清掉这条 ask(「运维写下的『每次问我』不是分类器的犹豫」),方向是收紧。
76
77
  */
77
- /** `behavior` = 这张表的态度 —— 型直接是 core 的 `PermissionRule["behavior"]`(不手抄:core 加 / 减一种态度,这里与它同步、读者 tsc 上见到)。 */
78
- export declare function createRuleListPolicy(names: readonly string[], behavior: PermissionRule["behavior"], source: string, pathBases: PathRuleBases | undefined): ToolPolicy;
78
+ /** `behavior` = 这张表的态度 —— 型直接是 core 的 `PermissionRule["behavior"]`(不手抄:core 加 / 减一种态度,这里与它同步、读者 tsc 上见到)。
79
+ * `rules` = 这一集的规则,**每条自带出处**({@link SourcedRule}:请求自己写的纯串 = 桶坐标;带层条目 = 桶坐标 + 层,{@link ruleSourceOf});
80
+ * `table` = 这一集出自哪只桶(只进编不出时的兜底句;逐条坐标由先跑的受理判 `rulePolicyBucketsRefusal` 给)。 */
81
+ export declare function createRuleListPolicy(rules: readonly SourcedRule[], behavior: PermissionRule["behavior"], table: string, pathBases: PathRuleBases | undefined): ToolPolicy;
82
+ /**
83
+ * deny / ask 桶的一个条目:**纯串** = 请求自己写的规则(今天的形,读在任务根);**带层条目** `{ rule, layer }` = 端从某个设置层读来的规则,`layer` 是
84
+ * 契约包 `SETTINGS_SOURCES` 的来源词(= 参照实现的设置来源词)。层只决定这条规则的 `/` 读在哪个根({@link layerRuleRoot}),**不决定准入档**
85
+ * (准入只由凭据推,`http/caller-trust.ts`):调用方自报任何层,挪动的只是它自己那条收紧读在哪,与它自己写 `//abs` 等价。
86
+ *
87
+ * 为什么是「条目带层」而不是一只新键:本版之前的每一个读者(回滚、滚动发布中的旧副本)对一只没见过的键是**静默忽略**(续跑重放走宽容层),
88
+ * 带层的收紧会整批消失;对一个不是串的条目则在桶门上 `bad_entry` —— 新提交 400、续跑 409,响亮拒。条数帽也因此仍是「一桶一张表」。
89
+ */
90
+ export type RuleListEntry = string | LayeredRule;
91
+ /** 带层条目(恰两键;受理判 {@link ruleListEntryOk})。 */
92
+ export interface LayeredRule {
93
+ readonly rule: string;
94
+ readonly layer: SettingsSource;
95
+ }
96
+ /** 一条进编译口的规则与它的出处串(core 规则行逐条带 `source`,问 / 拒句点名它)。 */
97
+ export interface SourcedRule {
98
+ readonly rule: string;
99
+ readonly source: string;
100
+ }
101
+ /** 一个条目合不合 deny / ask 桶的条目律:非空串;或恰 `rule`(非空串)+ `layer`(`SETTINGS_SOURCES` 里的词)两键的纯对象。别的形一律不合(多键、空串、未知层、数组、原型对象)。 */
102
+ export declare function ruleListEntryOk(x: unknown): x is RuleListEntry;
103
+ /** 条目的规则原文(纯串即它自己)。 */
104
+ export declare function ruleTextOf(entry: RuleListEntry): string;
105
+ /** 条目进编译口的出处串:纯串 = 桶坐标(今天的字面);带层 = 桶坐标 + 层(`settings.permissions.deny, layer userSettings`)。 */
106
+ export declare function ruleSourceOf(table: string, entry: RuleListEntry): string;
107
+ /** 规则的 `/` 读在哪个根(闭集,也是一桶里各集的编译序与判序):`task` = 任务根;`configuration` = 执行环境的配置根(`execution-config-root.ts`)。 */
108
+ export declare const RULE_ROOTS: readonly ["task", "configuration"];
109
+ export type RuleRoot = (typeof RULE_ROOTS)[number];
110
+ /**
111
+ * 一个设置层的 `/` 读在哪个根 —— 参照实现(CC 2.1.309)的表照录(`cli309.js:258818` `zBt` → `:252700` `D8o` / `swn`:user 层 ⇒ 配置目录 `Le()`〔`:251921`〕;
112
+ * project / local / policy ⇒ 工作目录;flag ⇒ flag 文件的目录,内联 ⇒ 工作目录)。本服务的两处取舍:`flagSettings` 读任务根(flag 文件的目录是端那台机的事实,本服务手里没有;
113
+ * 要固定位置写 `//abs`);`foreignProjectSettings`(仓库里写给别的工具的项目文件)与项目层同根。契约包加一个来源词 ⇒ 这里编译红。
114
+ */
115
+ export declare function layerRuleRoot(layer: SettingsSource): RuleRoot;
116
+ /**
117
+ * 一个条目编进哪一集:**只有 `/` 随层变** —— 带层条目的层根是配置根 ∧ 这条规则要 `root`(core 的必需基分类 `ruleBasesNeeded`)⇒ `configuration`;
118
+ * 其余一律 `task`(纯串、别的层、`~/` / 相对形、`//abs` 与 subpath 这类分类为空的规则 —— 后者在 Windows 族上仍随声明的根选盘,必须留在今天那一集)。
119
+ * 文法读不出的串 ⇒ `task`(受理判在前面就拒它,这里不另说一句)。
120
+ *
121
+ * 为什么按根分集、不按层分集:共根各层给出逐字相同的锚,合一集判决不变;而一只声明了 `root` 却没声明 `cwd` 的集把相对形读在声明的根上(core
122
+ * `policyAnchors`)—— 「一层一集」在任务根读不出的腿(未登记工作目录的 host 腿、每一条沙箱腿)上会把 user 层的相对规则读到配置根下。配置根那一集只装要
123
+ * `root` 的规则,`cwd` 永远不被问。
124
+ */
125
+ export declare function ruleRootOf(entry: RuleListEntry, behavior: PermissionRule["behavior"]): RuleRoot;
126
+ /** 一桶的条目按根分集(受理判与编译读同一只:判的就是要编的那一集);集内保持原序。 */
127
+ export declare function ruleSetsOf(entries: readonly RuleListEntry[], behavior: PermissionRule["behavior"]): Readonly<Record<RuleRoot, readonly RuleListEntry[]>>;
79
128
  //# sourceMappingURL=permission-rule-list.d.ts.map
@@ -1,5 +1,5 @@
1
- import { createPermissionRulePolicy } from "@sema-agent/core";
2
- import { stripControlAndBidi } from "@sema-agent/settings-schema";
1
+ import { createPermissionRulePolicy, parseRuleText, ruleBasesNeeded } from "@sema-agent/core";
2
+ import { SETTINGS_SOURCES, stripControlAndBidi } from "@sema-agent/settings-schema";
3
3
  export const MAX_PERMISSION_RULES_PER_LIST = 1_000;
4
4
  export function buildRuleListJudgeOptions(pathBases) {
5
5
  return {
@@ -7,13 +7,60 @@ export function buildRuleListJudgeOptions(pathBases) {
7
7
  ...(pathBases !== undefined ? { pathBases } : {}),
8
8
  };
9
9
  }
10
- export function createRuleListPolicy(names, behavior, source, pathBases) {
10
+ export function createRuleListPolicy(rules, behavior, table, pathBases) {
11
11
  try {
12
- return createPermissionRulePolicy(names.map((name) => ({ rule: name, behavior, source })), { defaultAction: "allow", ...buildRuleListJudgeOptions(pathBases) });
12
+ return createPermissionRulePolicy(rules.map(({ rule, source }) => ({ rule, behavior, source })), { defaultAction: "allow", ...buildRuleListJudgeOptions(pathBases) });
13
13
  }
14
14
  catch (e) {
15
15
  const coreMsg = stripControlAndBidi(e instanceof Error ? e.message : String(e));
16
- throw new Error(`${source.replace("settings.", "")} has rule(s) core cannot compile — ${coreMsg}`, { cause: e });
16
+ throw new Error(`${table.replace("settings.", "")} has rule(s) core cannot compile — ${coreMsg}`, { cause: e });
17
17
  }
18
18
  }
19
+ const SETTINGS_SOURCE_SET = new Set(SETTINGS_SOURCES);
20
+ export function ruleListEntryOk(x) {
21
+ if (typeof x === "string")
22
+ return x.length > 0;
23
+ if (x === null || typeof x !== "object" || Array.isArray(x) || Object.getPrototypeOf(x) !== Object.prototype)
24
+ return false;
25
+ const keys = Object.keys(x);
26
+ if (keys.length !== 2 || !Object.hasOwn(x, "rule") || !Object.hasOwn(x, "layer"))
27
+ return false;
28
+ const { rule, layer } = x;
29
+ return typeof rule === "string" && rule.length > 0 && typeof layer === "string" && SETTINGS_SOURCE_SET.has(layer);
30
+ }
31
+ export function ruleTextOf(entry) {
32
+ return typeof entry === "string" ? entry : entry.rule;
33
+ }
34
+ export function ruleSourceOf(table, entry) {
35
+ return typeof entry === "string" ? table : `${table}, layer ${entry.layer}`;
36
+ }
37
+ export const RULE_ROOTS = ["task", "configuration"];
38
+ export function layerRuleRoot(layer) {
39
+ switch (layer) {
40
+ case "userSettings":
41
+ return "configuration";
42
+ case "projectSettings":
43
+ case "localSettings":
44
+ case "flagSettings":
45
+ case "policySettings":
46
+ case "foreignProjectSettings":
47
+ return "task";
48
+ default: {
49
+ const unhandled = layer;
50
+ return unhandled;
51
+ }
52
+ }
53
+ }
54
+ export function ruleRootOf(entry, behavior) {
55
+ if (typeof entry === "string" || layerRuleRoot(entry.layer) === "task")
56
+ return "task";
57
+ const parsed = parseRuleText(entry.rule, behavior);
58
+ return "rule" in parsed && ruleBasesNeeded(parsed.rule).includes("root") ? "configuration" : "task";
59
+ }
60
+ export function ruleSetsOf(entries, behavior) {
61
+ const sets = { task: [], configuration: [] };
62
+ for (const entry of entries)
63
+ sets[ruleRootOf(entry, behavior)].push(entry);
64
+ return sets;
65
+ }
19
66
  //# sourceMappingURL=permission-rule-list.js.map
@@ -126,6 +126,10 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
126
126
  private replayRejectedCount;
127
127
  /** 重放期被丢弃的坏行数(撕尾不计:那是本格式存在的理由,由 {@link corruptQuarantinePath} 单独说)。 */
128
128
  private corruptLineCount;
129
+ /** 本次启动重放到的 v1 信封记录数(随 M34;{@link upgradeJournalEnvelope} 的读数)。 */
130
+ private journalV1Records;
131
+ /** 盘上还有 v1 信封记录(随 M34):重放到 v1 记录时置位,任何一次压实(整本重写成 v2)清位 —— 已被压实过的账本不再为升级重写第二次。 */
132
+ private journalV1OnDisk;
129
133
  /** 本次启动隔离出去的原件路径(无坏字节 ⇒ `undefined`)。 */
130
134
  readonly corruptQuarantinePath: string | undefined;
131
135
  /**
@@ -179,8 +183,8 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
179
183
  * 🔴 [ref] —— **出域的一次决议不落账本**。作用域的属主维(`owner`,判据 {@link askRowCarriesOwner})不在记录上:行此刻不带着门判过的那位属主
180
184
  * ⇒ 内核上这一笔必输、零写(读钟也在判定之后,不开账本窗)⇒ 没有要重放的东西,直调内核拿它的输者答案(与留存两动词「空的一次不落」同一条先例)。
181
185
  * 判定、落账、内核调用都在同一个同步前缀里,其间行不可能换属主。于是**落了账的每一条决议都在域内**,重放按行自己的属主喂属主维
182
- * ({@link FileApprovalAskStore.replayOp})—— 记录形一个字节不变:升级前写下的账本、升级后写下的账本、降级回老版本时老读者读新账本,三者同读,
183
- * 零迁移、零双读。
186
+ * ({@link FileApprovalAskStore.replayOp})—— 决议记录的形一个字节不变(没有属主格):升级前写下的决议记录与升级后写下的同读,零迁移、零双读
187
+ * (整本账的信封号另算 —— 见顶注「信封版本」)。
184
188
  */
185
189
  decideAsk(askId: string, batchId: string, owner: string | null, decision: DecideAskInput): Promise<DecideResult>;
186
190
  claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
@@ -208,14 +212,20 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
208
212
  corruptLines: number;
209
213
  records: number;
210
214
  };
211
- /** 释放追加 fd(`LocalBackend.close` → 优雅重启重开)。不包 try/catch 的理由同
212
- * {@link FileApprovalNonceStore.dispose}:core 的 `AppendLog.close()` 自己就是全函数。 */
215
+ /**
216
+ * 问行成因两列的 local 腿(一次性迁移 M34,窗口见 `plugins/one-time-migrations.ts` 登记表;`LocalBackend.runOneTimeMigrations` 调):本次启动重放到的 v1 信封记录
217
+ * 已由读侧半场按意义译进内存({@link translateJournalV1Record}),这里**一次压实**把整本账重写成 v2 快照(压实 = 既有整本原子重写)。返回 v1 记录数;没有 ⇒ 零写、0。
218
+ * 本次启动里账本已被别的压实整本重写过(撕尾 / 坏行收拢、M32 的车道回填)⇒ 盘上已无 v1,不再重写,读数照报。
219
+ */
220
+ upgradeJournalEnvelope(): number;
213
221
  /**
214
222
  * 执行放置切换 ④b 的 local 腿(一次性迁移登记表 M32;`LocalBackend.runOneTimeMigrations` 在任何读者之前调):升级前写下的问(日志 `ensureAsk` 记录行 /
215
223
  * 压实快照行没有 `executionLane`)记成本部署车道表的唯一项,然后**一次压实**把整本账重写成带车道的快照(压实 = 既有整本原子重写)。
216
224
  * 没有缺车道的行 ⇒ 零写。车道表不是一元而还有缺车道的行 ⇒ 抛与 SQL 同一只拒(`backfill_ambiguous`,由调用方铸),一个字节没写。
217
225
  */
218
226
  backfillExecutionLanes(lanes: readonly ExecutionLane[], refuse: (pending: number) => never): number;
227
+ /** 释放追加 fd(`LocalBackend.close` → 优雅重启重开)。不包 try/catch 的理由同
228
+ * {@link FileApprovalNonceStore.dispose}:core 的 `AppendLog.close()` 自己就是全函数。 */
219
229
  dispose(): void;
220
230
  }
221
231
  export {};
@@ -30,6 +30,29 @@ const JOURNAL_OP_WORDS = new Set(JOURNAL_OPS);
30
30
  export const JOURNAL_OP_TABLE_GATE = true;
31
31
  const JOURNAL_V1_CLAIM_INTENTS = ["expire", "cancel"];
32
32
  const JOURNAL_V1_CLAIM_INTENT_WORDS = new Set(JOURNAL_V1_CLAIM_INTENTS);
33
+ const JOURNAL_ENVELOPE = 2;
34
+ const JOURNAL_V1_ENVELOPE = 1;
35
+ const V1_ENDING_STATES = new Set(["VOID", "DENIED"]);
36
+ const V1_TRIGGER_STATES = new Set(["PARKING", "PARKED"]);
37
+ function translateJournalV1Record(raw, refuse) {
38
+ const rec = raw;
39
+ if (rec.k === "ask" && rec.row !== null && typeof rec.row === "object") {
40
+ const { deniedReason, ...row } = rec.row;
41
+ const cause = typeof deniedReason === "string" ? deniedReason : null;
42
+ if (cause !== null && row.state === "STREAM_PENDING") {
43
+ refuse(`an envelope-1 snapshot row in STREAM_PENDING carries the single cause field ${JSON.stringify(cause)} — no released server wrote that shape and it has no two-column reading`);
44
+ }
45
+ return { ...raw, row: { ...row, endingCause: V1_ENDING_STATES.has(row.state) ? cause : null, parkTrigger: V1_TRIGGER_STATES.has(row.state) ? cause : null } };
46
+ }
47
+ if (rec.k === "op" && (rec.op === "transitionAsk" || rec.op === "resolveProvisional") && rec.patch !== null && typeof rec.patch === "object" && "deniedReason" in rec.patch) {
48
+ const { deniedReason, ...patch } = rec.patch;
49
+ if (!V1_ENDING_STATES.has(rec.to) || (typeof deniedReason !== "string" && deniedReason !== null)) {
50
+ refuse(`an envelope-1 ${String(rec.op)} record sets the single cause field on a transition to ${JSON.stringify(rec.to)} with ${JSON.stringify(deniedReason)} — no released server wrote that shape and it has no two-column reading`);
51
+ }
52
+ return { ...raw, patch: { ...patch, endingCause: deniedReason } };
53
+ }
54
+ return raw;
55
+ }
33
56
  const ASK_JOURNAL_SHAPE = {
34
57
  what: "approval-ask journal record",
35
58
  read: (record) => record,
@@ -57,6 +80,8 @@ export class FileApprovalAskStore {
57
80
  pinnedNow = null;
58
81
  replayRejectedCount = 0;
59
82
  corruptLineCount = 0;
83
+ journalV1Records = 0;
84
+ journalV1OnDisk = false;
60
85
  corruptQuarantinePath;
61
86
  constructor(root) {
62
87
  const dir = join(root, APPROVAL_ASK_DIR);
@@ -95,7 +120,7 @@ export class FileApprovalAskStore {
95
120
  }
96
121
  write(call, run) {
97
122
  const t = Date.now();
98
- const record = { v: 1, k: "op", t, ...call };
123
+ const record = { v: JOURNAL_ENVELOPE, k: "op", t, ...call };
99
124
  const encoded = encodeStoreJsonPayload(`${call.op} call`, record);
100
125
  this.logFor().append(encoded, true);
101
126
  this.records += 1;
@@ -125,22 +150,29 @@ export class FileApprovalAskStore {
125
150
  }
126
151
  compact(snapshot = this.core.exportRows()) {
127
152
  const lines = [
128
- ...snapshot.batches.map((row) => `${JSON.stringify({ v: 1, k: "batch", row })}\n`),
129
- ...snapshot.asks.map((row) => `${JSON.stringify({ v: 1, k: "ask", row })}\n`),
153
+ ...snapshot.batches.map((row) => `${JSON.stringify({ v: JOURNAL_ENVELOPE, k: "batch", row })}\n`),
154
+ ...snapshot.asks.map((row) => `${JSON.stringify({ v: JOURNAL_ENVELOPE, k: "ask", row })}\n`),
130
155
  ];
131
156
  this.log?.closeForSwap();
132
157
  atomicWriteFile(this.tmpDir, this.path, lines.join(""));
158
+ this.journalV1OnDisk = false;
133
159
  this.records = lines.length;
134
160
  }
135
- replayRecord(raw) {
136
- const probe = raw;
161
+ replayRecord(disk) {
162
+ const head = disk;
137
163
  const refuse = (why) => {
138
164
  throw new StoreBootRefusalError(`FileApprovalAskStore: approval ask journal ${this.path} carries a record this build cannot replay — ${why} ` +
139
- `(v=${JSON.stringify(probe.v)} k=${JSON.stringify(probe.k)} op=${JSON.stringify(probe.op)}). ` +
165
+ `(v=${JSON.stringify(head.v)} k=${JSON.stringify(head.k)} op=${JSON.stringify(head.op)}). ` +
140
166
  "Replaying only the records it understands would resurrect decided asks or drop a human's approval. " +
141
167
  "Run the server version that wrote this journal, or move the file aside (every approval on it is abandoned) to continue.");
142
168
  };
143
- if (probe.v !== 1)
169
+ if (head.v === JOURNAL_V1_ENVELOPE) {
170
+ this.journalV1Records += 1;
171
+ this.journalV1OnDisk = true;
172
+ }
173
+ const raw = head.v === JOURNAL_V1_ENVELOPE ? translateJournalV1Record(disk, refuse) : disk;
174
+ const probe = raw;
175
+ if (probe.v !== JOURNAL_ENVELOPE && probe.v !== JOURNAL_V1_ENVELOPE)
144
176
  refuse("unknown envelope version");
145
177
  if (probe.k === "ask" || probe.k === "batch") {
146
178
  const row = probe.row;
@@ -296,6 +328,15 @@ export class FileApprovalAskStore {
296
328
  replayStats() {
297
329
  return { rejected: this.replayRejectedCount, corruptLines: this.corruptLineCount, records: this.records };
298
330
  }
331
+ upgradeJournalEnvelope() {
332
+ const upgraded = this.journalV1Records;
333
+ if (upgraded === 0)
334
+ return 0;
335
+ if (this.journalV1OnDisk)
336
+ this.compact();
337
+ this.journalV1Records = 0;
338
+ return upgraded;
339
+ }
299
340
  backfillExecutionLanes(lanes, refuse) {
300
341
  const pending = this.core.exportRows().asks.filter((r) => !isExecutionLane(String(r.executionLane ?? ""))).length;
301
342
  if (pending === 0)
@@ -101,7 +101,8 @@ export declare class InMemoryApprovalAskStore implements ApprovalAskStore {
101
101
  * 单调性论证)。
102
102
  */
103
103
  claimTerminal(askId: string, batchId: string, intent: AskTerminalClaimIntent, opts?: AskTerminalClaimOpts): Promise<AskTerminalClaimOutcome>;
104
- /** `cause`(可选)= 转 park 的触发成因,赢下 PARKING 的同一步落进 `deniedReason`(SQL twin 同一条 UPDATE 的镜像;接口上的 `expireAsk` 不带它,只有 `claimTerminal(expire, { cause })` 带)。 */
104
+ /** `cause`(可选)= 转 park 的触发成因,赢下 PARKING 的同一步落进 `parkTrigger`(SQL twin 同一条 UPDATE 的镜像;接口上的 `expireAsk` 不带它,只有 `claimTerminal(expire, { cause })` 带)——
105
+ * `parkTrigger` 的唯一写点:此后的转移(补丁里没有这一键)不再碰它。 */
105
106
  expireAsk(askId: string, batchId: string, cause?: AskTerminalClaimOpts["cause"]): Promise<ExpireResult>;
106
107
  bindBatch(batchId: string, askId: string, gate: BindGateInput): Promise<BindResult>;
107
108
  deferReconcile(askId: string, expectedState: AskState, expectedRev: number, nowMs: number): Promise<boolean>;