@sema-agent/server 7.142.0 → 7.144.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 (301) hide show
  1. package/MIGRATION.md +608 -0
  2. package/USAGE.md +63 -36
  3. package/dist/adoption/plan.js +22 -0
  4. package/dist/approval-ask-audit-store.d.ts +7 -5
  5. package/dist/approval-ask-audit-store.js +12 -6
  6. package/dist/approval-ask-machine.d.ts +15 -7
  7. package/dist/approval-ask-machine.js +10 -4
  8. package/dist/approval-card.d.ts +155 -39
  9. package/dist/approval-card.js +111 -31
  10. package/dist/approval-queue-kind.d.ts +27 -0
  11. package/dist/approval-queue-kind.js +6 -0
  12. package/dist/approval-reconciler.d.ts +8 -8
  13. package/dist/approval-reconciler.js +3 -3
  14. package/dist/audit.js +4 -2
  15. package/dist/boot/budget-tracing.d.ts +1 -1
  16. package/dist/boot/execution-env.js +1 -1
  17. package/dist/boot/governance-seams.d.ts +26 -41
  18. package/dist/boot/governance-seams.js +61 -28
  19. package/dist/boot/late-binds.d.ts +7 -4
  20. package/dist/boot/late-binds.js +3 -1
  21. package/dist/boot/leader.js +2 -4
  22. package/dist/boot/memory-consolidation.d.ts +0 -8
  23. package/dist/boot/memory-consolidation.js +3 -3
  24. package/dist/boot/reapers.js +5 -4
  25. package/dist/boot/resolve-spec.d.ts +1 -1
  26. package/dist/boot/resolve-spec.js +8 -8
  27. package/dist/boot/runner-deps.d.ts +14 -6
  28. package/dist/boot/runner-deps.js +6 -17
  29. package/dist/boot/shutdown.d.ts +9 -0
  30. package/dist/boot/shutdown.js +18 -2
  31. package/dist/boot/stage-01-config.js +2 -0
  32. package/dist/boot/stage-02-web-admin-pull.js +3 -0
  33. package/dist/boot/stage-03-store-backend.d.ts +4 -1
  34. package/dist/boot/stage-03-store-backend.js +1 -2
  35. package/dist/boot/stage-04-stores.d.ts +5 -2
  36. package/dist/boot/stage-04-stores.js +3 -5
  37. package/dist/boot/stage-05-execution-env.d.ts +5 -2
  38. package/dist/boot/stage-06-runners.d.ts +5 -2
  39. package/dist/boot/stage-06-runners.js +1 -3
  40. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  41. package/dist/boot/stage-07-capability-layer.js +6 -2
  42. package/dist/boot/stage-08-reapers.d.ts +5 -2
  43. package/dist/boot/stage-09-leader.d.ts +5 -2
  44. package/dist/boot/stage-10-http-server.d.ts +5 -2
  45. package/dist/boot/stage-10-http-server.js +4 -1
  46. package/dist/boot/stores.d.ts +5 -2
  47. package/dist/boot/stores.js +7 -7
  48. package/dist/boot/web-admin.d.ts +5 -1
  49. package/dist/boot/web-admin.js +31 -19
  50. package/dist/capabilities/hands-lane.d.ts +9 -39
  51. package/dist/capabilities/hands-lane.js +8 -13
  52. package/dist/capabilities/mcp-probe.d.ts +3 -2
  53. package/dist/capabilities/scenarios.d.ts +23 -17
  54. package/dist/capabilities/scenarios.js +2 -2
  55. package/dist/capabilities/skills.d.ts +1 -17
  56. package/dist/capabilities/skills.js +0 -8
  57. package/dist/capability-facts.d.ts +8 -8
  58. package/dist/capability-facts.js +4 -4
  59. package/dist/card-void.d.ts +6 -9
  60. package/dist/config-catalog.js +1 -1
  61. package/dist/config-d-migration.d.ts +2 -0
  62. package/dist/config-dir-trust.js +58 -29
  63. package/dist/config-invariants.d.ts +7 -1
  64. package/dist/config-invariants.js +7 -0
  65. package/dist/config-provider.d.ts +10 -0
  66. package/dist/config-provider.js +15 -4
  67. package/dist/config-root-migration.d.ts +13 -0
  68. package/dist/config-root-migration.js +99 -0
  69. package/dist/config-types.d.ts +5 -5
  70. package/dist/config.d.ts +4 -3
  71. package/dist/config.js +1 -1
  72. package/dist/delegated-session-owner.d.ts +35 -151
  73. package/dist/delegated-session-owner.js +1 -83
  74. package/dist/device-ws-protocol.d.ts +1 -0
  75. package/dist/device-ws-protocol.js +2 -1
  76. package/dist/entry-mode-clamp-notice.d.ts +1 -1
  77. package/dist/env-model-table-gate.d.ts +53 -0
  78. package/dist/env-model-table-gate.js +53 -0
  79. package/dist/execution-placement.d.ts +7 -4
  80. package/dist/fleet/fleet-bus.d.ts +28 -52
  81. package/dist/fleet/fleet-bus.js +35 -100
  82. package/dist/fleet/fleet-reconciler.d.ts +4 -4
  83. package/dist/fleet/fleet-reconciler.js +1 -1
  84. package/dist/fleet/subagent-tail-bus.d.ts +14 -11
  85. package/dist/fleet/subagent-tail-bus.js +15 -28
  86. package/dist/hooks/classic-stations.d.ts +3 -1
  87. package/dist/hooks/classic-stations.js +2 -0
  88. package/dist/hooks/hook-runner.js +28 -7
  89. package/dist/host-decision.d.ts +54 -20
  90. package/dist/host-decision.js +42 -14
  91. package/dist/http/admission.d.ts +1 -1
  92. package/dist/http/admission.js +7 -3
  93. package/dist/http/approval-sweeps.js +11 -8
  94. package/dist/http/caller-trust.d.ts +1 -1
  95. package/dist/http/durable-run-leg.js +2 -2
  96. package/dist/http/engine-refusal.d.ts +16 -0
  97. package/dist/http/engine-refusal.js +17 -0
  98. package/dist/http/memory-compliance-failure-reply.d.ts +1 -3
  99. package/dist/http/memory-compliance-failure-reply.js +4 -6
  100. package/dist/http/owner-access.d.ts +11 -0
  101. package/dist/http/owner-access.js +13 -0
  102. package/dist/http/principal-gate.d.ts +1 -1
  103. package/dist/http/refusal-body.d.ts +7 -3
  104. package/dist/http/refusal-body.js +1 -0
  105. package/dist/http/resume-legs.d.ts +21 -7
  106. package/dist/http/resume-legs.js +82 -53
  107. package/dist/http/resume-restatement.d.ts +39 -0
  108. package/dist/http/resume-restatement.js +25 -0
  109. package/dist/http/route-ctx.d.ts +35 -10
  110. package/dist/http/route-ctx.js +1 -1
  111. package/dist/http/routes/approvals-assistant.d.ts +9 -1
  112. package/dist/http/routes/approvals-assistant.js +143 -31
  113. package/dist/http/routes/capabilities.js +11 -2
  114. package/dist/http/routes/memory-policy.d.ts +2 -2
  115. package/dist/http/routes/memory-policy.js +1 -1
  116. package/dist/http/routes/notify-wake.js +23 -25
  117. package/dist/http/routes/rules.js +48 -13
  118. package/dist/http/routes/runs.js +9 -8
  119. package/dist/http/routes/session-memory-sync.d.ts +34 -6
  120. package/dist/http/routes/session-memory-sync.js +27 -10
  121. package/dist/http/routes/session-sync.js +13 -20
  122. package/dist/http/routes/sessions.d.ts +2 -2
  123. package/dist/http/routes/sessions.js +22 -23
  124. package/dist/http/routes/tasks.js +27 -49
  125. package/dist/http/server.d.ts +13 -7
  126. package/dist/http/server.js +2 -2
  127. package/dist/http/stored-body-replay.js +15 -4
  128. package/dist/http/wire-types.d.ts +17 -7
  129. package/dist/index.d.ts +1 -0
  130. package/dist/index.js +1 -0
  131. package/dist/lane-home-probe.d.ts +15 -0
  132. package/dist/lane-home-probe.js +4 -0
  133. package/dist/leader/leader.js +5 -2
  134. package/dist/leader/wire.d.ts +7 -1
  135. package/dist/leader/wire.js +6 -5
  136. package/dist/leg-facts.d.ts +37 -0
  137. package/dist/leg-facts.js +15 -0
  138. package/dist/memory-bundle-engine.d.ts +1 -4
  139. package/dist/memory-bundle-engine.js +2 -2
  140. package/dist/memory-operator-faces.d.ts +30 -66
  141. package/dist/memory-operator-faces.js +17 -88
  142. package/dist/memory-scope-stores.d.ts +38 -40
  143. package/dist/memory-scope-stores.js +51 -36
  144. package/dist/memory-scope.d.ts +0 -9
  145. package/dist/memory-scope.js +0 -9
  146. package/dist/memory-sync-client.js +14 -3
  147. package/dist/memory-sync.d.ts +20 -4
  148. package/dist/memory-sync.js +23 -4
  149. package/dist/model-plane-face.d.ts +13 -7
  150. package/dist/model-plane-face.js +2 -1
  151. package/dist/observability/corrupt-read-seat.d.ts +5 -9
  152. package/dist/observability/corrupt-read-seat.js +1 -2
  153. package/dist/observability/fail-open.d.ts +30 -10
  154. package/dist/observability/fail-open.js +27 -10
  155. package/dist/observability/run-terminal.d.ts +1 -1
  156. package/dist/observability/run-terminal.js +6 -2
  157. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -19
  158. package/dist/orchestration/workflow-completion-inbox.js +0 -12
  159. package/dist/orchestration/workflow-notify-journal.d.ts +3 -2
  160. package/dist/orchestration/workflow-park-decision.d.ts +8 -3
  161. package/dist/orchestration/workflow-park-decision.js +5 -3
  162. package/dist/org-delivery-gate.d.ts +11 -0
  163. package/dist/org-delivery-gate.js +19 -1
  164. package/dist/parked-decide.d.ts +13 -9
  165. package/dist/parked-decide.js +7 -7
  166. package/dist/parked-revive-surface.d.ts +6 -7
  167. package/dist/parked-revive-surface.js +1 -1
  168. package/dist/peer-inbound-face.d.ts +122 -0
  169. package/dist/peer-inbound-face.js +123 -0
  170. package/dist/permission-rule-list.d.ts +13 -9
  171. package/dist/permission-rule-list.js +7 -3
  172. package/dist/plugins/approval-ask-store-file.js +18 -6
  173. package/dist/plugins/approval-ask-store-memory.js +8 -6
  174. package/dist/plugins/approval-ask-store-sql.d.ts +28 -13
  175. package/dist/plugins/approval-ask-store-sql.js +30 -11
  176. package/dist/plugins/caching-session-store.d.ts +0 -3
  177. package/dist/plugins/caching-session-store.js +0 -3
  178. package/dist/plugins/checkpoint-store-sql.d.ts +45 -38
  179. package/dist/plugins/checkpoint-store-sql.js +108 -45
  180. package/dist/plugins/file-history-store-sql.d.ts +24 -18
  181. package/dist/plugins/file-history-store-sql.js +24 -19
  182. package/dist/plugins/file-run-store.d.ts +29 -0
  183. package/dist/plugins/file-run-store.js +97 -8
  184. package/dist/plugins/fork-routing-session-store.d.ts +8 -1
  185. package/dist/plugins/fork-routing-session-store.js +6 -0
  186. package/dist/plugins/imported-layer-root-retirement.d.ts +82 -0
  187. package/dist/plugins/imported-layer-root-retirement.js +96 -0
  188. package/dist/plugins/local-checkpoint-store.d.ts +6 -8
  189. package/dist/plugins/local-checkpoint-store.js +5 -4
  190. package/dist/plugins/local-session-store.d.ts +69 -30
  191. package/dist/plugins/local-session-store.js +133 -57
  192. package/dist/plugins/local-session-window.d.ts +4 -2
  193. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +9 -31
  194. package/dist/plugins/mailbox-recipient-lifecycle.js +4 -0
  195. package/dist/plugins/mailbox-store-sql.d.ts +12 -8
  196. package/dist/plugins/mailbox-store-sql.js +76 -7
  197. package/dist/plugins/memory-sync-store-pg.d.ts +15 -0
  198. package/dist/plugins/memory-sync-store-pg.js +21 -0
  199. package/dist/plugins/memory-sync-store-tidb.d.ts +2 -0
  200. package/dist/plugins/memory-sync-store-tidb.js +13 -1
  201. package/dist/plugins/one-time-migrations.d.ts +44 -14
  202. package/dist/plugins/one-time-migrations.js +118 -15
  203. package/dist/plugins/org-delivery-quota-store.d.ts +1 -1
  204. package/dist/plugins/org-delivery-quota-store.js +2 -12
  205. package/dist/plugins/park-commit-watch.d.ts +1 -1
  206. package/dist/plugins/permission-rule-store-file.d.ts +10 -1
  207. package/dist/plugins/permission-rule-store-file.js +15 -2
  208. package/dist/plugins/permission-rule-store-sql.d.ts +31 -1
  209. package/dist/plugins/permission-rule-store-sql.js +35 -2
  210. package/dist/plugins/pg-session-storage.d.ts +1 -9
  211. package/dist/plugins/pg-session-storage.js +2 -19
  212. package/dist/plugins/remote-env-device.d.ts +8 -11
  213. package/dist/plugins/remote-env-device.js +6 -3
  214. package/dist/plugins/remote-env-host.d.ts +8 -4
  215. package/dist/plugins/remote-env-host.js +7 -9
  216. package/dist/plugins/remote-env-k8s.d.ts +37 -24
  217. package/dist/plugins/remote-env-k8s.js +50 -17
  218. package/dist/plugins/remote-shell.js +12 -2
  219. package/dist/plugins/retention-store-sql.d.ts +2 -2
  220. package/dist/plugins/sandbox-read-floor.d.ts +5 -7
  221. package/dist/plugins/sandbox-read-floor.js +2 -7
  222. package/dist/plugins/schema-consistency.d.ts +5 -1
  223. package/dist/plugins/session-fork.d.ts +1 -1
  224. package/dist/plugins/session-ownership.d.ts +9 -0
  225. package/dist/plugins/session-ownership.js +6 -0
  226. package/dist/plugins/session-store.d.ts +13 -16
  227. package/dist/plugins/session-store.js +9 -10
  228. package/dist/plugins/sql-errors.d.ts +10 -1
  229. package/dist/plugins/sql-errors.js +13 -0
  230. package/dist/plugins/sql-json-column.d.ts +24 -2
  231. package/dist/plugins/sql-json-column.js +38 -3
  232. package/dist/plugins/store-backend.d.ts +23 -28
  233. package/dist/plugins/store-backend.js +12 -3
  234. package/dist/plugins/store-contracts.d.ts +29 -5
  235. package/dist/plugins/store-contracts.js +4 -1
  236. package/dist/plugins/task-attachment-store.d.ts +2 -2
  237. package/dist/plugins/task-attachment-store.js +37 -52
  238. package/dist/plugins/tidb-session-store.d.ts +2 -14
  239. package/dist/plugins/tidb-session-store.js +2 -20
  240. package/dist/plugins/tool-result-store-sql.d.ts +9 -8
  241. package/dist/plugins/tool-result-store-sql.js +23 -2
  242. package/dist/plugins/wiring-section-codec.d.ts +32 -0
  243. package/dist/plugins/wiring-section-codec.js +55 -0
  244. package/dist/plugins/workspace-fill.d.ts +1 -1
  245. package/dist/rule-entry-list.d.ts +3 -2
  246. package/dist/rule-entry-list.js +5 -1
  247. package/dist/rules-consent.d.ts +28 -5
  248. package/dist/rules-consent.js +40 -28
  249. package/dist/run-local.js +11 -6
  250. package/dist/runs.d.ts +12 -8
  251. package/dist/runs.js +10 -7
  252. package/dist/runtime-governance.d.ts +1 -1
  253. package/dist/security.d.ts +0 -4
  254. package/dist/server-secret-env.d.ts +4 -0
  255. package/dist/session-error-wire.d.ts +34 -23
  256. package/dist/session-error-wire.js +8 -3
  257. package/dist/session-incarnation.d.ts +3 -3
  258. package/dist/session-notify-door.d.ts +106 -0
  259. package/dist/session-notify-door.js +97 -0
  260. package/dist/session-sync.d.ts +9 -14
  261. package/dist/session-sync.js +5 -5
  262. package/dist/session-workspace.d.ts +3 -13
  263. package/dist/session-workspace.js +0 -9
  264. package/dist/standing-answer-grant.d.ts +5 -0
  265. package/dist/standing-answer-grant.js +10 -0
  266. package/dist/stores-sql/session-policy-store-sql.d.ts +4 -2
  267. package/dist/stores-sql/session-policy-store-sql.js +3 -13
  268. package/dist/task-mcp.d.ts +1 -1
  269. package/dist/task-settings.d.ts +11 -8
  270. package/dist/task-settings.js +31 -11
  271. package/dist/tool-approval.d.ts +55 -35
  272. package/dist/tool-approval.js +112 -74
  273. package/dist/trace/core-keyset-guard.d.ts +12 -9
  274. package/dist/trace/engine-notice-wire.d.ts +1 -1
  275. package/dist/trace/engine-notice-wire.js +4 -2
  276. package/dist/trace/exit-seat.d.ts +7 -1
  277. package/dist/trace/exit-seat.js +1 -0
  278. package/dist/trace/ledger-sink.d.ts +2 -2
  279. package/dist/trace/ledger-sink.js +12 -4
  280. package/dist/trace/model-segment.d.ts +74 -25
  281. package/dist/trace/model-segment.js +141 -32
  282. package/dist/trace/project.d.ts +15 -6
  283. package/dist/trace/project.js +12 -4
  284. package/dist/trace/redact.d.ts +72 -9
  285. package/dist/trace/redact.js +1509 -471
  286. package/dist/trace/self-minted-wire.d.ts +598 -47
  287. package/dist/trace/self-minted-wire.js +190 -20
  288. package/dist/trace/wire-projection-faces.d.ts +1 -1
  289. package/dist/trace/wire-projection-faces.js +1 -1
  290. package/dist/verifier-pause-card.d.ts +33 -5
  291. package/dist/verifier-pause-card.js +15 -10
  292. package/dist/web-admin-client/apply-effective.d.ts +12 -16
  293. package/dist/web-admin-client/apply-effective.js +90 -10
  294. package/dist/workspace-space.d.ts +19 -3
  295. package/dist/workspace-space.js +8 -4
  296. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -1
  297. package/package.json +3 -2
  298. package/wire-changes/server@7.143.0.json +1167 -0
  299. package/wire-changes/server@7.144.0.json +291 -0
  300. package/dist/session-records-skipped-notice.d.ts +0 -21
  301. package/dist/session-records-skipped-notice.js +0 -27
@@ -0,0 +1,122 @@
1
+ /**
2
+ * **被扣住的跨会话来信的收信方决断面**([ref])—— core 进程级 held 队列在本服务的**投影 + 决断转发**,不存第二份事实。
3
+ *
4
+ * ## 病
5
+ * 一封跨会话来信被扣住时,发信方 A 收到 `held` 回执,收信方 B 只见一帧 `engine_notice peer.inbound_disposition`(而且只在 B 有活腿时),
6
+ * `GET /v1/approvals` 里什么都没有 ⇒ 一封**等人审**的信(权限档对等判扣下的)B 没有地方批,只能等 `dialogExpiry` 带 `expired` 回执过期。
7
+ * core 早就给了宿主的决断口(`peerHeldQueueIfAny(scope, sessionId, box?)` → `PeerHeldQueue.list()` / `settle(seq, "approve" | "deny")`),本仓零处接。
8
+ *
9
+ * ## 规则
10
+ * · **事实只在 core**:一封信被扣住的那一刻起,它的决定是信箱记录自己的字段(store CAS),卡是 core 那只进程级队列上的一张;
11
+ * receipt / 投递 / 到期计时都是 core 绑在队列上的 sink(引擎在 run 的 drain 里自己 `configure`,宿主**不**接 sink —— 接了就是第二个写者)。
12
+ * 本面只做两件事:把队列里等人审的卡(见下「只有人能答的卡」)投成 `pending[]` 的 `peer_inbound` 行;把决断口的 approve / deny 原样交给 `settle`。
13
+ * 没有行店、没有落账:一张卡在不在、谁赢了 CAS、结局是什么,全是 core 的读数。
14
+ * · **属主 = 收信方**:列与决都只在调用方**自己**的信箱 scope 里找队列(`peerHeldQueueIfAny(调用方 scope, …)`;scope 读法与 core 的
15
+ * `taskScope = spec.principal ?? "default"` 同一条,见 {@link peerBoxScope})—— 别人的卡结构上取不到,决断口答的 404 与「没有这张卡」同码同文。
16
+ * 显式 operator **没有**额外射程:一封信是收信人的,不是运维治理面(与停驻门「operator 可决任意行」刻意不同,决断口按族分臂)。
17
+ * · **只有人能答的卡才进审批席**(core `PEER_HELD_REVIEW_CAUSES`:`mode-mismatch` / `no-mode-asserted` / `invalid-mode-attestation` —— 权限档
18
+ * 对等判扣下的信;CC 只对这几个成因开审阅框与到期计时,cli309.js:36111–36116 的句子逐条:`managed-setting` / `repo-setting` / `explicit-setting`
19
+ * 是「组织 / 仓库 / 你自己的设置在扣」,`your own "accept" cannot override managed policy; ask your admin` —— 收信方**不能**逐封放行)。
20
+ * 🔴 这是安全轴:core 的 `settle(approve)` 本身不看成因(它信宿主只把可审的卡呈给人),一张被部署策略
21
+ * `CROSS_SESSION_INBOUND=hold` 扣下的信若进了审批席,收信方一次 approve 就越过了组织策略。所以设置类 / `cross-principal-default` /
22
+ * `mode-unknown` / `bypass-default` 的卡**不列、不收**(决断口 404,与查无此卡同答);它们按 core 的规则等设置改变(每拍重判)。
23
+ * 成因会在重判时移动,列 / 决每次都读卡上**当下**的成因。
24
+ * · **一个调用方、一张卡、一个答案**:决断口只认列表会列出的卡(`decision === "held"` ∧ 可审成因);已批待投(`approved`)/ 已交接(`delivered`)的卡不列、不收。
25
+ *
26
+ * ## 枚举(core 7.62.0 `listPeerHeldQueues`)
27
+ * 列与关停都读 core 进程注册表的快照(`listPeerHeldQueues()`:每只队列的 scope / 打开它的会话 / 卡来自哪只箱 / 同一时刻的卡),本面不记任何表 ——
28
+ * 一张卡在不在、属于哪个 scope、哪只箱,全是那一读。决断口按行 id 的 (会话, 车道, seq) 回 `peerHeldQueueIfAny(调用方 scope, …)` 取同一只队列。
29
+ * 箱只有两种(会话自己的箱 / 它也排空的 principal 收件箱,core 的快照注逐字);读到第三种 ⇒ 那只队列的卡不列(没有车道就拼不出决断口 id)+ F 类
30
+ * `server.peer-inbound.queue-box-unknown` 留痕 —— 信仍被扣、按 core 的期限带回执过期(安全默认)。
31
+ *
32
+ * ## 到期
33
+ * 卡上的到期时刻是 core 的 `PeerHeldEntry.deadlineAt`(到期计时武装时在场:可审成因 ∧ 有限 `dialogExpiry` ∧ 卡仍 `held`;成因一动 core 重武装或清掉它)。
34
+ * 行的 `deadline` 就是它的原值,本仓不按 `heldAt + dialogExpiry` 自算(成因一动就是第二份、错的读法)。
35
+ *
36
+ * ## 关停
37
+ * CC `HBe` 在宿主:core 不调 `PeerHeldQueue.shutdown()`(全库零生产调用点)。{@link PeerInboundFace.shutdown} 在有序关停的审批收尾那一拍
38
+ * (`boot/shutdown.ts`,关池之前)对注册表里**每只**队列调它(进程的队列都是本进程的;不求 scope)—— 仍被扣的信按 core 的规则带 `expired` 回执结清
39
+ * (reason `shutdown`)。core 的 `shutdown()` 等它发出的每条回执落箱再兑现,没落的点名 seq 拒(卡照样全结);各队列互不等,一只拒不挡别的
40
+ * (`Promise.allSettled`),拒句进读数 `receiptFaults`,由关停链写一行 warn。
41
+ */
42
+ import { type PeerHeldEntry, type PeerHeldSettleOutcome } from "@sema-agent/core";
43
+ import { type PeerInboundLane } from "./org-delivery-gate.js";
44
+ /**
45
+ * 调用方 principal 的信箱 scope —— core 写侧 `taskScope = spec.principal ?? "default"` 的同一条读法(无主 ⇒ `"default"`)。列 / 决都按它找队列。
46
+ */
47
+ export declare function peerBoxScope(principal: string | null | undefined): string;
48
+ /** 决断口 id 的坐标(id 是它的唯一拼法,见 {@link peerInboundIdOf})。 */
49
+ export interface PeerInboundCoord {
50
+ readonly sessionId: string;
51
+ readonly lane: PeerInboundLane;
52
+ readonly seq: number;
53
+ }
54
+ /** 坐标 → 决断口 id:`peer_inbound:<收信会话 id>:<车道>:<箱内 seq>`(会话 id 的文法里没有 `:`,三段无歧义)。 */
55
+ export declare function peerInboundIdOf(c: PeerInboundCoord): string;
56
+ /** 决断口 id → 坐标;不成形(会话 id 不合 core 的地址文法 / 车道不在闭集 / seq 不是非负安全整数)⇒ `undefined`(决断口答 404,不判 400:不给 id 空间当谕示)。 */
57
+ export declare function parsePeerInboundId(id: string): PeerInboundCoord | undefined;
58
+ /**
59
+ * `pending[]` 的 `peer_inbound` 行(wire 形)。键全部是 core 那张卡的投影,本仓不派生任何一位:
60
+ * · `id` —— 决断口 `POST /v1/approvals/:id/decide` 的 `:id`;
61
+ * · `recipientSessionId` —— 收信会话(刻意**不**叫 `sessionId`:那个名字在本数组里是停驻门的 `:id`,不分族的消费方拿它去决断会决到
62
+ * 同会话的停驻卡上;换名之后它读到缺席,而不是错卡);
63
+ * · `cause` —— core 的扣信成因(`PeerHeldEntry.cause`,`CrossSessionHoldCause` 闭集);
64
+ * · `content` / `fromName` —— 信的存储正文(`[summary] message`,core 的规范存储形)与发信方的显示名,**过凭据脱敏**(显示面;与停驻行的工具入参同一条);
65
+ * · `fromSession` / `fromScope` —— 发信会话 / 跨 principal 投递的发信 scope(core 的 typed 归属锚),在场才带;
66
+ * · `sentAt` / `heldAt` —— epoch ms;
67
+ * · `deadline` —— core 卡上的到期时刻 `deadlineAt` 原值(绝对 epoch ms,与停驻行 `deadline` 同名同义;壳用 `deadline − serverNowMs` 渲倒计时),
68
+ * 到期计时没武装(`dialogExpiry` 为 `null`)⇒ 缺席。
69
+ */
70
+ export interface PeerInboundPendingRow {
71
+ readonly kind: "peer_inbound";
72
+ readonly id: string;
73
+ readonly recipientSessionId: string;
74
+ readonly lane: PeerInboundLane;
75
+ readonly cause: PeerHeldEntry["cause"];
76
+ readonly content: string;
77
+ readonly sentAt: number;
78
+ readonly heldAt: number;
79
+ readonly deadline?: number;
80
+ readonly fromSession?: string;
81
+ readonly fromName?: string;
82
+ readonly fromScope?: string;
83
+ }
84
+ /** 决断口给本族的决定词(停驻门的 `decision` 闭集原样;core 的 `cancel` 臂本仓没有门 —— 见收车档未闭环)。 */
85
+ export type PeerInboundDecision = "approve" | "deny";
86
+ /** 决断口 `peer_inbound` 臂的 200 体(键集属主;`outcome` = core `settle` 的答案原词,`gone` 不上 wire —— 它答 404)。 */
87
+ export interface PeerInboundDecideReply {
88
+ readonly kind: "peer_inbound";
89
+ readonly id: string;
90
+ readonly outcome: Exclude<PeerHeldSettleOutcome, "gone">;
91
+ }
92
+ /** 一次决断的结局:`not_found`(调用方 scope 里没有这张待决卡 —— 不存在 / 别人的 / 已不在 held)、`settled`(core 的答案原词)、`unrecorded`(店拒了这一步,core 说什么都没决;`error` = core 抛的那一只,交给调用方留痕)。 */
93
+ export type PeerInboundDecideResult = {
94
+ readonly kind: "not_found";
95
+ } | {
96
+ readonly kind: "settled";
97
+ readonly outcome: Exclude<PeerHeldSettleOutcome, "gone">;
98
+ } | {
99
+ readonly kind: "unrecorded";
100
+ readonly error: unknown;
101
+ };
102
+ /**
103
+ * 关停读数:`queues` = 关了几只队列(注册表里的每一只,含空的);`cards` = 关停那一刻快照里的卡数;`receiptFaults` = 各只拒了的队列的拒句
104
+ * (core 点名回执没落箱的 seq —— 卡已结清,只是发信方可能没收到那条 `expired`)。
105
+ */
106
+ export interface PeerInboundShutdownReading {
107
+ readonly queues: number;
108
+ readonly cards: number;
109
+ readonly receiptFaults: readonly string[];
110
+ }
111
+ export interface PeerInboundFace {
112
+ /** `scope` 里此刻等人审的信({@link awaitsReview} 的卡;读 core 的队列枚举),按 `heldAt` 升序(同刻按 id)。 */
113
+ list(scope: string): PeerInboundPendingRow[];
114
+ /** 决断转发:只认 `scope` 里此刻等人审的那张卡(与 {@link list} 同一只判据);`settle` 抛(店拒了 CAS)⇒ `unrecorded`。 */
115
+ decide(scope: string, coord: PeerInboundCoord, decision: PeerInboundDecision): Promise<PeerInboundDecideResult>;
116
+ /** 有序关停:core 注册表里每只队列 `shutdown()`(core 的规则:仍 held 的卡带 `expired` 回执结清,reason `shutdown`;等回执落箱)。永不抛。 */
117
+ shutdown(): Promise<PeerInboundShutdownReading>;
118
+ }
119
+ export declare function createPeerInboundFace(): PeerInboundFace;
120
+ /** 进程单例 —— 决断尾(一张卡一次只过一只决断)是进程级的;审批路由与关停链读的是同一只。 */
121
+ export declare const defaultPeerInboundFace: PeerInboundFace;
122
+ //# sourceMappingURL=peer-inbound-face.d.ts.map
@@ -0,0 +1,123 @@
1
+ import { PEER_HELD_REVIEW_CAUSES, isPeerSessionId, listPeerHeldQueues, peerHeldQueueIfAny, } from "@sema-agent/core";
2
+ import { PEER_INBOUND_LANES, boxOfLane, laneOfBox } from "./org-delivery-gate.js";
3
+ import { PEER_INBOUND_ID_PREFIX } from "./approval-queue-kind.js";
4
+ import { recordFailOpen } from "./observability/fail-open.js";
5
+ import { seatText } from "./trace/exit-seat.js";
6
+ export function peerBoxScope(principal) {
7
+ return principal ?? "default";
8
+ }
9
+ export function peerInboundIdOf(c) {
10
+ return `${PEER_INBOUND_ID_PREFIX}${c.sessionId}:${c.lane}:${c.seq}`;
11
+ }
12
+ export function parsePeerInboundId(id) {
13
+ if (!id.startsWith(PEER_INBOUND_ID_PREFIX))
14
+ return undefined;
15
+ const parts = id.slice(PEER_INBOUND_ID_PREFIX.length).split(":");
16
+ if (parts.length !== 3)
17
+ return undefined;
18
+ const [sessionId, lane, seqText] = parts;
19
+ if (!isPeerSessionId(sessionId))
20
+ return undefined;
21
+ if (!PEER_INBOUND_LANES.includes(lane))
22
+ return undefined;
23
+ if (!/^(0|[1-9][0-9]*)$/.test(seqText))
24
+ return undefined;
25
+ const seq = Number(seqText);
26
+ if (!Number.isSafeInteger(seq))
27
+ return undefined;
28
+ return { sessionId, lane: lane, seq };
29
+ }
30
+ function rowOf(sessionId, lane, card) {
31
+ const fromSession = card.peerMeta?.fromSession;
32
+ const fromScope = card.peerMeta?.fromScope;
33
+ return {
34
+ kind: "peer_inbound",
35
+ id: peerInboundIdOf({ sessionId, lane, seq: card.seq }),
36
+ recipientSessionId: sessionId,
37
+ lane,
38
+ cause: card.cause,
39
+ content: seatText("peer-inbound-card-text", card.content),
40
+ sentAt: card.sentAt,
41
+ heldAt: card.heldAt,
42
+ ...(card.deadlineAt !== undefined ? { deadline: card.deadlineAt } : {}),
43
+ ...(fromSession !== undefined ? { fromSession } : {}),
44
+ ...(card.from !== undefined ? { fromName: seatText("peer-inbound-card-text", card.from) } : {}),
45
+ ...(fromScope !== undefined ? { fromScope } : {}),
46
+ };
47
+ }
48
+ function laneOfQueue(q) {
49
+ const lane = laneOfBox(q.box, q.sessionId);
50
+ if (lane === undefined)
51
+ recordFailOpen("server.peer-inbound.queue-box-unknown", `cards=${q.size}`);
52
+ return lane;
53
+ }
54
+ function awaitsReview(card) {
55
+ return card.decision === "held" && isReviewCause(card.cause);
56
+ }
57
+ function isReviewCause(cause) {
58
+ return typeof cause === "string" && PEER_HELD_REVIEW_CAUSES.includes(cause);
59
+ }
60
+ function queueOf(scope, e) {
61
+ return peerHeldQueueIfAny(scope, e.sessionId, boxOfLane(e.lane));
62
+ }
63
+ export function createPeerInboundFace() {
64
+ const tails = new WeakMap();
65
+ const serially = (q, seq, step) => {
66
+ const perQueue = tails.get(q) ?? new Map();
67
+ tails.set(q, perQueue);
68
+ const prev = perQueue.get(seq);
69
+ const run = prev === undefined ? step() : prev.then(step);
70
+ perQueue.set(seq, run);
71
+ return run.finally(() => {
72
+ if (perQueue.get(seq) === run)
73
+ perQueue.delete(seq);
74
+ });
75
+ };
76
+ return {
77
+ list(scope) {
78
+ const rows = [];
79
+ for (const q of listPeerHeldQueues()) {
80
+ if (q.scope !== scope)
81
+ continue;
82
+ const reviewable = q.entries.filter(awaitsReview);
83
+ if (reviewable.length === 0)
84
+ continue;
85
+ const lane = laneOfQueue(q);
86
+ if (lane === undefined)
87
+ continue;
88
+ for (const card of reviewable)
89
+ rows.push(rowOf(q.sessionId, lane, card));
90
+ }
91
+ return rows.sort((a, b) => a.heldAt - b.heldAt || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
92
+ },
93
+ async decide(scope, coord, decision) {
94
+ const q = queueOf(scope, coord);
95
+ if (q === undefined)
96
+ return { kind: "not_found" };
97
+ return serially(q, coord.seq, async () => {
98
+ if (!q.list().some((c) => c.seq === coord.seq && awaitsReview(c)))
99
+ return { kind: "not_found" };
100
+ let outcome;
101
+ try {
102
+ outcome = await q.settle(coord.seq, decision);
103
+ }
104
+ catch (error) {
105
+ return { kind: "unrecorded", error };
106
+ }
107
+ return outcome === "gone" ? { kind: "not_found" } : { kind: "settled", outcome };
108
+ });
109
+ },
110
+ async shutdown() {
111
+ const snapshots = listPeerHeldQueues();
112
+ const queues = snapshots.flatMap((s) => {
113
+ const q = peerHeldQueueIfAny(s.scope, s.sessionId, s.box);
114
+ return q === undefined ? [] : [q];
115
+ });
116
+ const settled = await Promise.allSettled(queues.map((q) => q.shutdown()));
117
+ const receiptFaults = settled.flatMap((r) => (r.status === "rejected" ? [r.reason instanceof Error ? r.reason.message : String(r.reason)] : []));
118
+ return { queues: queues.length, cards: snapshots.reduce((n, s) => n + s.size, 0), receiptFaults };
119
+ },
120
+ };
121
+ }
122
+ export const defaultPeerInboundFace = createPeerInboundFace();
123
+ //# sourceMappingURL=peer-inbound-face.js.map
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * **规则表的唯一编译口** —— 一张工具名规则表(`ask` / `deny` 之一)编成一只 `ToolPolicy`。读者:请求腿的 `settings.permissions.{ask,deny}`
3
- * 两桶(`task-settings.ts` 的 `deriveSettingsPolicy`,每桶按根至多编两集,见文件尾 {@link ruleRootOf})。设置层条目的形与层 → 根表也住这里(文件尾一节)。
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`
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * 为什么住在一只叶模块里(不留在 `task-settings.ts`):本模块只 import core 与契约包 settings-schema(纯叶),读者引它不成环。
10
10
  */
11
- import { type PathRuleBases, type PermissionRule, type PermissionRulePolicyOptions, type ToolPolicy } from "@sema-agent/core";
11
+ import { type PathRuleBases, type PermissionRule, type PermissionRulePolicyOptions, type RuleBehavior, type ToolPolicy } from "@sema-agent/core";
12
12
  import { type SettingsSource } from "@sema-agent/settings-schema";
13
13
  /** 一张表里规则条数的上限 —— 请求腿 wire 门(`task-settings.ts` 的 deny / ask 两桶判形)读的也是这一个名字:core 的缺省帽是 256,
14
14
  * 不对齐的话第 257 条起编译当场抛,等于服务端自己收下的东西自己反悔。 */
@@ -80,15 +80,15 @@ export declare function buildRuleListJudgeOptions(pathBases: PathRuleBases | und
80
80
  * `table` = 这一集出自哪只桶(只进编不出时的兜底句;逐条坐标由先跑的受理判 `rulePolicyBucketsRefusal` 给)。 */
81
81
  export declare function createRuleListPolicy(rules: readonly SourcedRule[], behavior: PermissionRule["behavior"], table: string, pathBases: PathRuleBases | undefined): ToolPolicy;
82
82
  /**
83
- * deny / ask 桶的一个条目:**纯串** = 请求自己写的规则(今天的形,读在任务根);**带层条目** `{ rule, layer }` = 端从某个设置层读来的规则,`layer` 是
83
+ * 三桶(`allow` / `deny` / `ask`)的一个条目:**纯串** = 请求自己写的规则(今天的形,读在任务根);**带层条目** `{ rule, layer }` = 端从某个设置层读来的规则,`layer` 是
84
84
  * 契约包 `SETTINGS_SOURCES` 的来源词(= 参照实现的设置来源词)。层只决定这条规则的 `/` 读在哪个根({@link layerRuleRoot}),**不决定准入档**
85
- * (准入只由凭据推,`http/caller-trust.ts`):调用方自报任何层,挪动的只是它自己那条收紧读在哪,与它自己写 `//abs` 等价。
85
+ * (准入只由凭据推,`http/caller-trust.ts`):调用方自报任何层,挪动的只是它自己那条规则读在哪,与它自己写 `//abs` 等价(allow 照旧按放宽成员要它的档)。
86
86
  *
87
87
  * 为什么是「条目带层」而不是一只新键:本版之前的每一个读者(回滚、滚动发布中的旧副本)对一只没见过的键是**静默忽略**(续跑重放走宽容层),
88
88
  * 带层的收紧会整批消失;对一个不是串的条目则在桶门上 `bad_entry` —— 新提交 400、续跑 409,响亮拒。条数帽也因此仍是「一桶一张表」。
89
89
  */
90
90
  export type RuleListEntry = string | LayeredRule;
91
- /** 带层条目(恰两键;受理判 {@link ruleListEntryOk})。 */
91
+ /** 带层条目(恰两键;受理判 {@link layeredRuleOk})。 */
92
92
  export interface LayeredRule {
93
93
  readonly rule: string;
94
94
  readonly layer: SettingsSource;
@@ -98,8 +98,12 @@ export interface SourcedRule {
98
98
  readonly rule: string;
99
99
  readonly source: string;
100
100
  }
101
- /** 一个条目合不合 deny / ask 桶的条目律:非空串;或恰 `rule`(非空串)+ `layer`(`SETTINGS_SOURCES` 里的词)两键的纯对象。别的形一律不合(多键、空串、未知层、数组、原型对象)。 */
101
+ /** 带层条目的判据(三桶同一只):恰 `rule`(非空串)+ `layer`(`SETTINGS_SOURCES` 里的词)两键的纯对象。别的形一律不合(多键、空串、未知层、数组、原型对象)。 */
102
+ export declare function layeredRuleOk(x: unknown): x is LayeredRule;
103
+ /** deny / ask 桶的条目律:非空串(规则编译器读不出空串),或合形的带层条目。 */
102
104
  export declare function ruleListEntryOk(x: unknown): x is RuleListEntry;
105
+ /** allow 桶的条目律:任意串(空串交引擎的 allow 层,读成 `grammar` 并通告),或合形的带层条目。 */
106
+ export declare function allowListEntryOk(x: unknown): x is RuleListEntry;
103
107
  /** 条目的规则原文(纯串即它自己)。 */
104
108
  export declare function ruleTextOf(entry: RuleListEntry): string;
105
109
  /** 条目进编译口的出处串:纯串 = 桶坐标(今天的字面);带层 = 桶坐标 + 层(`settings.permissions.deny, layer userSettings`)。 */
@@ -114,7 +118,7 @@ export type RuleRoot = (typeof RULE_ROOTS)[number];
114
118
  */
115
119
  export declare function layerRuleRoot(layer: SettingsSource): RuleRoot;
116
120
  /**
117
- * 一个条目编进哪一集:**只有 `/` 随层变** —— 带层条目的层根是配置根 ∧ 这条规则要 `root`(core 的必需基分类 `ruleBasesNeeded`)⇒ `configuration`;
121
+ * 一个条目编进哪一集(deny / ask 编成哪只策略、allow 投成哪一员 —— 三桶同读):**只有 `/` 随层变** —— 带层条目的层根是配置根 ∧ 这条规则要 `root`(core 的必需基分类 `ruleBasesNeeded`)⇒ `configuration`;
118
122
  * 其余一律 `task`(纯串、别的层、`~/` / 相对形、`//abs` 与 subpath 这类分类为空的规则 —— 后者在 Windows 族上仍随声明的根选盘,必须留在今天那一集)。
119
123
  * 文法读不出的串 ⇒ `task`(受理判在前面就拒它,这里不另说一句)。
120
124
  *
@@ -122,7 +126,7 @@ export declare function layerRuleRoot(layer: SettingsSource): RuleRoot;
122
126
  * `policyAnchors`)—— 「一层一集」在任务根读不出的腿(未登记工作目录的 host 腿、每一条沙箱腿)上会把 user 层的相对规则读到配置根下。配置根那一集只装要
123
127
  * `root` 的规则,`cwd` 永远不被问。
124
128
  */
125
- export declare function ruleRootOf(entry: RuleListEntry, behavior: PermissionRule["behavior"]): RuleRoot;
129
+ export declare function ruleRootOf(entry: RuleListEntry, behavior: RuleBehavior): RuleRoot;
126
130
  /** 一桶的条目按根分集(受理判与编译读同一只:判的就是要编的那一集);集内保持原序。 */
127
- export declare function ruleSetsOf(entries: readonly RuleListEntry[], behavior: PermissionRule["behavior"]): Readonly<Record<RuleRoot, readonly RuleListEntry[]>>;
131
+ export declare function ruleSetsOf(entries: readonly RuleListEntry[], behavior: RuleBehavior): Readonly<Record<RuleRoot, readonly RuleListEntry[]>>;
128
132
  //# sourceMappingURL=permission-rule-list.d.ts.map
@@ -17,9 +17,7 @@ export function createRuleListPolicy(rules, behavior, table, pathBases) {
17
17
  }
18
18
  }
19
19
  const SETTINGS_SOURCE_SET = new Set(SETTINGS_SOURCES);
20
- export function ruleListEntryOk(x) {
21
- if (typeof x === "string")
22
- return x.length > 0;
20
+ export function layeredRuleOk(x) {
23
21
  if (x === null || typeof x !== "object" || Array.isArray(x) || Object.getPrototypeOf(x) !== Object.prototype)
24
22
  return false;
25
23
  const keys = Object.keys(x);
@@ -28,6 +26,12 @@ export function ruleListEntryOk(x) {
28
26
  const { rule, layer } = x;
29
27
  return typeof rule === "string" && rule.length > 0 && typeof layer === "string" && SETTINGS_SOURCE_SET.has(layer);
30
28
  }
29
+ export function ruleListEntryOk(x) {
30
+ return typeof x === "string" ? x.length > 0 : layeredRuleOk(x);
31
+ }
32
+ export function allowListEntryOk(x) {
33
+ return typeof x === "string" || layeredRuleOk(x);
34
+ }
31
35
  export function ruleTextOf(entry) {
32
36
  return typeof entry === "string" ? entry : entry.rule;
33
37
  }
@@ -32,6 +32,18 @@ const JOURNAL_V1_CLAIM_INTENTS = ["expire", "cancel"];
32
32
  const JOURNAL_V1_CLAIM_INTENT_WORDS = new Set(JOURNAL_V1_CLAIM_INTENTS);
33
33
  const JOURNAL_ENVELOPE = 2;
34
34
  const JOURNAL_V1_ENVELOPE = 1;
35
+ function askRowToDisk(row) {
36
+ const { sourceSessionId, ...rest } = row;
37
+ return { ...rest, sourceTaskId: sourceSessionId };
38
+ }
39
+ function askRowFromDisk(row, refuse) {
40
+ const sourceTaskId = row !== null && typeof row === "object" ? row.sourceTaskId : undefined;
41
+ if (typeof sourceTaskId !== "string")
42
+ return refuse("ask row without a string sourceTaskId (the on-disk name of the ask's source session)");
43
+ const { sourceTaskId: _onDisk, ...rest } = row;
44
+ return { ...rest, sourceSessionId: sourceTaskId };
45
+ }
46
+ const callToDisk = (call) => (call.op === "ensureAsk" ? { ...call, row: askRowToDisk(call.row) } : call);
35
47
  const V1_ENDING_STATES = new Set(["VOID", "DENIED"]);
36
48
  const V1_TRIGGER_STATES = new Set(["PARKING", "PARKED"]);
37
49
  function translateJournalV1Record(raw, refuse) {
@@ -120,9 +132,9 @@ export class FileApprovalAskStore {
120
132
  }
121
133
  write(call, run) {
122
134
  const t = Date.now();
123
- const record = { v: JOURNAL_ENVELOPE, k: "op", t, ...call };
124
- const encoded = encodeStoreJsonPayload(`${call.op} call`, record);
125
- this.logFor().append(encoded, true);
135
+ const encoded = encodeStoreJsonPayload(`${call.op} call`, { v: JOURNAL_ENVELOPE, k: "op", t, ...call });
136
+ const onDisk = { v: JOURNAL_ENVELOPE, k: "op", t, ...callToDisk(encoded) };
137
+ this.logFor().append(onDisk, true);
126
138
  this.records += 1;
127
139
  this.pinnedNow = t;
128
140
  let out;
@@ -151,7 +163,7 @@ export class FileApprovalAskStore {
151
163
  compact(snapshot = this.core.exportRows()) {
152
164
  const lines = [
153
165
  ...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`),
166
+ ...snapshot.asks.map((row) => `${JSON.stringify({ v: JOURNAL_ENVELOPE, k: "ask", row: askRowToDisk(row) })}\n`),
155
167
  ];
156
168
  this.log?.closeForSwap();
157
169
  atomicWriteFile(this.tmpDir, this.path, lines.join(""));
@@ -182,7 +194,7 @@ export class FileApprovalAskStore {
182
194
  if (typeof key !== "string" || key.length === 0)
183
195
  refuse(`snapshot record without a ${probe.k === "ask" ? "askId" : "batchId"}`);
184
196
  if (probe.k === "ask")
185
- this.core.importRows({ asks: [row] });
197
+ this.core.importRows({ asks: [askRowFromDisk(row, refuse)] });
186
198
  else
187
199
  this.core.importRows({ batches: [row] });
188
200
  return;
@@ -200,7 +212,7 @@ export class FileApprovalAskStore {
200
212
  if (!Array.isArray(anchors) || !anchors.every((a) => typeof a === "string"))
201
213
  refuse("reapSettledAsks record without a string-array keepAnchors");
202
214
  }
203
- const rec = raw;
215
+ const rec = (probe.op === "ensureAsk" ? { ...raw, row: askRowFromDisk(probe.row, refuse) } : raw);
204
216
  this.pinnedNow = rec.t;
205
217
  try {
206
218
  void this.replayOp(rec).catch((e) => {
@@ -1,7 +1,7 @@
1
1
  import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
2
2
  import { isExecutionLane } from "../execution-lane-caps.js";
3
3
  import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
4
- import { askRowCarriesOwner, askRowOnCall, assertDecideSettledBy, assertIdempotencyKeyShape, assertIntegralEpochMs, assertReapBounds, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth, projectDecideReading } from "./approval-ask-store-sql.js";
4
+ import { askRowCarriesOwner, askRowOnCall, assertDecideSource, assertIdempotencyKeyShape, assertIntegralEpochMs, assertReapBounds, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth, projectDecideReading } from "./approval-ask-store-sql.js";
5
5
  function applyPatch(row, patch) {
6
6
  if (patch.decision !== undefined)
7
7
  row.decision = patch.decision;
@@ -58,7 +58,7 @@ export class InMemoryApprovalAskStore {
58
58
  }
59
59
  importRows(snapshot) {
60
60
  for (const r of snapshot.asks ?? [])
61
- this.asks.set(r.askId, { ...r, settledBy: r.settledBy ?? null });
61
+ this.asks.set(r.askId, { ...r, settledBy: r.settledBy ?? null, decidedVia: r.decidedVia ?? null });
62
62
  for (const b of snapshot.batches ?? [])
63
63
  this.batches.set(b.batchId, { ...b });
64
64
  }
@@ -82,7 +82,7 @@ export class InMemoryApprovalAskStore {
82
82
  const created = {
83
83
  askId: row.askId,
84
84
  taskId: row.taskId,
85
- sourceTaskId: row.sourceTaskId,
85
+ sourceSessionId: row.sourceSessionId,
86
86
  sessionId: row.sessionId,
87
87
  owner: row.owner ?? null,
88
88
  executionLane: row.executionLane,
@@ -98,6 +98,7 @@ export class InMemoryApprovalAskStore {
98
98
  decisionActor: null,
99
99
  decisionNote: null,
100
100
  settledBy: null,
101
+ decidedVia: null,
101
102
  decidedAtMs: null,
102
103
  endingCause: null,
103
104
  parkTrigger: null,
@@ -132,7 +133,7 @@ export class InMemoryApprovalAskStore {
132
133
  async decideAsk(askId, batchId, owner, decision) {
133
134
  if (decision.idempotencyKey != null)
134
135
  assertIdempotencyKeyShape(decision.idempotencyKey);
135
- assertDecideSettledBy(decision);
136
+ assertDecideSource(decision);
136
137
  const reading = (row) => projectDecideReading(row ? { ...row } : null, owner);
137
138
  const batch = this.batches.get(batchId);
138
139
  if (!batch || batch.state !== "OPEN") {
@@ -160,6 +161,7 @@ export class InMemoryApprovalAskStore {
160
161
  ask.decisionActor = decision.decisionActor ?? null;
161
162
  ask.decisionNote = decision.decisionNote ?? null;
162
163
  ask.settledBy = decision.settledBy ?? null;
164
+ ask.decidedVia = decision.decidedVia ?? null;
163
165
  if (decision.updatedInput === undefined)
164
166
  delete ask.updatedInput;
165
167
  else
@@ -188,7 +190,7 @@ export class InMemoryApprovalAskStore {
188
190
  break;
189
191
  }
190
192
  case "refuse": {
191
- assertDecideSettledBy(POLICY_REFUSAL_DECISION);
193
+ assertDecideSource(POLICY_REFUSAL_DECISION);
192
194
  const to = claimTargetState(intent);
193
195
  if (!canAskTransition("STREAM_PENDING", to))
194
196
  throw new Error(`InMemoryApprovalAskStore.claimTerminal: illegal terminal claim STREAM_PENDING -> ${to} (askId=${askId})`);
@@ -390,7 +392,7 @@ export class InMemoryApprovalAskStore {
390
392
  }
391
393
  planReapSettledAsks(cutoffMs, limit, keepAnchors) {
392
394
  assertReapBounds(cutoffMs, limit);
393
- const anchored = (r) => keepAnchors.has(r.taskId) || keepAnchors.has(r.sessionId) || keepAnchors.has(r.sourceTaskId);
395
+ const anchored = (r) => keepAnchors.has(r.taskId) || keepAnchors.has(r.sessionId) || keepAnchors.has(r.sourceSessionId);
394
396
  return [...this.asks.values()]
395
397
  .filter((r) => ASK_TERMINAL.has(r.state) && !r.provisional && r.updatedAtMs < cutoffMs && !anchored(r))
396
398
  .sort((a, b) => a.updatedAtMs - b.updatedAtMs || (a.askId < b.askId ? -1 : a.askId > b.askId ? 1 : 0))
@@ -42,6 +42,7 @@ import type { PgQueryFn } from "./pg-query.js";
42
42
  import { type SqlDriver, type SqlDialect } from "./sql-driver.js";
43
43
  import { type AskState, type BatchState } from "../approval-ask-machine.js";
44
44
  import type { HostSettledBy } from "../host-decision.js";
45
+ import { type DecidedVia } from "@sema-agent/core";
45
46
  import { type AskRowEndingCause, type SettleCause, type ParkTriggerCause } from "../approval-deny-reasons.js";
46
47
  import { type IndexSpec } from "./ensure-index.js";
47
48
  import { type ExecutionLane } from "../execution-lane-caps.js";
@@ -49,7 +50,7 @@ export type AskDecision = "approve" | "deny";
49
50
  export interface AskRow {
50
51
  askId: string;
51
52
  taskId: string;
52
- sourceTaskId: string;
53
+ sourceSessionId: string;
53
54
  sessionId: string;
54
55
  /** 租户 scope——哨兵归一在消费层(车2+),店里存原值(设计定稿 §3)。 */
55
56
  owner: string | null;
@@ -69,7 +70,7 @@ export interface AskRow {
69
70
  * 与下面 PARKED 坐标里的 `gateBoundInputHash` 是**两件不同的东西**,别混:
70
71
  * - `boundInputHash`(本列)= ask **铸行时**的入参摘要,一次写定永不改;对账收敛器判据 1 用它跟
71
72
  * checkpoint 行的同名列做**硬相等**——它是**身份三元组之外的第二道等式**,身份本身是
72
- * (`sourceTaskId`, `toolCallId`, 因果下界)三维([ref] 件1 换轴,判据属主 = `approval-reconciler.ts`
73
+ * (`sourceSessionId`, `toolCallId`, 因果下界)三维([ref] 件1 换轴,判据属主 = `approval-reconciler.ts`
73
74
  * 的 `classifyGateMatch`;本注上一版写的「identity 四元组」是换轴前的旧口径,`sessionId` 从来不是
74
75
  * 内存判据维)。任一侧缺席 ⇒ 判据 1 **不命中**;归因看走到哪一层:身份先判(候选集非空却没有同身份
75
76
  * 的一条 ⇒ `identity_miss`),身份这层还够得着时缺席才记 `single_mint`(禁「能取到时才比」的可选谓词
@@ -90,7 +91,7 @@ export interface AskRow {
90
91
  /**
91
92
  * 🔴 [ref]([ref] 的持久半场)—— 这条决议的**结算来源**:`null` = 一个人答的(= 缺席律,core 对
92
93
  * 不带这一格的 outcome 逐字按 `"human"` 读),非 null = 宿主自报的那个词([ref] 起写进来的只有决定体出处位
93
- * `decidedBy:"policy"` 经 `liveSettledBy` 映射来的 `"policy"`,allow 与 deny 两向都写;词表属主恒是 core —— 见 {@link HostSettledBy})。
94
+ * `decidedBy:"policy"` 经 `liveHostSource` 映射来的 `"policy"`,allow 与 deny 两向都写;词表属主恒是 core —— 见 {@link HostSettledBy})。
94
95
  *
95
96
  * **为什么必须是行上的一格**(病灶,7.93.3 合并树 codex ① 验真):`settledBy` 此前只到达**当次**回决的
96
97
  * 即时回调,行上不留痕 ⇒ 重放 / 再入 / 同 askId 的兄弟等待者 / 歧义提交恢复读回的是裸 `false`,
@@ -113,6 +114,14 @@ export interface AskRow {
113
114
  * 三态收成两态是刻意的:`"human"` 与缺席在 core 那边逐字同义,故店只存「非人」的那些词,`null` 即人。
114
115
  */
115
116
  settledBy: HostSettledBy | null;
117
+ /**
118
+ * core 7.61.0([ref])—— `settledBy = "policy"` 旁的**机器座位**:哪一个机器座位替这台部署答的(core `DECIDED_VIA`:`"policy"` 部署自己的策略 /
119
+ * `"permission_request_hook"` 一只 PermissionRequest 钩子)。`null` = 没陈述(旧行同读)= 部署自己的策略。它决定模型读哪一句拒句
120
+ * (「这一跑没有交互审批通道」vs「一只 PermissionRequest 钩子拒了」),所以与 {@link settledBy} 同一条理由住在行上、与决议同一条 UPDATE 落列:
121
+ * 活卡腿的结算输入只从行读,不在行上 = 重放 / 兄弟 / 再入 / 歧义恢复 / 迟到成功 / 跨副本广播六条路把钩子的拒读回成部署策略的拒。
122
+ * 只在 `settledBy = "policy"` 旁(core 同一条律;店门 {@link assertDecideSource} 守)。
123
+ */
124
+ decidedVia: DecidedVia | null;
116
125
  /**
117
126
  * [ref]([ref] / [ref] 收口):人批准时带的 **ctrl+g 编辑实参**(wire `updatedInput`),与决议**同一条
118
127
  * UPDATE** 原子落列 `updated_input`。语义三态,读法**严格**:
@@ -186,7 +195,7 @@ export interface AskRow {
186
195
  export interface NewAskRow {
187
196
  askId: string;
188
197
  taskId: string;
189
- sourceTaskId: string;
198
+ sourceSessionId: string;
190
199
  sessionId: string;
191
200
  owner?: string | null;
192
201
  /** 出处的执行车道(必填;语义见 {@link AskRow.executionLane})。 */
@@ -250,10 +259,12 @@ export interface DecideAskInput {
250
259
  * 🔴 [ref]:本次回决的**结算来源**,与决议同一条 UPDATE 落列 `settled_by`(语义/两态读法/为什么是
251
260
  * 自己一列,全在 {@link AskRow.settledBy})。缺席 ⇒ 列 NULL ⇒ 读回是「一个人答的」。
252
261
  *
253
- * 店**不判词**(词表属主是 core):写什么存什么。店只判**形** —— `assertDecideSettledBy` 的那一条
262
+ * 店**不判词**(词表属主是 core):写什么存什么。店只判**形** —— `assertDecideSource` 的那一条
254
263
  * (长度 ≤ 列宽;[ref] 起 approve 行也可以带来源位),理由见那只门的顶注;三 twin 同判。
255
264
  */
256
265
  settledBy?: HostSettledBy;
266
+ /** core 7.61.0([ref]):`settledBy:"policy"` 旁的机器座位,与决议同一条 UPDATE 落列 `decided_via`(语义见 {@link AskRow.decidedVia});缺席 ⇒ 列 NULL。 */
267
+ decidedVia?: DecidedVia;
257
268
  /** [ref]:ctrl+g 编辑实参,与决议同一条 UPDATE 落列(语义三态见 {@link AskRow.updatedInput})。
258
269
  * 缺席 / `undefined` ⇒ 列 NULL(没带编辑);**`null` 是值**(编辑成 null),照存 JSON `"null"`。
259
270
  * 调用方纪律:deny 不传(店不替调用方判决议方向,写什么存什么)。 */
@@ -293,7 +304,7 @@ export declare function assertReapBounds(cutoffMs: number, limit: number): void;
293
304
  export declare function assertIdempotencyKeyShape(key: string): void;
294
305
  /** `settled_by` 列宽 = 这一格的唯一尺(两方言 DDL 与下面的形门读同一个常量,不手抄第二份)。 */
295
306
  export declare const MAX_SETTLED_BY_CHARS = 16;
296
- /** `settled_by` 这一列装得下这个词吗(1..{@link MAX_SETTLED_BY_CHARS})—— 店门口形门({@link assertDecideSettledBy})的**谓词本身**。
307
+ /** `settled_by` 这一列装得下这个词吗(1..{@link MAX_SETTLED_BY_CHARS})—— 店门口形门({@link assertDecideSource})的**谓词本身**。
297
308
  * [ref] 起决定口不再读它:wire 上的出处位是闭集(`host-decision.ts` 的 `APPROVAL_DECIDED_BY`),映射到这一列的词恒在尺内。 */
298
309
  export declare function settledByFitsColumn(v: string): boolean;
299
310
  /**
@@ -307,9 +318,11 @@ export declare function settledByFitsColumn(v: string): boolean;
307
318
  * 🔴 [ref]:此前的第二条「只与 `deny` 同行」**删了** —— 它的前提(「只有人能批准一个要执行的动作,core 对 `{allow:true,settledBy}`
308
319
  * 一律 fail-closed 拒」)自 core 7.39.0 起不成立:策略的放行铸 `policy_allowed`,决定体的出处位 `decidedBy:"policy"` 在 allow 与 deny
309
320
  * 上都落这一列,approve 行带来源位是一条合法的行(读回铸点 `outcomeOfDecidedRow` 两向都读它)。今天写这一列的只有两条路:决定口经
310
- * `liveSettledBy` 映射来的 `"policy"`,与 `claimTerminal(refuse)` 的 {@link POLICY_REFUSAL_DECISION} —— 都是 core 在两向上都收的词。
321
+ * `liveHostSource` 映射来的 `"policy"`,与 `claimTerminal(refuse)` 的 {@link POLICY_REFUSAL_DECISION} —— 都是 core 在两向上都收的词。
311
322
  */
312
- export declare function assertDecideSettledBy(decision: Pick<DecideAskInput, "settledBy">): void;
323
+ export declare function assertDecideSource(decision: Pick<DecideAskInput, "settledBy" | "decidedVia">): void;
324
+ /** `decided_via` 列宽 = 这一格的唯一尺(两方言 DDL 读同一个常量)。core `DECIDED_VIA` 今天最长 23 字符(`permission_request_hook`)。 */
325
+ export declare const MAX_DECIDED_VIA_CHARS = 32;
313
326
  /**
314
327
  * [ref] 车3 刀 3b(§14.2 遗留记账):两条**重放读口**被调用方的 deadline 掐断时抛的具名错误。
315
328
  *
@@ -353,7 +366,7 @@ export interface AskTerminalClaimOpts {
353
366
  /**
354
367
  * `refuse` 意图落行的决议(NP-2)—— **部署政策拒**:deny + 结算来源 `policy`。来源词的型取 core 的 `HostSettledBy`
355
368
  * (core 改词 / 退词 ⇒ 这里 tsc 红);协调器交给 core 的那一只(`tool-approval.ts` 的 `UnattendedPolicyRefusal`)读的是
356
- * **同一个**常量,于是「行上记的来源」与「首判交出去的来源」在构造上是一个词。三 twin 落行前都过 {@link assertDecideSettledBy}
369
+ * **同一个**常量,于是「行上记的来源」与「首判交出去的来源」在构造上是一个词。三 twin 落行前都过 {@link assertDecideSource}
357
370
  * (settled_by 的唯一形门:长度 ≤ 列宽)。
358
371
  */
359
372
  export declare const POLICY_REFUSAL_DECISION: {
@@ -391,6 +404,8 @@ export type AskTerminalClaimCurrent = {
391
404
  /** [ref]:赢家决议的**结算来源**(语义与两态读法见 {@link AskRow.settledBy})。**必填**(不是可选)——
392
405
  * 可选会让投影漏挑这一列时静默编译过,而漏挑的后果恰是本批要修的病:CAS 输家把赢家的策略拒读成人拒。 */
393
406
  settledBy: HostSettledBy | null;
407
+ /** core 7.61.0([ref]):赢家决议的机器座位(语义见 {@link AskRow.decidedVia});必填,理由与 `settledBy` 同。 */
408
+ decidedVia: DecidedVia | null;
394
409
  /** 行上记着的终态成因(`ending_cause` 列,经唯一读口 `askRowEndingCause` 读出)。**只在读得出时在场**(没写 / 与 state 不配 ⇒ 缺席;DECIDED 的败方读数逐字不变)。
395
410
  * CAS 输家据它按**行上那个成因**结算(而不是把别人撤的卡读成一次拒绝);投影的唯一函数是 {@link projectAskTerminalClaimTruth},三 twin 不各自挑列,原始列值不出店层。 */
396
411
  endingCause?: AskRowEndingCause;
@@ -451,7 +466,7 @@ export interface BatchRow {
451
466
  }
452
467
  /** design 定稿 §4 的持久层接口。协调器接线(车2)、恢复扫描消费(车5)不在本车范围——本车只落这些方法。 */
453
468
  /** 一次调用在一条腿上的坐标({@link ApprovalAskStore.listByCall} 的四列;型从 {@link AskRow} 派生,不手抄)。 */
454
- export type AskCallCoordinate = Pick<AskRow, "taskId" | "legKey" | "toolCallId" | "sourceTaskId">;
469
+ export type AskCallCoordinate = Pick<AskRow, "taskId" | "legKey" | "toolCallId" | "sourceSessionId">;
455
470
  /** 「这一行在这次调用上」的唯一判据(四列逐字相等):InMemory twin 的筛子与 SQL twin 的出口对齐读同一只。 */
456
471
  export declare function askRowOnCall(row: AskRow, call: AskCallCoordinate): boolean;
457
472
  /**
@@ -535,12 +550,12 @@ export interface ApprovalAskStore {
535
550
  * 后续件。 */
536
551
  listPendingBySession(sessionId: string, owner: string | null, signal?: AbortSignal): Promise<AskRow[]>;
537
552
  /**
538
- * 🔴 [ref] / SN-C1:**这次调用在这条腿上的全部行** —— 任意状态、任意 ask 身份(同一调用不同类集 = 不同 askId 的几只问、升级前按旧派生
553
+ * 🔴 [ref] / SN-C1:**这次调用在这条腿上的全部行** —— 任意状态、任意 ask 身份(同一调用不同类集 / 不同展示动作 = 不同 askId 的几只问、升级前按旧派生
539
554
  * 写的行,都在其中)。坐标 = {@link AskCallCoordinate} 四列**精确**匹配;序 = `created_at_ms ASC, ask_id ASC`(确定性)。
540
555
  *
541
556
  * 唯一消费者 = 会话授权短路的争议判据(`tool-approval.ts` `sessionGrantUncontestedByRow`):「这次调用在腿内有行 ⇒ 一揽子放行不得代答」。
542
557
  * 为什么按调用而不是按 askId 点读:争议的真源是**调用**(人对这次调用说过什么 / 这次调用还有一张卡挂着),ask 身份只是其中一问的主键;
543
- * 身份可以加轴([ref] 加了类集),争议判据不得依赖身份 —— 按身份点读在身份加轴的那一刻就把同调用的别的问、旧派生的行读成「不存在」。
558
+ * 身份可以加轴([ref] 加了类集,7.143.0 加了展示的动作),争议判据不得依赖身份 —— 按身份点读在身份加轴的那一刻就把同调用的别的问、旧派生的行读成「不存在」。
544
559
  *
545
560
  * 三 twin 同答:SQL twin 读后按四列逐字再滤一遍 —— MySQL 协议的 VARCHAR 等值带 PAD SPACE(`'tc'` = `'tc '`),PG(`COLLATE "C"`)与
546
561
  * InMemory 严格比较;不在入口拒(调用 id 是引擎铸的原值,首尾空白不归本店判),在出口对齐。索引:走 `idx_ask_task_state`
@@ -568,7 +583,7 @@ export interface ApprovalAskStore {
568
583
  /**
569
584
  * **审批账本留存律的问账半场之一:问行**(律、节律与「还能动的 run」一处成文在 `approval-ask-retention.ts`;维护 tick 是唯一调用方)。
570
585
  * 删至多 `limit` 只**结了**的问行 —— `state ∈ ASK_TERMINAL` ∧ `provisional = false`(provisional 行还能被 {@link resolveProvisional} 改判,没结)
571
- * ∧ `updatedAtMs < cutoffMs` ∧ 行挂靠的三格(`taskId` / `sessionId` / `sourceTaskId`)都不在 `keepAnchors`(还能动的东西的挂靠集:
586
+ * ∧ `updatedAtMs < cutoffMs` ∧ 行挂靠的三格(`taskId` / `sessionId` / `sourceSessionId`)都不在 `keepAnchors`(还能动的东西的挂靠集:
572
587
  * 它还会回读这些行 —— 会话授权的争议判据 {@link listByCall} 把「这次调用在腿内没有行」读成可以代答)。非终态行多老都不删(它们归对账器
573
588
  * 与起服收敛)。只删不改。
574
589
  * 一条语句:要么删了并交回删掉的行数,要么一行没删就抛。三形同答(InMemory = 内核;File 形把这次调用照常落成一条账本动词,