@sema-agent/server 7.145.0 → 7.147.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 (526) hide show
  1. package/MIGRATION.md +876 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +374 -99
  5. package/deploy/facts/CHANGES.json +25 -0
  6. package/deploy/facts/README.md +57 -0
  7. package/deploy/facts/lanes.json +61 -0
  8. package/deploy/facts/lanes.schema.json +57 -0
  9. package/deploy/facts/sandbox-rbac.json +113 -0
  10. package/deploy/facts/sandbox-rbac.schema.json +74 -0
  11. package/deploy/facts/sealed-key.json +9 -0
  12. package/deploy/facts/sealed-key.schema.json +52 -0
  13. package/deploy/sema-up/README.md +2 -2
  14. package/deploy/sema-up/chart/templates/_helpers.tpl +41 -0
  15. package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
  16. package/deploy/sema-up/chart/templates/server-v4pro.yaml +9 -4
  17. package/deploy/sema-up/chart/templates/server.yaml +9 -4
  18. package/deploy/sema-up/chart/values.yaml +8 -1
  19. package/deploy/sema-up/cluster-up.sh +2 -1
  20. package/deploy/sema-up/lib/flagshape.sh +4 -0
  21. package/deploy/sema-up/lib/lanes.json +2 -5
  22. package/deploy/sema-up/sema-up.sh +12 -9
  23. package/deploy/sema-up/stack/compose.yaml +5 -4
  24. package/deploy/sema-up/stack/sema-stack-cluster.yaml +34 -17
  25. package/dist/adoption/import-limits.d.ts +6 -0
  26. package/dist/adoption/import-limits.js +7 -0
  27. package/dist/adoption/import.d.ts +62 -0
  28. package/dist/adoption/import.js +107 -0
  29. package/dist/adoption/wire.d.ts +52 -0
  30. package/dist/adoption/wire.js +9 -0
  31. package/dist/approval-ask-machine.d.ts +2 -2
  32. package/dist/approval-card.d.ts +7 -16
  33. package/dist/approval-card.js +0 -4
  34. package/dist/approval-gate-currency.d.ts +1 -1
  35. package/dist/approval-gate-currency.js +1 -1
  36. package/dist/approval-reconciler.d.ts +1 -1
  37. package/dist/approval.d.ts +3 -3
  38. package/dist/audit.js +2 -2
  39. package/dist/bench/s1/live-deps.js +3 -1
  40. package/dist/boot/crash-last.js +2 -1
  41. package/dist/boot/deployment-mods.d.ts +11 -1
  42. package/dist/boot/deployment-mods.js +15 -2
  43. package/dist/boot/device-lane.js +7 -5
  44. package/dist/boot/engine-lease.d.ts +195 -22
  45. package/dist/boot/engine-lease.js +146 -20
  46. package/dist/boot/execution-env.d.ts +5 -0
  47. package/dist/boot/execution-env.js +57 -33
  48. package/dist/boot/execution-lane.d.ts +37 -9
  49. package/dist/boot/execution-lane.js +74 -20
  50. package/dist/boot/exit-boundary.d.ts +50 -0
  51. package/dist/boot/exit-boundary.js +32 -0
  52. package/dist/boot/exit-last.d.ts +20 -4
  53. package/dist/boot/exit-last.js +23 -5
  54. package/dist/boot/governance-seams.d.ts +2 -2
  55. package/dist/boot/governance-seams.js +1 -0
  56. package/dist/boot/leader.js +1 -1
  57. package/dist/boot/memory-boundary.d.ts +11 -11
  58. package/dist/boot/memory-boundary.js +3 -3
  59. package/dist/boot/memory-consolidation.d.ts +42 -41
  60. package/dist/boot/memory-consolidation.js +26 -28
  61. package/dist/boot/org-memory.js +3 -2
  62. package/dist/boot/parked-revive-gate.d.ts +1 -1
  63. package/dist/boot/parked-revive-gate.js +1 -1
  64. package/dist/boot/reapers.d.ts +12 -4
  65. package/dist/boot/reapers.js +40 -16
  66. package/dist/boot/resolve-spec.d.ts +2 -0
  67. package/dist/boot/resolve-spec.js +126 -123
  68. package/dist/boot/retention-lane.d.ts +4 -42
  69. package/dist/boot/runner-deps.js +1 -1
  70. package/dist/boot/session-faces.d.ts +1 -0
  71. package/dist/boot/session-faces.js +11 -3
  72. package/dist/boot/shutdown.js +27 -9
  73. package/dist/boot/stage-01-config.d.ts +1 -1
  74. package/dist/boot/stage-01-config.js +7 -3
  75. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -1
  76. package/dist/boot/stage-02-web-admin-pull.js +10 -8
  77. package/dist/boot/stage-03-store-backend.d.ts +2 -1
  78. package/dist/boot/stage-03-store-backend.js +2 -2
  79. package/dist/boot/stage-04-stores.d.ts +2 -1
  80. package/dist/boot/stage-05-execution-env.d.ts +4 -1
  81. package/dist/boot/stage-05-execution-env.js +6 -6
  82. package/dist/boot/stage-06-runners.d.ts +5 -2
  83. package/dist/boot/stage-06-runners.js +12 -13
  84. package/dist/boot/stage-07-capability-layer.d.ts +5 -2
  85. package/dist/boot/stage-07-capability-layer.js +7 -7
  86. package/dist/boot/stage-08-reapers.d.ts +5 -2
  87. package/dist/boot/stage-08-reapers.js +3 -3
  88. package/dist/boot/stage-09-leader.d.ts +6 -2
  89. package/dist/boot/stage-10-http-server.d.ts +6 -2
  90. package/dist/boot/stage-10-http-server.js +5 -3
  91. package/dist/boot/stores.d.ts +10 -0
  92. package/dist/boot/stores.js +53 -26
  93. package/dist/boot/web-admin.js +3 -3
  94. package/dist/boot/workflow-orchestration.d.ts +1 -1
  95. package/dist/boot/workflow-orchestration.js +2 -1
  96. package/dist/capabilities/center-plugins.d.ts +2 -0
  97. package/dist/capabilities/center-plugins.js +6 -2
  98. package/dist/capabilities/composite-members.d.ts +4 -1
  99. package/dist/capabilities/composite-members.js +4 -1
  100. package/dist/capabilities/execution-lane.d.ts +7 -7
  101. package/dist/capabilities/execution-lane.js +2 -2
  102. package/dist/capabilities/hands-lane.d.ts +1 -1
  103. package/dist/capabilities/scenarios.d.ts +34 -20
  104. package/dist/capabilities/scenarios.js +13 -9
  105. package/dist/capabilities/settings-contract.d.ts +29 -0
  106. package/dist/capabilities/settings-contract.js +14 -0
  107. package/dist/capabilities/skills.d.ts +5 -5
  108. package/dist/capability-facts.d.ts +56 -1
  109. package/dist/capability-facts.js +59 -8
  110. package/dist/center-credential-renewer.js +3 -2
  111. package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
  112. package/dist/config-catalog.js +19 -9
  113. package/dist/config-invariants.d.ts +18 -3
  114. package/dist/config-invariants.js +36 -19
  115. package/dist/config-lkg.d.ts +1 -1
  116. package/dist/config-lkg.js +5 -6
  117. package/dist/config-provider.d.ts +2 -2
  118. package/dist/config-provider.js +2 -0
  119. package/dist/config-types.d.ts +228 -185
  120. package/dist/config.d.ts +74 -6
  121. package/dist/config.js +146 -64
  122. package/dist/degenerate-instrument.js +1 -1
  123. package/dist/deleted-session-ids.d.ts +19 -0
  124. package/dist/deleted-session-ids.js +26 -0
  125. package/dist/deploy-facts.d.ts +155 -0
  126. package/dist/deploy-facts.js +95 -0
  127. package/dist/deployment-shape.d.ts +21 -12
  128. package/dist/deployment-shape.js +24 -6
  129. package/dist/device-ws-hub.d.ts +48 -3
  130. package/dist/device-ws-hub.js +48 -2
  131. package/dist/env-facts.d.ts +7 -5
  132. package/dist/env-facts.js +10 -6
  133. package/dist/error-code-family.d.ts +35 -0
  134. package/dist/error-code-family.js +26 -0
  135. package/dist/execution-config-root.d.ts +5 -5
  136. package/dist/execution-config-root.js +5 -5
  137. package/dist/execution-env-purpose.d.ts +14 -0
  138. package/dist/execution-env-purpose.js +29 -0
  139. package/dist/execution-lane-caps.d.ts +49 -86
  140. package/dist/execution-lane-caps.js +52 -62
  141. package/dist/execution-placement.d.ts +18 -2
  142. package/dist/execution-placement.js +19 -0
  143. package/dist/file-history-disclosure.d.ts +2 -2
  144. package/dist/fleet/drain-entry.d.ts +46 -3
  145. package/dist/fleet/drain-entry.js +6 -0
  146. package/dist/fleet/fleet-bus.d.ts +2 -1
  147. package/dist/fleet/serving-lease.js +3 -4
  148. package/dist/history-anchor.d.ts +22 -0
  149. package/dist/history-anchor.js +14 -0
  150. package/dist/hooks/branch-transcript.d.ts +1 -1
  151. package/dist/hooks/branch-transcript.js +1 -0
  152. package/dist/hooks/classic-stations.d.ts +25 -8
  153. package/dist/hooks/classic-stations.js +29 -3
  154. package/dist/hooks/command-spawn.d.ts +1 -4
  155. package/dist/hooks/command-spawn.js +0 -11
  156. package/dist/hooks/hook-runner.d.ts +85 -24
  157. package/dist/hooks/hook-runner.js +326 -109
  158. package/dist/hooks/hook-source.d.ts +14 -2
  159. package/dist/hooks/hook-source.js +8 -0
  160. package/dist/http/active-run-conflict.d.ts +1 -1
  161. package/dist/http/active-run-conflict.js +1 -1
  162. package/dist/http/admission.js +11 -1
  163. package/dist/http/approval-sweeps.d.ts +2 -5
  164. package/dist/http/approval-sweeps.js +3 -2
  165. package/dist/http/checkpoint-ctx.d.ts +76 -71
  166. package/dist/http/checkpoint-ctx.js +40 -40
  167. package/dist/http/claim-latency.d.ts +10 -0
  168. package/dist/http/claim-latency.js +12 -0
  169. package/dist/http/dispatch.js +19 -3
  170. package/dist/http/durable-run-leg.d.ts +5 -0
  171. package/dist/http/durable-run-leg.js +3 -1
  172. package/dist/http/execution-context.d.ts +7 -1
  173. package/dist/http/execution-context.js +13 -0
  174. package/dist/http/health-self-report.d.ts +6 -2
  175. package/dist/http/health-self-report.js +3 -2
  176. package/dist/http/memory-compliance-failure-reply.d.ts +2 -2
  177. package/dist/http/memory-compliance-failure-reply.js +5 -15
  178. package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
  179. package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
  180. package/dist/http/memory-store-refusal-reply.d.ts +3 -3
  181. package/dist/http/memory-store-refusal-reply.js +3 -0
  182. package/dist/http/owner-access.d.ts +11 -0
  183. package/dist/http/owner-access.js +7 -4
  184. package/dist/http/principal-gate.d.ts +28 -16
  185. package/dist/http/principal-gate.js +10 -19
  186. package/dist/http/request-guards.d.ts +2 -2
  187. package/dist/http/request-guards.js +5 -5
  188. package/dist/http/resume-legs.d.ts +29 -10
  189. package/dist/http/resume-legs.js +94 -56
  190. package/dist/http/rewind-files-reply.d.ts +34 -0
  191. package/dist/http/rewind-files-reply.js +36 -0
  192. package/dist/http/route-ctx.d.ts +43 -26
  193. package/dist/http/route-table.js +4 -0
  194. package/dist/http/routes/adoption.d.ts +11 -0
  195. package/dist/http/routes/adoption.js +80 -6
  196. package/dist/http/routes/agents-roster.js +1 -0
  197. package/dist/http/routes/approvals-assistant.js +7 -26
  198. package/dist/http/routes/background.js +1 -0
  199. package/dist/http/routes/capabilities.js +12 -3
  200. package/dist/http/routes/devices.js +1 -1
  201. package/dist/http/routes/diagnostics.d.ts +9 -14
  202. package/dist/http/routes/diagnostics.js +10 -20
  203. package/dist/http/routes/engine.d.ts +31 -0
  204. package/dist/http/routes/engine.js +81 -0
  205. package/dist/http/routes/memory-bundle.js +3 -0
  206. package/dist/http/routes/memory-consolidation.js +3 -5
  207. package/dist/http/routes/memory-origin.d.ts +2 -2
  208. package/dist/http/routes/memory-origin.js +2 -4
  209. package/dist/http/routes/memory-policy.js +5 -1
  210. package/dist/http/routes/notify-wake.js +2 -1
  211. package/dist/http/routes/observability.js +3 -6
  212. package/dist/http/routes/projects.d.ts +70 -0
  213. package/dist/http/routes/projects.js +86 -0
  214. package/dist/http/routes/rules.js +30 -2
  215. package/dist/http/routes/runs.js +15 -11
  216. package/dist/http/routes/session-sync.js +39 -35
  217. package/dist/http/routes/sessions-list.d.ts +1 -1
  218. package/dist/http/routes/sessions.d.ts +7 -1
  219. package/dist/http/routes/sessions.js +108 -2
  220. package/dist/http/routes/tasks.js +32 -8
  221. package/dist/http/routes/trace-usage.js +1 -1
  222. package/dist/http/run-concurrency-cap.d.ts +86 -0
  223. package/dist/http/run-concurrency-cap.js +54 -0
  224. package/dist/http/server.d.ts +31 -14
  225. package/dist/http/server.js +29 -3
  226. package/dist/http/stored-body-replay.d.ts +12 -7
  227. package/dist/http/stored-body-replay.js +28 -5
  228. package/dist/http/wire-types.d.ts +27 -13
  229. package/dist/index.d.ts +2 -2
  230. package/dist/index.js +1 -1
  231. package/dist/instruction-source-notice.d.ts +1 -1
  232. package/dist/json-body-limit.d.ts +8 -0
  233. package/dist/json-body-limit.js +2 -0
  234. package/dist/leader/diffout.d.ts +1 -1
  235. package/dist/local-data-root.d.ts +2 -2
  236. package/dist/memory-embedder-reembed.d.ts +38 -0
  237. package/dist/memory-embedder-reembed.js +20 -0
  238. package/dist/memory-incident-sink.d.ts +0 -28
  239. package/dist/memory-incident-sink.js +5 -0
  240. package/dist/memory-operator-faces.d.ts +9 -4
  241. package/dist/memory-scope-stores.d.ts +9 -7
  242. package/dist/memory-scope-stores.js +3 -14
  243. package/dist/memory-scope.d.ts +38 -2
  244. package/dist/memory-scope.js +33 -2
  245. package/dist/memory-store-refusal.d.ts +17 -4
  246. package/dist/memory-store-refusal.js +1 -1
  247. package/dist/memory-sync-client.d.ts +2 -0
  248. package/dist/memory-sync-client.js +13 -11
  249. package/dist/memory-sync.js +1 -1
  250. package/dist/model-compat.d.ts +35 -21
  251. package/dist/model-compat.js +49 -21
  252. package/dist/model-entry-refusal.d.ts +11 -1
  253. package/dist/model-entry-refusal.js +7 -0
  254. package/dist/namespaced-rule-name.d.ts +18 -1
  255. package/dist/namespaced-rule-name.js +12 -0
  256. package/dist/net-address.d.ts +53 -0
  257. package/dist/net-address.js +111 -0
  258. package/dist/observability/fail-open.d.ts +29 -17
  259. package/dist/observability/fail-open.js +29 -17
  260. package/dist/observability/metrics.d.ts +2 -0
  261. package/dist/observability/metrics.js +2 -0
  262. package/dist/observability/secret-env-scrub.d.ts +30 -4
  263. package/dist/observability/secret-env-scrub.js +15 -3
  264. package/dist/orchestration/workflow-agent-session-index.d.ts +7 -12
  265. package/dist/orchestration/workflow-agent-session-index.js +1 -6
  266. package/dist/orchestration/workflow-completion-inbox.d.ts +26 -6
  267. package/dist/orchestration/workflow-completion-inbox.js +63 -45
  268. package/dist/orchestration/workflow-notify-journal.js +2 -3
  269. package/dist/org-delivery-gate.js +1 -1
  270. package/dist/org-memory-admission.d.ts +5 -2
  271. package/dist/org-memory-admission.js +25 -10
  272. package/dist/parked-decide.d.ts +33 -26
  273. package/dist/parked-decide.js +36 -44
  274. package/dist/peer-directory.d.ts +6 -12
  275. package/dist/peer-directory.js +5 -1
  276. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  277. package/dist/plugins/approval-ask-store-file.js +37 -4
  278. package/dist/plugins/approval-ask-store-memory.d.ts +5 -3
  279. package/dist/plugins/approval-ask-store-memory.js +11 -2
  280. package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
  281. package/dist/plugins/approval-ask-store-sql.js +3 -1
  282. package/dist/plugins/blob-backend.d.ts +38 -195
  283. package/dist/plugins/blob-backend.js +33 -229
  284. package/dist/plugins/caching-session-store.d.ts +12 -5
  285. package/dist/plugins/caching-session-store.js +3 -0
  286. package/dist/plugins/checkpoint-index-columns.d.ts +1 -1
  287. package/dist/plugins/checkpoint-index-columns.js +3 -2
  288. package/dist/plugins/checkpoint-store-face.d.ts +426 -0
  289. package/dist/plugins/checkpoint-store-face.js +152 -0
  290. package/dist/plugins/device-store-sql.d.ts +2 -2
  291. package/dist/plugins/device-store-sql.js +5 -10
  292. package/dist/plugins/execution-lane-rename-m40.d.ts +20 -0
  293. package/dist/plugins/execution-lane-rename-m40.js +10 -0
  294. package/dist/plugins/execution-placement-sql.d.ts +63 -88
  295. package/dist/plugins/execution-placement-sql.js +94 -54
  296. package/dist/plugins/file-run-store.d.ts +26 -18
  297. package/dist/plugins/file-run-store.js +64 -75
  298. package/dist/plugins/fork-routing-session-store.d.ts +6 -2
  299. package/dist/plugins/fork-routing-session-store.js +2 -2
  300. package/dist/plugins/imported-layer-root-retirement.d.ts +22 -11
  301. package/dist/plugins/imported-layer-root-retirement.js +4 -8
  302. package/dist/plugins/inbox-payload-column.d.ts +14 -0
  303. package/dist/plugins/inbox-payload-column.js +13 -0
  304. package/dist/plugins/local-checkpoint-store.d.ts +7 -2
  305. package/dist/plugins/local-checkpoint-store.js +13 -8
  306. package/dist/plugins/local-data-dir-modes-m39.d.ts +48 -0
  307. package/dist/plugins/local-data-dir-modes-m39.js +182 -0
  308. package/dist/plugins/local-engine-high-water.d.ts +11 -0
  309. package/dist/plugins/local-engine-high-water.js +59 -0
  310. package/dist/plugins/local-session-store.d.ts +119 -30
  311. package/dist/plugins/local-session-store.js +156 -53
  312. package/dist/plugins/local-task-attachment-store.d.ts +2 -0
  313. package/dist/plugins/local-task-attachment-store.js +10 -11
  314. package/dist/plugins/memory-embedder-fingerprint.d.ts +33 -10
  315. package/dist/plugins/memory-embedder-fingerprint.js +52 -12
  316. package/dist/plugins/memory-embedder.d.ts +1 -1
  317. package/dist/plugins/memory-embedder.js +2 -2
  318. package/dist/plugins/memory-engine-pg.d.ts +30 -6
  319. package/dist/plugins/memory-engine-pg.js +96 -214
  320. package/dist/plugins/memory-engine-sql-apply.d.ts +130 -0
  321. package/dist/plugins/memory-engine-sql-apply.js +358 -0
  322. package/dist/plugins/memory-engine-tidb.d.ts +38 -9
  323. package/dist/plugins/memory-engine-tidb.js +99 -230
  324. package/dist/plugins/memory-key-guards.d.ts +1 -1
  325. package/dist/plugins/memory-origin-law.d.ts +3 -3
  326. package/dist/plugins/one-time-migrations.d.ts +169 -83
  327. package/dist/plugins/one-time-migrations.js +295 -235
  328. package/dist/plugins/owner-only-file-mode.d.ts +29 -0
  329. package/dist/plugins/owner-only-file-mode.js +141 -0
  330. package/dist/plugins/permission-rule-store-file.d.ts +17 -3
  331. package/dist/plugins/permission-rule-store-file.js +20 -1
  332. package/dist/plugins/pg-pool.d.ts +2 -1
  333. package/dist/plugins/pg-pool.js +12 -5
  334. package/dist/plugins/release-version.d.ts +20 -0
  335. package/dist/plugins/release-version.js +28 -0
  336. package/dist/plugins/remote-env-adb.d.ts +20 -4
  337. package/dist/plugins/remote-env-adb.js +39 -11
  338. package/dist/plugins/remote-env-e2b.d.ts +12 -3
  339. package/dist/plugins/remote-env-e2b.js +6 -4
  340. package/dist/plugins/remote-env-host.d.ts +9 -1
  341. package/dist/plugins/remote-env-host.js +6 -5
  342. package/dist/plugins/remote-env-k8s.d.ts +5 -2
  343. package/dist/plugins/remote-env-k8s.js +15 -4
  344. package/dist/plugins/remote-env-local-docker.d.ts +4 -1
  345. package/dist/plugins/remote-env-local-docker.js +3 -3
  346. package/dist/plugins/remote-env-ssh.d.ts +8 -2
  347. package/dist/plugins/remote-env-ssh.js +26 -24
  348. package/dist/plugins/remote-shell.d.ts +59 -30
  349. package/dist/plugins/remote-shell.js +175 -62
  350. package/dist/plugins/retention-declaration.d.ts +37 -0
  351. package/dist/plugins/retention-declaration.js +9 -0
  352. package/dist/plugins/retention-deployment.d.ts +126 -0
  353. package/dist/plugins/retention-deployment.js +88 -0
  354. package/dist/plugins/retention-lane-store-sql.d.ts +74 -4
  355. package/dist/plugins/retention-lane-store-sql.js +103 -1
  356. package/dist/plugins/retention-sql.d.ts +29 -0
  357. package/dist/plugins/retention-sql.js +8 -0
  358. package/dist/plugins/run-store-sql.js +2 -2
  359. package/dist/plugins/schema-consistency.d.ts +0 -6
  360. package/dist/plugins/schema-consistency.js +1 -2
  361. package/dist/plugins/schema-ensure-legs.d.ts +1 -1
  362. package/dist/plugins/schema-ensure-legs.js +13 -3
  363. package/dist/plugins/session-ext-table.d.ts +74 -0
  364. package/dist/plugins/session-ext-table.js +121 -0
  365. package/dist/plugins/session-fork.d.ts +31 -9
  366. package/dist/plugins/session-fork.js +3 -2
  367. package/dist/plugins/session-placement.d.ts +4 -2
  368. package/dist/plugins/session-row-sql.d.ts +8 -7
  369. package/dist/plugins/session-row-sql.js +15 -7
  370. package/dist/plugins/session-store-sql.d.ts +131 -0
  371. package/dist/plugins/session-store-sql.js +298 -0
  372. package/dist/plugins/session-workspace-cell.d.ts +2 -2
  373. package/dist/plugins/session-workspace-cell.js +2 -2
  374. package/dist/plugins/shared-memory-store-sql.d.ts +10 -0
  375. package/dist/plugins/shared-memory-store-sql.js +13 -13
  376. package/dist/plugins/snapshot-bucket-presence.js +1 -1
  377. package/dist/plugins/sql-driver.d.ts +23 -7
  378. package/dist/plugins/sql-driver.js +41 -8
  379. package/dist/plugins/sql-errors.d.ts +5 -2
  380. package/dist/plugins/sql-errors.js +4 -0
  381. package/dist/plugins/sql-json-column.d.ts +5 -1
  382. package/dist/plugins/store-backend.d.ts +46 -16
  383. package/dist/plugins/store-backend.js +192 -69
  384. package/dist/plugins/store-contracts.d.ts +10 -13
  385. package/dist/plugins/store-contracts.js +1 -9
  386. package/dist/plugins/store-sweep.d.ts +63 -0
  387. package/dist/plugins/store-sweep.js +48 -0
  388. package/dist/plugins/task-attachment-store.d.ts +6 -8
  389. package/dist/plugins/task-attachment-store.js +15 -10
  390. package/dist/plugins/task-list-store-sql.d.ts +1 -1
  391. package/dist/plugins/text-column-capacity.d.ts +21 -0
  392. package/dist/plugins/text-column-capacity.js +5 -0
  393. package/dist/plugins/tidb-pool.d.ts +3 -2
  394. package/dist/plugins/tidb-pool.js +20 -32
  395. package/dist/plugins/user-input-ledger-sql.d.ts +1 -1
  396. package/dist/prompt-screen.d.ts +6 -9
  397. package/dist/prompt-screen.js +2 -5
  398. package/dist/read-face-posture.d.ts +6 -6
  399. package/dist/read-face-posture.js +2 -2
  400. package/dist/request-value-closure.d.ts +5 -5
  401. package/dist/request-value-closure.js +1 -1
  402. package/dist/rules-consent.d.ts +48 -27
  403. package/dist/rules-consent.js +147 -23
  404. package/dist/run-cancel-context.d.ts +4 -1
  405. package/dist/run-cancel-context.js +2 -1
  406. package/dist/run-local.js +17 -13
  407. package/dist/runs.d.ts +48 -30
  408. package/dist/runs.js +27 -17
  409. package/dist/runtime-caps-resolver.d.ts +15 -8
  410. package/dist/runtime-caps-resolver.js +14 -10
  411. package/dist/sandbox-egress-policy.js +6 -11
  412. package/dist/sandbox-rbac-rules.d.ts +88 -0
  413. package/dist/sandbox-rbac-rules.js +80 -0
  414. package/dist/sealed-key-layout.d.ts +16 -0
  415. package/dist/sealed-key-layout.js +3 -0
  416. package/dist/sealed-key.js +8 -6
  417. package/dist/security.d.ts +43 -9
  418. package/dist/security.js +9 -2
  419. package/dist/sema-config-dir.js +2 -2
  420. package/dist/server-secret-env.d.ts +2 -0
  421. package/dist/service-version.d.ts +2 -1
  422. package/dist/service-version.js +2 -2
  423. package/dist/session-error-wire.d.ts +1 -1
  424. package/dist/session-incarnation.d.ts +8 -5
  425. package/dist/session-notify-door.d.ts +5 -3
  426. package/dist/session-resident-background.d.ts +110 -0
  427. package/dist/session-resident-background.js +88 -0
  428. package/dist/session-row.d.ts +1 -1
  429. package/dist/session-sync-kernel.d.ts +53 -14
  430. package/dist/session-sync-kernel.js +70 -1
  431. package/dist/session-sync.d.ts +5 -3
  432. package/dist/session-workspace.d.ts +12 -2
  433. package/dist/session-workspace.js +6 -0
  434. package/dist/sighup-idle.d.ts +9 -7
  435. package/dist/sighup-idle.js +4 -5
  436. package/dist/spec-fields.d.ts +16 -0
  437. package/dist/spec-fields.js +7 -0
  438. package/dist/store-posture.d.ts +2 -1
  439. package/dist/stored-request-env-credentials-m38.d.ts +3 -0
  440. package/dist/stored-request-env-credentials-m38.js +8 -0
  441. package/dist/stored-request-hooks-m20.d.ts +3 -2
  442. package/dist/stored-request-hooks-m20.js +19 -14
  443. package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
  444. package/dist/stores-sql/blob-index-sql.d.ts +148 -0
  445. package/dist/stores-sql/blob-index-sql.js +154 -0
  446. package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +14 -428
  447. package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +43 -220
  448. package/dist/{plugins → stores-sql}/file-history-store-sql.d.ts +23 -11
  449. package/dist/{plugins → stores-sql}/file-history-store-sql.js +23 -18
  450. package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
  451. package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
  452. package/dist/{plugins → stores-sql}/pg-session-storage.d.ts +27 -67
  453. package/dist/{plugins → stores-sql}/pg-session-storage.js +82 -207
  454. package/dist/{plugins → stores-sql}/retention-store-sql.d.ts +123 -217
  455. package/dist/{plugins → stores-sql}/retention-store-sql.js +66 -190
  456. package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
  457. package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
  458. package/dist/{plugins → stores-sql}/session-event-payload.d.ts +1 -1
  459. package/dist/{plugins → stores-sql}/session-event-payload.js +1 -1
  460. package/dist/stores-sql/session-policy-store-sql.d.ts +1 -1
  461. package/dist/stores-sql/session-write-hooks.d.ts +59 -0
  462. package/dist/stores-sql/session-write-hooks.js +2 -0
  463. package/dist/{plugins → stores-sql}/tidb-session-storage.d.ts +10 -11
  464. package/dist/{plugins → stores-sql}/tidb-session-storage.js +10 -21
  465. package/dist/{plugins → stores-sql}/tidb-session-store.d.ts +35 -112
  466. package/dist/{plugins → stores-sql}/tidb-session-store.js +73 -169
  467. package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +3 -3
  468. package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
  469. package/dist/stores-sql/windowed-session-storage.d.ts +56 -0
  470. package/dist/stores-sql/windowed-session-storage.js +61 -0
  471. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
  472. package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
  473. package/dist/task-a2a.d.ts +7 -1
  474. package/dist/task-a2a.js +15 -14
  475. package/dist/task-cwd.d.ts +11 -3
  476. package/dist/task-cwd.js +17 -1
  477. package/dist/task-mcp.d.ts +10 -2
  478. package/dist/task-mcp.js +13 -16
  479. package/dist/task-settings.d.ts +68 -8
  480. package/dist/task-settings.js +63 -6
  481. package/dist/tool-approval.d.ts +23 -10
  482. package/dist/tool-approval.js +10 -6
  483. package/dist/trace/core-keyset-guard.d.ts +17 -10
  484. package/dist/trace/engine-notice-wire.d.ts +1 -1
  485. package/dist/trace/engine-notice-wire.js +4 -0
  486. package/dist/trace/exit-seat.d.ts +29 -1
  487. package/dist/trace/exit-seat.js +4 -0
  488. package/dist/trace/project.d.ts +33 -16
  489. package/dist/trace/project.js +21 -9
  490. package/dist/trace/redact.d.ts +13 -2
  491. package/dist/trace/redact.js +347 -78
  492. package/dist/trace/self-minted-wire.d.ts +587 -16
  493. package/dist/trace/self-minted-wire.js +215 -9
  494. package/dist/trace/sema-provenance.d.ts +45 -41
  495. package/dist/trace/sema-provenance.js +81 -10
  496. package/dist/trace/task-notification-facets.d.ts +70 -2
  497. package/dist/trace/task-notification-facets.js +71 -1
  498. package/dist/trace/wire-projection-faces.d.ts +39 -3
  499. package/dist/trace/wire-projection-faces.js +27 -2
  500. package/dist/unread-request-members.d.ts +66 -0
  501. package/dist/unread-request-members.js +41 -0
  502. package/dist/usage-analytics.d.ts +4 -0
  503. package/dist/user-input-ledger.d.ts +2 -2
  504. package/dist/web-admin-client/apply-effective.js +22 -26
  505. package/dist/web-admin-client/candidate-gate.d.ts +35 -3
  506. package/dist/web-admin-client/candidate-gate.js +53 -5
  507. package/dist/web-admin-client/center-request.d.ts +4 -1
  508. package/dist/web-admin-client/center-request.js +2 -1
  509. package/dist/web-admin-client/effective-keys.js +0 -1
  510. package/dist/web-admin-client/facade.d.ts +1 -1
  511. package/dist/web-admin-client/http-client.d.ts +1 -0
  512. package/dist/web-admin-client/http-client.js +17 -9
  513. package/dist/web-admin-client/restart-signal.js +5 -2
  514. package/dist/web-admin-client/skills-mcp.d.ts +20 -6
  515. package/dist/web-admin-client/skills-mcp.js +26 -21
  516. package/dist/web-admin-client/types.d.ts +34 -49
  517. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +2 -2
  518. package/package.json +22 -13
  519. package/scripts/drain-check-local.mjs +8 -55
  520. package/scripts/drain-check.mysql.sql +3 -40
  521. package/scripts/drain-check.pg.sql +3 -40
  522. package/skills/update-config.md +1 -1
  523. package/wire-changes/server@7.146.0.json +3221 -0
  524. package/wire-changes/server@7.147.0.json +813 -0
  525. package/dist/approval-batch-census.d.ts +0 -27
  526. package/dist/approval-batch-census.js +0 -27
@@ -12,19 +12,15 @@
12
12
  */
13
13
  import { type AcquiredSession, type PlacedSessionRow, type SessionHead, type SessionPlacementRecord, type SessionStore, type SessionTreeEntry } from "@sema-agent/core";
14
14
  import type { Pool } from "mysql2/promise";
15
- import { type InstructionSourceBaseline } from "../instruction-source-notice.js";
16
- import type { SessionWorkspace } from "../session-workspace.js";
17
15
  import type { RetentionDeclaration } from "@sema-agent/core";
18
- import type { SessionSummary } from "./store-contracts.js";
19
- import { type ClientSessionRowWrite, type ReplaySessionRowWrite, type ReplaySessionRowWriteOutcome, type SessionRowPatch, type SessionRowWriteOutcome } from "../session-row.js";
20
- import { type PlacementAcquireOpts } from "./session-placement.js";
16
+ import type { SessionWriteHooks } from "./session-write-hooks.js";
17
+ import { type PlacementAcquireOpts } from "../plugins/session-placement.js";
21
18
  import { type StagingHandle } from "../session-sync-kernel.js";
22
- import { type ForkTarget, type ForkToOutcome } from "./session-fork.js";
23
- import { type ExecutionPlacementSeat } from "../execution-placement.js";
19
+ import { type ForkPlacement, type ForkTarget, type ForkToOutcome } from "../plugins/session-fork.js";
24
20
  export declare class TiDBSessionStore implements SessionStore {
25
21
  private readonly pool;
26
22
  /** 托管留存声明 = 装配交给它的词(后端按扫车道开不开给;不给 ⇒ `"none"`)。读法逐字见
27
- * `retention-store-sql.ts` 的 {@link MANAGED_RETENTION}。 */
23
+ * `retention-declaration.ts` 的 {@link MANAGED_RETENTION}。 */
28
24
  readonly retention: RetentionDeclaration;
29
25
  /** [ref] C18(core 5.48.0 [ref]):placement **声明**。六条义务的实装点逐条见
30
26
  * {@link SESSION_PLACEMENTS} 顶注与本类的 acquire / listSessions / release / listPlaced /
@@ -39,15 +35,11 @@ export declare class TiDBSessionStore implements SessionStore {
39
35
  readonly durability: "durable";
40
36
  /** In-flight acquisitions keyed by id, so concurrent acquire(sameId) in one process share one. */
41
37
  private readonly pending;
42
- /**
43
- * 执行席 —— 根会话行上这一位住客的那一格的唯一读口与 place / markMirror 两只写口(规则在 `src/execution-placement.ts`;
44
- * 实现与语句的唯一属主 = `execution-placement-sql.ts`,两孪生同一只实现,本店只交自己的池)。
45
- */
46
- readonly executionPlacement: ExecutionPlacementSeat;
47
- /** 本店的池包成的 SqlDriver:执行席与会话行格写原语的事务都经它借连接(连接初始化钉住事务读语义,`sql-driver.ts`)。 */
48
- private readonly db;
49
- constructor(pool: Pool, opts?: {
38
+ /** 部署的事务内钩(组合座交入;导入 / 删除 / fork 的写事务锁住根行之后、写之前调,`session-write-hooks.ts`)。 */
39
+ private readonly hooks;
40
+ constructor(pool: Pool, opts: {
50
41
  readonly retention?: RetentionDeclaration;
42
+ readonly hooks: SessionWriteHooks;
51
43
  });
52
44
  acquire(sessionId?: string, opts?: PlacementAcquireOpts): Promise<AcquiredSession>;
53
45
  private load;
@@ -111,77 +103,6 @@ export declare class TiDBSessionStore implements SessionStore {
111
103
  /** 真删除一只 placed 会话的两张表(条件删门在 `placement_kind IS NOT NULL`;事务内 meta 先删=提交点,
112
104
  * 与 {@link deleteSession} 同序)。任何 I/O 失败上浮成 `SessionError("storage")`,绝不吞。 */
113
105
  private deletePlacedSession;
114
- /**
115
- * E16 — list the caller's sessions (CC /resume picker), keyset-paginated newest-first by last activity.
116
- * Session IDENTITY/enumeration comes from `session_meta` (owner-filtered on the service's OWN owner column);
117
- * a LEFT JOIN onto the `task_run` runs ledger supplies run-level PREVIEW (latest run's objective/status,
118
- * run count) but never gates which sessions exist — a session with zero runs still lists (preview null/0).
119
- * Keyset on `(updated_at, session_id)`; `updated_at` is the session's last-activity marker (touch on every
120
- * run/append). The shipped {@link SessionSummary} shape is preserved (additive): `lastActivityAt` =
121
- * `session_meta.updated_at`, `firstActivityAt` = `session_meta.created_at`.
122
- */
123
- listSessions(opts: {
124
- owner?: string;
125
- includeUnowned?: boolean;
126
- cursor?: {
127
- lastActivityAt: string;
128
- sessionId: string;
129
- };
130
- limit: number;
131
- q?: string;
132
- }): Promise<SessionSummary[]>;
133
- /** ONE session's list row — {@link listSessions}' projection (same SELECT, same mapper) scoped to one id; `undefined` = not a
134
- * row of the list face (no such session / placed). The run-preview subqueries are scoped to the same id (an index read on
135
- * `task_run.session_id`, not the whole-ledger aggregate the list pays). */
136
- sessionSummaryOf(sessionId: string): Promise<SessionSummary | undefined>;
137
- /** {@link sessionSummaryOf} 那一读 + 读到它时行上的住客(写原语的应答行要复核住客,`settleSessionRowWrite`)。 */
138
- private listRowOf;
139
- /**
140
- * 会话行格写原语 —— 用户标题 / 置顶的唯一写口(规则与判序:`src/session-row.ts` 头注「写原语」;事务与语句:`session-row-sql.ts`,PG 孪生同一只实现)。
141
- * 一只经 `SqlDriver.connect()` 的事务锁住根行、交 core 判官判行期望(属主 + 化身)→ 写者身份(客户端写在镜像上 ⇒ `mirror`)→ 标签 → 写;
142
- * 应答行在提交 / 回滚之后另读(列表行同一只投影,含新 `rowTag`)、复核仍是判过的那一位住客。只写列表面上的行(委派分区行 ⇒ `absent`);
143
- * `updated_at` 不动(改名不是活动)。重载按入口分:`replay` 的结局只有 `written` / `absent`。
144
- */
145
- writeSessionRowCells(sessionId: string, cells: SessionRowPatch, how: ClientSessionRowWrite): Promise<SessionRowWriteOutcome<SessionSummary>>;
146
- writeSessionRowCells(sessionId: string, cells: SessionRowPatch, how: ReplaySessionRowWrite): Promise<ReplaySessionRowWriteOutcome<SessionSummary>>;
147
- /**
148
- * The list projection's ONE SELECT + row mapper ({@link listSessions} and {@link sessionSummaryOf} both read through it).
149
- * `runSessionId` scopes the two run-preview subqueries to one session (the single-row read); the list leaves them whole.
150
- * Placeholder order follows the text: subquery params, then `where` params, then the LIMIT.
151
- * `sm.incarnation` is read for the row tag only (`rowTag`, `sessionRowTagKey` — present-iff the row has a token) and the answer row's occupant
152
- * check; it never enters the list row itself.
153
- */
154
- private selectListRows;
155
- /** Cheap pre-LLM probe: "none" = session_meta row not yet REGISTERED (createRun fires
156
- * before the Runner's session acquire — the titler retries), "untitled" / "titled" as read. */
157
- probeTitle(sessionId: string): Promise<"none" | "untitled" | "titled">;
158
- /** The auto-titler's write: lands ONLY when title IS NULL (a concurrent/prior title — the user's included — wins;
159
- * writer order: `src/session-row.ts`). Returns whether this call set it. */
160
- setTitleIfNull(sessionId: string, title: string): Promise<boolean>;
161
- /**
162
- * [ref] —— 换源通告的基线读(一行 PK SELECT;列的三态读法见 `tidb-pool.ts` 的列注)。行不在 ⇒ 读作「没记过」
163
- * (loader 跑在 acquire 之后,行恒在;不在 = 被带外删了,没有下一 run 可比)。
164
- */
165
- readInstructionSource(sessionId: string): Promise<InstructionSourceBaseline>;
166
- /**
167
- * [ref] —— CAS 覆写基线(期望值 = 调用方读到的那一份;不符 ⇒ 0 行、不抛 —— 契约见 `InstructionSourceStore`)。
168
- * **不**碰 `updated_at`(那是会话活动时刻,留存腿按它判龄;记基线不是一次会话活动)。
169
- * 两条基线查询都带每查询超时(`BASELINE_IO_DEADLINE_MS`,mysql2 的 `{sql, timeout}` 形):被行锁卡住的写由查询自己
170
- * 在期限内拒。⚠️ mysql2 的超时是客户端侧的 —— 拒之后那条连接仍被挂着的命令占到服务端结束(S-9 同形,如实披露)。
171
- */
172
- recordInstructionSource(sessionId: string, expected: InstructionSourceBaseline, source: string | null): Promise<void>;
173
- /**
174
- * 会话行的位置与项目那一格(`session_meta.workspace`;域律 `src/session-workspace.ts`,列属主 `session-workspace-cell.ts`)。
175
- * 一行 PK 读,按文本投影、恰好解一次;行不在 / 格为 NULL ⇒ `undefined`;格读不出 ⇒ 读作没有记录并计一次(F 类,见列属主头注)。
176
- */
177
- readSessionWorkspace(sessionId: string): Promise<SessionWorkspace | undefined>;
178
- /**
179
- * 整格覆写(最后一帧胜;唯一调用方 = 腿的记录座,它在腿的流循环里一笔一笔等写完)。**住客围栏**:只写化身仍是 `incarnation` 的那一行 ——
180
- * 行不在、或同 id 已换了住客 ⇒ 0 行、不抛(那一行不是这条腿的)。**不**碰 `updated_at`(那是会话活动时刻,留存腿按它判龄;记下站在哪里不是一次会话活动)。
181
- * 走店的一般查询面(与腿循环里的账本追加同一形,不另设每查询的客户端超时):mysql2 的客户端超时只是不再等,服务端照把那条 UPDATE 跑完 ——
182
- * 一笔「放弃等待却仍会落地」的写正是这条腿离开之后还能盖掉下一条腿的那一形。卡住的上界归服务端(锁等待超时)与部署的池策略。
183
- */
184
- recordSessionWorkspace(sessionId: string, incarnation: string, workspace: SessionWorkspace): Promise<void>;
185
106
  /**
186
107
  * E17 — fork a session: copy its WHOLE durable history to a NEW uuidv7 session owned by `owner`, mirroring
187
108
  * core's `PgSessionRepo.fork`. Entry ids / parent links are per-session and copy VERBATIM; only `seq` is
@@ -194,14 +115,17 @@ export declare class TiDBSessionStore implements SessionStore {
194
115
  * We instead SELECT the entire `session_event` log ordered by seq — every durable entry, compaction floor
195
116
  * bypassed — and re-seq it dense from 0 into the new session, ids/parents/payload byte-verbatim.
196
117
  */
197
- fork(sourceId: string, owner?: string | null): Promise<string | null>;
118
+ fork(sourceId: string, owner?: string | null, opts?: {
119
+ placement?: ForkPlacement;
120
+ }): Promise<string | null>;
198
121
  /**
199
122
  * **fork 续写的店契约**(三店同一只私有体;core `fork` 是它的投影):把源会话的整份历史拷进调用方给的目标 id。
200
- * 1. **原子目标创建**:事务首句 `INSERT INTO session_meta (…, fork_key) VALUES (:target, …)`,主键即排他(meta 先,随后拷事件、COMMIT);
201
- * 2. **赢家分类**:撞上已在的目标 ⇒ 输方整笔回滚,在新视图里读目标、按属主与 fork 键分类({@link classifyForkTarget});读不到(赢方在分类前回滚或被删)
123
+ * 1. **原子目标创建**:事务首句 `INSERT INTO session_meta (…) VALUES (:target, …)`,主键即排他(meta 先,随后事务内钩 `fork` —— 部署在同一事务里记下 fork 键
124
+ * (部署格,旁表)—— 再拷事件、COMMIT);
125
+ * 2. **赢家分类**:撞上已在的目标 ⇒ 输方整笔回滚,经钩读目标那一行的属主与 fork 键(一次读)、分类({@link classifyForkTarget});读不到(赢方在分类前回滚或被删)
202
126
  * ⇒ 整笔重跑一次,再撞而仍读不到 ⇒ {@link ForkTargetUnsettledError}(不猜);
203
- * 3. 单事务 ⇒ 无中间态;`fork_key` 只由这里写(此后每个写携带:UPDATE 只碰点名列,整段换装经 `carriedRowCells` 带回)。
204
- * 源不在 ⇒ `null`(先于目标判:源被删之后的同键重试答「源不在」)。新行 = 新住客(化身新铸),执行格 absent(INSERT 列名显式)。
127
+ * 3. 单事务 ⇒ 无中间态;fork 键只由这一钩写。
128
+ * 源不在 ⇒ `null`(先于目标判:源被删之后的同键重试答「源不在」)。新行 = 新住客(化身新铸),部署格全缺省(执行格 absent)。
205
129
  */
206
130
  forkTo(sourceId: string, owner: string | null, target: ForkTarget): Promise<ForkToOutcome | null>;
207
131
  /** {@link forkTo} 的一次尝试:`"unsettled"` = 撞上目标而回滚之后读不到它(交给外层重跑一次)。 */
@@ -253,33 +177,32 @@ export declare class TiDBSessionStore implements SessionStore {
253
177
  * re-implement any of those checks in SQL ([[core-service-boundary]]; the same discipline fork's hand-SQL
254
178
  * was warned about). A violation throws `SessionError("invalid_session")` and NOTHING is written.
255
179
  * The write: re-seq dense from 0, entry_id/parent_id/payload/ts verbatim, in ONE txn (COMMIT is the sole commit point), written
256
- * **lock-or-create, meta first** (读写序律,`execution-placement-sql.ts` 头注):the `session_meta` row is occupied first (an existing row ⇒
257
- * refused before anything else about it is read), locked, the run claim on the root is read on THIS connection (a claim ⇒
258
- * `SessionActiveRunError`, nothing written), then the entries. The leaf is the replayed leaf (core's `leafIdAfterEntry`), matching core's import.
180
+ * **lock-or-create, meta first**:the `session_meta` row is occupied first (an existing row ⇒ refused before anything else about it is read), locked,
181
+ * then the deployment's transaction hook `import` runs on THIS connection (its run claim on the root ⇒ refused, nothing written; its cells for an
182
+ * imported row), then the entries. The leaf is the replayed leaf (core's `leafIdAfterEntry`), matching core's import.
259
183
  */
260
184
  importEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
261
185
  /**
262
- * 2c session-sync (§7/§8) — IDEMPOTENT REPLACE: purge any existing conversation log for `sessionId` then re-import
263
- * `entries` verbatim under `owner`, ALL in ONE txn. Unlike {@link importEntries} (fresh-only: an existing session at the
264
- * destination is refused), replace is safe to run OVER an existing session
265
- * — the §7 fast-forward path and a §8 retry-to-completion both land on it. The validate gate runs FIRST (fail-closed,
266
- * before any write), then the OWNER-GUARDED meta delete (defense-in-depth, mirrors {@link deleteSession}
267
- * — a wrong-owner caller can never replace another tenant's session: its delete matches nothing and the re-insert collides on the PK),
268
- * then the SAME shared write body importEntries uses (session_meta first, the in-txn claim read, then the events swapped) — ONE source of
269
- * the write logic, no copy-paste drift. COMMIT is the sole commit point: a mid-replace crash rolls the whole swap back.
186
+ * 2c session-sync (§7/§8) — IDEMPOTENT REPLACE: rewrite the conversation log of `sessionId` with `entries` verbatim under `owner`, ALL in ONE txn.
187
+ * Unlike {@link importEntries} (fresh-only: an existing session at the destination is refused), replace is safe to run OVER an existing
188
+ * session — the §7 fast-forward path and a §8 retry-to-completion both land on it. The validate gate runs FIRST (fail-closed, before any write),
189
+ * then the row: an absent id is created (a fresh occupant, like {@link importEntries}); an existing row is rewritten IN PLACE only when it is the
190
+ * importer's (`owner <=> ?`, defense-in-depth — a wrong-owner caller can never replace another tenant's session: nothing is written) and not a
191
+ * delegation transcript — the same occupant keeps its incarnation and its deployment cells (the side table row is not touched by the store), its
192
+ * created time is the replace's (the log is new). Then the SAME shared write body importEntries uses (the hook, then the events swapped) —
193
+ * ONE source of the write logic. COMMIT is the sole commit point: a mid-replace crash rolls the whole swap back.
270
194
  */
271
195
  replaceEntries(sessionId: string, owner: string | null, entries: SessionTreeEntry[]): Promise<void>;
272
196
  /**
273
197
  * Shared write half of {@link importEntries} / {@link replaceEntries} (ONE source of the write logic, no drift), in the global lock
274
- * order (根 `session_meta` 先于 `task_*` 与 `session_event`):
275
- * ① session_meta FIRST — `create` occupies the row (`ON DUPLICATE KEY UPDATE session_id = session_id`: an existing row is left as it is),
276
- * `replace` is a plain INSERT after the caller's owner-guarded delete (a foreign row collides on the PK — the existing refusal);
277
- * owner = the RE-STAMPED authenticated principal (never the bundle's); 化身与 fork 键按 `carriedRowCells`(或新铸),执行格 = mirror
278
- * —— 导入落下的行是「写者在别处」(列名与值同出 `importedRowCells`);
279
- * ② `create`: lock the row and judge it is the one this call occupied (化身 = 本次铸的)— a row that existed before ⇒ refused before
280
- * anything else about it is read (no claim read on someone else's row);
281
- * ③ 读写序律:the run claim on the root, read on THIS connection after the row lock (`lockRootAndReadClaim`) — a claim ⇒ `SessionActiveRunError`;
282
- * ④ the validated entries re-seq dense from 0 (entry_id/parent_id/type/ts verbatim); `replace` drops the old log first.
198
+ * order (根 `session_meta` 先于 `task_*`、部署格与 `session_event`):
199
+ * ① session_meta FIRST — `create` occupies the row (`ON DUPLICATE KEY UPDATE leaf_seq = leaf_seq`: an existing row is left as it is) and then
200
+ * locks it and judges it is the one this call occupied (化身 = 本次铸的;a row that existed before ⇒ refused before anything else about it is
201
+ * read — no hook on someone else's row); `replace` rewrites the locked row in place (owner-guarded, not placed — 0 rows = another owner's row,
202
+ * refused, nothing written). owner = the RE-STAMPED authenticated principal (never the bundle's);
203
+ * ② the deployment's transaction hook `import` on THIS connection after the row lock (`session-write-hooks.ts`):its run claim on the root ⇒
204
+ * refused; its cells for an imported row (a writer here ⇒ refused; otherwise the row is a mirror —「写者在别处」);
205
+ * ③ the validated entries re-seq dense from 0 (entry_id/parent_id/type/ts verbatim); `replace` drops the old log first.
283
206
  * Runs inside the caller's open transaction (`conn`) so the caller owns begin/commit/rollback (COMMIT is the sole commit point).
284
207
  * `validated` is the OUTPUT of {@link validateEntriesForImport} (the caller runs the gate before opening the txn).
285
208
  */
@@ -1,38 +1,43 @@
1
1
  import { StoredSession, SessionError, uuidv7, validateEntriesForImport, leafIdAfterEntry, admitSessionPlacement } from "@sema-agent/core";
2
2
  import { TiDBSessionStorage } from "./tidb-session-storage.js";
3
3
  import { decodeSessionEventPayload } from "./session-event-payload.js";
4
- import { jsonColumnText } from "./sql-json-column.js";
5
- import { escapeLike } from "./sql-escape.js";
6
- import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSource } from "../instruction-source-notice.js";
7
- import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
8
- import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
9
- import { carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
10
- import { sessionRowTagKey } from "../session-row.js";
11
- import { writeSessionRowCellsSql } from "./session-row-sql.js";
4
+ import { jsonColumnText } from "../plugins/sql-json-column.js";
5
+ import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "../plugins/retention-declaration.js";
6
+ import { headFromRow, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
12
7
  import { contentForkRelation, fastForwardSharedContentDiverged } from "../session-sync-content.js";
13
- import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SESSION_DURABILITY, SESSION_PLACEMENTS, } from "./session-placement.js";
8
+ import { placementCapabilityOf, LIST_PLACED_BATCH, PLACEMENT_SELECT_COLUMNS, placedRowFromRow, placementFromRow, placementInsertValues, SESSION_DURABILITY, SESSION_PLACEMENTS, } from "../plugins/session-placement.js";
14
9
  import { classifySyncRelationshipByIds, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
15
- import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue, placedSessionOwnerOf } from "./session-ownership.js";
10
+ import { assertFirstOwnerWritable, forkOwnerOf, isSessionOwnerValue, placedSessionOwnerOf } from "../plugins/session-ownership.js";
16
11
  import { selectForkRows } from "@sema-agent/core";
17
- import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf } from "./session-fork.js";
18
- import { isMysqlDupKeyError } from "./sql-errors.js";
19
- import { mysqlDriver } from "./sql-driver.js";
20
- import { MIRROR_ON_COMMIT_GUARD, MIRROR_ON_COMMIT_SET, SESSION_META_PLACEMENT_READ_COLUMNS, createSqlExecutionPlacementSeat, importedRowCells, lockRootAndReadClaim, placementCellFromRow, readPlacementRowStatement } from "./execution-placement-sql.js";
21
- import { SessionActiveRunError, SessionWriterPresentError } from "../execution-placement.js";
22
- const rowsOnConnection = (conn) => async (sql, values) => (await conn.query(sql, [...values]))[0];
12
+ import { FORK_LOG_ROW_SHAPE, ForkTargetUnsettledError, classifyForkTarget, forkByFreshId, forkPointOf } from "../plugins/session-fork.js";
13
+ import { isMysqlDupKeyError } from "../plugins/sql-errors.js";
14
+ const statementsOn = (conn) => ({
15
+ async query(sql, params) {
16
+ const [res] = await conn.query(sql, params ?? []);
17
+ return Array.isArray(res) ? { rows: res, affected: 0 } : { rows: [], affected: res.affectedRows };
18
+ },
19
+ });
20
+ class HookRefusal {
21
+ refuse;
22
+ constructor(refuse) {
23
+ this.refuse = refuse;
24
+ }
25
+ }
26
+ function settleHookVerdict(verdict) {
27
+ if (verdict !== "proceed")
28
+ throw new HookRefusal(verdict.refuse);
29
+ }
23
30
  export class TiDBSessionStore {
24
31
  pool;
25
32
  retention;
26
33
  placements = SESSION_PLACEMENTS;
27
34
  durability = SESSION_DURABILITY;
28
35
  pending = new Map();
29
- executionPlacement;
30
- db;
31
- constructor(pool, opts = {}) {
36
+ hooks;
37
+ constructor(pool, opts) {
32
38
  this.pool = pool;
33
39
  this.retention = opts.retention ?? UNMANAGED_RETENTION;
34
- this.db = mysqlDriver(pool);
35
- this.executionPlacement = createSqlExecutionPlacementSeat(this.db);
40
+ this.hooks = opts.hooks;
36
41
  }
37
42
  async acquire(sessionId, opts) {
38
43
  if (!sessionId) {
@@ -83,7 +88,7 @@ export class TiDBSessionStore {
83
88
  const now = new Date();
84
89
  await this.pool.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, " +
85
90
  "placement_kind, placement_scope, placement_parent_session_id, placement_root_session_id, placement_handle, placed_at) " +
86
- "VALUES (?,?,?,?,NULL,0,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE session_id = session_id", [sessionId, owner, now, now, mintSessionIncarnation(), ...placementInsertValues(placement, now)]);
91
+ "VALUES (?,?,?,?,NULL,0,?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE leaf_seq = leaf_seq", [sessionId, owner, now, now, mintSessionIncarnation(), ...placementInsertValues(placement, now)]);
87
92
  }
88
93
  async placementOf(sessionId) {
89
94
  const [rows] = await this.pool.query(`SELECT ${PLACEMENT_SELECT_COLUMNS} FROM session_meta WHERE session_id = ?`, [sessionId]);
@@ -151,103 +156,8 @@ export class TiDBSessionStore {
151
156
  conn.release();
152
157
  }
153
158
  }
154
- async listSessions(opts) {
155
- const params = [];
156
- const where = ["sm.placement_kind IS NULL"];
157
- if (opts.owner !== undefined) {
158
- where.push(opts.includeUnowned ? "(sm.owner = ? OR sm.owner IS NULL)" : "sm.owner = ?");
159
- params.push(opts.owner);
160
- }
161
- if (opts.q) {
162
- where.push("EXISTS (SELECT 1 FROM task_run tr WHERE tr.session_id = sm.session_id AND LOWER(tr.objective_preview) LIKE LOWER(?) ESCAPE '\\\\')");
163
- params.push(`%${escapeLike(opts.q)}%`);
164
- }
165
- if (opts.cursor) {
166
- where.push("(sm.updated_at < ? OR (sm.updated_at = ? AND sm.session_id < ?))");
167
- params.push(new Date(opts.cursor.lastActivityAt), new Date(opts.cursor.lastActivityAt), opts.cursor.sessionId);
168
- }
169
- return (await this.selectListRows({ where, params, limit: opts.limit })).map((a) => a.row);
170
- }
171
- async sessionSummaryOf(sessionId) {
172
- return (await this.listRowOf(sessionId))?.row;
173
- }
174
- async listRowOf(sessionId) {
175
- return (await this.selectListRows({ where: ["sm.placement_kind IS NULL", "sm.session_id = ?"], params: [sessionId], limit: 1, runSessionId: sessionId }))[0];
176
- }
177
- async writeSessionRowCells(sessionId, cells, how) {
178
- return writeSessionRowCellsSql(this.db, sessionId, cells, how, (id) => this.listRowOf(id));
179
- }
180
- async selectListRows(q) {
181
- const runWhere = q.runSessionId !== undefined ? " WHERE session_id = ?" : "";
182
- const runParams = q.runSessionId !== undefined ? [q.runSessionId, q.runSessionId] : [];
183
- const sql = "SELECT sm.session_id, sm.owner, sm.incarnation, sm.updated_at AS last_activity, sm.created_at AS first_activity, sm.title, sm.pinned, " +
184
- " COALESCE(r.run_count, 0) AS run_count, latest.objective_preview, latest.status, latest.task_id AS last_run_id, latest.last_task_run_id " +
185
- "FROM session_meta sm " +
186
- "LEFT JOIN (SELECT session_id, COUNT(*) AS run_count FROM task_run" + runWhere + " GROUP BY session_id) r ON r.session_id = sm.session_id " +
187
- "LEFT JOIN (SELECT session_id, objective_preview, status, task_id, last_task_run_id FROM (" +
188
- " SELECT session_id, objective_preview, status, task_id, JSON_UNQUOTE(JSON_EXTRACT(result, '$.runId')) AS last_task_run_id, " +
189
- " ROW_NUMBER() OVER (PARTITION BY session_id ORDER BY created_at DESC, task_id DESC) AS rn FROM task_run" + runWhere +
190
- " ) lr WHERE rn = 1) latest ON latest.session_id = sm.session_id " +
191
- (q.where.length ? "WHERE " + q.where.join(" AND ") + " " : "") +
192
- "ORDER BY sm.updated_at DESC, sm.session_id DESC LIMIT ?";
193
- const [rows] = await this.pool.query(sql, [...runParams, ...q.params, q.limit]);
194
- return rows.map((r) => {
195
- const runId = r.last_task_run_id;
196
- const sessionId = String(r.session_id);
197
- const owner = r.owner ?? null;
198
- const incarnation = incarnationFromCell(sessionId, r.incarnation, "absent");
199
- const title = r.title ?? null;
200
- const pinned = Number(r.pinned) === 1;
201
- const row = {
202
- sessionId,
203
- owner,
204
- lastActivityAt: r.last_activity instanceof Date ? r.last_activity.toISOString() : String(r.last_activity),
205
- firstActivityAt: r.first_activity instanceof Date ? r.first_activity.toISOString() : String(r.first_activity),
206
- runCount: Number(r.run_count),
207
- objectivePreview: r.objective_preview ?? null,
208
- lastStatus: r.status == null ? "" : String(r.status),
209
- lastRunId: r.last_run_id ?? null,
210
- ...(typeof runId === "string" && runId !== "" ? { lastTaskRunId: runId } : {}),
211
- title,
212
- pinned,
213
- ...sessionRowTagKey({ sessionId, owner, incarnation, cells: { title, pinned } }),
214
- };
215
- return { row, occupant: { owner, incarnation } };
216
- });
217
- }
218
- async probeTitle(sessionId) {
219
- const [rows] = await this.pool.query("SELECT title FROM session_meta WHERE session_id = ?", [sessionId]);
220
- if (!rows[0])
221
- return "none";
222
- return rows[0].title == null ? "untitled" : "titled";
223
- }
224
- async setTitleIfNull(sessionId, title) {
225
- const [r] = await this.pool.query("UPDATE session_meta SET title = ? WHERE session_id = ? AND title IS NULL", [title, sessionId]);
226
- return r.affectedRows > 0;
227
- }
228
- async readInstructionSource(sessionId) {
229
- const [rows] = await this.pool.query({ sql: "SELECT instruction_source FROM session_meta WHERE session_id = ?", timeout: BASELINE_IO_DEADLINE_MS }, [sessionId]);
230
- return decodeInstructionSource(rows[0]?.instruction_source);
231
- }
232
- async recordInstructionSource(sessionId, expected, source) {
233
- const next = encodeInstructionSource(source);
234
- if (expected.recorded) {
235
- await this.pool.query({ sql: "UPDATE session_meta SET instruction_source = ? WHERE session_id = ? AND instruction_source = ?", timeout: BASELINE_IO_DEADLINE_MS }, [next, sessionId, encodeInstructionSource(expected.source)]);
236
- }
237
- else {
238
- await this.pool.query({ sql: "UPDATE session_meta SET instruction_source = ? WHERE session_id = ? AND instruction_source IS NULL", timeout: BASELINE_IO_DEADLINE_MS }, [next, sessionId]);
239
- }
240
- }
241
- async readSessionWorkspace(sessionId) {
242
- const [rows] = await this.pool.query(`SELECT ${jsonColumnText("mysql", "workspace")} FROM session_meta WHERE session_id = ?`, [sessionId]);
243
- const row = rows[0];
244
- return row === undefined ? undefined : decodeSessionWorkspaceCell(row.workspace, sessionId);
245
- }
246
- async recordSessionWorkspace(sessionId, incarnation, workspace) {
247
- await this.pool.query("UPDATE session_meta SET workspace = ? WHERE session_id = ? AND incarnation = ?", [encodeSessionWorkspaceCell(workspace, JSON.stringify, sessionId), sessionId, incarnation]);
248
- }
249
- async fork(sourceId, owner) {
250
- return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner);
159
+ async fork(sourceId, owner, opts) {
160
+ return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner, opts);
251
161
  }
252
162
  async forkTo(sourceId, owner, target) {
253
163
  for (let attempt = 0; attempt < 2; attempt++) {
@@ -275,7 +185,8 @@ export class TiDBSessionStore {
275
185
  try {
276
186
  await conn.beginTransaction();
277
187
  try {
278
- await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, fork_key) VALUES (?,?,?,?,?,?,?,?)", [target.id, forkOwner, now, now, forkLeafId, eventRows.length, mintSessionIncarnation(), target.key ?? null]);
188
+ await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, " +
189
+ "placement_kind, placement_scope, placement_parent_session_id, placement_root_session_id, placement_handle, placed_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", [target.id, forkOwner, now, now, forkLeafId, eventRows.length, mintSessionIncarnation(), ...placementInsertValues(target.placement, now)]);
279
190
  }
280
191
  catch (err) {
281
192
  if (!isMysqlDupKeyError(err))
@@ -283,6 +194,8 @@ export class TiDBSessionStore {
283
194
  collided = true;
284
195
  }
285
196
  if (!collided) {
197
+ if (target.key !== undefined)
198
+ await this.hooks.keyedFork.record(statementsOn(conn), { rootSessionId: target.id, forkKey: target.key });
286
199
  for (let seq = 0; seq < eventRows.length; seq++) {
287
200
  const r = eventRows[seq];
288
201
  const payload = String(r.payload);
@@ -299,14 +212,15 @@ export class TiDBSessionStore {
299
212
  }
300
213
  catch {
301
214
  }
215
+ if (err instanceof HookRefusal)
216
+ throw err.refuse;
302
217
  throw err instanceof SessionError ? err : new SessionError("storage", `fork of session ${sourceId} failed`, err instanceof Error ? err : undefined);
303
218
  }
304
219
  finally {
305
220
  conn.release();
306
221
  }
307
- const [existing] = await this.pool.query("SELECT owner, fork_key FROM session_meta WHERE session_id = ?", [target.id]);
308
- const row = existing[0];
309
- return row === undefined ? "unsettled" : classifyForkTarget(target, { owner: row.owner ?? null, forkKey: row.fork_key ?? null }, forkOwner);
222
+ const existing = await this.hooks.keyedFork.targetOf(target.id);
223
+ return existing === undefined ? "unsettled" : classifyForkTarget(target, existing, forkOwner);
310
224
  }
311
225
  async exportEntries(sessionId) {
312
226
  const [metaRows] = await this.pool.query("SELECT 1 FROM session_meta WHERE session_id = ?", [sessionId]);
@@ -346,7 +260,7 @@ export class TiDBSessionStore {
346
260
  const conn = await this.pool.getConnection();
347
261
  try {
348
262
  await conn.beginTransaction();
349
- await this.writeImportedLog(conn, sessionId, owner, validated, { incarnation: mintSessionIncarnation(), forkKey: null }, "create");
263
+ await this.writeImportedLog(conn, sessionId, owner, validated, "create");
350
264
  await conn.commit();
351
265
  }
352
266
  catch (err) {
@@ -355,7 +269,9 @@ export class TiDBSessionStore {
355
269
  }
356
270
  catch {
357
271
  }
358
- if (err instanceof SessionError || err instanceof SessionActiveRunError)
272
+ if (err instanceof HookRefusal)
273
+ throw err.refuse;
274
+ if (err instanceof SessionError)
359
275
  throw err;
360
276
  throw new SessionError("storage", `import of session ${sessionId} failed`, err instanceof Error ? err : undefined);
361
277
  }
@@ -369,21 +285,13 @@ export class TiDBSessionStore {
369
285
  const conn = await this.pool.getConnection();
370
286
  try {
371
287
  await conn.beginTransaction();
372
- const [placedRows] = await conn.query(`SELECT ${SESSION_META_PLACEMENT_READ_COLUMNS}, fork_key FROM session_meta WHERE session_id = ? FOR UPDATE`, [sessionId]);
373
- const prior = placedRows[0];
288
+ const [lockedRows] = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = ? FOR UPDATE", [sessionId]);
289
+ const prior = lockedRows[0];
374
290
  if (prior?.placement_kind != null) {
375
291
  await conn.rollback();
376
292
  throw new SessionError("placement_refused", `session ${sessionId} belongs to the ${String(prior.placement_kind)} placement partition — a session-sync replace cannot overwrite a delegation transcript`);
377
293
  }
378
- const [purged] = await conn.query("DELETE FROM session_meta WHERE session_id = ? AND owner <=> ? AND placement_kind IS NULL", [sessionId, owner]);
379
- const swapped = purged.affectedRows === 1 ? prior : undefined;
380
- const cells = carriedRowCells(sessionId, swapped === undefined ? undefined : { incarnation: swapped.incarnation, forkKey: swapped.fork_key });
381
- const priorCell = swapped === undefined ? undefined : placementCellFromRow(sessionId, swapped);
382
- if (priorCell?.role === "writer") {
383
- await conn.rollback();
384
- throw new SessionWriterPresentError(sessionId, priorCell.lane);
385
- }
386
- await this.writeImportedLog(conn, sessionId, owner, validated, cells, "replace");
294
+ await this.writeImportedLog(conn, sessionId, owner, validated, prior === undefined ? "create" : "replace");
387
295
  await conn.commit();
388
296
  }
389
297
  catch (err) {
@@ -392,7 +300,9 @@ export class TiDBSessionStore {
392
300
  }
393
301
  catch {
394
302
  }
395
- if (err instanceof SessionError || err instanceof SessionWriterPresentError || err instanceof SessionActiveRunError)
303
+ if (err instanceof HookRefusal)
304
+ throw err.refuse;
305
+ if (err instanceof SessionError)
396
306
  throw err;
397
307
  throw new SessionError("storage", `replace of session ${sessionId} failed`, err instanceof Error ? err : undefined);
398
308
  }
@@ -400,23 +310,26 @@ export class TiDBSessionStore {
400
310
  conn.release();
401
311
  }
402
312
  }
403
- async writeImportedLog(conn, sessionId, owner, validated, cells, mode) {
313
+ async writeImportedLog(conn, sessionId, owner, validated, mode) {
404
314
  let leafId = null;
405
315
  for (const e of validated)
406
316
  leafId = leafIdAfterEntry(e);
407
317
  const now = new Date();
408
- const imported = importedRowCells(cells.forkKey);
409
- await conn.query(`INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation, ${imported.columns}) VALUES (?,?,?,?,?,?,?,?,?)` +
410
- (mode === "create" ? " ON DUPLICATE KEY UPDATE session_id = session_id" : ""), [sessionId, owner, now, now, leafId, validated.length, cells.incarnation, ...imported.values]);
411
318
  if (mode === "create") {
319
+ const minted = mintSessionIncarnation();
320
+ await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES (?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE leaf_seq = leaf_seq", [sessionId, owner, now, now, leafId, validated.length, minted]);
412
321
  const [held] = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = ? FOR UPDATE", [sessionId]);
413
- if (String(held[0]?.incarnation) !== cells.incarnation) {
322
+ if (String(held[0]?.incarnation) !== minted) {
414
323
  throw new SessionError("storage", `import of session ${sessionId} failed`, new Error(`session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`));
415
324
  }
416
325
  }
417
- const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "mysql", sessionId);
418
- if (claim.activeTaskId !== null)
419
- throw new SessionActiveRunError(sessionId, claim.activeTaskId);
326
+ else {
327
+ const [rewritten] = await conn.query("UPDATE session_meta SET owner = ?, created_at = ?, updated_at = ?, leaf_id = ?, leaf_seq = ? WHERE session_id = ? AND owner <=> ? AND placement_kind IS NULL", [owner, now, now, leafId, validated.length, sessionId, owner]);
328
+ if (rewritten.affectedRows !== 1) {
329
+ throw new SessionError("storage", `replace of session ${sessionId} failed`, new Error(`session ${sessionId} belongs to another owner — a replace rewrites only the importer's own session; nothing was written`));
330
+ }
331
+ }
332
+ settleHookVerdict(await this.hooks.beforeCommit(statementsOn(conn), { op: "import", rootSessionId: sessionId }));
420
333
  if (mode === "replace")
421
334
  await conn.query("DELETE FROM session_event WHERE session_id = ?", [sessionId]);
422
335
  for (let seq = 0; seq < validated.length; seq++) {
@@ -425,7 +338,7 @@ export class TiDBSessionStore {
425
338
  }
426
339
  }
427
340
  beginImportStaging(realSessionId, token) {
428
- return new TiDBStagingHandle(this.pool, realSessionId, stagingIdFor(realSessionId, token));
341
+ return new TiDBStagingHandle(this.pool, this.hooks, realSessionId, stagingIdFor(realSessionId, token));
429
342
  }
430
343
  async assertNotPlaced(sessionId, what) {
431
344
  const placed = await this.placementOf(sessionId);
@@ -453,11 +366,9 @@ export class TiDBSessionStore {
453
366
  const conn = await this.pool.getConnection();
454
367
  try {
455
368
  await conn.beginTransaction();
456
- const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "mysql", sessionId);
457
- if (claim.activeTaskId !== null) {
458
- await conn.rollback();
459
- throw new SessionActiveRunError(sessionId, claim.activeTaskId);
460
- }
369
+ const [locked] = await conn.query("SELECT session_id FROM session_meta WHERE session_id = ? FOR UPDATE", [sessionId]);
370
+ if (locked.length > 0)
371
+ settleHookVerdict(await this.hooks.beforeCommit(statementsOn(conn), { op: "delete", rootSessionId: sessionId }));
461
372
  const [metaRes] = await conn.query("DELETE FROM session_meta WHERE session_id = ? AND owner <=> ?", [sessionId, owner]);
462
373
  if (metaRes.affectedRows > 0)
463
374
  await conn.query("DELETE FROM session_event WHERE session_id = ?", [sessionId]);
@@ -470,6 +381,8 @@ export class TiDBSessionStore {
470
381
  }
471
382
  catch {
472
383
  }
384
+ if (err instanceof HookRefusal)
385
+ throw err.refuse;
473
386
  throw err;
474
387
  }
475
388
  finally {
@@ -485,12 +398,14 @@ export class TiDBSessionStore {
485
398
  }
486
399
  class TiDBStagingHandle {
487
400
  pool;
401
+ hooks;
488
402
  realSessionId;
489
403
  stagingId;
490
404
  nextSeq = 0;
491
405
  seen = new Set();
492
- constructor(pool, realSessionId, stagingId) {
406
+ constructor(pool, hooks, realSessionId, stagingId) {
493
407
  this.pool = pool;
408
+ this.hooks = hooks;
494
409
  this.realSessionId = realSessionId;
495
410
  this.stagingId = stagingId;
496
411
  }
@@ -529,18 +444,14 @@ class TiDBStagingHandle {
529
444
  }
530
445
  }
531
446
  else {
532
- await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES (?,?,?,?,NULL,0,?) ON DUPLICATE KEY UPDATE session_id = session_id", [this.realSessionId, owner, now, now, minted]);
447
+ await conn.query("INSERT INTO session_meta (session_id, owner, created_at, updated_at, leaf_id, leaf_seq, incarnation) VALUES (?,?,?,?,NULL,0,?) ON DUPLICATE KEY UPDATE leaf_seq = leaf_seq", [this.realSessionId, owner, now, now, minted]);
533
448
  const [claimed] = await conn.query("SELECT incarnation FROM session_meta WHERE session_id = ? FOR UPDATE", [this.realSessionId]);
534
449
  if (String(claimed[0]?.incarnation) !== minted) {
535
450
  await conn.rollback();
536
451
  throw new StagedCommitOwnerChangedError(this.realSessionId);
537
452
  }
538
453
  }
539
- const claim = await lockRootAndReadClaim(rowsOnConnection(conn), "mysql", this.realSessionId);
540
- if (claim.activeTaskId !== null) {
541
- await conn.rollback();
542
- throw new SessionActiveRunError(this.realSessionId, claim.activeTaskId);
543
- }
454
+ settleHookVerdict(await this.hooks.beforeCommit(statementsOn(conn), { op: "import", rootSessionId: this.realSessionId }));
544
455
  const [stagedRows] = await conn.query("SELECT entry_id FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE", [this.stagingId]);
545
456
  const [dstRows] = await conn.query("SELECT entry_id FROM session_event WHERE session_id = ? ORDER BY seq ASC FOR UPDATE", [this.realSessionId]);
546
457
  const [dstMeta] = await conn.query("SELECT placement_kind FROM session_meta WHERE session_id = ?", [this.realSessionId]);
@@ -575,16 +486,7 @@ class TiDBStagingHandle {
575
486
  let leafId = null;
576
487
  if (lastRow[0])
577
488
  leafId = leafIdAfterEntry(decodeSessionEventPayload(lastRow[0].payload, this.realSessionId, "last staged row"));
578
- const [settle] = await conn.query(`UPDATE session_meta SET owner = ?, updated_at = ?, leaf_id = ?, leaf_seq = ?, ${MIRROR_ON_COMMIT_SET} WHERE session_id = ? AND ${MIRROR_ON_COMMIT_GUARD}`, [owner, now, leafId, srcIds.length, this.realSessionId]);
579
- if (settle.affectedRows !== 1) {
580
- const locked = readPlacementRowStatement("mysql", this.realSessionId, { lock: true });
581
- const [lockedRows] = await conn.query(locked.text, [...locked.values]);
582
- const cell = lockedRows[0] === undefined ? undefined : placementCellFromRow(this.realSessionId, lockedRows[0]);
583
- await conn.rollback();
584
- if (cell?.role === "writer")
585
- throw new SessionWriterPresentError(this.realSessionId, cell.lane);
586
- throw new SessionError("storage", `staged commit for ${this.realSessionId}: the commit point matched no row under a held row lock and the row is not a writer — refusing to guess`);
587
- }
489
+ await conn.query("UPDATE session_meta SET owner = ?, updated_at = ?, leaf_id = ?, leaf_seq = ? WHERE session_id = ?", [owner, now, leafId, srcIds.length, this.realSessionId]);
588
490
  const [afterMeta] = await conn.query("SELECT placement_kind, owner, incarnation FROM session_meta WHERE session_id = ?", [this.realSessionId]);
589
491
  if (afterMeta[0]?.placement_kind != null) {
590
492
  await conn.rollback();
@@ -599,7 +501,9 @@ class TiDBStagingHandle {
599
501
  await conn.rollback();
600
502
  }
601
503
  catch { }
602
- if (err instanceof SyncConflictError || err instanceof SessionError || err instanceof SessionWriterPresentError || err instanceof SessionActiveRunError)
504
+ if (err instanceof HookRefusal)
505
+ throw err.refuse;
506
+ if (err instanceof SyncConflictError || err instanceof SessionError)
603
507
  throw err;
604
508
  throw new SessionError("storage", `staged commit for ${this.realSessionId} failed`, err instanceof Error ? err : undefined);
605
509
  }