@sema-agent/server 7.132.0 → 7.134.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 (473) hide show
  1. package/MIGRATION.md +353 -0
  2. package/README.md +8 -9
  3. package/README.zh-CN.md +7 -6
  4. package/USAGE.md +301 -64
  5. package/deploy/sema-up/README.md +35 -1
  6. package/deploy/sema-up/chart/Chart.yaml +1 -1
  7. package/deploy/sema-up/chart/templates/NOTES.txt +15 -0
  8. package/deploy/sema-up/chart/templates/_helpers.tpl +129 -0
  9. package/deploy/sema-up/chart/templates/sealed-key.yaml +24 -0
  10. package/deploy/sema-up/chart/templates/server-v4pro.yaml +19 -2
  11. package/deploy/sema-up/chart/templates/server.yaml +19 -2
  12. package/deploy/sema-up/chart/values-full-single.yaml +5 -1
  13. package/deploy/sema-up/chart/values.yaml +34 -0
  14. package/deploy/sema-up/stack/compose.yaml +3 -0
  15. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  16. package/dist/adoption/plan.d.ts +3 -2
  17. package/dist/adoption/plan.js +7 -5
  18. package/dist/adoption/runner.js +3 -10
  19. package/dist/adoption/sql.d.ts +3 -2
  20. package/dist/adoption/sql.js +3 -5
  21. package/dist/adoption/wire.d.ts +4 -1
  22. package/dist/adoption/wire.js +1 -0
  23. package/dist/approval-ask-audit-store.d.ts +32 -14
  24. package/dist/approval-ask-audit-store.js +272 -74
  25. package/dist/approval-ask-machine.d.ts +15 -0
  26. package/dist/approval-ask-machine.js +6 -0
  27. package/dist/approval-ask-retention.d.ts +136 -0
  28. package/dist/approval-ask-retention.js +102 -0
  29. package/dist/approval-card.d.ts +83 -23
  30. package/dist/approval-card.js +45 -15
  31. package/dist/approval-content-kind.d.ts +5 -2
  32. package/dist/approval-content-kind.js +16 -3
  33. package/dist/approval-deny-reasons.d.ts +1 -1
  34. package/dist/approval-gate-currency.d.ts +72 -0
  35. package/dist/approval-gate-currency.js +94 -0
  36. package/dist/approval.d.ts +15 -0
  37. package/dist/approval.js +55 -17
  38. package/dist/audit.d.ts +27 -10
  39. package/dist/audit.js +12 -16
  40. package/dist/bake-runner/main.js +3 -2
  41. package/dist/boot/coordinators.d.ts +6 -1
  42. package/dist/boot/coordinators.js +6 -3
  43. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  44. package/dist/boot/execution-env.js +9 -7
  45. package/dist/boot/execution-lane.js +11 -0
  46. package/dist/boot/leader.d.ts +2 -0
  47. package/dist/boot/leader.js +4 -1
  48. package/dist/boot/memory-boundary.js +2 -2
  49. package/dist/boot/memory-consolidation.d.ts +5 -4
  50. package/dist/boot/memory-consolidation.js +7 -6
  51. package/dist/boot/org-delivery.d.ts +28 -0
  52. package/dist/boot/org-delivery.js +20 -0
  53. package/dist/boot/read-deny-anchor-audit.js +2 -2
  54. package/dist/boot/reapers.d.ts +3 -0
  55. package/dist/boot/reapers.js +21 -1
  56. package/dist/boot/resolve-spec.d.ts +3 -0
  57. package/dist/boot/resolve-spec.js +60 -81
  58. package/dist/boot/runner-deps.d.ts +18 -5
  59. package/dist/boot/runner-deps.js +10 -0
  60. package/dist/boot/session-faces.d.ts +1 -0
  61. package/dist/boot/session-faces.js +4 -2
  62. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  63. package/dist/boot/stage-06-runners.d.ts +6 -3
  64. package/dist/boot/stage-06-runners.js +20 -4
  65. package/dist/boot/stage-07-capability-layer.d.ts +4 -1
  66. package/dist/boot/stage-07-capability-layer.js +24 -3
  67. package/dist/boot/stage-08-reapers.d.ts +4 -1
  68. package/dist/boot/stage-08-reapers.js +2 -2
  69. package/dist/boot/stage-09-leader.d.ts +5 -1
  70. package/dist/boot/stage-09-leader.js +2 -2
  71. package/dist/boot/stage-10-http-server.d.ts +5 -1
  72. package/dist/boot/stage-10-http-server.js +6 -6
  73. package/dist/boot/stores.d.ts +8 -0
  74. package/dist/boot/stores.js +10 -14
  75. package/dist/boot/web-admin.d.ts +10 -0
  76. package/dist/boot/web-admin.js +162 -65
  77. package/dist/boot/workflow-orchestration.d.ts +2 -0
  78. package/dist/boot/workflow-orchestration.js +2 -1
  79. package/dist/brain.js +8 -3
  80. package/dist/breaker-key.d.ts +6 -0
  81. package/dist/breaker-key.js +29 -0
  82. package/dist/budget.js +5 -1
  83. package/dist/caller-text-echo.d.ts +11 -6
  84. package/dist/caller-text-echo.js +10 -12
  85. package/dist/canonical-json.d.ts +2 -41
  86. package/dist/canonical-json.js +2 -13
  87. package/dist/capabilities/center-plugins.d.ts +7 -0
  88. package/dist/capabilities/center-plugins.js +1 -1
  89. package/dist/capabilities/composite-members.d.ts +1 -1
  90. package/dist/capabilities/composite-members.js +1 -1
  91. package/dist/capabilities/execution-lane.js +2 -2
  92. package/dist/capabilities/hands-lane.d.ts +21 -0
  93. package/dist/capabilities/hands-lane.js +10 -0
  94. package/dist/capabilities/mcp-probe.d.ts +36 -4
  95. package/dist/capabilities/mcp-probe.js +29 -6
  96. package/dist/capabilities/scenarios.d.ts +63 -7
  97. package/dist/capabilities/scenarios.js +39 -7
  98. package/dist/capability-facts.d.ts +36 -4
  99. package/dist/capability-facts.js +30 -4
  100. package/dist/center-config-tier.d.ts +69 -0
  101. package/dist/center-config-tier.js +42 -0
  102. package/dist/checkpoint-ctx-seal-m28.d.ts +42 -0
  103. package/dist/checkpoint-ctx-seal-m28.js +19 -0
  104. package/dist/checkpoint-identity.d.ts +16 -0
  105. package/dist/checkpoint-identity.js +19 -0
  106. package/dist/config-catalog.d.ts +17 -10
  107. package/dist/config-catalog.js +43 -28
  108. package/dist/config-d-migration.d.ts +3 -1
  109. package/dist/config-d-migration.js +2 -2
  110. package/dist/config-dir-trust.d.ts +114 -0
  111. package/dist/config-dir-trust.js +388 -0
  112. package/dist/config-invariants.d.ts +18 -5
  113. package/dist/config-invariants.js +35 -3
  114. package/dist/config-provider.d.ts +6 -1
  115. package/dist/config-provider.js +6 -2
  116. package/dist/config-types.d.ts +71 -14
  117. package/dist/config-types.js +8 -0
  118. package/dist/config.d.ts +13 -0
  119. package/dist/config.js +121 -59
  120. package/dist/delegated-session-owner.d.ts +185 -0
  121. package/dist/delegated-session-owner.js +119 -0
  122. package/dist/device-protocol.d.ts +5 -3
  123. package/dist/device-protocol.js +1 -1
  124. package/dist/device-ws-hub.js +21 -2
  125. package/dist/device-ws-protocol.d.ts +70 -6
  126. package/dist/device-ws-protocol.js +50 -23
  127. package/dist/elicitation.js +3 -2
  128. package/dist/entry-problems.d.ts +34 -0
  129. package/dist/entry-problems.js +14 -0
  130. package/dist/execution-lane-caps.d.ts +49 -10
  131. package/dist/execution-lane-caps.js +43 -4
  132. package/dist/field-conflict.d.ts +44 -0
  133. package/dist/field-conflict.js +21 -0
  134. package/dist/file-history-disclosure.d.ts +34 -5
  135. package/dist/file-history-disclosure.js +2 -2
  136. package/dist/fleet/fleet-bus.d.ts +84 -6
  137. package/dist/fleet/fleet-bus.js +42 -9
  138. package/dist/hooks/classic-stations.d.ts +10 -5
  139. package/dist/hooks/classic-stations.js +4 -4
  140. package/dist/hooks/command-spawn.d.ts +57 -0
  141. package/dist/hooks/command-spawn.js +139 -0
  142. package/dist/hooks/hook-llm.js +2 -2
  143. package/dist/hooks/hook-runner.d.ts +127 -25
  144. package/dist/hooks/hook-runner.js +668 -287
  145. package/dist/hooks/hook-source.d.ts +44 -0
  146. package/dist/hooks/hook-source.js +40 -0
  147. package/dist/http/active-run-conflict.d.ts +5 -13
  148. package/dist/http/active-run-conflict.js +17 -27
  149. package/dist/http/admission.js +111 -60
  150. package/dist/http/approval-sweeps.js +58 -19
  151. package/dist/http/caller-trust.d.ts +77 -0
  152. package/dist/http/caller-trust.js +143 -0
  153. package/dist/http/checkpoint-ctx.d.ts +173 -18
  154. package/dist/http/checkpoint-ctx.js +171 -12
  155. package/dist/http/claimed-run.d.ts +4 -3
  156. package/dist/http/device-placement.d.ts +2 -4
  157. package/dist/http/device-placement.js +2 -1
  158. package/dist/http/dispatch.js +13 -7
  159. package/dist/http/durable-run-leg.d.ts +4 -32
  160. package/dist/http/durable-run-leg.js +37 -12
  161. package/dist/http/idempotency.d.ts +2 -2
  162. package/dist/http/idempotency.js +2 -2
  163. package/dist/http/owner-access.d.ts +56 -0
  164. package/dist/http/owner-access.js +42 -0
  165. package/dist/http/principal-gate.d.ts +78 -27
  166. package/dist/http/principal-gate.js +64 -14
  167. package/dist/http/readiness.d.ts +11 -5
  168. package/dist/http/readiness.js +10 -3
  169. package/dist/http/refusal-body.d.ts +12 -0
  170. package/dist/http/refusal-body.js +11 -0
  171. package/dist/http/request-guards.d.ts +16 -32
  172. package/dist/http/request-guards.js +31 -30
  173. package/dist/http/resume-ctx-read.d.ts +50 -6
  174. package/dist/http/resume-ctx-read.js +33 -7
  175. package/dist/http/resume-legs.d.ts +5 -5
  176. package/dist/http/resume-legs.js +163 -91
  177. package/dist/http/retired-service-deps.d.ts +43 -0
  178. package/dist/http/retired-service-deps.js +18 -0
  179. package/dist/http/route-ctx.d.ts +44 -37
  180. package/dist/http/route-ctx.js +4 -0
  181. package/dist/http/route-table.js +2 -0
  182. package/dist/http/routes/a2a-serve.js +3 -4
  183. package/dist/http/routes/admin-config-refresh.js +1 -1
  184. package/dist/http/routes/admin-drain.js +1 -1
  185. package/dist/http/routes/admin-memory-optout.js +5 -1
  186. package/dist/http/routes/adoption.js +2 -9
  187. package/dist/http/routes/agents-roster.js +2 -3
  188. package/dist/http/routes/approvals-assistant.js +91 -56
  189. package/dist/http/routes/background.js +3 -2
  190. package/dist/http/routes/capabilities.js +20 -4
  191. package/dist/http/routes/config-catalog.d.ts +3 -3
  192. package/dist/http/routes/config-catalog.js +1 -1
  193. package/dist/http/routes/devices.js +3 -17
  194. package/dist/http/routes/diagnostics.d.ts +3 -3
  195. package/dist/http/routes/diagnostics.js +6 -2
  196. package/dist/http/routes/fleet.js +4 -4
  197. package/dist/http/routes/images.js +6 -7
  198. package/dist/http/routes/leader.js +1 -8
  199. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  200. package/dist/http/routes/mcp-reconnect.js +3 -2
  201. package/dist/http/routes/memory-bundle.js +1 -1
  202. package/dist/http/routes/memory-compliance.js +1 -1
  203. package/dist/http/routes/memory-consolidation.js +1 -1
  204. package/dist/http/routes/memory-origin.d.ts +1 -1
  205. package/dist/http/routes/memory-origin.js +1 -1
  206. package/dist/http/routes/memory-policy.d.ts +1 -1
  207. package/dist/http/routes/memory-policy.js +3 -2
  208. package/dist/http/routes/notify-wake.js +8 -21
  209. package/dist/http/routes/observability.js +2 -2
  210. package/dist/http/routes/principal-inbox.d.ts +39 -0
  211. package/dist/http/routes/principal-inbox.js +76 -0
  212. package/dist/http/routes/retention-ops.js +2 -9
  213. package/dist/http/routes/rules.js +9 -22
  214. package/dist/http/routes/runs.d.ts +5 -0
  215. package/dist/http/routes/runs.js +63 -115
  216. package/dist/http/routes/session-sync.d.ts +3 -2
  217. package/dist/http/routes/session-sync.js +53 -48
  218. package/dist/http/routes/sessions-list.d.ts +1 -1
  219. package/dist/http/routes/sessions-list.js +3 -4
  220. package/dist/http/routes/sessions.js +33 -43
  221. package/dist/http/routes/side-query.js +6 -4
  222. package/dist/http/routes/tasks.js +71 -30
  223. package/dist/http/routes/trace-usage.js +7 -9
  224. package/dist/http/routes/workflows.d.ts +351 -0
  225. package/dist/http/routes/workflows.js +153 -119
  226. package/dist/http/run-declared-families.d.ts +16 -0
  227. package/dist/http/run-declared-families.js +12 -0
  228. package/dist/http/send.d.ts +24 -3
  229. package/dist/http/send.js +14 -2
  230. package/dist/http/server.d.ts +23 -18
  231. package/dist/http/server.js +20 -15
  232. package/dist/http/stored-body-replay.js +17 -4
  233. package/dist/http/submit-ctx.d.ts +4 -3
  234. package/dist/http/submit-ctx.js +23 -6
  235. package/dist/http/submit-replay.d.ts +4 -3
  236. package/dist/http/submit-replay.js +5 -4
  237. package/dist/http/wire-types.d.ts +17 -10
  238. package/dist/index.d.ts +3 -1
  239. package/dist/index.js +1 -1
  240. package/dist/lane-home-probe.d.ts +78 -9
  241. package/dist/lane-home-probe.js +78 -12
  242. package/dist/leader/endpoint.d.ts +4 -1
  243. package/dist/leader/endpoint.js +11 -10
  244. package/dist/leader/wire.d.ts +4 -0
  245. package/dist/leader/wire.js +8 -7
  246. package/dist/limit-refusal.d.ts +17 -5
  247. package/dist/limit-refusal.js +4 -1
  248. package/dist/main.js +1 -1
  249. package/dist/mcp-assertion-header.d.ts +31 -10
  250. package/dist/mcp-assertion-header.js +10 -3
  251. package/dist/memory-origin-fence.js +2 -2
  252. package/dist/memory-project-layer.d.ts +37 -0
  253. package/dist/memory-project-layer.js +46 -0
  254. package/dist/memory-scope.d.ts +23 -16
  255. package/dist/memory-scope.js +24 -8
  256. package/dist/memory-sync-client.d.ts +27 -13
  257. package/dist/memory-sync-client.js +107 -18
  258. package/dist/model-compat.d.ts +1 -1
  259. package/dist/model-entry-refusal.d.ts +9 -1
  260. package/dist/model-entry-refusal.js +6 -1
  261. package/dist/model-plane-face.d.ts +51 -0
  262. package/dist/model-plane-face.js +24 -0
  263. package/dist/model-provider.d.ts +5 -0
  264. package/dist/model-provider.js +4 -2
  265. package/dist/model-route-endpoint.d.ts +8 -11
  266. package/dist/model-route-endpoint.js +4 -7
  267. package/dist/model-route-env.d.ts +22 -0
  268. package/dist/model-route-env.js +2 -0
  269. package/dist/model-route-gap.d.ts +60 -30
  270. package/dist/model-route-gap.js +21 -15
  271. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  272. package/dist/observability/corrupt-read-seat.js +9 -0
  273. package/dist/observability/err-text.d.ts +6 -0
  274. package/dist/observability/err-text.js +18 -4
  275. package/dist/observability/fail-open.d.ts +35 -9
  276. package/dist/observability/fail-open.js +35 -9
  277. package/dist/observability/metrics.js +1 -0
  278. package/dist/observability/prompt-manifest.d.ts +22 -25
  279. package/dist/observability/prompt-manifest.js +54 -50
  280. package/dist/observability/tool-trace.js +9 -10
  281. package/dist/operator-ask.d.ts +10 -0
  282. package/dist/operator-ask.js +3 -0
  283. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  284. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  285. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  286. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  287. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  288. package/dist/orchestration/workflow-notify-journal.js +27 -34
  289. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  290. package/dist/orchestration/workflow-park-decision.js +119 -0
  291. package/dist/org-delivery-gate.d.ts +81 -0
  292. package/dist/org-delivery-gate.js +59 -0
  293. package/dist/panel-tool-names.d.ts +23 -0
  294. package/dist/panel-tool-names.js +8 -0
  295. package/dist/parked-decide.d.ts +43 -11
  296. package/dist/parked-decide.js +100 -51
  297. package/dist/parked-revive-surface.d.ts +9 -2
  298. package/dist/parked-revive-surface.js +11 -8
  299. package/dist/permission-rule-list.d.ts +1 -1
  300. package/dist/permission-rule-list.js +2 -2
  301. package/dist/permission-rule-vocab.d.ts +1 -23
  302. package/dist/permission-rule-vocab.js +0 -13
  303. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  304. package/dist/plugins/approval-ask-store-file.js +21 -0
  305. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  306. package/dist/plugins/approval-ask-store-memory.js +34 -2
  307. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  308. package/dist/plugins/approval-ask-store-sql.js +37 -1
  309. package/dist/plugins/approval-payload-fingerprint.d.ts +14 -6
  310. package/dist/plugins/approval-payload-fingerprint.js +1 -1
  311. package/dist/plugins/blob-backend.d.ts +38 -25
  312. package/dist/plugins/blob-backend.js +29 -19
  313. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  314. package/dist/plugins/breaker-state-sql.js +4 -4
  315. package/dist/plugins/caching-session-store.d.ts +4 -1
  316. package/dist/plugins/caching-session-store.js +7 -3
  317. package/dist/plugins/checkpoint-store-sql.d.ts +36 -1
  318. package/dist/plugins/checkpoint-store-sql.js +65 -3
  319. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  320. package/dist/plugins/file-history-store-sql.js +3 -1
  321. package/dist/plugins/file-run-store.d.ts +5 -13
  322. package/dist/plugins/file-run-store.js +9 -0
  323. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  324. package/dist/plugins/fork-routing-session-store.js +12 -1
  325. package/dist/plugins/host-platform.d.ts +2 -0
  326. package/dist/plugins/host-platform.js +2 -1
  327. package/dist/plugins/local-checkpoint-store.d.ts +15 -2
  328. package/dist/plugins/local-checkpoint-store.js +32 -1
  329. package/dist/plugins/local-session-store.d.ts +13 -10
  330. package/dist/plugins/local-session-store.js +23 -14
  331. package/dist/plugins/local-session-window.d.ts +45 -0
  332. package/dist/plugins/local-session-window.js +38 -0
  333. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  334. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  335. package/dist/plugins/mailbox-store-sql.js +77 -52
  336. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  337. package/dist/plugins/memory-run-store.d.ts +5 -13
  338. package/dist/plugins/memory-run-store.js +8 -0
  339. package/dist/plugins/one-time-migrations.d.ts +121 -5
  340. package/dist/plugins/one-time-migrations.js +332 -8
  341. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  342. package/dist/plugins/org-delivery-quota-store.js +228 -0
  343. package/dist/plugins/permission-rule-store-file.js +10 -6
  344. package/dist/plugins/permission-rule-store-sql.js +8 -8
  345. package/dist/plugins/pg-pool.js +39 -7
  346. package/dist/plugins/pg-session-storage.d.ts +18 -5
  347. package/dist/plugins/pg-session-storage.js +68 -23
  348. package/dist/plugins/remote-env-adb.d.ts +6 -12
  349. package/dist/plugins/remote-env-adb.js +12 -2
  350. package/dist/plugins/remote-env-device.d.ts +14 -7
  351. package/dist/plugins/remote-env-device.js +23 -15
  352. package/dist/plugins/remote-env-k8s.d.ts +8 -12
  353. package/dist/plugins/remote-env-k8s.js +23 -5
  354. package/dist/plugins/remote-env-local-docker.d.ts +10 -15
  355. package/dist/plugins/remote-env-local-docker.js +12 -9
  356. package/dist/plugins/remote-env-ssh.d.ts +10 -16
  357. package/dist/plugins/remote-env-ssh.js +17 -11
  358. package/dist/plugins/run-store-sql.d.ts +16 -14
  359. package/dist/plugins/run-store-sql.js +22 -2
  360. package/dist/plugins/schema-indexes.js +2 -0
  361. package/dist/plugins/send-user-file.js +2 -2
  362. package/dist/plugins/session-epoch-anchor.d.ts +32 -6
  363. package/dist/plugins/session-epoch-anchor.js +2 -3
  364. package/dist/plugins/session-ownership.d.ts +11 -0
  365. package/dist/plugins/session-ownership.js +11 -0
  366. package/dist/plugins/session-placement.d.ts +12 -14
  367. package/dist/plugins/session-placement.js +2 -15
  368. package/dist/plugins/session-store.d.ts +18 -8
  369. package/dist/plugins/session-store.js +15 -4
  370. package/dist/plugins/sql-driver.d.ts +74 -11
  371. package/dist/plugins/sql-driver.js +210 -12
  372. package/dist/plugins/sql-errors.d.ts +23 -0
  373. package/dist/plugins/sql-errors.js +21 -0
  374. package/dist/plugins/store-backend.d.ts +36 -1
  375. package/dist/plugins/store-backend.js +40 -14
  376. package/dist/plugins/store-contracts.d.ts +42 -0
  377. package/dist/plugins/store-contracts.js +1 -0
  378. package/dist/plugins/tidb-pool.d.ts +6 -1
  379. package/dist/plugins/tidb-pool.js +44 -10
  380. package/dist/plugins/tidb-session-storage.d.ts +3 -2
  381. package/dist/plugins/tidb-session-storage.js +2 -2
  382. package/dist/plugins/tidb-session-store.d.ts +25 -5
  383. package/dist/plugins/tidb-session-store.js +67 -21
  384. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  385. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  386. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  387. package/dist/plugins/workflow-run-store-sql.js +50 -9
  388. package/dist/posture-source.d.ts +18 -10
  389. package/dist/posture-source.js +4 -1
  390. package/dist/project-identity.d.ts +45 -6
  391. package/dist/project-identity.js +162 -8
  392. package/dist/project-layer-key.d.ts +28 -0
  393. package/dist/project-layer-key.js +29 -0
  394. package/dist/question.js +3 -2
  395. package/dist/read-deny-entry.d.ts +25 -0
  396. package/dist/read-deny-entry.js +12 -0
  397. package/dist/read-face-posture.d.ts +37 -11
  398. package/dist/read-face-posture.js +32 -3
  399. package/dist/request-key-closure.d.ts +7 -0
  400. package/dist/request-key-closure.js +9 -1
  401. package/dist/request-tool-axes.d.ts +12 -0
  402. package/dist/request-tool-axes.js +20 -0
  403. package/dist/request-value-closure.d.ts +5 -0
  404. package/dist/request-value-closure.js +11 -3
  405. package/dist/rule-entry-list.d.ts +26 -2
  406. package/dist/rule-entry-list.js +72 -12
  407. package/dist/rules-consent.js +3 -4
  408. package/dist/run-acceptance.d.ts +19 -3
  409. package/dist/run-acceptance.js +13 -1
  410. package/dist/run-local.js +37 -6
  411. package/dist/runs.d.ts +12 -10
  412. package/dist/runs.js +25 -6
  413. package/dist/runtime-caps-resolver.d.ts +16 -6
  414. package/dist/runtime-caps-resolver.js +18 -5
  415. package/dist/sandbox-pkg-source.d.ts +19 -1
  416. package/dist/sandbox-pkg-source.js +29 -7
  417. package/dist/sealed-key.d.ts +32 -0
  418. package/dist/sealed-key.js +55 -1
  419. package/dist/security.d.ts +95 -15
  420. package/dist/security.js +52 -9
  421. package/dist/session-error-wire.d.ts +31 -7
  422. package/dist/session-error-wire.js +9 -2
  423. package/dist/session-sync-kernel.d.ts +37 -10
  424. package/dist/session-sync-kernel.js +10 -0
  425. package/dist/session-titled-frame.d.ts +33 -0
  426. package/dist/session-titled-frame.js +4 -0
  427. package/dist/session-titler.d.ts +9 -1
  428. package/dist/session-titler.js +15 -4
  429. package/dist/spec-fields.js +21 -2
  430. package/dist/sql-engine-posture.d.ts +3 -1
  431. package/dist/task-a2a.js +4 -3
  432. package/dist/task-cwd.d.ts +1 -1
  433. package/dist/task-mcp.d.ts +5 -5
  434. package/dist/task-mcp.js +27 -74
  435. package/dist/task-settings.d.ts +93 -88
  436. package/dist/task-settings.js +71 -63
  437. package/dist/text-bidi.d.ts +0 -4
  438. package/dist/text-bidi.js +1 -3
  439. package/dist/tighten-only-seats.d.ts +150 -0
  440. package/dist/tighten-only-seats.js +196 -0
  441. package/dist/tool-approval.d.ts +451 -136
  442. package/dist/tool-approval.js +733 -256
  443. package/dist/tool-rule-names.d.ts +6 -0
  444. package/dist/trace/core-keyset-guard.d.ts +108 -25
  445. package/dist/trace/exit-seat.d.ts +40 -0
  446. package/dist/trace/exit-seat.js +130 -0
  447. package/dist/trace/ledger-events.d.ts +6 -3
  448. package/dist/trace/project.d.ts +98 -13
  449. package/dist/trace/project.js +57 -32
  450. package/dist/trace/projection-table.d.ts +100 -0
  451. package/dist/trace/projection-table.js +130 -0
  452. package/dist/trace/redact.d.ts +57 -0
  453. package/dist/trace/redact.js +962 -168
  454. package/dist/trace/self-minted-wire.d.ts +349 -1
  455. package/dist/trace/self-minted-wire.js +118 -1
  456. package/dist/trace/wire-projection-faces.d.ts +304 -2
  457. package/dist/trace/wire-projection-faces.js +160 -13
  458. package/dist/url-refusal.js +2 -0
  459. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  460. package/dist/web-admin-client/apply-effective.js +56 -5
  461. package/dist/web-admin-client/facade.d.ts +1 -1
  462. package/dist/web-admin-client/facade.js +1 -1
  463. package/dist/web-admin-client/read-face.js +5 -9
  464. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  465. package/dist/web-admin-client/skills-mcp.js +6 -8
  466. package/dist/web-admin-client/types.d.ts +1 -1
  467. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
  468. package/package.json +8 -4
  469. package/scripts/drain-check-local.mjs +34 -2
  470. package/scripts/drain-check.mysql.sql +20 -2
  471. package/scripts/drain-check.pg.sql +20 -2
  472. package/dist/mcp-tool-faces.d.ts +0 -69
  473. package/dist/mcp-tool-faces.js +0 -106
@@ -3,6 +3,15 @@ import type { Pool as PgPool } from "pg";
3
3
  import { type ExecutionOutcomeRecordWord, type GateOutcome, type Checkpoint, type CheckpointGate, type CheckpointState, type CheckpointStore, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ResumeOutcome, type ResolvedOutcome, type ResolveExpectation, type ReopenReason, type RiskDescriptor, type StoreDurability, type StoreFidelity, type ToolApprovalCard } from "@sema-agent/core";
4
4
  import { type ParkRowFacts, type RuleOfferProjection } from "../approval-card.js";
5
5
  import { type SqlDialect, type SqlDriver } from "./sql-driver.js";
6
+ import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
7
+ /** M28 店腿的读数(两孪生同形;随 M28 删):扫到的可读行 · 回写成的行 · 回写成的行里被改写的值 · 读不出的行 · 封好之后一行装不下的行。 */
8
+ export interface CtxRowsRewriteM28 {
9
+ readonly scanned: number;
10
+ readonly rewritten: number;
11
+ readonly valuesRewritten: number;
12
+ readonly unreadable: number;
13
+ readonly tooLarge: number;
14
+ }
6
15
  export { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
7
16
  /**
8
17
  * [ref] D-1 (§3 invariant #3 — crash-safe reaper backstop): an ABSOLUTE upper bound on a pending
@@ -37,6 +46,14 @@ export declare function tokenFingerprint(token: string): string;
37
46
  * 交给它、原样展开)。继承而不是在这里逐键再写一遍:core 给这一臂再加一位同类事实时,改的只有那张 Pick 表。 */
38
47
  export interface PendingCheckpoint extends ParkRowFacts {
39
48
  sessionId: string;
49
+ /**
50
+ * 这道停驻门的**非秘密稳定身份**(core `Checkpoint.checkpointId`,引擎每次停驻铸)—— 三处 durable 读面(本行 = `GET /v1/approvals` 的 `pending[]`
51
+ * 与 `/v1/approvals/stream` 的 `pending` 帧;停驻收件 `GET /v1/assistant/inbox` 的 `inbox[]`)共用的 **join 键**,也是 409 载体 A
52
+ * `pendingGate.checkpointId` / `terminal.paused.checkpointId` 的同一个值。**在场就是可信的那一个值,缺席就是缺席**:读法唯一 = `checkpoint-identity.ts` 的
53
+ * `durableRowIdentity`(过信任边界 —— 不含 token、有界),没有它的行(core 5.42.0 之前停驻的老行 / 没过边界的值)**照列**、键缺席(这一行没有可 join 的身份)、
54
+ * 每行计一次 `server.approvals.durable-row-checkpoint-id-missing`。两只店各读自己手里的那一份(SQL 孪生读 `checkpoint` 列的成员,LOCAL 孪生读 blob)。
55
+ */
56
+ checkpointId?: string;
40
57
  scope: string;
41
58
  toolName: string | null;
42
59
  toolCallId: string | null;
@@ -705,6 +722,12 @@ export declare class SqlCheckpointStore implements CheckpointStore {
705
722
  * {@link SqlCheckpointStore.pendingRowQuery};语义与三态全文见 {@link PendingRowBinding} 顶注。
706
723
  * `scope` 传 `""`(空串)历史上与缺席同义(旧形 `scope ?` 真值判),这里逐字保留。
707
724
  */
725
+ /**
726
+ * 每一只**待决** checkpoint 的会话(去重)—— 审批账本留存律的挂靠源(`approval-ask-retention.ts`:待决 checkpoint = 一个还能动的停驻,
727
+ * 它的会话就是停驻那条腿上问行的 `source_task_id`)。**不按门型过滤**:运维队列 {@link listPending} 不列 `gate_kind` 读不出的行,
728
+ * 而决断口的令牌回落仍认它 —— 留存若跟着队列漏掉它,就会删掉一只还能被决的停驻的问。只读 `session_id`,不读 blob。
729
+ */
730
+ listPendingSessionIds(): Promise<string[]>;
708
731
  findPendingTokenBySession(sessionId: string, scope?: string, binding?: PendingRowBinding): Promise<CheckpointToken | null>;
709
732
  /**
710
733
  * 🔴 [ref] 车5 §9 C4 —— 流内审批对账收敛器判据 1 的**窄读口**(店面承载;收敛器本体不在本车)。
@@ -804,8 +827,20 @@ export declare class SqlCheckpointStore implements CheckpointStore {
804
827
  * SUCCEED and replace a readable durable row with an unreadable one (caller sees 2xx, the next resume
805
828
  * 409s forever). So the serialized value is judged by {@link assertJsonColumnWritable} — the SAME shape
806
829
  * reader the read leg uses — BEFORE the statement runs: refuse loudly, leave the row on disk untouched. */
807
- putCtx(sessionId: string, ctx: object): Promise<void>;
830
+ putCtx(sessionId: string, ctx: SealedCheckpointCtx): Promise<void>;
808
831
  getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
832
+ /**
833
+ * 🔴 **一次性迁移 M28 的店腿**(窗与删法见 `plugins/one-time-migrations.ts` 登记表 M28;编排 = `checkpoint-ctx-seal-m28.ts`,行改写 =
834
+ * `http/checkpoint-ctx.ts` 的 `sealStoredCheckpointCtxM28`;本店不知道「封」是什么,只负责扫与回写):按 `session_id` 键集分页扫 `checkpoint_ctx`
835
+ * (每页 {@link CTX_REWRITE_PAGE} 行,只取会话 id 与 ctx 文本),行体经读腿**同一只**判形({@link decodeCtxColumn})—— 读不出 ⇒ 计数、跳过、不动;
836
+ * `rewrite` 交回新体 ⇒ 经写腿同一只编码与形闸,**按原文 CAS** 回写(MySQL 协议两边 `CAST(… AS BINARY)`,与 M17 / M18 同一条理由:连接排序规则大小写
837
+ * 不敏感;PG `text` 等值)。`updated_at_ms` **不动**(收割按它判,迁移不许延长或缩短一行的寿命)。CAS 打空(扫描与回写之间这一行被续跑腿 / 别的副本写过)
838
+ * ⇒ 不计、不重试(新写者是本版就已封好;是旧版副本就由之后的起服再扫到)。一行装不下(封签比明文长)⇒ 计 `tooLarge`、那一行留原样,其余照扫。
839
+ */
840
+ rewriteCtxRowsM28(rewrite: (sessionId: string, ctx: object) => {
841
+ readonly ctx: SealedCheckpointCtx;
842
+ readonly values: number;
843
+ } | undefined): Promise<CtxRowsRewriteM28>;
809
844
  /**
810
845
  * GC ctx rows whose lifecycle is over (bound to the checkpoint, don't leak):
811
846
  * a session with NO pending checkpoint, NO active run, AND not touched since `cutoff`.
@@ -6,11 +6,13 @@ import { redactDeep, redactSecrets } from "../trace/redact.js";
6
6
  import { MAX_RULE_OFFERS, MAX_RULE_OFFER_BATCH_MEMBERS, RuleOfferRawSchema, backfillLegacyBatchMemberKind, parkRowFacts, screenUncoveredDetailSeat } from "../approval-card.js";
7
7
  import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonArrayColumn, readJsonObjectColumn, readNonNullJsonValueColumn, } from "./sql-json-column.js";
8
8
  import { recordFailOpen } from "../observability/fail-open.js";
9
+ import { durableRowIdentity } from "../checkpoint-identity.js";
9
10
  import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
10
- import { isDupKeyError, isMissingColumnError, rowTooLargeOrSelf } from "./sql-errors.js";
11
+ import { isDupKeyError, isMissingColumnError, rowTooLargeOrSelf, StoreRowTooLargeError } from "./sql-errors.js";
11
12
  import { MANAGED_RETENTION } from "./retention-store-sql.js";
12
13
  import { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
13
14
  const REAP_CLASSIFY_BATCH = 200;
15
+ const CTX_REWRITE_PAGE = 500;
14
16
  export { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
15
17
  const MAX_TOOL_INPUT_CHARS = 8192;
16
18
  export const TERMINAL_BACKSTOP_MS = Math.max(60_000, (Number.isFinite(Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS)) ? Number(process.env.APPROVAL_TERMINAL_BACKSTOP_MS) : 0) || 30 * 86_400_000);
@@ -183,6 +185,18 @@ function readParkRowFactsCell(cell) {
183
185
  return parkRowFacts(pendingAction);
184
186
  }
185
187
  const PARK_ROW_PENDING_ACTION = { key: "pendingAction", as: "pending_action" };
188
+ const PARK_ROW_CHECKPOINT_ID = { key: "checkpointId", as: "checkpoint_id" };
189
+ const readCheckpointIdMemberBody = (body) => (typeof body === "string" ? { value: body } : { problem: "checkpointId is not a JSON string" });
190
+ function readCheckpointIdCell(cell) {
191
+ try {
192
+ return decodeNullableJsonColumn(cell, readCheckpointIdMemberBody, "checkpoint.checkpoint $.checkpointId (operator queue row)");
193
+ }
194
+ catch (e) {
195
+ if (!isJsonColumnBodyProblem(e))
196
+ throw e;
197
+ return null;
198
+ }
199
+ }
186
200
  const PARK_ROW_GATE = { key: "gate", as: "park_gate" };
187
201
  function readParkGateCell(cell) {
188
202
  try {
@@ -581,9 +595,9 @@ export class SqlCheckpointStore {
581
595
  }
582
596
  async listPending(scope) {
583
597
  const d = this.db.dialect;
584
- const base = `SELECT c.session_id, c.scope, c.tool_name, c.tool_call_id, ${jsonColumnText(d, "tool_input")}, c.bound_input_hash, ` +
598
+ const base = `SELECT c.session_id, c.token, c.scope, c.tool_name, c.tool_call_id, ${jsonColumnText(d, "tool_input")}, c.bound_input_hash, ` +
585
599
  `${jsonColumnText(d, "risk_descriptor")}, ${jsonColumnText(d, "rule_suggestions")}, c.has_bidi_controls, c.created_at_ms, c.deadline, c.gate_kind, ta.task_id, ` +
586
- `${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_GATE)} ` +
600
+ `${jsonColumnText(d, "checkpoint", PARK_ROW_PENDING_ACTION)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_GATE)}, ${jsonColumnText(d, "checkpoint", PARK_ROW_CHECKPOINT_ID)} ` +
587
601
  "FROM checkpoint c LEFT JOIN task_active ta ON ta.session_id = c.session_id WHERE c.status='pending'";
588
602
  const { rows } = scope
589
603
  ? await this.db.query(`${base}${this.q(" AND c.scope=?", " AND c.scope=$1")} ORDER BY c.created_at_ms ASC`, [scope])
@@ -599,6 +613,7 @@ export class SqlCheckpointStore {
599
613
  const approvalCard = parkedApprovalCard(parkedGateOfRow(r));
600
614
  return [{
601
615
  sessionId: String(r.session_id),
616
+ ...durableRowIdentity("approvals-queue", readCheckpointIdCell(r[PARK_ROW_CHECKPOINT_ID.as]), r.token),
602
617
  scope: String(r.scope),
603
618
  toolName,
604
619
  toolCallId,
@@ -628,6 +643,10 @@ export class SqlCheckpointStore {
628
643
  }
629
644
  return out;
630
645
  }
646
+ async listPendingSessionIds() {
647
+ const { rows } = await this.db.query("SELECT DISTINCT session_id FROM checkpoint WHERE status='pending'", []);
648
+ return rows.map((r) => String(r.session_id));
649
+ }
631
650
  async findPendingTokenBySession(sessionId, scope, binding) {
632
651
  const { sql, params } = this.pendingRowQuery("token", sessionId, scope || undefined, binding);
633
652
  const { rows } = await this.db.query(sql, params);
@@ -737,6 +756,49 @@ export class SqlCheckpointStore {
737
756
  const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "ctx")} FROM checkpoint_ctx WHERE session_id=?`, `SELECT ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id=$1`), [sessionId]);
738
757
  return rows[0] ? refusing("a resume context nobody can read", () => decodeCtxColumn(rows[0].ctx, `for session ${JSON.stringify(sessionId)}`)) : null;
739
758
  }
759
+ async rewriteCtxRowsM28(rewrite) {
760
+ const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
761
+ let after = "";
762
+ for (;;) {
763
+ const { rows } = await this.db.query(this.q(`SELECT session_id, ${jsonColumnText("tidb", "ctx")} FROM checkpoint_ctx WHERE session_id > ? ORDER BY session_id LIMIT ${CTX_REWRITE_PAGE}`, `SELECT session_id, ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id > $1 ORDER BY session_id LIMIT ${CTX_REWRITE_PAGE}`), [after]);
764
+ for (const row of rows) {
765
+ const sessionId = String(row.session_id);
766
+ after = sessionId;
767
+ let ctx;
768
+ try {
769
+ ctx = decodeCtxColumn(row.ctx, `for session ${JSON.stringify(sessionId)} (M28 scan)`);
770
+ }
771
+ catch {
772
+ out.unreadable += 1;
773
+ continue;
774
+ }
775
+ out.scanned += 1;
776
+ const next = rewrite(sessionId, ctx);
777
+ if (next === undefined)
778
+ continue;
779
+ const text = this.json(next.ctx, "checkpoint ctx");
780
+ assertJsonColumnWritable(text, createObjectColumnReader(), `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (M28 write)`);
781
+ let affected;
782
+ try {
783
+ ({ affected } = await this.db.query(this.q("UPDATE checkpoint_ctx SET ctx = ? WHERE session_id = ? AND CAST(CAST(ctx AS CHAR) AS BINARY) = CAST(? AS BINARY)", "UPDATE checkpoint_ctx SET ctx = $1::jsonb WHERE session_id = $2 AND ctx::text = $3"), [text, sessionId, String(row.ctx)]));
784
+ }
785
+ catch (e) {
786
+ const folded = rowTooLargeOrSelf(this.db.dialect, e, `checkpoint_ctx.ctx for session ${JSON.stringify(sessionId)} (M28 write)`);
787
+ if (!(folded instanceof StoreRowTooLargeError))
788
+ throw folded;
789
+ out.tooLarge += 1;
790
+ continue;
791
+ }
792
+ if (affected > 0) {
793
+ out.rewritten += 1;
794
+ out.valuesRewritten += next.values;
795
+ }
796
+ }
797
+ if (rows.length < CTX_REWRITE_PAGE)
798
+ break;
799
+ }
800
+ return out;
801
+ }
740
802
  async reapCtx(cutoff) {
741
803
  const res = await this.db.query(this.q("DELETE FROM checkpoint_ctx WHERE updated_at_ms < ? " +
742
804
  "AND session_id NOT IN (SELECT session_id FROM checkpoint WHERE status='pending') " +
@@ -2,6 +2,7 @@ import type { Pool as MySqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
3
  import { type FileHistoryRetentionPolicy, type ExecutionEnv, type FileHistoryStore, type FileHistoryResult, type FileHistoryTrackResult, type FileHistoryRestoreResult, type FileHistoryExport } from "@sema-agent/core";
4
4
  import { type BlobBackend } from "./blob-backend.js";
5
+ import type { SyncBlobPutResult } from "./store-backend.js";
5
6
  import { type SqlDriver } from "./sql-driver.js";
6
7
  /** The file-history blob domain's SQL table (BlobBackend `table`/MinIO index binding + the A7 assert). */
7
8
  export declare const FILE_HISTORY_BLOB_TABLE = "file_history_blob";
@@ -147,8 +148,10 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
147
148
  * Domain-isolated by construction (this store's blob table only — [ref] D1). Reaper-driven. */
148
149
  sweepOrphanBlobs(): Promise<number>;
149
150
  /** Upload ONE content-addressed blob ahead of an importHistory (§5 streamed negotiation). Store-level
150
- * integrity re-check (the route pre-verifies too — the internal caller might not). Never-throw. */
151
- putBlob(hash: string, bytes: Uint8Array): Promise<FileHistoryResult>;
151
+ * integrity re-check (the route pre-verifies too — the internal caller might not). Never-throw.
152
+ * The SQL blob judge's refusal ({@link BlobTooLargeError}, judged on the connection that would have written the blob)
153
+ * keeps its own code — the route answers it 413 (the sentence names that connection's walls), every other write failure 502 ("retry"). */
154
+ putBlob(hash: string, bytes: Uint8Array): Promise<SyncBlobPutResult>;
152
155
  getBlob(hash: string): Promise<Uint8Array | undefined>;
153
156
  /** The subset of `hashes` already present — one SQL INDEX probe (the import presence pre-check). */
154
157
  hasBlobs(hashes: string[]): Promise<Set<string>>;
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
3
- import { SqlBlobBackend } from "./blob-backend.js";
3
+ import { BlobTooLargeError, SqlBlobBackend } from "./blob-backend.js";
4
4
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
5
5
  import { isMissingColumnError } from "./sql-errors.js";
6
6
  import { recordFailOpen } from "../observability/fail-open.js";
@@ -594,6 +594,8 @@ export class SqlFileHistoryStore {
594
594
  return { ok: true };
595
595
  }
596
596
  catch (err) {
597
+ if (err instanceof BlobTooLargeError)
598
+ return { ok: false, error: { code: err.code, message: err.message, wallSource: err.cap.source } };
597
599
  return { ok: false, error: { code: "snapshot_failed", message: `putBlob ${hash} write/upload failed: ${discloseCaughtFailure(err)}` } };
598
600
  }
599
601
  }
@@ -1,6 +1,6 @@
1
1
  import { type UsageRow } from "../usage-analytics.js";
2
2
  import type { TaskStatus } from "@sema-agent/core";
3
- import { StoreBootRefusalError, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
3
+ import { StoreBootRefusalError, type FenceLanded, type InProcessClaimFence, type CreateRunOutcome, type NewRunMeta, type RunIdemBinding, type RunListQuery, type RunRecord, type SessionSummary, type RunEvent, type PersistedTaskResult, type LatestRunRef } from "./store-contracts.js";
4
4
  import type { RunStoreCheckpointProbe } from "./memory-run-store.js";
5
5
  import type { LedgerEventType } from "../trace/ledger-events.js";
6
6
  import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState } from "../workspace-space-commit.js";
@@ -22,7 +22,7 @@ import { type SpaceCommitClaim, type SpaceCommitReading, type SpaceCommitState }
22
22
  export declare class RunStoreStrictHydrateError extends StoreBootRefusalError {
23
23
  readonly name = "RunStoreStrictHydrateError";
24
24
  }
25
- export declare class FileRunStore {
25
+ export declare class FileRunStore implements InProcessClaimFence {
26
26
  private readonly runsDir;
27
27
  private readonly activeDir;
28
28
  private readonly tmpDir;
@@ -148,6 +148,8 @@ export declare class FileRunStore {
148
148
  * [ref](codex R1 [medium]):认领 = 接管 `instanceId`(= 当前持有副本;SQL 孪生同注)。CAS 输不改。 */
149
149
  markResuming(taskId: string, instanceId: string): Promise<boolean>;
150
150
  getActiveTaskId(sessionId: string): Promise<string | undefined>;
151
+ /** 认领当写栅栏用(契约 = `store-contracts.ts` 的 `InProcessClaimFence`):判、刷新活性(同步落盘)与 `write` 是同一个同步段。 */
152
+ whileHoldingClaim(sessionId: string, taskId: string, write: () => FenceLanded): boolean;
151
153
  getRun(taskId: string): Promise<RunRecord | undefined>;
152
154
  /** Undo THIS submission's binding (key + body fingerprint → null) — called only where the submit surface refuses the submission
153
155
  * before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, a device-placement reject):
@@ -161,17 +163,7 @@ export declare class FileRunStore {
161
163
  /** The run a submission's scoped `Idempotency-Key` names (the file twin of `uq_task_run_idem`), if any. */
162
164
  getRunByIdem(idemKey: string): Promise<RunRecord | undefined>;
163
165
  /** Runs newest-first, keyset on (createdAt, taskId) DESC; optional exact-match filters (owner is exact, like the SQL). */
164
- listRuns(opts: {
165
- status?: string;
166
- jobId?: string;
167
- source?: string;
168
- owner?: string;
169
- cursor?: {
170
- createdAt: string;
171
- taskId: string;
172
- };
173
- limit: number;
174
- }): Promise<RunRecord[]>;
166
+ listRuns(opts: RunListQuery): Promise<RunRecord[]>;
175
167
  /**
176
168
  * S-297 —— 这个会话**最近一条腿**的窄指针({@link SqlRunStore.latestRunForSession} 的孪生:同一个排序口径
177
169
  * `createdAt DESC, taskId DESC`,与本店 {@link FileRunStore.listSessions} 挑 `rn=1` 那一行的比较器逐字同一份)。
@@ -525,6 +525,15 @@ export class FileRunStore {
525
525
  async getActiveTaskId(sessionId) {
526
526
  return this.active.get(sessionId);
527
527
  }
528
+ whileHoldingClaim(sessionId, taskId, write) {
529
+ const r = this.runs.get(taskId);
530
+ if (this.active.get(sessionId) !== taskId || r === undefined || r.status !== "running")
531
+ return false;
532
+ r.updatedAt = new Date();
533
+ this.persist(r);
534
+ write();
535
+ return true;
536
+ }
528
537
  async getRun(taskId) {
529
538
  const r = this.runs.get(taskId);
530
539
  return r ? this.toRecord(r) : undefined;
@@ -24,6 +24,7 @@
24
24
  * (requireExisting 的 host-first 重试仍落对店),中途 touch/noteTaskRun 缺路由=落 transient(no-op 级)。
25
25
  */
26
26
  import { type SessionStore, type AcquiredSession, type SessionStoreSummary, type SessionPlacement, type PlacedSessionRow } from "@sema-agent/core";
27
+ import { type DelegationTree } from "../delegated-session-owner.js";
27
28
  /** acquire 选项形(core 5.48 SessionStore 契约):[ref] placement=受信 spawner 的分区建会话声明。 */
28
29
  type AcquireOpts = {
29
30
  requireExisting?: boolean;
@@ -85,8 +86,15 @@ export declare class ForkRoutingSessionStore implements SessionStore {
85
86
  * unpin 不落错店)。
86
87
  *
87
88
  * 已在 host(路由或真行)⇒ 幂等直返。两店都无此会话 ⇒ 抛(调用方=park veto,诚实 fail-closed)。
89
+ *
90
+ * S-1770:宿主行**在建行那一笔里**带属主 `owner` —— 调用方按委派继承律算好(`delegated-session-owner.ts`:委派树根的属主,
91
+ * 推不出 ⇒ 孤行哨兵;`tree` = 判据读到的委派树,判断用:宿主行记着别的树 ⇒ 拒),这里经宿主店的首写登记写下、再按律定夺(core `register` 契约:登记不判授权,判在复读)。
92
+ * 宿主店里同 id 已有一行:记着这一棵委派树且无主 ⇒ 按同一条律认领(只有记录委派树的店);别的树的 / 属主不是它(别人先认领的空壳,或 local 店的无主空壳)⇒ 抛(停驻否决),
93
+ * 转录不拷进别人的行;宿主店记不了 / 读不回属主 ⇒ 同样抛 —— 晋升出一只无主的转录行就是把它交给任何拿到 id 的人。
88
94
  */
89
- promoteToHost(sessionId: string): Promise<void>;
95
+ promoteToHost(sessionId: string, owner: string | null, tree: DelegationTree): Promise<void>;
96
+ /** 晋升的建行那一笔(S-1770,规则见 {@link promoteToHost} 头注):宿主店首写登记 `owner`,然后按律定下宿主行的属主(与停驻快路同一只 `settleDelegatedOwner`)。 */
97
+ private registerPromotedOwner;
90
98
  fork(sourceId: string, owner?: string | null): Promise<string | null>;
91
99
  get size(): number;
92
100
  dispose(): void | Promise<void>;
@@ -1,5 +1,6 @@
1
1
  import { SessionError } from "@sema-agent/core";
2
2
  import { recordFailOpen } from "../observability/fail-open.js";
3
+ import { delegatedOwnerHostOf, settleDelegatedOwner } from "../delegated-session-owner.js";
3
4
  const ROUTE_MAX = 8192;
4
5
  export class ForkRoutingSessionStore {
5
6
  host;
@@ -170,7 +171,7 @@ export class ForkRoutingSessionStore {
170
171
  list() {
171
172
  return this.transient.list?.() ?? Promise.resolve([]);
172
173
  }
173
- async promoteToHost(sessionId) {
174
+ async promoteToHost(sessionId, owner, tree) {
174
175
  if (this.promoted.has(sessionId))
175
176
  return;
176
177
  if (this.route.get(sessionId) === "host") {
@@ -190,6 +191,7 @@ export class ForkRoutingSessionStore {
190
191
  }
191
192
  const entries = await src.session.getEntries();
192
193
  const leaf = await src.session.getLeafId();
194
+ await this.registerPromotedOwner(sessionId, owner, tree);
193
195
  const dst = await this.host.acquire(sessionId);
194
196
  try {
195
197
  const storage = dst.session.getStorage();
@@ -217,6 +219,15 @@ export class ForkRoutingSessionStore {
217
219
  this.promoted.add(sessionId);
218
220
  this.remember(sessionId, "host");
219
221
  }
222
+ async registerPromotedOwner(sessionId, owner, tree) {
223
+ const register = this.host.register;
224
+ const host = delegatedOwnerHostOf(this.host);
225
+ if (register === undefined || host === undefined) {
226
+ throw new Error(`session "${sessionId}" cannot be promoted: the host session store cannot record or read back a session owner (register / ownerOf / sessionFaceOf; placementOf and claimOwnerless together or neither), so the promoted transcript would land ownerless`);
227
+ }
228
+ await register.call(this.host, sessionId, owner);
229
+ await settleDelegatedOwner(host, sessionId, owner, tree);
230
+ }
220
231
  async fork(sourceId, owner) {
221
232
  const routed = this.route.get(sourceId);
222
233
  const tryFork = async (store, where) => {
@@ -34,6 +34,8 @@
34
34
  import type { ChildProcess } from "node:child_process";
35
35
  import { killProcessTree, type BackgroundHardKillOutcome } from "@sema-agent/core";
36
36
  import type { Logger } from "../observability/logger.js";
37
+ /** 平台读点全仓一处(`test/host-shell-settle-order.test.ts` 源码钉);别处要平台串从这里取,不直读 Node 的平台字段。 */
38
+ export declare const HOST_PLATFORM: NodeJS.Platform;
37
39
  export declare const IS_WIN32: boolean;
38
40
  export interface HostShell {
39
41
  shell: string;
@@ -1,7 +1,8 @@
1
1
  import os from "node:os";
2
2
  import { getShellConfig, killProcessTree, signalProcessTree } from "@sema-agent/core";
3
3
  import { redactSecrets } from "../trace/redact.js";
4
- export const IS_WIN32 = process.platform === "win32";
4
+ export const HOST_PLATFORM = process.platform;
5
+ export const IS_WIN32 = HOST_PLATFORM === "win32";
5
6
  const POSIX_SH_FALLBACK = { shell: "/bin/sh", args: ["-c"] };
6
7
  let cachedPosixShell;
7
8
  let winShellState = { kind: "unsettled" };
@@ -1,5 +1,6 @@
1
1
  import { type Checkpoint, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ReopenReason, type ResolveExpectation, type ExecutionOutcomeRecordWord, type GateOutcome, type ResumeOutcome, type StoreDurability, type StoreFidelity } from "@sema-agent/core";
2
- import { type CheckpointAskCandidate, type DecidedApprovalRecord, type PendingCheckpoint, type PendingRowBinding } from "./checkpoint-store-sql.js";
2
+ import { type CheckpointAskCandidate, type CtxRowsRewriteM28, type DecidedApprovalRecord, type PendingCheckpoint, type PendingRowBinding } from "./checkpoint-store-sql.js";
3
+ import type { SealedCheckpointCtx } from "../http/checkpoint-ctx.js";
3
4
  export interface LocalCheckpointStoreOptions {
4
5
  /** taskId join for the pending card (the local FileRunStore's `getActiveTaskId`). Absent ⇒ `taskId: null`. */
5
6
  getActiveTaskId?: (sessionId: string) => Promise<string | undefined>;
@@ -98,6 +99,8 @@ export declare class LocalCheckpointStore {
98
99
  /** All still-pending checkpoints across the registered scopes (summary + full row via get). */
99
100
  private pendings;
100
101
  /** Operator approval queue — the same NON-secret card as the SQL twins (never the token). */
102
+ /** 每一只待决 checkpoint 的会话(去重;不按门型过滤 —— 理由与 SQL 孪生 `listPendingSessionIds` 逐字同,见那里)。 */
103
+ listPendingSessionIds(): Promise<string[]>;
101
104
  listPending(scope?: string): Promise<PendingCheckpoint[]>;
102
105
  /**
103
106
  * [ref]:与 SQL 孪生**同判**的 session 键读口 —— `binding.gateKinds` 过滤门型、序恒「最新优先,token 兜平局」
@@ -197,7 +200,7 @@ export declare class LocalCheckpointStore {
197
200
  * 一个 `toJSON(k){ return k === "ctx" ? null : {…} }` 的对象验的是 `{…}`、写的是 `{"ctx":null}`
198
201
  * (实测两串逐字不同)。有状态的序列化器同理。⇒ 整条记录**只序列化一次**,闸判那一串,写也写那一串。
199
202
  */
200
- putCtx(sessionId: string, ctx: object): Promise<void>;
203
+ putCtx(sessionId: string, ctx: SealedCheckpointCtx): Promise<void>;
201
204
  /**
202
205
  * 🔴 **坏体 ⇒ 拒,绝不读成缺席**(S-577,与 SQL 孪生同一条判据)。修前这里是
203
206
  * `catch { return null }` + `(rec.ctx as T) ?? null`,于是一个撕裂 / 手改 / 权限读不动的 ctx 文件与
@@ -209,6 +212,16 @@ export declare class LocalCheckpointStore {
209
212
  * 只收对象,读腿只认对象,两条腿一个类型。
210
213
  */
211
214
  getCtx<T extends object = object>(sessionId: string): Promise<T | null>;
215
+ /**
216
+ * 🔴 **一次性迁移 M28 的店腿**(local 孪生;窗与删法见 `plugins/one-time-migrations.ts` 登记表 M28,判词与 SQL 孪生 `rewriteCtxRowsM28` 逐条同):
217
+ * 逐档读(信封经读腿**同一只**判形 —— 读不出 ⇒ 计数、跳过、**不删**),`rewrite` 交回新体 ⇒ 信封原样(`sessionId` / `updatedAt` 不动:收割按 `updatedAt` 判)
218
+ * 只换 `ctx`,经写腿同一只形闸、同一只原子写落回原档。封签绑的是信封里的 `sessionId`(与 `getCtx` 认行的那一格同一个)。
219
+ * 数据根独占 + 起服在任何读写者之前跑 ⇒ 本车道没有并发写者,不需要 CAS;没有一行装不下这回事(`tooLarge` 恒 0)。
220
+ */
221
+ rewriteCtxRowsM28(rewrite: (sessionId: string, ctx: object) => {
222
+ readonly ctx: SealedCheckpointCtx;
223
+ readonly values: number;
224
+ } | undefined): Promise<CtxRowsRewriteM28>;
212
225
  /** GC ctx files whose lifecycle is over — same three guards as the SQL twins (stale ∧ no pending ∧ no active run). */
213
226
  reapCtx(cutoff: number): Promise<number>;
214
227
  /**
@@ -1,6 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { parkedApprovalCard } from "../approval-content-kind.js";
3
3
  import { parkRowFacts } from "../approval-card.js";
4
+ import { durableRowIdentity } from "../checkpoint-identity.js";
4
5
  import { createHash } from "node:crypto";
5
6
  import { existsSync, readFileSync, readdirSync, unlinkSync, mkdirSync } from "node:fs";
6
7
  import { join } from "node:path";
@@ -160,9 +161,12 @@ export class LocalCheckpointStore {
160
161
  }
161
162
  return out;
162
163
  }
164
+ async listPendingSessionIds() {
165
+ return [...new Set((await this.pendings()).map(({ cp }) => cp.sessionId))];
166
+ }
163
167
  async listPending(scope) {
164
168
  const rows = [];
165
- for (const { cp } of await this.pendings(scope)) {
169
+ for (const { token, cp } of await this.pendings(scope)) {
166
170
  const pa = cp.pendingAction;
167
171
  const gate = cp.gate;
168
172
  const gateKind = queueRowGateKind(gate?.kind);
@@ -171,6 +175,7 @@ export class LocalCheckpointStore {
171
175
  const toolCallId = pa?.toolCallId ?? null;
172
176
  rows.push({
173
177
  sessionId: cp.sessionId,
178
+ ...durableRowIdentity("approvals-queue", cp.checkpointId, token),
174
179
  scope: cp.scope,
175
180
  toolName: pa?.toolName ?? null,
176
181
  toolCallId,
@@ -322,6 +327,32 @@ export class LocalCheckpointStore {
322
327
  const rec = refusingCtx(sessionId, () => decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${ctxFileOf(sessionId)} for session ${JSON.stringify(sessionId)}`));
323
328
  return rec.sessionId === sessionId ? rec.ctx : null;
324
329
  }
330
+ async rewriteCtxRowsM28(rewrite) {
331
+ const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
332
+ for (const f of readdirSync(this.ctxDir)) {
333
+ if (!f.endsWith(".json"))
334
+ continue;
335
+ const p = join(this.ctxDir, f);
336
+ let rec;
337
+ try {
338
+ rec = decodeJsonColumn(readFileSync(p, "utf8"), createCtxRecordReader(), `checkpoint-ctx ${f} (M28 scan)`);
339
+ }
340
+ catch {
341
+ out.unreadable += 1;
342
+ continue;
343
+ }
344
+ out.scanned += 1;
345
+ const next = rewrite(rec.sessionId, rec.ctx);
346
+ if (next === undefined)
347
+ continue;
348
+ const text = JSON.stringify({ sessionId: rec.sessionId, updatedAt: rec.updatedAt, ctx: next.ctx });
349
+ assertJsonColumnWritable(text, createCtxRecordReader(), `checkpoint ctx for session ${JSON.stringify(rec.sessionId)} (local M28 write)`);
350
+ atomicWriteFile(this.tmpDir, p, text);
351
+ out.rewritten += 1;
352
+ out.valuesRewritten += next.values;
353
+ }
354
+ return out;
355
+ }
325
356
  async reapCtx(cutoff) {
326
357
  let n = 0;
327
358
  for (const f of readdirSync(this.ctxDir)) {
@@ -4,10 +4,10 @@ import type { SessionRowPatch } from "../session-row.js";
4
4
  import { type Logger } from "../observability/logger.js";
5
5
  import { type SessionFace } from "../session-incarnation.js";
6
6
  import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
7
- import { type StagingHandle, type SyncRelation } from "../session-sync-kernel.js";
7
+ import { type StagedCommitOptions, type StagingHandle, type SyncRelation } from "../session-sync-kernel.js";
8
8
  /**
9
9
  * local 车道会话存档的**唯一 repo 出口**:本仓里每一只读 `<root>/sessions/*.jsonl` 的 `FileSessionRepo` 都从这里取(`LocalSessionStore` 的底座与会话视图口
10
- * `audit.ts#buildSessionAuditLocal` 的读旁路),所以坏记录的披露座只有这一份接线 —— 修前视图口自建一只不带座的 repo,同一份坏档在这里读到有计数 / 探针,
10
+ * 窗口 `local-session-window.ts#LocalSessionWindow.wake` 的读旁路),所以坏记录的披露座只有这一份接线 —— 修前视图口自建一只不带座的 repo,同一份坏档在这里读到有计数 / 探针,
11
11
  * 在视图口读到一个字都没有。座 = F 类 `server.session-repo.corrupt-record-skipped`(会话存档里读不出的记录丢的是可用性:历史少一截 / 列表少一行,不开任何门;
12
12
  * 计数单位 = 一条被跳过的记录,逐命中探针行,warn 每进程一次),外加会话打开作用域的计数腿(`noteReplayInSessionOpenScope`:只在 `openSessionForItself`
13
13
  * 的作用域里才记,作用域外 —— 列表 / 导出 / 视图口 —— 是空操作,那些读只进运维面)。
@@ -299,9 +299,13 @@ export declare class LocalSessionStore implements SessionStore {
299
299
  /** Synthesize a session's objective preview = the most-recent USER text message (the objective the run last saw).
300
300
  * Reads the session's entries through the repo; any read error degrades to null (never fails the list). */
301
301
  private previewOf;
302
- /** E17 — fork a session's WHOLE history to a NEW id owned by `owner` (core SessionRepo.fork). Returns the new id,
303
- * or null if the source does not exist (the route maps that to 404). */
304
- fork(sourceId: string, owner: string | null): Promise<string | null>;
302
+ /** E17 — fork a session's WHOLE history to a NEW id (core SessionRepo.fork). Returns the new id, or null if the source does not exist.
303
+ * 属主按 core `SessionStore.fork` 契约(core 7.54.0 `sessionStoreContract` 的 [ownership] 格,唯一读法 {@link forkOwnerOf}):显式串 ⇒ 该串;
304
+ * 无参 / 显式 `null` ⇒ **源会话记下的属主**(一份有主历史的拷贝永不降成匿名 —— 匿名的拷贝谁都能认领);源会话没记属主 ⇒ 拷贝也不记(读作无主)。
305
+ * 修前把参数原样写进新格:`fork(owned, null)` 铸出一份匿名的有主历史。
306
+ * 🚧 已知分歧(不在本版收):拷的是整份日志,含在飞的 assistant 轮(契约:只拷**已提交**的叶)—— 剪在飞轮的判据只能是引擎的 `settledForkOptions`
307
+ * (core 7.54.0 未导出,#2098 排在 7.55.0),本仓不写第二份;钉 `test/session-store-contract-kit.test.ts` 的 `it.fails` 格。 */
308
+ fork(sourceId: string, owner?: string | null): Promise<string | null>;
305
309
  /** 2c session-sync — EXPORT a session's full durable entry log (core `SessionRepo.exportEntries`; the
306
310
  * in-memory/file repo holds the whole log so there is no floor to bypass). null if the source is unknown / the
307
311
  * underlying repo lacks the optional seam. The owner is NOT exported — import re-stamps it. */
@@ -352,14 +356,13 @@ export declare class LocalSessionStore implements SessionStore {
352
356
  /** Drop a finished/aborted local staging handle from the registry (called by the handle on commit/abort). */
353
357
  forgetStaging(stagingId: string): void;
354
358
  /**
355
- * {@link LocalStagingHandle.commit} 的整段 —— **一次持锁**(这只会话的写锁,{@link withSessionWriteLock}):提交时重新分类(目的端此刻的日志 vs staged)
359
+ * {@link LocalStagingHandle.commit} 的整段 —— **一次持锁**(这只会话的写锁,{@link withSessionWriteLock}):属主 CAS(此刻的属主格 = 这次导入的门判过的那一格,
360
+ * 否则抛 {@link StagedCommitOwnerChangedError},一个字节不写;在判目的端日志之前判,SQL 孪生同序)→ 提交时重新分类(目的端此刻的日志 vs staged)
356
361
  * → 拒未经同意的 fork / stale / 载荷分叉 → 换装(validate-first → 删 → 导入,化身按换装律沿用)或 identical 且内容相同时只重戳属主 → **在同一次持锁里**
357
362
  * 读出落定的会话面交回。锁放下之前谁也换不掉这一位住客,所以交回的面就是这次提交落下的那一位(SQL 孪生在提交事务里读同一行,同义);
358
- * 提交之后按住客条件写的重放(会话行两位)只认这只面,不再重读。
363
+ * 提交之后按住客条件写的重放(会话行两位)只认这只面,不再重读。改属主的每一条写腿(登记 / 删除 / 导入 / 换装)都在同一把锁里,所以锁内这一读到锁放下都成立。
359
364
  */
360
- commitStagedImport(sessionId: string, owner: string | null, staged: SessionTreeEntry[], opts?: {
361
- resolution?: "overwrite-dst";
362
- }): Promise<{
365
+ commitStagedImport(sessionId: string, owner: string | null, staged: SessionTreeEntry[], opts: StagedCommitOptions): Promise<{
363
366
  relation: SyncRelation["relation"];
364
367
  face: SessionFace;
365
368
  }>;
@@ -1,5 +1,6 @@
1
1
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
2
2
  import { join, resolve } from "node:path";
3
+ import { assertFirstOwnerWritable, forkOwnerOf } from "./session-ownership.js";
3
4
  import { FileSessionRepo, SessionError, StoredSession, validateEntriesForImport } from "@sema-agent/core";
4
5
  import { z } from "zod";
5
6
  import { contentForkRelation, fastForwardSharedContentDiverged, identicalIdsAlsoIdenticalContent } from "../session-sync-content.js";
@@ -13,7 +14,7 @@ import { decodeInstructionSource, encodeInstructionSource, sameInstructionSource
13
14
  import { noteReplayInSessionOpenScope, openSessionForItself } from "../session-records-skipped-notice.js";
14
15
  import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
15
16
  import { buildObjectivePreview, isAttachmentFrame } from "../user-authored-text.js";
16
- import { classifySyncRelationshipByIds, SyncConflictError, stagingIdFor, } from "../session-sync-kernel.js";
17
+ import { classifySyncRelationshipByIds, ownerCellIs, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, } from "../session-sync-kernel.js";
17
18
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
18
19
  const isNotFound = (e) => e instanceof SessionError && e.code === "not_found";
19
20
  const SESSION_OBJECTIVE_PREVIEW_CHARS = 200;
@@ -459,6 +460,7 @@ export class LocalSessionStore {
459
460
  await this.meta.ensureLoaded();
460
461
  if (ownerRecorded(this.meta.state.entries.get(sessionId)))
461
462
  return;
463
+ assertFirstOwnerWritable(sessionId, owner);
462
464
  const existed = await this.existsInRepo(sessionId);
463
465
  const claim = existed && owner !== null && ownerClaimWindowOpen();
464
466
  const wrote = await this.writeMeta(sessionId, (before) => {
@@ -580,17 +582,21 @@ export class LocalSessionStore {
580
582
  }
581
583
  async fork(sourceId, owner) {
582
584
  await this.meta.ensureLoaded();
583
- try {
584
- const session = await this.repo.fork({ id: sourceId, createdAt: "" }, {});
585
- const id = (await session.getMetadata()).id;
586
- await this.writeMeta(id, () => ({ incarnation: mintSessionIncarnation(), owner }));
587
- return id;
588
- }
589
- catch (e) {
590
- if (isNotFound(e))
591
- return null;
592
- throw e;
593
- }
585
+ return this.withSessionWriteLock(sourceId, async () => {
586
+ const source = this.meta.state.entries.get(sourceId);
587
+ const forkOwner = forkOwnerOf(owner, source === undefined ? undefined : readSessionMetaCell(source).owner);
588
+ try {
589
+ const session = await this.repo.fork({ id: sourceId, createdAt: "" }, {});
590
+ const id = (await session.getMetadata()).id;
591
+ await this.writeMeta(id, () => (forkOwner === undefined ? mintSessionIncarnation() : { incarnation: mintSessionIncarnation(), owner: forkOwner }));
592
+ return id;
593
+ }
594
+ catch (e) {
595
+ if (isNotFound(e))
596
+ return null;
597
+ throw e;
598
+ }
599
+ });
594
600
  }
595
601
  async exportEntries(sessionId) {
596
602
  if (!this.repo.exportEntries)
@@ -657,12 +663,15 @@ export class LocalSessionStore {
657
663
  }
658
664
  async commitStagedImport(sessionId, owner, staged, opts) {
659
665
  return this.withSessionWriteLock(sessionId, async () => {
666
+ const current = await this.metaOf(sessionId);
667
+ if (!ownerCellIs(opts.expectedOwner, current === undefined ? undefined : (current.owner ?? null)))
668
+ throw new StagedCommitOwnerChangedError(sessionId);
660
669
  const dstEntries = await this.exportEntries(sessionId);
661
670
  const rel = classifySyncRelationshipByIds(staged.map((e) => e.id), dstEntries === null ? null : dstEntries.map((e) => e.id));
662
- if ((rel.relation === "fork" || rel.relation === "stale") && opts?.resolution !== "overwrite-dst") {
671
+ if ((rel.relation === "fork" || rel.relation === "stale") && opts.resolution !== "overwrite-dst") {
663
672
  throw new SyncConflictError(sessionId, rel);
664
673
  }
665
- if (rel.relation === "fast_forward" && opts?.resolution !== "overwrite-dst" && dstEntries !== null && dstEntries.length > 0) {
674
+ if (rel.relation === "fast_forward" && opts.resolution !== "overwrite-dst" && dstEntries !== null && dstEntries.length > 0) {
666
675
  const dstSet = new Set(dstEntries.map((e) => e.id));
667
676
  const diverged = fastForwardSharedContentDiverged(staged.filter((e) => dstSet.has(e.id)), dstEntries);
668
677
  if (diverged)