@sema-agent/server 7.150.0 → 7.151.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 (341) hide show
  1. package/MIGRATION.md +185 -0
  2. package/USAGE.md +53 -25
  3. package/deploy/sema-up/README.md +7 -1
  4. package/deploy/sema-up/chart/Chart.yaml +1 -1
  5. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  6. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  8. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  9. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  10. package/deploy/sema-up/chart/values.yaml +8 -1
  11. package/deploy/sema-up/cluster-up.sh +1 -0
  12. package/deploy/sema-up/kube-up.sh +1 -1
  13. package/deploy/sema-up/lib/flagshape.sh +1 -1
  14. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  16. package/dist/adoption/import.js +3 -3
  17. package/dist/approval-ask-machine.d.ts +1 -1
  18. package/dist/approval-card.d.ts +2 -2
  19. package/dist/approval-gate-currency.d.ts +1 -1
  20. package/dist/auth-bridge.d.ts +20 -0
  21. package/dist/auth-bridge.js +9 -0
  22. package/dist/background-session-reap.d.ts +1 -1
  23. package/dist/bake-runner/main.d.ts +4 -2
  24. package/dist/bind-host.d.ts +2 -3
  25. package/dist/bind-host.js +2 -4
  26. package/dist/boot/budget-tracing.js +1 -1
  27. package/dist/boot/engine-lease.d.ts +5 -0
  28. package/dist/boot/engine-lease.js +1 -0
  29. package/dist/boot/execution-env.d.ts +8 -14
  30. package/dist/boot/execution-env.js +54 -17
  31. package/dist/boot/execution-lane.js +32 -5
  32. package/dist/boot/late-binds.d.ts +8 -4
  33. package/dist/boot/late-binds.js +7 -1
  34. package/dist/boot/memory-consolidation.d.ts +3 -2
  35. package/dist/boot/memory-consolidation.js +6 -6
  36. package/dist/boot/reapers.d.ts +5 -0
  37. package/dist/boot/reapers.js +12 -1
  38. package/dist/boot/resolve-spec.d.ts +3 -3
  39. package/dist/boot/resolve-spec.js +31 -22
  40. package/dist/boot/runner-deps.d.ts +9 -2
  41. package/dist/boot/runner-deps.js +3 -3
  42. package/dist/boot/session-age-out.d.ts +36 -0
  43. package/dist/boot/session-age-out.js +101 -0
  44. package/dist/boot/session-faces.d.ts +5 -14
  45. package/dist/boot/session-faces.js +9 -5
  46. package/dist/boot/stage-01-config.js +2 -0
  47. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  48. package/dist/boot/stage-05-execution-env.js +2 -2
  49. package/dist/boot/stage-06-runners.d.ts +7 -4
  50. package/dist/boot/stage-06-runners.js +15 -6
  51. package/dist/boot/stage-07-capability-layer.d.ts +7 -4
  52. package/dist/boot/stage-07-capability-layer.js +2 -1
  53. package/dist/boot/stage-08-reapers.d.ts +7 -4
  54. package/dist/boot/stage-08-reapers.js +4 -2
  55. package/dist/boot/stage-09-leader.d.ts +11 -5
  56. package/dist/boot/stage-09-leader.js +12 -3
  57. package/dist/boot/stage-10-http-server.d.ts +11 -5
  58. package/dist/boot/stage-10-http-server.js +3 -6
  59. package/dist/boot/web-admin.js +3 -1
  60. package/dist/boot/workflow-orchestration.d.ts +7 -2
  61. package/dist/boot/workflow-orchestration.js +24 -7
  62. package/dist/bounded-rotation.d.ts +29 -0
  63. package/dist/bounded-rotation.js +24 -0
  64. package/dist/capability-facts.d.ts +22 -13
  65. package/dist/capability-facts.js +28 -18
  66. package/dist/center-credential.d.ts +25 -6
  67. package/dist/center-credential.js +25 -5
  68. package/dist/config-catalog.d.ts +26 -7
  69. package/dist/config-catalog.js +14 -11
  70. package/dist/config-invariants.d.ts +2 -1
  71. package/dist/config-invariants.js +4 -4
  72. package/dist/config-types.d.ts +10 -4
  73. package/dist/config.d.ts +51 -2
  74. package/dist/config.js +35 -24
  75. package/dist/deployment-shape.d.ts +9 -1
  76. package/dist/deployment-shape.js +2 -1
  77. package/dist/device-protocol.d.ts +5 -4
  78. package/dist/device-protocol.js +1 -1
  79. package/dist/device-ws-hub.js +3 -2
  80. package/dist/device-ws-protocol.d.ts +63 -10
  81. package/dist/device-ws-protocol.js +56 -25
  82. package/dist/engine-identity.d.ts +19 -7
  83. package/dist/engine-identity.js +10 -2
  84. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  85. package/dist/execution-env-refusal.d.ts +23 -0
  86. package/dist/execution-env-refusal.js +5 -0
  87. package/dist/execution-lane-caps.d.ts +3 -1
  88. package/dist/field-conflict.d.ts +1 -1
  89. package/dist/file-history-capture.d.ts +11 -4
  90. package/dist/file-history-capture.js +9 -6
  91. package/dist/file-history-disclosure.d.ts +7 -5
  92. package/dist/file-history-disclosure.js +7 -5
  93. package/dist/fleet/drain-entry.d.ts +4 -0
  94. package/dist/hooks/hook-runner.js +2 -2
  95. package/dist/host-lsp-manager.d.ts +2 -2
  96. package/dist/http/admission.js +17 -2
  97. package/dist/http/approval-sweeps.js +5 -5
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +2 -1
  101. package/dist/http/dispatch.js +34 -27
  102. package/dist/http/durable-run-leg.js +16 -16
  103. package/dist/http/health-self-report.d.ts +9 -1
  104. package/dist/http/health-self-report.js +2 -0
  105. package/dist/http/owner-access.d.ts +31 -11
  106. package/dist/http/owner-access.js +37 -6
  107. package/dist/http/resume-ctx-read.js +25 -3
  108. package/dist/http/resume-legs.d.ts +2 -1
  109. package/dist/http/resume-legs.js +259 -79
  110. package/dist/http/route-ctx.d.ts +18 -8
  111. package/dist/http/route-table.d.ts +1 -1
  112. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  113. package/dist/http/routes/agents-roster.js +2 -3
  114. package/dist/http/routes/approvals-assistant.js +4 -3
  115. package/dist/http/routes/background.d.ts +3 -3
  116. package/dist/http/routes/capabilities.js +8 -5
  117. package/dist/http/routes/fleet.js +10 -8
  118. package/dist/http/routes/projects.d.ts +3 -2
  119. package/dist/http/routes/projects.js +4 -5
  120. package/dist/http/routes/runs.js +102 -36
  121. package/dist/http/routes/session-sync.d.ts +4 -3
  122. package/dist/http/routes/session-sync.js +172 -77
  123. package/dist/http/routes/sessions-list.js +13 -7
  124. package/dist/http/routes/sessions.js +10 -6
  125. package/dist/http/routes/tasks.js +30 -16
  126. package/dist/http/routes/trace-usage.js +2 -2
  127. package/dist/http/server.d.ts +21 -27
  128. package/dist/http/server.js +2 -2
  129. package/dist/http/submit-ctx.js +5 -4
  130. package/dist/http/wire-types.d.ts +14 -4
  131. package/dist/identity-source.d.ts +20 -3
  132. package/dist/identity-source.js +14 -0
  133. package/dist/index.d.ts +4 -0
  134. package/dist/lane-home-probe.d.ts +3 -7
  135. package/dist/lane-home-probe.js +0 -1
  136. package/dist/leader/leader.d.ts +2 -1
  137. package/dist/leg-facts.d.ts +13 -5
  138. package/dist/leg-facts.js +2 -1
  139. package/dist/memory-scope.d.ts +5 -4
  140. package/dist/memory-scope.js +4 -4
  141. package/dist/memory-sync-client.d.ts +30 -2
  142. package/dist/memory-sync-client.js +16 -9
  143. package/dist/observability/err-text.d.ts +7 -0
  144. package/dist/observability/err-text.js +5 -0
  145. package/dist/observability/fail-open.d.ts +7 -3
  146. package/dist/observability/fail-open.js +7 -3
  147. package/dist/observability/metrics.js +1 -0
  148. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  149. package/dist/orchestration/workflow-notify-journal.js +12 -1
  150. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  151. package/dist/orchestration/workflow-park-decision.js +49 -10
  152. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  153. package/dist/orchestration/workflow-park-holder.js +98 -0
  154. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  155. package/dist/orchestration/workflow-record-fence.js +42 -0
  156. package/dist/owner-faces.d.ts +1 -1
  157. package/dist/parked-decide.d.ts +104 -53
  158. package/dist/parked-decide.js +153 -117
  159. package/dist/peer-directory.d.ts +2 -2
  160. package/dist/peer-inbound-face.d.ts +2 -6
  161. package/dist/peer-inbound-face.js +0 -3
  162. package/dist/permission-mode-admission.d.ts +41 -5
  163. package/dist/permission-mode-admission.js +29 -3
  164. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  165. package/dist/plugins/adoption-log-sql.js +7 -1
  166. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  167. package/dist/plugins/approval-ask-store-file.js +8 -0
  168. package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
  169. package/dist/plugins/approval-ask-store-memory.js +28 -9
  170. package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
  171. package/dist/plugins/approval-ask-store-sql.js +34 -5
  172. package/dist/plugins/approval-exemption-store.js +3 -0
  173. package/dist/plugins/approval-nonce-store.js +2 -0
  174. package/dist/plugins/breaker-state-sql.js +2 -0
  175. package/dist/plugins/caching-session-store.d.ts +9 -15
  176. package/dist/plugins/caching-session-store.js +6 -0
  177. package/dist/plugins/device-store-sql.js +6 -0
  178. package/dist/plugins/error-origin.d.ts +18 -0
  179. package/dist/plugins/error-origin.js +10 -0
  180. package/dist/plugins/execution-placement-sql.js +6 -6
  181. package/dist/plugins/image-bake-store-sql.js +6 -0
  182. package/dist/plugins/image-index-sql.js +4 -0
  183. package/dist/plugins/landed-attendants.d.ts +212 -0
  184. package/dist/plugins/landed-attendants.js +161 -0
  185. package/dist/plugins/leader-run-store-sql.js +4 -0
  186. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  187. package/dist/plugins/local-session-live-reference.js +39 -0
  188. package/dist/plugins/local-session-store.d.ts +71 -33
  189. package/dist/plugins/local-session-store.js +129 -27
  190. package/dist/plugins/memory-engine-pg.js +4 -0
  191. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  192. package/dist/plugins/memory-engine-tidb.js +4 -0
  193. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  194. package/dist/plugins/memory-sync-store-pg.js +5 -0
  195. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  196. package/dist/plugins/one-time-migrations.d.ts +57 -63
  197. package/dist/plugins/one-time-migrations.js +162 -90
  198. package/dist/plugins/org-delivery-quota-store.js +4 -1
  199. package/dist/plugins/outcome-ledger-sql.js +2 -0
  200. package/dist/plugins/permission-rule-store-sql.js +11 -4
  201. package/dist/plugins/pg-cost-quota.js +4 -0
  202. package/dist/plugins/pg-pool.js +24 -1
  203. package/dist/plugins/pg-rate-limiter.js +2 -0
  204. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  205. package/dist/plugins/posix-shell-fs.js +4 -1
  206. package/dist/plugins/remote-env-adb.d.ts +19 -15
  207. package/dist/plugins/remote-env-adb.js +41 -15
  208. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  209. package/dist/plugins/remote-env-connect-seat.js +72 -0
  210. package/dist/plugins/remote-env-device.d.ts +22 -5
  211. package/dist/plugins/remote-env-device.js +106 -8
  212. package/dist/plugins/remote-env-e2b.d.ts +15 -6
  213. package/dist/plugins/remote-env-e2b.js +42 -32
  214. package/dist/plugins/remote-env-file-error.js +6 -2
  215. package/dist/plugins/remote-env-host.d.ts +17 -0
  216. package/dist/plugins/remote-env-host.js +30 -38
  217. package/dist/plugins/remote-env-k8s.d.ts +41 -39
  218. package/dist/plugins/remote-env-k8s.js +53 -30
  219. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  220. package/dist/plugins/remote-env-local-docker.js +34 -41
  221. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  222. package/dist/plugins/remote-env-ssh.js +60 -59
  223. package/dist/plugins/remote-shell.d.ts +98 -1
  224. package/dist/plugins/remote-shell.js +218 -0
  225. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  226. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  227. package/dist/plugins/retention-lane-store-sql.js +4 -0
  228. package/dist/plugins/retention-sql.d.ts +20 -2
  229. package/dist/plugins/retention-sql.js +11 -0
  230. package/dist/plugins/run-store-sql.d.ts +21 -2
  231. package/dist/plugins/run-store-sql.js +31 -7
  232. package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
  233. package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
  234. package/dist/plugins/schema-consistency.d.ts +4 -2
  235. package/dist/plugins/schema-consistency.js +19 -2
  236. package/dist/plugins/send-file-ledger.js +3 -0
  237. package/dist/plugins/serving-requirement.d.ts +26 -7
  238. package/dist/plugins/serving-requirement.js +11 -4
  239. package/dist/plugins/session-birth.d.ts +6 -2
  240. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  241. package/dist/plugins/session-ext-table.d.ts +14 -1
  242. package/dist/plugins/session-ext-table.js +5 -1
  243. package/dist/plugins/session-row-sql.d.ts +49 -2
  244. package/dist/plugins/session-row-sql.js +56 -13
  245. package/dist/plugins/session-store-sql.d.ts +34 -18
  246. package/dist/plugins/session-store-sql.js +98 -17
  247. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  248. package/dist/plugins/shared-memory-store-sql.js +6 -2
  249. package/dist/plugins/sql-driver.d.ts +2 -1
  250. package/dist/plugins/sql-driver.js +18 -3
  251. package/dist/plugins/sql-errors.d.ts +11 -6
  252. package/dist/plugins/sql-errors.js +5 -6
  253. package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
  254. package/dist/plugins/stop-then-start-sentence.js +14 -10
  255. package/dist/plugins/stop-then-start.d.ts +109 -0
  256. package/dist/plugins/stop-then-start.js +104 -0
  257. package/dist/plugins/store-backend.d.ts +40 -13
  258. package/dist/plugins/store-backend.js +169 -27
  259. package/dist/plugins/store-boundary.d.ts +57 -0
  260. package/dist/plugins/store-boundary.js +139 -0
  261. package/dist/plugins/store-contracts.d.ts +17 -0
  262. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  263. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  264. package/dist/plugins/store-fault.d.ts +26 -7
  265. package/dist/plugins/store-fault.js +18 -2
  266. package/dist/plugins/task-attachment-store.js +9 -0
  267. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  268. package/dist/plugins/task-list-store-sql.js +10 -8
  269. package/dist/plugins/tidb-cost-quota.js +4 -0
  270. package/dist/plugins/tidb-pool.js +2 -1
  271. package/dist/plugins/tidb-rate-limiter.js +4 -0
  272. package/dist/plugins/user-input-ledger-sql.js +4 -0
  273. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  274. package/dist/plugins/workflow-run-store-sql.js +64 -4
  275. package/dist/plugins/workspace-fill.d.ts +4 -7
  276. package/dist/plugins/workspace-fill.js +2 -1
  277. package/dist/plugins/write-behind-counter.js +5 -0
  278. package/dist/project-context.d.ts +0 -2
  279. package/dist/project-context.js +4 -8
  280. package/dist/project-registry.d.ts +46 -0
  281. package/dist/project-registry.js +44 -0
  282. package/dist/request-key-closure.js +1 -1
  283. package/dist/request-value-closure.d.ts +4 -0
  284. package/dist/request-value-closure.js +10 -0
  285. package/dist/run-acceptance.d.ts +3 -2
  286. package/dist/run-acceptance.js +4 -6
  287. package/dist/run-local.js +1 -0
  288. package/dist/runs.d.ts +37 -8
  289. package/dist/runs.js +53 -12
  290. package/dist/runtime-governance.d.ts +5 -4
  291. package/dist/security.d.ts +30 -32
  292. package/dist/security.js +41 -7
  293. package/dist/server-secret-env.d.ts +12 -10
  294. package/dist/server-secret-env.js +24 -15
  295. package/dist/session-bindings.d.ts +82 -0
  296. package/dist/session-bindings.js +18 -0
  297. package/dist/session-children-retire.d.ts +2 -2
  298. package/dist/session-children-retire.js +2 -6
  299. package/dist/session-deletion.d.ts +16 -1
  300. package/dist/session-error-wire.d.ts +3 -3
  301. package/dist/session-error-wire.js +3 -3
  302. package/dist/session-incarnation.d.ts +10 -0
  303. package/dist/session-incarnation.js +10 -1
  304. package/dist/session-kind.d.ts +57 -0
  305. package/dist/session-kind.js +22 -0
  306. package/dist/session-row.d.ts +21 -1
  307. package/dist/session-row.js +7 -1
  308. package/dist/session-sync-kernel.d.ts +24 -14
  309. package/dist/session-sync-kernel.js +3 -6
  310. package/dist/session-sync.d.ts +4 -98
  311. package/dist/session-sync.js +1 -71
  312. package/dist/session-titler.d.ts +10 -3
  313. package/dist/session-titler.js +19 -6
  314. package/dist/ssh-host-key.d.ts +5 -0
  315. package/dist/ssh-host-key.js +3 -0
  316. package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
  317. package/dist/stores-sql/file-history-store-sql.js +108 -70
  318. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  319. package/dist/stores-sql/pg-session-storage.js +37 -13
  320. package/dist/stores-sql/retention-store-sql.d.ts +1 -9
  321. package/dist/stores-sql/retention-store-sql.js +2 -10
  322. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  323. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  324. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  325. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  326. package/dist/stores-sql/tidb-session-store.js +37 -13
  327. package/dist/submission-exit.d.ts +3 -2
  328. package/dist/submission-exit.js +9 -1
  329. package/dist/task-cwd.d.ts +2 -2
  330. package/dist/task-settings.d.ts +6 -5
  331. package/dist/terminal.d.ts +33 -2
  332. package/dist/terminal.js +139 -9
  333. package/dist/tool-approval.d.ts +140 -6
  334. package/dist/tool-approval.js +165 -72
  335. package/dist/trace/self-minted-wire.d.ts +290 -27
  336. package/dist/trace/self-minted-wire.js +101 -13
  337. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +78 -27
  338. package/package.json +2 -2
  339. package/wire-changes/server@7.151.0.json +762 -0
  340. package/dist/http/session-inherit-env.d.ts +0 -36
  341. package/dist/http/session-inherit-env.js +0 -6
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * ── 读口:逐字投 core `list()` 行 ─────────────────────────────────────────────────────────────────
11
11
  * 读的是**进程内**注册表(`defaultTaskRegistry.list`),访问元组 = 会话自己的 `{ owner: sid, sessionId: sid, scope }`
12
- * (scope = 会话行属主 `owner ?? "default"`,与 core 注册铸值同源)⇒ 可见集完全由 core 的 `canAccess` 判(fail-closed:
12
+ * (scope = 会话的引擎租户分区 `sessionTenantScope`,经唯一换算 `engineScopeOfOwner`,与 core 注册铸值同源)⇒ 可见集完全由 core 的 `canAccess` 判(fail-closed:
13
13
  * 缺轴即拒;本会话的会话驻留行、task id 即会话 id 的 run 行、本会话发起的 workflow、根会话是本会话的后台 agent)。
14
14
  * 键逐个处置(下面的 {@link ROW_KEY_DISPOSITION},core 给行加键 ⇒ 编译红):`task_id` 改名 `id`;`retained` **真才带**
15
15
  * (core:缺席 ⇒ 这一行在 `includeRetained:false` 的硬面上会停);`description` 是模型写的描述、缺席时是启动时的命令文本 ——
@@ -40,8 +40,8 @@
40
40
  *
41
41
  * 🔴 **停止口在无凭据写门内**(`credentialGated: ["POST"]`;车OL F1 = 7.102.0 合并复审 gpt-6 腿 [high]):门的两个合取项它都中
42
42
  * —— ① 授权的唯一输入是 principal 头(属主判 `admitSessionRow` 与 operator 臂 `explicitOperatorOk` 读的都是 `gatedSubject`,
43
- * 无 service 凭证的部署上它收调用方自报的头);② 不可撤销、爆炸半径跨租户(知道会话 id + 自报它的属主 ⇒ 停掉那个会话的后台
44
- * 进程,`includeRetained:true` 连显式 retain 的活服务一起停)。于是无 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` 的部署上它与
43
+ * 没带过门凭据的请求上它收调用方自报的头);② 不可撤销、爆炸半径跨租户(知道会话 id + 自报它的属主 ⇒ 停掉那个会话的后台
44
+ * 进程,`includeRetained:true` 连显式 retain 的活服务一起停)。于是无 service 凭证且未开 `ALLOW_UNAUTHED_WRITES` 的部署上,不带过门凭据的请求打它与
45
45
  * 其余改写门同码同句 503 `auth.service_token_required`(在路由之前,`dispatch.ts` 那一道),同一只会话级收敛函数的另一个调用点
46
46
  * `DELETE /v1/sessions/:id` 早在同一形下被 `isDestructiveSessionWrite` 拒。读口 `GET …/background` 不进:它有属主门兜底、只读、
47
47
  * 只列本会话,与 `GET /v1/rules` 同判。已知同族缺口(不在本口单独打补丁):能力位 `exitFaces` 不含「凭据就绪」这一项,见契约
@@ -19,7 +19,7 @@ import { sendJson, sendError } from "../send.js";
19
19
  import { codeFieldOf } from "../../terminal.js";
20
20
  import { engineRefusalAnswerOf } from "../engine-refusal.js";
21
21
  import { assertNever } from "../route-ctx.js";
22
- import { fileHistoryCaptureOf } from "../../file-history-capture.js";
22
+ import { fileHistoryCaptureOf, fileHistoryWired } from "../../file-history-capture.js";
23
23
  import { projectMemoryEngineCapability } from "../../memory-posture.js";
24
24
  import { projectWriteProtectionCapability } from "../../write-protection.js";
25
25
  import { projectStoreCapability, unwiredStorePosture } from "../../store-posture.js";
@@ -27,6 +27,7 @@ import { projectWebSearchCapability } from "../../plugins/web-search.js";
27
27
  import { projectExecutionLaneCapability } from "../../capabilities/execution-lane.js";
28
28
  import { SETTINGS_CONTRACT_CAPABILITY } from "../../capabilities/settings-contract.js";
29
29
  import { judgeSandboxGitPlane, sandboxGitPlanePremisesOf } from "../../sandbox-git-plane.js";
30
+ import { SESSION_KINDS } from "../../session-kind.js";
30
31
  import { MCP_ASSERTION_HEADER_SENT, MCP_REQUEST_TRANSPORT_KINDS } from "../../mcp-assertion-header.js";
31
32
  import { gateCompositeMembers } from "../../capabilities/composite-members.js";
32
33
  import { projectApprovalsStreamLiveCapability } from "../../capabilities/approvals-push.js";
@@ -84,6 +85,7 @@ export async function handleCapabilities(req, res, match, ctx) {
84
85
  deps.logger?.warn("auto_mode_face_resolver_fault", { fault: autoModeFault });
85
86
  const rulesLaneForCaller = facts.ruleLaneForCaller(deps, gatedPerson(req, deps.config)).available;
86
87
  const streamApprovalFact = facts.streamApproval(deps);
88
+ const historyWired = fileHistoryWired(deps.backend);
87
89
  sendJson(res, 200, gateCompositeMembers({
88
90
  ...deps.capabilities,
89
91
  ...facts.engineCapabilityBits(),
@@ -213,17 +215,18 @@ export async function handleCapabilities(req, res, match, ctx) {
213
215
  scenarioSubject: true,
214
216
  sessionList: facts.sessionList(deps).available,
215
217
  sessionSearch: facts.sessionList(deps).available,
218
+ sessionKinds: facts.sessionKinds(deps).available ? [...SESSION_KINDS] : [],
216
219
  sessionFork: facts.sessionFork(deps).available,
217
220
  sessionDelete: facts.sessionDelete(deps).available,
218
- resumeAt: Boolean(deps.resumeAnchorStore && headReadPresent(deps.sessionStorage)),
221
+ resumeAt: deps.backend !== undefined && headReadPresent(deps.sessionStorage),
219
222
  promptSuggestions: true,
220
223
  sessionPolicy: facts.sessionPolicy(deps).available,
221
224
  sessionRulesNamespaceForms: sessionRulesNamespaceFormsHonored(),
222
- restoreFiles: Boolean(deps.fileHistoryStore),
223
- rewindFilesTo: Boolean(deps.fileHistoryStore && deps.resumeAnchorStore && headReadPresent(deps.sessionStorage)),
225
+ restoreFiles: historyWired,
226
+ rewindFilesTo: historyWired && headReadPresent(deps.sessionStorage),
224
227
  sessionRewindFiles: facts.sessionRewindFiles(deps).available,
225
228
  sessionRewindFilesPreview: facts.sessionRewindFilesPreview(deps).available,
226
- fileHistoryCapture: fileHistoryCaptureOf(deps.fileHistoryStore, deps.config.fileHistoryCapture),
229
+ fileHistoryCapture: fileHistoryCaptureOf(historyWired, deps.config.fileHistoryCapture),
227
230
  scheduler: facts.hostScheduler(deps.config).available,
228
231
  sessionSync: facts.sessionSync(deps).available,
229
232
  sessionSyncFileHistory: facts.sessionSyncFileHistory(deps).available,
@@ -3,7 +3,7 @@ import { FLEET_SNAPSHOT_TERMINAL_MAX_ROWS, isTerminalWorkflowRowStatus, recentTe
3
3
  import { taskNotificationFoldKey } from "../../orchestration/workflow-completion-inbox.js";
4
4
  import { sendJson, sendError, sseHeaders } from "../send.js";
5
5
  import { gatedSubject, sendPrincipalRefusal } from "../principal-gate.js";
6
- import { routeListFilter } from "../owner-access.js";
6
+ import { routeListFilter, workflowScopeOf } from "../owner-access.js";
7
7
  import { assertNever } from "../route-ctx.js";
8
8
  import * as facts from "../../capability-facts.js";
9
9
  export async function handleFleet(req, res, match, ctx) {
@@ -24,22 +24,24 @@ export async function handleFleet(req, res, match, ctx) {
24
24
  const fleetParams = new URLSearchParams(match.search);
25
25
  const callerSession = fleetParams.get("session");
26
26
  const observeOnly = fleetParams.get("observe") === "1";
27
- await streamFleet(req, res, fleet.fleetBus, fleetWide ? null : (principal ?? "default"), callerSession || null, observeOnly ? undefined : deps.workflowCompletionInbox, { store: deps.workflowRunStore, scope: fleetWide ? null : (principal ?? "default"), pullScope: principal ?? "default", ready: deps.fleetRecoveryDone });
27
+ const workflowScope = workflowScopeOf(principal);
28
+ const viewer = fleetWide ? null : { rows: principal ?? "default", workflows: workflowScope };
29
+ await streamFleet(req, res, fleet.fleetBus, viewer, callerSession || null, observeOnly ? undefined : deps.workflowCompletionInbox, { store: deps.workflowRunStore, scope: viewer?.workflows ?? null, pullScope: workflowScope, ready: deps.fleetRecoveryDone });
28
30
  return;
29
31
  }
30
32
  default:
31
33
  return assertNever(match.decl.id);
32
34
  }
33
35
  }
34
- function streamFleet(req, res, bus, callerScope, callerSession = null, completionInbox, terminal) {
36
+ function streamFleet(req, res, bus, viewer, callerSession = null, completionInbox, terminal) {
35
37
  return new Promise((resolve) => {
36
38
  sseHeaders(res);
37
- res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, scoped: callerScope !== null, sessionScoped: callerSession !== null, ...(callerSession !== null ? { session: callerSession } : {}), bgNotifyFailClosed: true })}\n\n`);
39
+ res.write(`event: meta\ndata: ${JSON.stringify({ type: "meta", version: 1, scoped: viewer !== null, sessionScoped: callerSession !== null, ...(callerSession !== null ? { session: callerSession } : {}), bgNotifyFailClosed: true })}\n\n`);
38
40
  const seenTasks = new Set();
39
41
  const seenWf = new Set();
40
42
  const sessionOk = (rowSession) => callerSession === null || rowSession === undefined || rowSession === callerSession;
41
- const visT = (r) => (callerScope === null || r.scope === callerScope) && sessionOk(r.sessionId);
42
- const visW = (r) => (callerScope === null || r.scope === callerScope) && sessionOk(r.sessionId);
43
+ const visT = (r) => (viewer === null || r.scope === viewer.rows) && sessionOk(r.sessionId);
44
+ const visW = (r) => (viewer === null || r.scope === viewer.workflows) && sessionOk(r.sessionId);
43
45
  const SCOPED_TASK_KEY_DISPOSITION = {
44
46
  id: "keep", parentId: "keep", parentToolCallId: "keep", workflowRunId: "keep", status: "keep",
45
47
  startedAt: "keep", elapsedMs: "keep", tokens: "keep",
@@ -144,7 +146,7 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
144
146
  break;
145
147
  case "hook_notice": {
146
148
  const hn = frame.notice;
147
- const hScopeOk = callerScope === null || hn.ownerScope === callerScope;
149
+ const hScopeOk = viewer === null || hn.ownerScope === viewer.rows;
148
150
  const hSessionOk = callerSession === null || hn.ownerSessionId === callerSession;
149
151
  if (hScopeOk && hSessionOk) {
150
152
  send("hook_notice", { type: "hook_notice", ...hookNoticeWire(hn), ts: frame.ts });
@@ -153,7 +155,7 @@ function streamFleet(req, res, bus, callerScope, callerSession = null, completio
153
155
  }
154
156
  case "bg_notification": {
155
157
  const n = frame.notification;
156
- const scopeOk = callerScope === null || n.ownerScope === callerScope;
158
+ const scopeOk = viewer === null || n.ownerScope === viewer.rows;
157
159
  const sessionInjectOk = callerSession === null || n.ownerSessionId === callerSession;
158
160
  if (scopeOk && sessionInjectOk) {
159
161
  const { ownerScope: _os, ownerSessionId: _osid, ...wire } = n;
@@ -9,8 +9,9 @@
9
9
  * · **登记簿只由配置中心下发**:settings-schema 的本地读取集不收 `projects` 域 ⇒ 没有中心腿在应用配置的部署上没有登记簿 —— 两口 501
10
10
  * `capability.unavailable`,能力位 `capabilities.projects`(布尔)读同一只事实(`capability-facts.ts` 的 `projectRegistry`)答 `false`;
11
11
  * 有登记簿时答 `true`。本面不开写动词(写到哪里由另一张设计定;写面到货时另立一位,本位不改义)。
12
- * · **可见性 fail-closed**:身份判在登记簿在场判**之前**(否则「这台有没有登记簿」先于 401 泄出去);fleet 调用方(列表面 `list.read` 读档(`listFilterOf`))看全部;
13
- * 带验过主体的调用方只看 `registeredBy` 恰是自己的登记;看不见的与不存在的同码同句(404 `not_found.project`,不给存在性预言机)。
12
+ * · **可见性 fail-closed**:身份判在登记簿在场判**之前**(否则「这台有没有登记簿」先于 401 泄出去);判与按 id 取只有一只(`project-registry.ts`:
13
+ * fleet 调用方(列表面 `list.read` 读档(`listFilterOf`))看全部,带验过主体的调用方只看 `registeredBy` 恰是自己的登记 —— 项目上下文继承与登记簿默认读层种子同读它);
14
+ * 看不见的与不存在的同码同句(404 `not_found.project`,不给存在性预言机)。
14
15
  *
15
16
  * 归档:`archivedAt` 在场 = 已归档。列表缺省不列,`?archived=true` 只列已归档的,`?archived=false` 与缺省同;单项读不论归档与否都答(会话行引用它)。
16
17
  */
@@ -4,7 +4,7 @@ import { sendUnauthorized } from "../principal-gate.js";
4
4
  import { routeListFilter } from "../owner-access.js";
5
5
  import { assertNever } from "../route-ctx.js";
6
6
  import * as facts from "../../capability-facts.js";
7
- import { projectVisible } from "../../project-context.js";
7
+ import { projectVisibleTo, visibleProjectOf } from "../../project-registry.js";
8
8
  import { projectByTable } from "../../trace/projection-table.js";
9
9
  export const PROJECT_NOT_FOUND_MESSAGE = "no such project";
10
10
  export const PROJECT_ROW_DISPOSITIONS = {
@@ -49,7 +49,6 @@ export async function handleProjects(req, res, match, ctx) {
49
49
  sendError(res, 501, "capability.unavailable", "the project registry is not available on this deployment: projects are published only by the configuration center, and no center configuration is applied here");
50
50
  return;
51
51
  }
52
- const visible = (registration) => projectVisible(registration, viewer);
53
52
  switch (match.decl.id) {
54
53
  case "projects-list": {
55
54
  const archived = readArchivedQuery(new URLSearchParams(match.search).get("archived"));
@@ -58,7 +57,7 @@ export async function handleProjects(req, res, match, ctx) {
58
57
  return;
59
58
  }
60
59
  const projects = Object.entries(registry.registry)
61
- .filter(([, registration]) => visible(registration) && (registration.archivedAt !== undefined) === archived)
60
+ .filter(([, registration]) => projectVisibleTo(viewer, registration) && (registration.archivedAt !== undefined) === archived)
62
61
  .map(([id, registration]) => projectRowOf(id, registration));
63
62
  sendJson(res, 200, { projects });
64
63
  return;
@@ -70,8 +69,8 @@ export async function handleProjects(req, res, match, ctx) {
70
69
  return;
71
70
  }
72
71
  const id = raw.toLowerCase();
73
- const registration = Object.hasOwn(registry.registry, id) ? registry.registry[id] : undefined;
74
- if (registration === undefined || !visible(registration)) {
72
+ const registration = visibleProjectOf(registry.registry, id, viewer);
73
+ if (registration === undefined) {
75
74
  sendError(res, 404, "not_found.project", PROJECT_NOT_FOUND_MESSAGE);
76
75
  return;
77
76
  }
@@ -16,8 +16,8 @@ import { idemCallerOf, idemKeyOf, inflightKeyOf, submissionIdemOf } from "../ide
16
16
  import { streamSseLog } from "../sse-log.js";
17
17
  import { bindSseLifecycle } from "../sse-lifecycle.js";
18
18
  import { buildApprovalPreamble, buildApprovalPreambleSseFrames, readClosedWordSeat, storedCardDemandsAPerson } from "../../approval-card.js";
19
- import { heldAskOf, liveHostSource, readDecider, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF, POLICY_CANNOT_ALLOW_PERSON_ASK } from "../../host-decision.js";
20
- import { ASK_DECISION_CONSUMER_ABSENT, approvalPreambleReplays, outcomeOfDecidedRow, parkSessionOf, presentedApprovalFrameFamily, resolveStreamApprovalGate } from "../../tool-approval.js";
19
+ import { heldAskOf, readDecider, retiredDecisionKeyFacts, DECIDED_BY_NOT_IN_PROOF, POLICY_CANNOT_ALLOW_PERSON_ASK } from "../../host-decision.js";
20
+ import { ASK_DECISION_CONSUMER_ABSENT, approvalPreambleReplays, decidedRowFacts, outcomeOfDecidedRow, parkSessionOf, presentedApprovalFrameFamily, resolveStreamApprovalGate } from "../../tool-approval.js";
21
21
  import { MAX_DECISION_NOTE_CHARS, noteLandedOnRow, noteRecordedReceipt } from "../../approval-ask-machine.js";
22
22
  import { askRowEndingCause } from "../../approval-deny-reasons.js";
23
23
  import { normalizeRunEventType, screenedStoredEventData } from "../../trace/project.js";
@@ -35,6 +35,7 @@ import { mintAndDriveDurableRun } from "../durable-run-leg.js";
35
35
  import { SUBMISSION_CLAIM_FACE } from "../execution-context.js";
36
36
  import { admitRunById, admitRunByIdem, enter, readVisibleRun, sessionFactsReadOf } from "../owner-access.js";
37
37
  import { keyAdmission, ownerCellOf, recoveredSubject } from "../../owner-faces.js";
38
+ import { engineScopeOfOwner } from "../../engine-identity.js";
38
39
  import { personOf } from "../../owner-brands.js";
39
40
  import { withSubject } from "../../observability/principal-context.js";
40
41
  import { readResumeCtx, resumeCtxUnreadableAnswer } from "../resume-ctx-read.js";
@@ -49,6 +50,11 @@ const STEER_PRIORITY_CHOICES = STEER_PRIORITY_WORDS.length <= 2 ? STEER_PRIORITY
49
50
  const ASK_PARKING_RETRY_AFTER_SEC = 2;
50
51
  const ASK_ACTOR_LABEL_MAX = 256;
51
52
  const AskDecisionActorSchema = ActorAssertionWire.strip();
53
+ function mirrorRetryAfter(res, body) {
54
+ const retryAfterSec = body !== null && typeof body === "object" ? body.retryAfterSec : undefined;
55
+ if (typeof retryAfterSec === "number" && Number.isFinite(retryAfterSec) && retryAfterSec >= 0)
56
+ res.setHeader("retry-after", String(Math.ceil(retryAfterSec)));
57
+ }
52
58
  const AskDecisionBodySchema = z
53
59
  .object({
54
60
  decision: z.enum(["approve", "deny"]),
@@ -885,7 +891,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
885
891
  if (!gated)
886
892
  return;
887
893
  const { run } = gated;
888
- const out = await subagentTaskOutput(target, { owner: runId, scope: run.owner ?? "default", ...(run.sessionId ? { sessionId: run.sessionId } : {}) });
894
+ const out = await subagentTaskOutput(target, { owner: runId, scope: engineScopeOfOwner(run.owner), ...(run.sessionId ? { sessionId: run.sessionId } : {}) });
889
895
  const details = out.details;
890
896
  if (details?.error === "not_found" || details?.type !== "background_agent") {
891
897
  if (!sendHandleMiss(res, run, deps.instanceId))
@@ -914,7 +920,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
914
920
  return;
915
921
  const { run } = gatedTail;
916
922
  const it = defaultSubagentTailBus.subscribe(target);
917
- const probe = await subagentTaskOutput(target, { owner: runId, scope: run.owner ?? "default", ...(run.sessionId ? { sessionId: run.sessionId } : {}) });
923
+ const probe = await subagentTaskOutput(target, { owner: runId, scope: engineScopeOfOwner(run.owner), ...(run.sessionId ? { sessionId: run.sessionId } : {}) });
918
924
  const probeDetails = probe.details;
919
925
  if (probeDetails?.error === "not_found" || probeDetails?.type !== "background_agent") {
920
926
  void it.return?.();
@@ -982,7 +988,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
982
988
  return;
983
989
  const { run } = gatedB;
984
990
  const q = new URLSearchParams(match.search);
985
- const access = { owner: runId, scope: run.owner ?? "default", ...(run.sessionId ? { sessionId: run.sessionId } : {}) };
991
+ const access = { owner: runId, scope: engineScopeOfOwner(run.owner), ...(run.sessionId ? { sessionId: run.sessionId } : {}) };
986
992
  if (q.get("filter") !== null) {
987
993
  sendError(res, 400, "request.param_unsupported", "filter is not accepted on the wire — fetch the output and filter client-side (note: the wire serves the clipped projection)");
988
994
  return;
@@ -1189,10 +1195,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
1189
1195
  }
1190
1196
  const body = await readJson(req);
1191
1197
  const { status, body: respBody } = await approvalFact.coordinator.respond(id, principal, body, req, { readRootGrantAccepted: facts.readRootGrant(deps).available });
1192
- const retryAfterSec = respBody.retryAfterSec;
1193
- if (typeof retryAfterSec === "number" && Number.isFinite(retryAfterSec) && retryAfterSec >= 0) {
1194
- res.setHeader("retry-after", String(Math.ceil(retryAfterSec)));
1195
- }
1198
+ mirrorRetryAfter(res, respBody);
1196
1199
  sendJson(res, status, respBody);
1197
1200
  return;
1198
1201
  }
@@ -1205,6 +1208,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
1205
1208
  return;
1206
1209
  }
1207
1210
  const askStore = streamFact.askStore;
1211
+ const coordinator = streamFact.coordinator;
1208
1212
  const principal = gatedSubject(req, deps.config);
1209
1213
  if (deps.config.requirePrincipal && principal === undefined) {
1210
1214
  sendPrincipalRefusal(res, req, deps.config);
@@ -1247,7 +1251,8 @@ export async function handleRunVerbs(req, res, match, ctx) {
1247
1251
  askNotFound();
1248
1252
  return;
1249
1253
  }
1250
- const askAdmission = keyAdmission("ask.decide", { subject: principal, operator: explicitOperatorOk(principal === undefined ? undefined : personOf(principal), deps.config) }, ownerCellOf(row0.owner));
1254
+ const callerIsOperator = explicitOperatorOk(principal === undefined ? undefined : personOf(principal), deps.config);
1255
+ const askAdmission = keyAdmission("ask.decide", { subject: principal, operator: callerIsOperator }, ownerCellOf(row0.owner));
1251
1256
  if (askAdmission === undefined) {
1252
1257
  askNotFound();
1253
1258
  return;
@@ -1259,7 +1264,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
1259
1264
  via: askAdmission === "row" ? "owner" : "operator",
1260
1265
  };
1261
1266
  const decidedRowIsAuthoritative = (row) => {
1262
- if (row.state !== "DECIDED" || row.decision !== "approve" && row.decision !== "deny" || row.provisional || row.decidedAtMs === null) {
1267
+ if (decidedRowFacts(row) === undefined) {
1263
1268
  deps.logger?.error?.("ask_decision_row_not_authoritative", {
1264
1269
  taskId,
1265
1270
  askId,
@@ -1290,8 +1295,8 @@ export async function handleRunVerbs(req, res, match, ctx) {
1290
1295
  const replayDecided = (row) => {
1291
1296
  const storedActor = projectDecisionActor(row.decisionActor);
1292
1297
  sendJson(res, 200, {
1293
- askId: row.askId,
1294
- taskId: row.taskId,
1298
+ askId,
1299
+ taskId,
1295
1300
  decision: row.decision,
1296
1301
  decidedAtMs: row.decidedAtMs,
1297
1302
  ...(storedActor ? { actor: storedActor } : {}),
@@ -1299,7 +1304,78 @@ export async function handleRunVerbs(req, res, match, ctx) {
1299
1304
  ...noteReceipt(row),
1300
1305
  });
1301
1306
  };
1302
- const sendTerminal = (row) => {
1307
+ const policyMayNotApprove = (row) => body.decision === "approve" && policyCannotAnswer(heldAskOf(storedCardDemandsAPerson(row.cardJson)), decider.decidedBy);
1308
+ const refusePolicyApprove = () => sendError(res, 400, "request.field_conflict", POLICY_CANNOT_ALLOW_PERSON_ASK, fieldConflictFacts("policy_person_ask", ["decidedBy", "decision"]));
1309
+ const conflictWithFirst = (row) => {
1310
+ const first = projectDecisionActor(row.decisionActor);
1311
+ sendError(res, 409, "conflict.ask_decided", "this ask was already decided — the first decision stands and is echoed here", {
1312
+ decision: row.decision,
1313
+ decidedAtMs: row.decidedAtMs,
1314
+ ...(first ? { actor: first } : {}),
1315
+ ...decisionNoteEcho(row),
1316
+ });
1317
+ };
1318
+ const answerParked = async (row) => {
1319
+ const answer = await coordinator.decideParkedAsk(askStore, row, {
1320
+ write: { decision: body.decision, decider, ...(body.note !== undefined ? { note: body.note } : {}), actor, ...(body.idempotencyKey !== undefined ? { idempotencyKey: body.idempotencyKey } : {}) },
1321
+ ...(body.decision === "approve" && hasUpdatedInput ? { updatedInput: body.updatedInput } : {}),
1322
+ ...(principal !== undefined ? { principal } : {}),
1323
+ operator: callerIsOperator,
1324
+ policyMayNotApprove: policyMayNotApprove(row),
1325
+ httpReq: req,
1326
+ }, body.idempotencyKey);
1327
+ switch (answer.kind) {
1328
+ case "standing": {
1329
+ const first = { decision: answer.decision, decidedAtMs: answer.decidedAtMs, decisionActor: answer.actor ?? null, decisionNote: answer.decisionNote ?? null };
1330
+ if (answer.byThisRequest)
1331
+ replayDecided(first);
1332
+ else
1333
+ conflictWithFirst(first);
1334
+ return;
1335
+ }
1336
+ case "refused":
1337
+ if (answer.status === 404) {
1338
+ askNotFound();
1339
+ return;
1340
+ }
1341
+ mirrorRetryAfter(res, answer.body);
1342
+ sendJson(res, answer.status, answer.body);
1343
+ return;
1344
+ case "gone":
1345
+ askNotFound();
1346
+ return;
1347
+ case "policy_refused":
1348
+ refusePolicyApprove();
1349
+ return;
1350
+ case "gate_only":
1351
+ sendError(res, 410, "gone.ask_parked", "the in-stream window closed and this ask was parked onto the durable approval gate — decide it through that gate for the session echoed here", {
1352
+ sessionId: parkSessionOf(row),
1353
+ gateBoundCallId: row.gateBoundCallId,
1354
+ gateBoundInputHash: row.gateBoundInputHash,
1355
+ });
1356
+ return;
1357
+ case "accepted": {
1358
+ const recorded = answer.recorded ?? { decisionNote: null };
1359
+ sendJson(res, 200, {
1360
+ ...answer.body,
1361
+ askId,
1362
+ taskId,
1363
+ decision: body.decision,
1364
+ decidedAtMs: answer.recorded?.decidedAtMs ?? Date.now(),
1365
+ actor,
1366
+ accepted: "parked_redeemed",
1367
+ ...decisionNoteEcho(recorded),
1368
+ ...noteReceipt(recorded),
1369
+ });
1370
+ return;
1371
+ }
1372
+ default: {
1373
+ const unhandled = answer;
1374
+ throw new Error(`unhandled parked ask answer: ${JSON.stringify(unhandled)}`);
1375
+ }
1376
+ }
1377
+ };
1378
+ const sendTerminal = async (row) => {
1303
1379
  switch (row.state) {
1304
1380
  case "DECIDED": {
1305
1381
  if (!syncLiveFromDecidedRow(row))
@@ -1308,21 +1384,11 @@ export async function handleRunVerbs(req, res, match, ctx) {
1308
1384
  replayDecided(row);
1309
1385
  return;
1310
1386
  }
1311
- const first = projectDecisionActor(row.decisionActor);
1312
- sendError(res, 409, "conflict.ask_decided", "this ask was already decided — the first decision stands and is echoed here", {
1313
- decision: row.decision,
1314
- decidedAtMs: row.decidedAtMs,
1315
- ...(first ? { actor: first } : {}),
1316
- ...decisionNoteEcho(row),
1317
- });
1387
+ conflictWithFirst(row);
1318
1388
  return;
1319
1389
  }
1320
1390
  case "PARKED":
1321
- sendError(res, 410, "gone.ask_parked", "the in-stream window closed and this ask was parked onto the durable approval gate — decide it through that gate for the session echoed here", {
1322
- sessionId: parkSessionOf(row),
1323
- gateBoundCallId: row.gateBoundCallId,
1324
- gateBoundInputHash: row.gateBoundInputHash,
1325
- });
1391
+ await answerParked(row);
1326
1392
  return;
1327
1393
  case "DENIED":
1328
1394
  case "VOID": {
@@ -1367,23 +1433,23 @@ export async function handleRunVerbs(req, res, match, ctx) {
1367
1433
  }
1368
1434
  }
1369
1435
  if (row0.state !== "STREAM_PENDING") {
1370
- sendTerminal(row0);
1436
+ await sendTerminal(row0);
1371
1437
  return;
1372
1438
  }
1373
- if (body.decision === "approve" && policyCannotAnswer(heldAskOf(storedCardDemandsAPerson(row0.cardJson)), decider.decidedBy)) {
1374
- sendError(res, 400, "request.field_conflict", POLICY_CANNOT_ALLOW_PERSON_ASK, fieldConflictFacts("policy_person_ask", ["decidedBy", "decision"]));
1439
+ if (policyMayNotApprove(row0)) {
1440
+ refusePolicyApprove();
1375
1441
  return;
1376
1442
  }
1377
1443
  let outcome;
1378
1444
  const releaseEditMark = body.decision === "approve" && hasUpdatedInput ? deps.toolApproval?.markEditedDecisionInFlight(askId) : undefined;
1379
1445
  try {
1380
- outcome = await askStore.decideAsk(askId, row0.batchId, row0.owner, {
1446
+ outcome = await coordinator.writeAskDecision(askStore, askId, { kind: "live", batchId: row0.batchId }, row0.owner, {
1381
1447
  decision: body.decision,
1382
- decisionActor: actor,
1383
- decisionNote: body.note ?? null,
1384
- idempotencyKey: body.idempotencyKey ?? null,
1385
- ...liveHostSource(decider),
1386
- ...(body.decision === "approve" && hasUpdatedInput ? { updatedInput: body.updatedInput } : {}),
1448
+ decider,
1449
+ actor,
1450
+ ...(body.note !== undefined ? { note: body.note } : {}),
1451
+ ...(body.idempotencyKey !== undefined ? { idempotencyKey: body.idempotencyKey } : {}),
1452
+ ...(hasUpdatedInput ? { updatedInput: body.updatedInput } : {}),
1387
1453
  });
1388
1454
  }
1389
1455
  catch (err) {
@@ -1443,7 +1509,7 @@ export async function handleRunVerbs(req, res, match, ctx) {
1443
1509
  askNotFound();
1444
1510
  return;
1445
1511
  }
1446
- sendTerminal(outcome.row);
1512
+ await sendTerminal(outcome.row);
1447
1513
  return;
1448
1514
  }
1449
1515
  if (!decidedRowIsAuthoritative(outcome.row))
@@ -13,6 +13,7 @@ import { type ImportAnchor, type StagingHandle } from "../../session-sync-kernel
13
13
  import type { DurableOwnerKey } from "../../owner-brands.js";
14
14
  import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
15
15
  import { type SessionRowPatch } from "../../session-row.js";
16
+ import { type SessionFace } from "../../session-incarnation.js";
16
17
  export type PendingStaging = {
17
18
  handle: StagingHandle;
18
19
  realSessionId: string;
@@ -33,9 +34,9 @@ export type PendingStaging = {
33
34
  /** The principal that opened this staging, kept as the handle's owner key (`null` = a fleet caller opened it) — a different caller may not drive it
34
35
  * (404, no oracle; compared through `owner-faces.ts` `sameKey` only). */
35
36
  openedBy: DurableOwnerKey | null;
36
- /** The owner cell the gate judged when it opened this staging (head-read reading `headOf(id)?.owner`: `undefined` = no such session) — the commit lands only on
37
- * this same cell (`StagedCommitOptions.expectedOwner`, `session-sync-kernel.ts`); `owner` above (the re-stamped importing principal) is derived from it for a fleet caller. */
38
- expectedOwner: string | null | undefined;
37
+ /** The live occupant the gate judged when it opened this staging (owner ∧ incarnation, the head read: `undefined` = no live session) — the commit lands only on
38
+ * this same occupant (`StagedCommitOptions.expectedOccupant`, `session-sync-kernel.ts`); `owner` above (the re-stamped importing principal) is derived from its owner for a fleet caller. */
39
+ expectedOccupant: SessionFace | undefined;
39
40
  /** 开这张暂存时占下的导入租约的键({@link importLeaseKey});续期与放掉都按它,不再从会话 id 重算。 */
40
41
  leaseKey: string;
41
42
  };