@sema-agent/server 7.139.0 → 7.141.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 (328) hide show
  1. package/MIGRATION.md +471 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +94 -26
  5. package/dist/adoption/runner.js +1 -1
  6. package/dist/adoption/sql.js +1 -1
  7. package/dist/approval-batch-census.d.ts +27 -0
  8. package/dist/approval-batch-census.js +27 -0
  9. package/dist/approval-card.d.ts +37 -22
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval.d.ts +15 -0
  12. package/dist/approval.js +10 -0
  13. package/dist/audit.d.ts +4 -2
  14. package/dist/audit.js +2 -2
  15. package/dist/bench/s1/live-deps.js +3 -2
  16. package/dist/bench/s1/row.d.ts +5 -2
  17. package/dist/bench/s1/row.js +2 -22
  18. package/dist/boot/adoption.js +1 -1
  19. package/dist/boot/device-lane.d.ts +11 -0
  20. package/dist/boot/device-lane.js +16 -0
  21. package/dist/boot/engine-lease.d.ts +24 -3
  22. package/dist/boot/engine-lease.js +9 -2
  23. package/dist/boot/execution-env.js +18 -12
  24. package/dist/boot/execution-lane.d.ts +8 -0
  25. package/dist/boot/execution-lane.js +6 -1
  26. package/dist/boot/memory-boundary.d.ts +16 -53
  27. package/dist/boot/memory-boundary.js +10 -20
  28. package/dist/boot/memory-consolidation.d.ts +16 -12
  29. package/dist/boot/memory-consolidation.js +24 -13
  30. package/dist/boot/reapers.d.ts +2 -2
  31. package/dist/boot/resolve-spec.d.ts +3 -5
  32. package/dist/boot/resolve-spec.js +9 -8
  33. package/dist/boot/runner-deps.d.ts +3 -3
  34. package/dist/boot/runner-deps.js +1 -0
  35. package/dist/boot/session-faces.d.ts +1 -1
  36. package/dist/boot/session-faces.js +4 -3
  37. package/dist/boot/shutdown.js +5 -0
  38. package/dist/boot/stage-03-store-backend.d.ts +2 -6
  39. package/dist/boot/stage-03-store-backend.js +2 -3
  40. package/dist/boot/stage-04-stores.d.ts +2 -6
  41. package/dist/boot/stage-04-stores.js +5 -5
  42. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  43. package/dist/boot/stage-05-execution-env.js +3 -1
  44. package/dist/boot/stage-06-runners.d.ts +2 -8
  45. package/dist/boot/stage-06-runners.js +3 -3
  46. package/dist/boot/stage-07-capability-layer.d.ts +2 -7
  47. package/dist/boot/stage-07-capability-layer.js +8 -10
  48. package/dist/boot/stage-08-reapers.d.ts +2 -7
  49. package/dist/boot/stage-09-leader.d.ts +2 -7
  50. package/dist/boot/stage-10-http-server.d.ts +2 -7
  51. package/dist/boot/stage-10-http-server.js +6 -4
  52. package/dist/boot/stores.d.ts +3 -6
  53. package/dist/boot/stores.js +37 -44
  54. package/dist/boot/task-list-lane.d.ts +1 -1
  55. package/dist/boot/task-list-lane.js +1 -1
  56. package/dist/boot/web-admin.js +36 -9
  57. package/dist/capabilities/execution-lane.d.ts +2 -2
  58. package/dist/capabilities/scenarios.d.ts +1 -1
  59. package/dist/capability-facts.d.ts +9 -0
  60. package/dist/capability-facts.js +9 -0
  61. package/dist/card-void.d.ts +38 -0
  62. package/dist/card-void.js +23 -0
  63. package/dist/config-catalog.js +12 -8
  64. package/dist/config-provider.d.ts +18 -17
  65. package/dist/config-provider.js +28 -36
  66. package/dist/config-types.d.ts +36 -9
  67. package/dist/config.js +56 -46
  68. package/dist/delegated-session-owner.d.ts +23 -20
  69. package/dist/delegated-session-owner.js +2 -2
  70. package/dist/deployment-shape.d.ts +9 -8
  71. package/dist/deployment-shape.js +4 -4
  72. package/dist/device-store.d.ts +7 -0
  73. package/dist/device-store.js +3 -0
  74. package/dist/execution-config-root.d.ts +22 -0
  75. package/dist/execution-config-root.js +35 -0
  76. package/dist/execution-lane-caps.d.ts +145 -107
  77. package/dist/execution-lane-caps.js +144 -83
  78. package/dist/execution-placement.d.ts +3 -0
  79. package/dist/execution-placement.js +7 -0
  80. package/dist/fleet/drain-entry.d.ts +3 -0
  81. package/dist/hosted-posture.d.ts +8 -0
  82. package/dist/hosted-posture.js +4 -0
  83. package/dist/http/admission.js +3 -3
  84. package/dist/http/caller-trust.d.ts +3 -2
  85. package/dist/http/caller-trust.js +2 -1
  86. package/dist/http/dispatch.js +4 -0
  87. package/dist/http/live-frame.d.ts +24 -0
  88. package/dist/http/live-frame.js +16 -0
  89. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  90. package/dist/http/memory-compliance-failure-reply.js +5 -0
  91. package/dist/http/memory-layer-reply.d.ts +30 -0
  92. package/dist/http/memory-layer-reply.js +24 -0
  93. package/dist/http/resume-legs.js +49 -35
  94. package/dist/http/retired-service-deps.d.ts +1 -1
  95. package/dist/http/retired-service-deps.js +1 -1
  96. package/dist/http/route-table.js +2 -0
  97. package/dist/http/routes/adoption.js +1 -1
  98. package/dist/http/routes/approvals-assistant.js +1 -0
  99. package/dist/http/routes/capabilities.js +5 -4
  100. package/dist/http/routes/diagnostics.d.ts +0 -19
  101. package/dist/http/routes/diagnostics.js +16 -2
  102. package/dist/http/routes/memory-bundle.d.ts +2 -2
  103. package/dist/http/routes/memory-bundle.js +4 -1
  104. package/dist/http/routes/memory-compliance.js +9 -2
  105. package/dist/http/routes/memory-consolidation.js +12 -1
  106. package/dist/http/routes/memory-origin.js +14 -2
  107. package/dist/http/routes/memory-policy.js +7 -2
  108. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  109. package/dist/http/routes/run-permission-mode.js +170 -0
  110. package/dist/http/routes/session-sync.d.ts +3 -6
  111. package/dist/http/routes/session-sync.js +7 -2
  112. package/dist/http/routes/sessions.js +33 -15
  113. package/dist/http/routes/tasks.js +67 -68
  114. package/dist/http/run-read-face.js +3 -3
  115. package/dist/http/send.d.ts +1 -1
  116. package/dist/http/send.js +2 -2
  117. package/dist/http/server.d.ts +14 -27
  118. package/dist/http/server.js +2 -2
  119. package/dist/http/sse-log.d.ts +3 -8
  120. package/dist/http/stored-body-replay.d.ts +3 -3
  121. package/dist/http/stored-body-replay.js +3 -1
  122. package/dist/identity-source.d.ts +1 -1
  123. package/dist/identity-source.js +1 -1
  124. package/dist/instruction-source-notice.d.ts +7 -0
  125. package/dist/instruction-source-notice.js +6 -6
  126. package/dist/lane-home-probe.d.ts +68 -42
  127. package/dist/lane-home-probe.js +53 -30
  128. package/dist/leader/wire.js +5 -4
  129. package/dist/mcp-assertion-header.d.ts +4 -0
  130. package/dist/mcp-assertion-header.js +5 -0
  131. package/dist/memory-bundle-engine.d.ts +16 -9
  132. package/dist/memory-bundle-engine.js +29 -6
  133. package/dist/memory-operator-faces.d.ts +85 -30
  134. package/dist/memory-operator-faces.js +136 -30
  135. package/dist/memory-posture.d.ts +1 -1
  136. package/dist/memory-project-layer.d.ts +3 -21
  137. package/dist/memory-project-layer.js +11 -8
  138. package/dist/memory-scope-stores.d.ts +73 -0
  139. package/dist/memory-scope-stores.js +86 -0
  140. package/dist/memory-scope.d.ts +33 -9
  141. package/dist/memory-scope.js +17 -5
  142. package/dist/memory-sync-client.d.ts +34 -9
  143. package/dist/memory-sync-client.js +108 -28
  144. package/dist/observability/fail-open.d.ts +30 -2
  145. package/dist/observability/fail-open.js +30 -2
  146. package/dist/observability/metrics.js +0 -1
  147. package/dist/observability/run-terminal.d.ts +6 -2
  148. package/dist/observability/run-terminal.js +25 -26
  149. package/dist/observability/secret-env-scrub.d.ts +20 -1
  150. package/dist/observability/secret-env-scrub.js +14 -0
  151. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  152. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  153. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  154. package/dist/orchestration/workflow-notify-journal.js +2 -1
  155. package/dist/permission-mode-admission.d.ts +68 -0
  156. package/dist/permission-mode-admission.js +23 -0
  157. package/dist/permission-rule-list.d.ts +55 -6
  158. package/dist/permission-rule-list.js +52 -5
  159. package/dist/plugins/adoption-log-sql.js +4 -4
  160. package/dist/plugins/approval-ask-store-sql.js +14 -14
  161. package/dist/plugins/approval-nonce-store.js +4 -4
  162. package/dist/plugins/blob-backend.d.ts +4 -4
  163. package/dist/plugins/blob-backend.js +7 -7
  164. package/dist/plugins/breaker-state-sql.js +2 -2
  165. package/dist/plugins/caching-session-store.d.ts +6 -0
  166. package/dist/plugins/caching-session-store.js +3 -0
  167. package/dist/plugins/checkpoint-index-columns.js +2 -2
  168. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  169. package/dist/plugins/checkpoint-store-sql.js +90 -19
  170. package/dist/plugins/device-store-sql.d.ts +5 -2
  171. package/dist/plugins/device-store-sql.js +23 -20
  172. package/dist/plugins/ensure-index.d.ts +1 -2
  173. package/dist/plugins/execution-placement-sql.d.ts +19 -9
  174. package/dist/plugins/execution-placement-sql.js +20 -9
  175. package/dist/plugins/file-history-store-sql.d.ts +2 -2
  176. package/dist/plugins/file-history-store-sql.js +4 -4
  177. package/dist/plugins/file-run-store.d.ts +2 -2
  178. package/dist/plugins/file-run-store.js +1 -1
  179. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  180. package/dist/plugins/fork-routing-session-store.js +2 -61
  181. package/dist/plugins/image-bake-store-sql.js +9 -9
  182. package/dist/plugins/image-index-sql.js +11 -11
  183. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  184. package/dist/plugins/k8s-exec-protocol.js +6 -1
  185. package/dist/plugins/leader-run-store-sql.js +4 -4
  186. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  187. package/dist/plugins/local-checkpoint-store.js +22 -0
  188. package/dist/plugins/local-session-store.d.ts +227 -35
  189. package/dist/plugins/local-session-store.js +518 -37
  190. package/dist/plugins/local-session-window.d.ts +17 -3
  191. package/dist/plugins/local-session-window.js +7 -13
  192. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  193. package/dist/plugins/mailbox-store-sql.js +4 -4
  194. package/dist/plugins/memory-engine-tidb.js +5 -5
  195. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  196. package/dist/plugins/memory-run-store.d.ts +2 -2
  197. package/dist/plugins/memory-run-store.js +1 -1
  198. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  199. package/dist/plugins/one-time-migrations.d.ts +52 -11
  200. package/dist/plugins/one-time-migrations.js +101 -39
  201. package/dist/plugins/org-delivery-quota-store.js +2 -2
  202. package/dist/plugins/outcome-ledger-sql.js +2 -2
  203. package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
  204. package/dist/plugins/permission-rule-store-sql.js +16 -16
  205. package/dist/plugins/pg-pool.js +3 -2
  206. package/dist/plugins/pg-session-storage.d.ts +2 -0
  207. package/dist/plugins/pg-session-storage.js +3 -2
  208. package/dist/plugins/remote-env-adb.d.ts +7 -24
  209. package/dist/plugins/remote-env-adb.js +22 -5
  210. package/dist/plugins/remote-env-device.d.ts +2 -28
  211. package/dist/plugins/remote-env-device.js +2 -1
  212. package/dist/plugins/remote-env-e2b.d.ts +40 -27
  213. package/dist/plugins/remote-env-e2b.js +180 -20
  214. package/dist/plugins/remote-env-file-error.d.ts +1 -1
  215. package/dist/plugins/remote-env-file-error.js +8 -2
  216. package/dist/plugins/remote-env-host.d.ts +19 -35
  217. package/dist/plugins/remote-env-host.js +26 -3
  218. package/dist/plugins/remote-env-k8s.d.ts +22 -14
  219. package/dist/plugins/remote-env-k8s.js +150 -26
  220. package/dist/plugins/remote-env-local-docker.d.ts +27 -24
  221. package/dist/plugins/remote-env-local-docker.js +91 -24
  222. package/dist/plugins/remote-env-ssh.d.ts +4 -24
  223. package/dist/plugins/remote-env-ssh.js +4 -3
  224. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  225. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  226. package/dist/plugins/retention-lane-store-sql.js +2 -2
  227. package/dist/plugins/retention-store-sql.d.ts +1 -1
  228. package/dist/plugins/retention-store-sql.js +6 -6
  229. package/dist/plugins/run-store-sql.d.ts +3 -3
  230. package/dist/plugins/run-store-sql.js +19 -18
  231. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  232. package/dist/plugins/sandbox-read-floor.js +608 -0
  233. package/dist/plugins/schema-consistency.d.ts +6 -6
  234. package/dist/plugins/schema-consistency.js +1 -1
  235. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  236. package/dist/plugins/schema-ensure-legs.js +2 -2
  237. package/dist/plugins/schema-live-columns.d.ts +1 -1
  238. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  239. package/dist/plugins/session-ownership.d.ts +2 -1
  240. package/dist/plugins/session-placement.d.ts +17 -4
  241. package/dist/plugins/session-placement.js +2 -1
  242. package/dist/plugins/session-rules-column.d.ts +1 -1
  243. package/dist/plugins/session-rules-column.js +1 -1
  244. package/dist/plugins/session-store.d.ts +18 -20
  245. package/dist/plugins/session-store.js +4 -12
  246. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  247. package/dist/plugins/session-workspace-cell.js +1 -1
  248. package/dist/plugins/shared-memory-store-sql.js +2 -2
  249. package/dist/plugins/sql-driver.d.ts +10 -5
  250. package/dist/plugins/sql-driver.js +6 -6
  251. package/dist/plugins/sql-errors.js +1 -1
  252. package/dist/plugins/store-backend.d.ts +35 -6
  253. package/dist/plugins/store-backend.js +27 -10
  254. package/dist/plugins/store-contracts.d.ts +9 -2
  255. package/dist/plugins/task-list-store-sql.js +11 -11
  256. package/dist/plugins/tidb-pool.js +7 -6
  257. package/dist/plugins/tidb-session-storage.js +4 -4
  258. package/dist/plugins/tidb-session-store.d.ts +4 -1
  259. package/dist/plugins/tidb-session-store.js +16 -15
  260. package/dist/plugins/tool-result-store-sql.d.ts +2 -2
  261. package/dist/plugins/tool-result-store-sql.js +3 -3
  262. package/dist/plugins/user-input-ledger-sql.js +2 -2
  263. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  264. package/dist/plugins/workflow-run-store-sql.js +19 -19
  265. package/dist/plugins/write-behind-counter.d.ts +2 -3
  266. package/dist/plugins/write-behind-counter.js +5 -5
  267. package/dist/resource-suspend.d.ts +14 -12
  268. package/dist/resource-suspend.js +1 -1
  269. package/dist/resource-window.d.ts +4 -3
  270. package/dist/run-cancel-context.d.ts +34 -0
  271. package/dist/run-cancel-context.js +7 -0
  272. package/dist/run-local.js +14 -19
  273. package/dist/runs.d.ts +47 -16
  274. package/dist/runs.js +112 -113
  275. package/dist/security.d.ts +2 -2
  276. package/dist/server-secret-env.d.ts +2 -1
  277. package/dist/server-secret-env.js +2 -4
  278. package/dist/session-error-wire.d.ts +22 -4
  279. package/dist/session-error-wire.js +16 -0
  280. package/dist/session-incarnation.d.ts +10 -4
  281. package/dist/session-incarnation.js +1 -1
  282. package/dist/session-policy-wire.d.ts +1 -1
  283. package/dist/session-sync-kernel.d.ts +42 -1
  284. package/dist/session-sync-kernel.js +17 -0
  285. package/dist/session-sync.js +16 -4
  286. package/dist/sql-engine-coordinates.d.ts +49 -0
  287. package/dist/sql-engine-coordinates.js +27 -0
  288. package/dist/sql-engine-posture.d.ts +1 -1
  289. package/dist/sql-engine-posture.js +1 -1
  290. package/dist/standing-answer-grant.d.ts +31 -2
  291. package/dist/standing-answer-grant.js +14 -2
  292. package/dist/store-posture.d.ts +1 -1
  293. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  294. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  295. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  296. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  297. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  298. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  299. package/dist/task-a2a.d.ts +1 -1
  300. package/dist/task-a2a.js +5 -6
  301. package/dist/task-mcp.d.ts +21 -8
  302. package/dist/task-mcp.js +17 -9
  303. package/dist/task-settings.d.ts +52 -25
  304. package/dist/task-settings.js +91 -28
  305. package/dist/terminal.d.ts +103 -19
  306. package/dist/terminal.js +72 -20
  307. package/dist/trace/core-keyset-guard.d.ts +79 -2
  308. package/dist/trace/exit-seat.d.ts +7 -0
  309. package/dist/trace/exit-seat.js +1 -0
  310. package/dist/trace/model-segment.d.ts +2 -2
  311. package/dist/trace/redact.d.ts +3 -1
  312. package/dist/trace/redact.js +395 -110
  313. package/dist/trace/self-minted-wire.d.ts +226 -2
  314. package/dist/trace/self-minted-wire.js +79 -0
  315. package/dist/verifier-pause-card.d.ts +8 -0
  316. package/dist/verifier-pause-card.js +19 -0
  317. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  318. package/dist/web-admin-client/apply-effective.js +2 -2
  319. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  320. package/dist/web-admin-client/candidate-gate.js +1 -1
  321. package/dist/web-admin-client/http-client.d.ts +5 -4
  322. package/dist/web-admin-client/http-client.js +2 -2
  323. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  324. package/dist/web-admin-client/read-warnings.js +5 -9
  325. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  326. package/package.json +2 -2
  327. package/wire-changes/server@7.140.0.json +184 -0
  328. package/wire-changes/server@7.141.0.json +367 -0
@@ -0,0 +1,75 @@
1
+ /**
2
+ * 在飞 run 的**权限模式切档口** —— 引擎动词 `TaskStream.setPermissionMode(word)` 的 wire 面:
3
+ *
4
+ * `POST /v1/runs/:id/permission-mode` body `{ mode }`(`mode` ∈ 引擎 `PERMISSION_MODES` 闭集)
5
+ * → 200 `{ taskId, sessionId, status: "running", permissionMode, admission?, persisted, persistMiss? }`
6
+ *
7
+ * 语义 = 引擎的切档:改写这条腿的请求词,部署的折叠(`RuntimeCaps` 拒位 / 分类器席缺席)由引擎重判,**下一次**工具调用按新档仲裁;
8
+ * 进 `auto` 当场武装分类器,武装失败 ⇒ 引擎回滚(被替换的词照旧)并拒。`permissionMode` 是引擎回执原样(`{ requested?, effective, foldedBy? }`,
9
+ * 与 `wiring_manifest.permissionMode` 同形):每次都回,动没动都回 —— 壳渲染引擎说的,不渲染它自己发的。
10
+ *
11
+ * ── 一条规则:切档口与提交口是同一只受理判官(`permission-mode-admission.ts`)───────────────────────────────
12
+ * 交给引擎的不是调用方的原词,是受理判官对它的答:本任务 settings 的 kill-switch 折叠(读这条 run 的**存储体**)→ 运维 `AUTONOMY` 入口地板钳
13
+ * → 托管写围栏拒。提交口在受理时拒过 / 折过的词,切档口同样拒 / 折(两口一张表的钉在 `test/run-permission-mode-admission.test.ts`);
14
+ * 引擎在根腿上没有这三问的拒位,所以它们必须在这里答。折了 ⇒ 回执旁带 `admission: { requested, admitted, foldedBy }`(present-iff:本判官改了词);
15
+ * 拒 ⇒ 422 `request.host_posture_refused`(与提交口同码同句)。
16
+ *
17
+ * ── 回写:准入的词写回存储体(run 模式词的唯一来源)────────────────────────────────────────────────────────
18
+ * 续跑腿(停驻后续跑 / 重启后续跑)按存储体顶层 `permissionMode` 重建 spec;不写回 = 下一腿回到切档前的词。写回走**认领栅栏**
19
+ * (checkpoint 店的 `rewriteCtxWhileClaimed`:只在**接下切档的那条腿**此刻仍持有这条 run 时写,只改既有行)—— 这条 run 终局放掉认领、同会话新 run
20
+ * 已写下它自己的存储体之后,或这条 run 停驻、续跑的新腿(同一个 taskId 回到 running、认领仍在)已写下它续跑用的体之后,迟到的回写零写,
21
+ * 不会拿旧腿的体盖掉新腿的。腿身份 = {@link switchLegHolds}(栅栏里问)。同一条腿上的切档在本进程按活流对象串行(引擎受理序 = 落盘序;活流只在本副本)。
22
+ * 写不进(`persisted:false` + `persistMiss` ∈ {@link PERMISSION_MODE_PERSIST_MISSES})⇒ 本腿的切档照样生效、下一腿读到的是切档前的词:
23
+ * 一行运维 warn + fail-open 计数(`server.run-permission-mode.write-back-unpersisted`),壳据 `persisted` 判要不要在续跑后重切。
24
+ *
25
+ * ── 门(与 steer 同一组)──────────────────────────────────────────────────────────────────────────────
26
+ * 零模型工作(不进 billable);mutating ⇒ `rateLimited`。属主门与 steer / interrupt 同一组放行臂(行经属主存取器 `readOwnedRun` 取,`owner_or_operator`):
27
+ * 显式 operator 可切任一租户的 run(监督动作),普通调用方只切自己的(非属主 404 同串,不给存在性 oracle);`?session=` 的受理期断言同族。非 live 四臂一码 409 `steering.not_running`
28
+ * (停驻 / 终局 / 他副本 / 本副本无活流);坏词 422 `steering.invalid_content`(引擎同码,受理前判)。能力位 `permissionModeWrite`
29
+ * = 事实 `permissionModeWrite`(持久 run 账面 ∧ checkpoint 店),与两道 501 同一只。
30
+ */
31
+ import type { IncomingMessage, ServerResponse } from "node:http";
32
+ import type { PermissionMode, PermissionModeReceipt } from "@sema-agent/core";
33
+ import { type FencedRun } from "../../plugins/store-contracts.js";
34
+ import { type RouteCtx, type RouteMatch, type RouteIdsOf } from "../route-ctx.js";
35
+ import { type PermissionModeAdmissionFold } from "../../permission-mode-admission.js";
36
+ /** 回写没落定的成因(闭集;wire `persistMiss`):`claim_released` 接下切档的那条腿这一刻已不持有这条 run(终局 / 停驻 / 已由续跑的新腿接手);
37
+ * `context_absent` 持有但会话没有存储体行;`store_failed` 店写抛(库抖 / 一行装不下 / 封签失败)。 */
38
+ export declare const PERMISSION_MODE_PERSIST_MISSES: readonly ["claim_released", "context_absent", "store_failed"];
39
+ export type PermissionModePersistMiss = (typeof PERMISSION_MODE_PERSIST_MISSES)[number];
40
+ /**
41
+ * **接下切档的那条腿此刻是否仍持有这条 run** —— 回写的腿身份,在认领栅栏里问(`rewriteCtxWhileClaimed` 的 `legHolds`,锁住 / 同步段里那一行的 {@link FencedRun})。
42
+ * 认领与 `running` 只认 taskId,续跑腿让同一个 taskId 停驻之后回到 `running`、认领仍在(ABA);分开两条腿的是:
43
+ * · run 行仍在本副本(`runProvenElsewhere` 的反面 —— 别的副本续跑了它,续跑的 CAS 赢点把行上的副本改成它自己);
44
+ * · 本副本这条 run 此刻登记的活流仍是接下切档的那一只(本副本上续跑的新腿登记的是另一只流对象;腿收场时按对象身份摘掉自己的)。
45
+ */
46
+ export declare function switchLegHolds<S extends object>(fenced: FencedRun, thisInstanceId: string | undefined, liveNow: S | undefined, switched: S): boolean;
47
+ /** 本判官改了词时回执旁的那一格(`admission`)。 */
48
+ interface PermissionModeAdmissionNote {
49
+ readonly requested: PermissionMode;
50
+ readonly admitted: PermissionMode;
51
+ readonly foldedBy: PermissionModeAdmissionFold;
52
+ }
53
+ /** 200 体(键集属主:`trace/self-minted-wire.ts` 的 `permissionModeVerbBodyKey` 按它的键 `satisfies`)。`permissionMode` = 引擎回执原样。 */
54
+ export interface PermissionModeVerbReply {
55
+ readonly taskId: string;
56
+ readonly sessionId: string;
57
+ readonly status: "running";
58
+ readonly permissionMode: PermissionModeReceipt;
59
+ /** present-iff 受理判官改了词。 */
60
+ readonly admission?: PermissionModeAdmissionNote;
61
+ readonly persisted: boolean;
62
+ /** present-iff `persisted: false`。 */
63
+ readonly persistMiss?: PermissionModePersistMiss;
64
+ }
65
+ export declare function handlePermissionMode(req: IncomingMessage, res: ServerResponse, match: RouteMatch<RunPermissionModeRouteId>, ctx: RouteCtx): Promise<void>;
66
+ export declare const RUN_PERMISSION_MODE_ROUTES: readonly [{
67
+ readonly id: "run-permission-mode";
68
+ readonly pattern: RegExp;
69
+ readonly label: "/v1/runs/:id/permission-mode";
70
+ readonly methods: readonly ["POST"];
71
+ }];
72
+ /** 本域可分派行的 `id` 闭集 —— handler 的 `switch` 按它判穷尽(漏一口 = 编译红)。 */
73
+ export type RunPermissionModeRouteId = RouteIdsOf<typeof RUN_PERMISSION_MODE_ROUTES>;
74
+ export {};
75
+ //# sourceMappingURL=run-permission-mode.d.ts.map
@@ -0,0 +1,170 @@
1
+ import { z } from "zod";
2
+ import { isParkedRunStatus, isTerminalRunStatus, runProvenElsewhere } from "../../plugins/store-contracts.js";
3
+ import { sendJson, sendError } from "../send.js";
4
+ import { gatedPrincipal, sendPrincipalRefusal } from "../principal-gate.js";
5
+ import { readOwnedRun } from "../owner-access.js";
6
+ import { runNotFoundMessage } from "../route-ctx.js";
7
+ import { assertNever } from "../route-ctx.js";
8
+ import * as facts from "../../capability-facts.js";
9
+ import { PERMISSION_MODE_WORDS, coercePermissionModeStrict, parseTaskSettings } from "../../task-settings.js";
10
+ import { admitPermissionModeWord } from "../../permission-mode-admission.js";
11
+ import { readResumeCtx, resumeCtxCorruptAnswer, resumeCtxUnsealableAnswer } from "../resume-ctx-read.js";
12
+ import { sealCheckpointCtx, withPermissionModeRewritten } from "../checkpoint-ctx.js";
13
+ import { serviceVersion } from "../../service-version.js";
14
+ import { recordFailOpen } from "../../observability/fail-open.js";
15
+ export const PERMISSION_MODE_PERSIST_MISSES = ["claim_released", "context_absent", "store_failed"];
16
+ const BodySchema = z.object({ mode: z.string() }).strict();
17
+ const switchChains = new WeakMap();
18
+ function serialOnStream(stream, work) {
19
+ const prior = switchChains.get(stream) ?? Promise.resolve();
20
+ const job = prior.then(work, work);
21
+ switchChains.set(stream, job.catch(() => undefined));
22
+ return job;
23
+ }
24
+ export function switchLegHolds(fenced, thisInstanceId, liveNow, switched) {
25
+ return !runProvenElsewhere(fenced, thisInstanceId) && liveNow === switched;
26
+ }
27
+ export async function handlePermissionMode(req, res, match, ctx) {
28
+ const { deps } = ctx;
29
+ switch (match.decl.id) {
30
+ case "run-permission-mode": {
31
+ if (ctx.helpers.rateLimited(req, res))
32
+ return;
33
+ const seat = facts.permissionModeWrite(deps);
34
+ if (!seat.available) {
35
+ switch (seat.why) {
36
+ case "run_store_absent":
37
+ sendError(res, 501, "capability.run_store_required", "changing a running run's permission mode requires a durable run store (DB_BACKEND=mysql|pg|local)");
38
+ return;
39
+ case "checkpoint_store_absent":
40
+ sendError(res, 501, "capability.checkpoint_store_required", "changing a running run's permission mode requires the checkpoint store (DURABLE_APPROVAL): the run's stored request is where its permission mode lives and where the task's own settings are read");
41
+ return;
42
+ default:
43
+ return assertNever(seat.why);
44
+ }
45
+ }
46
+ const { runStore, checkpointStore } = seat;
47
+ const principal = gatedPrincipal(req, deps.config);
48
+ if (deps.config.requirePrincipal && principal === undefined) {
49
+ sendPrincipalRefusal(res, req, deps.config);
50
+ return;
51
+ }
52
+ const taskId = match.params.taskId;
53
+ const parsed = BodySchema.safeParse((await ctx.helpers.readJson(req)) ?? {});
54
+ if (!parsed.success) {
55
+ sendError(res, 400, "request.body_shape", `body must be { mode: ${PERMISSION_MODE_WORDS} } — the permission-mode verb takes no other keys`);
56
+ return;
57
+ }
58
+ const word = coercePermissionModeStrict(parsed.data.mode);
59
+ if (word === undefined) {
60
+ sendError(res, 422, "steering.invalid_content", `mode must be one of ${PERMISSION_MODE_WORDS} (got ${JSON.stringify(parsed.data.mode.slice(0, 64))})`);
61
+ return;
62
+ }
63
+ const run = await readOwnedRun(deps.config, runStore, principal, taskId, "owner_or_operator");
64
+ if (!run) {
65
+ sendError(res, 404, "not_found.run", runNotFoundMessage(taskId));
66
+ return;
67
+ }
68
+ if (!ctx.helpers.runSessionAcceptOk(req, res, run, "run.permission_mode"))
69
+ return;
70
+ const live = ctx.registry.steerableRuns.get(taskId);
71
+ if (!live) {
72
+ if (isParkedRunStatus(run.status)) {
73
+ sendError(res, 409, "steering.not_running", "run is waiting on a pending decision — the permission-mode verb changes a running run; when it resumes, its mode is rebuilt from the run's stored request (a plan review's approval names the mode it continues in)");
74
+ return;
75
+ }
76
+ if (isTerminalRunStatus(run.status)) {
77
+ sendError(res, 409, "steering.not_running", "run already ended — the permission-mode verb changes a running run; submit the next turn of this session with the permissionMode you want");
78
+ return;
79
+ }
80
+ if (runProvenElsewhere(run, deps.instanceId)) {
81
+ sendError(res, 409, "steering.not_running", "run is live on another replica — the permission-mode verb must reach the replica holding the stream; resend it there");
82
+ return;
83
+ }
84
+ sendError(res, 409, "steering.not_running", "no live steerable face for this run on this replica — a verify or cascade submission exposes none, so the permission-mode verb has nothing to switch");
85
+ return;
86
+ }
87
+ const sessionId = run.sessionId;
88
+ const out = await serialOnStream(live, async () => {
89
+ const read = await readResumeCtx(checkpointStore.getCtx(sessionId), { sessionId, opener: deps.checkpointCtxSealing });
90
+ if (read.kind === "corrupt" || read.kind === "unsealable") {
91
+ const { status, body: { error, errorCode, ...facts } } = read.kind === "corrupt" ? resumeCtxCorruptAnswer(read.where) : resumeCtxUnsealableAnswer(read);
92
+ sendError(res, status, errorCode, error, facts);
93
+ return { kind: "answered" };
94
+ }
95
+ if (read.kind === "absent") {
96
+ sendError(res, 409, "conflict.resume_context_unavailable", "this run's stored request is missing — its permission mode cannot be changed (the task's settings are read from it and the new mode is written back to it)");
97
+ return { kind: "answered" };
98
+ }
99
+ const stored = read.ctx;
100
+ const admission = admitPermissionModeWord(word, { deployment: deps.config, settings: parseTaskSettings(stored.body.settings).settings });
101
+ if (admission.kind === "refused") {
102
+ sendError(res, admission.status, admission.code, admission.message);
103
+ return { kind: "answered" };
104
+ }
105
+ const admitted = admission.admitted ?? word;
106
+ let receipt;
107
+ try {
108
+ receipt = await live.setPermissionMode(admitted);
109
+ }
110
+ catch (e) {
111
+ const code = e.code;
112
+ if (code === "steering.invalid_content") {
113
+ sendError(res, 422, "steering.invalid_content", e instanceof Error ? e.message : String(e));
114
+ return { kind: "answered" };
115
+ }
116
+ if (code === "steering.not_running") {
117
+ sendError(res, 409, "steering.not_running", "run just finished — the permission-mode verb needs a running run; submit the next turn of this session with the permissionMode you want");
118
+ return { kind: "answered" };
119
+ }
120
+ throw e;
121
+ }
122
+ let persistMiss;
123
+ try {
124
+ const next = sealCheckpointCtx({ ...withPermissionModeRewritten(stored, { ...stored.body, permissionMode: admitted }), writerVersion: serviceVersion() }, deps.checkpointCtxSealing, sessionId);
125
+ const rewrite = await checkpointStore.rewriteCtxWhileClaimed(sessionId, taskId, next, (fenced) => switchLegHolds(fenced, deps.instanceId, ctx.registry.steerableRuns.get(taskId), live));
126
+ persistMiss = rewrite === "written" ? undefined : rewrite;
127
+ }
128
+ catch (e) {
129
+ persistMiss = "store_failed";
130
+ deps.logger?.warn?.("run_permission_mode_write_back_failed", { taskId, sessionId, err: e instanceof Error ? e.message : String(e) });
131
+ }
132
+ if (persistMiss !== undefined) {
133
+ recordFailOpen("server.run-permission-mode.write-back-unpersisted", `miss=${persistMiss} mode=${admitted}`);
134
+ deps.logger?.warn?.("run_permission_mode_unpersisted", { taskId, sessionId, mode: admitted, miss: persistMiss });
135
+ }
136
+ return {
137
+ kind: "switched",
138
+ receipt,
139
+ ...(admission.foldedBy !== undefined ? { admission: { requested: word, admitted, foldedBy: admission.foldedBy } } : {}),
140
+ ...(persistMiss !== undefined ? { persistMiss } : {}),
141
+ };
142
+ });
143
+ switch (out.kind) {
144
+ case "answered":
145
+ return;
146
+ case "switched": {
147
+ const reply = {
148
+ taskId,
149
+ sessionId,
150
+ status: "running",
151
+ permissionMode: out.receipt,
152
+ ...(out.admission !== undefined ? { admission: out.admission } : {}),
153
+ persisted: out.persistMiss === undefined,
154
+ ...(out.persistMiss !== undefined ? { persistMiss: out.persistMiss } : {}),
155
+ };
156
+ sendJson(res, 200, reply);
157
+ return;
158
+ }
159
+ default:
160
+ return assertNever(out);
161
+ }
162
+ }
163
+ default:
164
+ return assertNever(match.decl.id);
165
+ }
166
+ }
167
+ export const RUN_PERMISSION_MODE_ROUTES = [
168
+ { id: "run-permission-mode", pattern: /^\/v1\/runs\/(?<taskId>[^/]+)\/permission-mode$/, label: "/v1/runs/:id/permission-mode", methods: ["POST"] },
169
+ ];
170
+ //# sourceMappingURL=run-permission-mode.js.map
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import type { IncomingMessage, ServerResponse } from "node:http";
11
11
  import { type SessionRulesRecord, type FileHistoryExport } from "@sema-agent/core";
12
- import { type StagingHandle } from "../../session-sync-kernel.js";
12
+ import { type ImportAnchor, type StagingHandle } from "../../session-sync-kernel.js";
13
13
  import type { RouteCtx, RouteMatch, RouteIdsOf } from "../route-ctx.js";
14
14
  import { type SessionRowPatch } from "../../session-row.js";
15
15
  export type PendingStaging = {
@@ -21,11 +21,8 @@ export type PendingStaging = {
21
21
  /** [ref]: the session's file-history envelope (or null — no history travelled). Replaces the E19 per-key manifests. */
22
22
  fileHistory: FileHistoryExport | null;
23
23
  policy: SessionRulesRecord[];
24
- anchors: Array<{
25
- eventId: string;
26
- entryId: string;
27
- owner: string | null;
28
- }>;
24
+ /** 已在 Phase A 入口判过的锚(`readImportAnchors`:每条 `entryId` 都在 `declaredEntryIds` 里)—— 提交后的重放腿只迭代它,不再判;owner 不带(重钉到 `owner`)。 */
25
+ anchors: ImportAnchor[];
29
26
  /** 会话行的两位可写位(Phase A 体里带了哪几键就重放哪几键;老客户端不带 ⇒ `{}`,目的端两位不动)。提交后经会话店唯一写口落。 */
30
27
  row: SessionRowPatch;
31
28
  /** 交叉复查 A-3(HIGH):Phase A's DECLARED entry-id set — Phase B verifies the streamed NDJSON matches it
@@ -4,7 +4,7 @@ import { sessionLogDigest, sessionLogDigestsComparable, StreamingImportValidator
4
4
  import { StringDecoder } from "node:string_decoder";
5
5
  import { FileHistoryUnreadableError, blobGetterOf, exportSessionManifest, exportedFileHistory, fileHistoryImportFace, replayFileHistory, replayPolicyRecords, wipeSessionAttendants } from "../../session-sync.js";
6
6
  import * as facts from "../../capability-facts.js";
7
- import { classifySyncRelationshipByIds, SyncConflictError } from "../../session-sync-kernel.js";
7
+ import { classifySyncRelationshipByIds, readImportAnchors, SyncConflictError } from "../../session-sync-kernel.js";
8
8
  import { isUuidShape, legacyUnownedSessionOk, sessionOwnerCellOf } from "../../security.js";
9
9
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, logRequestError, msg } from "../send.js";
10
10
  import { buildDisplaySafeUrl } from "../../trace/redact.js";
@@ -307,6 +307,11 @@ export async function handleSessionSync(req, res, match, ctx) {
307
307
  sendError(res, 400, "request.body_shape", "malformed import metadata");
308
308
  return;
309
309
  }
310
+ const anchorsRead = readImportAnchors(body.anchors, body.entryIds);
311
+ if ("problem" in anchorsRead) {
312
+ sendError(res, 400, "request.field_invalid", anchorsRead.problem);
313
+ return;
314
+ }
310
315
  const policyRead = readSessionRulesRecords(body.policy);
311
316
  if (policyRead.problem !== undefined) {
312
317
  if (policyRead.problem.kind === "rules") {
@@ -350,7 +355,7 @@ export async function handleSessionSync(req, res, match, ctx) {
350
355
  const srcLogDigest = typeof body.logDigest === "string" && body.logDigest.length > 0 ? body.logDigest : undefined;
351
356
  const entryIds = body.entryIds;
352
357
  const policy = policyRead.records;
353
- const anchors = body.anchors;
358
+ const anchors = anchorsRead.anchors;
354
359
  const dstIds = (await sessionStorage.listEntryIds?.(sessionId)) ?? null;
355
360
  const relA = classifySyncRelationshipByIds(entryIds, dstIds);
356
361
  if ((relA.relation === "fork" || relA.relation === "stale") && resolution !== "overwrite-dst") {
@@ -7,6 +7,8 @@ import { seatText } from "../../trace/exit-seat.js";
7
7
  import { recordFailOpen } from "../../observability/fail-open.js";
8
8
  import { MCP_DIAL_DEADLINE_MS, raceDeadline } from "../mcp-dial-deadline.js";
9
9
  import { sentMemoryComplianceFailure } from "../memory-compliance-failure-reply.js";
10
+ import { readLayerSeat, sentMemoryLayerRefusal } from "../memory-layer-reply.js";
11
+ import { foldPlaneStatuses } from "../../memory-operator-faces.js";
10
12
  import { MAX_SETTINGS_OUTPUT_STYLE_CHARS, MAX_SETTINGS_ENV_VARS, MAX_SETTINGS_ENV_KEY_CHARS, MAX_SETTINGS_ENV_VALUE_CHARS } from "../../task-settings.js";
11
13
  import { readWritableSessionPermissionRules } from "../../session-policy-wire.js";
12
14
  import { sendJson, sendError, sendSessionError, sendSessionPolicyError, sseHeaders, httpErrorBody } from "../send.js";
@@ -524,14 +526,14 @@ export async function handleSessions(req, res, match, ctx) {
524
526
  }
525
527
  const auditFact = facts.sessionAuditView(deps);
526
528
  if (!auditFact.available) {
527
- sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/tidb memory backends do not (their control plane would land on a replica's local disk)");
529
+ sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
528
530
  return;
529
531
  }
530
532
  if ((await readOwnedSessionView(auditFact.sessionAudit, sessionId)) === undefined)
531
533
  return;
532
534
  const faces = deps.memoryCompliance;
533
535
  if (faces === undefined) {
534
- sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/tidb memory backends do not (their control plane would land on a replica's local disk)");
536
+ sendError(res, 501, "capability.memory_engine_required", "memory provenance and erasure are not available on this deployment — they require a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
535
537
  return;
536
538
  }
537
539
  const raw = (await readJson(req));
@@ -551,11 +553,17 @@ export async function handleSessions(req, res, match, ctx) {
551
553
  sendError(res, 400, "request.field_invalid", "allowUnevidenced must be a boolean when present");
552
554
  return;
553
555
  }
556
+ const at = readLayerSeat(res, Object.prototype.hasOwnProperty.call(raw, "scope"), raw.scope);
557
+ if (at === null)
558
+ return;
559
+ const withheld = (coreMessage) => deps.logger?.warn?.("session_memory_erase_refused", { sessionId, err: coreMessage });
554
560
  try {
555
- sendJson(res, 200, await faces.erase({ requestId: raw.requestId, select: { sessionId }, ...(raw.allowUnevidenced !== undefined ? { allowUnevidenced: raw.allowUnevidenced } : {}) }));
561
+ const input = { requestId: raw.requestId, select: { sessionId }, ...(raw.allowUnevidenced !== undefined ? { allowUnevidenced: raw.allowUnevidenced } : {}) };
562
+ sendJson(res, 200, await (at !== undefined ? faces.erase(input, at) : faces.erase(input)));
556
563
  }
557
564
  catch (err) {
558
- const withheld = (coreMessage) => deps.logger?.warn?.("session_memory_erase_refused", { sessionId, err: coreMessage });
565
+ if (sentMemoryLayerRefusal(res, err, { kind: "session_owner", withheld }))
566
+ return;
559
567
  if (sentMemoryComplianceFailure(res, err, { kind: "session_owner", withheld }, deps.logger))
560
568
  return;
561
569
  deps.logger?.warn?.("session_memory_erase_failed", { sessionId, err: String(err) });
@@ -577,33 +585,41 @@ export async function handleSessions(req, res, match, ctx) {
577
585
  }
578
586
  const auditFact = facts.sessionAuditView(deps);
579
587
  if (!auditFact.available) {
580
- sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/tidb memory backends do not (their control plane would land on a replica's local disk)");
588
+ sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
581
589
  return;
582
590
  }
583
591
  if ((await readOwnedSessionView(auditFact.sessionAudit, sessionId)) === undefined)
584
592
  return;
585
593
  const face = deps.sessionMemoryStatus;
586
594
  if (face === undefined) {
587
- sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/tidb memory backends do not (their control plane would land on a replica's local disk)");
595
+ sendError(res, 501, "capability.memory_engine_required", "session memory status is not available on this deployment — it requires a wired memory engine (MEMORY_ENGINE=on) whose backend owns the engine control plane (controlPlaneRoot); the file memory backend does, the pg/mysql memory backends do not (their control plane would land on a replica's local disk)");
588
596
  return;
589
597
  }
598
+ const query = new URLSearchParams(match.search);
599
+ const at = readLayerSeat(res, query.has("scope"), query.get("scope"));
600
+ if (at === null)
601
+ return;
590
602
  try {
591
603
  const { memoryStatusCache } = ctx.local.sessions;
592
- let entry = memoryStatusCache.get(sessionId);
593
- if (!entry || Date.now() - entry.at > MEMORY_STATUS_TTL_MS) {
604
+ const statusOf = (store) => {
605
+ const cacheKey = JSON.stringify([sessionId, store.controlPlane]);
606
+ const cached = memoryStatusCache.get(cacheKey);
607
+ if (cached && Date.now() - cached.at <= MEMORY_STATUS_TTL_MS)
608
+ return cached.status;
594
609
  if (memoryStatusCache.size > 1024)
595
610
  memoryStatusCache.clear();
596
- const status = face.status(sessionId);
611
+ const status = store.status(sessionId);
597
612
  const fresh = { at: Date.now(), status };
598
- memoryStatusCache.set(sessionId, fresh);
599
- status.catch(() => { if (memoryStatusCache.get(sessionId) === fresh)
600
- memoryStatusCache.delete(sessionId); });
601
- entry = fresh;
602
- }
603
- const s = await entry.status;
613
+ memoryStatusCache.set(cacheKey, fresh);
614
+ status.catch(() => { if (memoryStatusCache.get(cacheKey) === fresh)
615
+ memoryStatusCache.delete(cacheKey); });
616
+ return status;
617
+ };
618
+ const s = foldPlaneStatuses(await Promise.all(face.storesAt(at).map(statusOf)));
604
619
  sendJson(res, 200, {
605
620
  sessionId,
606
621
  autoConsolidationArmed: deps.memoryAutoConsolidationArmed,
622
+ ...(deps.memorySyncLeg !== undefined ? { memorySync: { parked: deps.memorySyncLeg.parked().map((p) => ({ scope: p.scope, wayOut: p.wayOut })) } } : {}),
607
623
  ...(s.captureOptedOut !== undefined ? { captureOptedOut: s.captureOptedOut } : {}),
608
624
  ...(s.committedCount !== undefined ? { committedCount: s.committedCount } : {}),
609
625
  ...(s.foldedCount !== undefined ? { foldedCount: s.foldedCount } : {}),
@@ -612,6 +628,8 @@ export async function handleSessions(req, res, match, ctx) {
612
628
  });
613
629
  }
614
630
  catch (err) {
631
+ if (sentMemoryLayerRefusal(res, err, { kind: "session_owner", withheld: (message) => deps.logger?.warn?.("session_memory_status_refused", { sessionId, err: message }) }))
632
+ return;
615
633
  deps.logger?.warn?.("session_memory_status_failed", { sessionId, err: String(err) });
616
634
  sendError(res, 500, "internal.error", "session memory status read failed");
617
635
  }