@sema-agent/server 7.149.0 → 7.151.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (441) hide show
  1. package/MIGRATION.md +307 -9
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +92 -40
  5. package/deploy/sema-up/README.md +7 -1
  6. package/deploy/sema-up/chart/Chart.yaml +1 -1
  7. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  8. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  9. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  10. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  11. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  12. package/deploy/sema-up/chart/values.yaml +8 -1
  13. package/deploy/sema-up/cluster-up.sh +1 -0
  14. package/deploy/sema-up/kube-up.sh +1 -1
  15. package/deploy/sema-up/lib/flagshape.sh +1 -1
  16. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  17. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  18. package/dist/adoption/import.js +3 -3
  19. package/dist/approval-ask-machine.d.ts +1 -1
  20. package/dist/approval-card.d.ts +3 -3
  21. package/dist/approval-gate-currency.d.ts +2 -2
  22. package/dist/audit.d.ts +1 -1
  23. package/dist/audit.js +2 -2
  24. package/dist/auth-bridge.d.ts +20 -0
  25. package/dist/auth-bridge.js +9 -0
  26. package/dist/background-session-reap.d.ts +2 -2
  27. package/dist/bake-runner/main.d.ts +4 -2
  28. package/dist/bind-host.d.ts +2 -3
  29. package/dist/bind-host.js +2 -4
  30. package/dist/boot/budget-tracing.js +1 -1
  31. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  32. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  33. package/dist/boot/engine-lease.d.ts +5 -0
  34. package/dist/boot/engine-lease.js +1 -0
  35. package/dist/boot/execution-env.d.ts +8 -14
  36. package/dist/boot/execution-env.js +54 -17
  37. package/dist/boot/execution-lane.js +32 -5
  38. package/dist/boot/late-binds.d.ts +8 -4
  39. package/dist/boot/late-binds.js +7 -1
  40. package/dist/boot/memory-consolidation.d.ts +3 -2
  41. package/dist/boot/memory-consolidation.js +6 -6
  42. package/dist/boot/parked-revive-gate.d.ts +5 -5
  43. package/dist/boot/parked-revive-gate.js +2 -3
  44. package/dist/boot/reapers.d.ts +5 -0
  45. package/dist/boot/reapers.js +12 -1
  46. package/dist/boot/resolve-spec.d.ts +7 -8
  47. package/dist/boot/resolve-spec.js +80 -35
  48. package/dist/boot/retention-lane.d.ts +1 -1
  49. package/dist/boot/runner-deps.d.ts +9 -2
  50. package/dist/boot/runner-deps.js +3 -3
  51. package/dist/boot/session-age-out.d.ts +36 -0
  52. package/dist/boot/session-age-out.js +101 -0
  53. package/dist/boot/session-faces.d.ts +5 -14
  54. package/dist/boot/session-faces.js +28 -24
  55. package/dist/boot/stage-01-config.js +2 -0
  56. package/dist/boot/stage-03-store-backend.js +2 -2
  57. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  58. package/dist/boot/stage-05-execution-env.js +2 -2
  59. package/dist/boot/stage-06-runners.d.ts +7 -4
  60. package/dist/boot/stage-06-runners.js +16 -8
  61. package/dist/boot/stage-07-capability-layer.d.ts +7 -4
  62. package/dist/boot/stage-07-capability-layer.js +3 -2
  63. package/dist/boot/stage-08-reapers.d.ts +7 -4
  64. package/dist/boot/stage-08-reapers.js +4 -2
  65. package/dist/boot/stage-09-leader.d.ts +11 -5
  66. package/dist/boot/stage-09-leader.js +12 -3
  67. package/dist/boot/stage-10-http-server.d.ts +44 -6
  68. package/dist/boot/stage-10-http-server.js +12 -10
  69. package/dist/boot/web-admin.d.ts +1 -1
  70. package/dist/boot/web-admin.js +3 -1
  71. package/dist/boot/workflow-orchestration.d.ts +7 -2
  72. package/dist/boot/workflow-orchestration.js +24 -7
  73. package/dist/bounded-rotation.d.ts +29 -0
  74. package/dist/bounded-rotation.js +24 -0
  75. package/dist/capability-facts.d.ts +84 -66
  76. package/dist/capability-facts.js +58 -41
  77. package/dist/center-credential.d.ts +25 -6
  78. package/dist/center-credential.js +25 -5
  79. package/dist/config-catalog.d.ts +26 -7
  80. package/dist/config-catalog.js +15 -12
  81. package/dist/config-invariants.d.ts +2 -1
  82. package/dist/config-invariants.js +4 -4
  83. package/dist/config-types.d.ts +22 -15
  84. package/dist/config.d.ts +51 -2
  85. package/dist/config.js +35 -24
  86. package/dist/deployment-governance.d.ts +7 -28
  87. package/dist/deployment-governance.js +1 -14
  88. package/dist/deployment-shape.d.ts +9 -1
  89. package/dist/deployment-shape.js +2 -1
  90. package/dist/device-protocol.d.ts +5 -4
  91. package/dist/device-protocol.js +1 -1
  92. package/dist/device-ws-hub.d.ts +1 -0
  93. package/dist/device-ws-hub.js +66 -29
  94. package/dist/device-ws-protocol.d.ts +79 -12
  95. package/dist/device-ws-protocol.js +64 -26
  96. package/dist/engine-identity.d.ts +23 -5
  97. package/dist/engine-identity.js +13 -0
  98. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  99. package/dist/execution-env-refusal.d.ts +23 -0
  100. package/dist/execution-env-refusal.js +5 -0
  101. package/dist/execution-lane-caps.d.ts +3 -1
  102. package/dist/field-conflict.d.ts +3 -3
  103. package/dist/file-history-capture.d.ts +11 -4
  104. package/dist/file-history-capture.js +9 -6
  105. package/dist/file-history-disclosure.d.ts +7 -5
  106. package/dist/file-history-disclosure.js +7 -5
  107. package/dist/fleet/drain-entry.d.ts +4 -0
  108. package/dist/fleet/engine-signal.d.ts +14 -1
  109. package/dist/fleet/engine-signal.js +5 -1
  110. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  111. package/dist/fleet/serving-lease.d.ts +18 -3
  112. package/dist/fleet/serving-lease.js +81 -35
  113. package/dist/fleet-client.d.ts +13 -11
  114. package/dist/fleet-client.js +53 -33
  115. package/dist/history-anchor.js +1 -1
  116. package/dist/hitl-delivery.d.ts +1 -3
  117. package/dist/hitl-delivery.js +1 -1
  118. package/dist/hooks/hook-runner.js +2 -2
  119. package/dist/host-lsp-manager.d.ts +2 -2
  120. package/dist/http/active-run-conflict.d.ts +3 -7
  121. package/dist/http/active-run-conflict.js +2 -2
  122. package/dist/http/admission.js +19 -4
  123. package/dist/http/approval-sweeps.js +7 -6
  124. package/dist/http/caller-trust.d.ts +2 -2
  125. package/dist/http/caller-trust.js +3 -3
  126. package/dist/http/checkpoint-ctx.d.ts +20 -63
  127. package/dist/http/checkpoint-ctx.js +2 -24
  128. package/dist/http/claim-heartbeat.d.ts +4 -4
  129. package/dist/http/claim-heartbeat.js +3 -3
  130. package/dist/http/dispatch.d.ts +2 -1
  131. package/dist/http/dispatch.js +36 -29
  132. package/dist/http/durable-run-leg.js +17 -17
  133. package/dist/http/health-self-report.d.ts +9 -1
  134. package/dist/http/health-self-report.js +2 -0
  135. package/dist/http/owner-access.d.ts +264 -20
  136. package/dist/http/owner-access.js +215 -5
  137. package/dist/http/readiness.d.ts +4 -2
  138. package/dist/http/resume-ctx-read.js +38 -1
  139. package/dist/http/resume-legs.d.ts +2 -1
  140. package/dist/http/resume-legs.js +273 -89
  141. package/dist/http/route-ctx.d.ts +41 -19
  142. package/dist/http/route-table.d.ts +1 -1
  143. package/dist/http/routes/a2a-serve.js +15 -14
  144. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  145. package/dist/http/routes/agents-roster.js +2 -3
  146. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  147. package/dist/http/routes/approvals-assistant.js +12 -12
  148. package/dist/http/routes/background.d.ts +3 -3
  149. package/dist/http/routes/capabilities.js +10 -6
  150. package/dist/http/routes/config-catalog.d.ts +1 -1
  151. package/dist/http/routes/fleet.js +10 -8
  152. package/dist/http/routes/mcp-reconnect.js +2 -2
  153. package/dist/http/routes/memory-origin.d.ts +1 -1
  154. package/dist/http/routes/notify-wake.js +2 -3
  155. package/dist/http/routes/projects.d.ts +4 -2
  156. package/dist/http/routes/projects.js +7 -6
  157. package/dist/http/routes/retention-ops.d.ts +1 -1
  158. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  159. package/dist/http/routes/run-permission-mode.js +2 -2
  160. package/dist/http/routes/runs.d.ts +1 -1
  161. package/dist/http/routes/runs.js +125 -66
  162. package/dist/http/routes/session-memory-sync.js +4 -5
  163. package/dist/http/routes/session-sync.d.ts +4 -3
  164. package/dist/http/routes/session-sync.js +174 -83
  165. package/dist/http/routes/sessions-list.js +13 -7
  166. package/dist/http/routes/sessions.d.ts +1 -1
  167. package/dist/http/routes/sessions.js +26 -48
  168. package/dist/http/routes/tasks.js +38 -23
  169. package/dist/http/routes/trace-usage.js +14 -14
  170. package/dist/http/routes/workflows.d.ts +1 -0
  171. package/dist/http/routes/workflows.js +20 -38
  172. package/dist/http/server.d.ts +28 -30
  173. package/dist/http/server.js +7 -3
  174. package/dist/http/stored-body-replay.d.ts +3 -24
  175. package/dist/http/stored-body-replay.js +2 -12
  176. package/dist/http/submit-ctx.d.ts +1 -2
  177. package/dist/http/submit-ctx.js +8 -6
  178. package/dist/http/submit-replay.d.ts +15 -2
  179. package/dist/http/submit-replay.js +10 -4
  180. package/dist/http/wire-types.d.ts +19 -6
  181. package/dist/identity-source.d.ts +20 -3
  182. package/dist/identity-source.js +14 -0
  183. package/dist/index.d.ts +4 -0
  184. package/dist/lane-home-probe.d.ts +3 -7
  185. package/dist/lane-home-probe.js +0 -1
  186. package/dist/leader/endpoint.js +9 -8
  187. package/dist/leader/leader.d.ts +2 -1
  188. package/dist/leg-facts.d.ts +19 -5
  189. package/dist/leg-facts.js +3 -1
  190. package/dist/mcp-authorization-face.d.ts +18 -14
  191. package/dist/mcp-authorization-face.js +3 -2
  192. package/dist/memory-scope.d.ts +5 -4
  193. package/dist/memory-scope.js +4 -4
  194. package/dist/memory-store-refusal.d.ts +1 -1
  195. package/dist/memory-sync-client.d.ts +30 -2
  196. package/dist/memory-sync-client.js +16 -9
  197. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  198. package/dist/observability/err-text.d.ts +7 -0
  199. package/dist/observability/err-text.js +5 -0
  200. package/dist/observability/fail-open.d.ts +12 -8
  201. package/dist/observability/fail-open.js +12 -8
  202. package/dist/observability/logger.d.ts +7 -0
  203. package/dist/observability/logger.js +3 -1
  204. package/dist/observability/metrics.js +1 -0
  205. package/dist/observability/run-terminal.d.ts +1 -1
  206. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  207. package/dist/orchestration/workflow-notify-journal.js +12 -1
  208. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  209. package/dist/orchestration/workflow-park-decision.js +49 -10
  210. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  211. package/dist/orchestration/workflow-park-holder.js +98 -0
  212. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  213. package/dist/orchestration/workflow-record-fence.js +42 -0
  214. package/dist/owner-faces.d.ts +28 -4
  215. package/dist/owner-faces.js +2 -0
  216. package/dist/parked-decide.d.ts +104 -53
  217. package/dist/parked-decide.js +153 -117
  218. package/dist/peer-directory.d.ts +2 -2
  219. package/dist/peer-inbound-face.d.ts +2 -6
  220. package/dist/peer-inbound-face.js +0 -3
  221. package/dist/peer-seat.d.ts +1 -1
  222. package/dist/peer-seat.js +52 -59
  223. package/dist/permission-mode-admission.d.ts +41 -5
  224. package/dist/permission-mode-admission.js +29 -3
  225. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  226. package/dist/plugins/adoption-log-sql.js +7 -1
  227. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  228. package/dist/plugins/approval-ask-store-file.js +8 -0
  229. package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
  230. package/dist/plugins/approval-ask-store-memory.js +28 -9
  231. package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
  232. package/dist/plugins/approval-ask-store-sql.js +34 -5
  233. package/dist/plugins/approval-exemption-store.js +3 -0
  234. package/dist/plugins/approval-nonce-store.js +2 -0
  235. package/dist/plugins/breaker-state-sql.js +2 -0
  236. package/dist/plugins/caching-session-store.d.ts +9 -15
  237. package/dist/plugins/caching-session-store.js +6 -0
  238. package/dist/plugins/device-store-sql.js +6 -0
  239. package/dist/plugins/error-origin.d.ts +18 -0
  240. package/dist/plugins/error-origin.js +10 -0
  241. package/dist/plugins/exec-path-law.d.ts +34 -0
  242. package/dist/plugins/exec-path-law.js +13 -0
  243. package/dist/plugins/execution-placement-sql.js +6 -6
  244. package/dist/plugins/file-run-store.js +1 -1
  245. package/dist/plugins/image-bake-store-sql.js +6 -0
  246. package/dist/plugins/image-index-sql.js +4 -0
  247. package/dist/plugins/landed-attendants.d.ts +212 -0
  248. package/dist/plugins/landed-attendants.js +161 -0
  249. package/dist/plugins/leader-run-store-sql.js +4 -0
  250. package/dist/plugins/local-checkpoint-store.d.ts +4 -1
  251. package/dist/plugins/local-checkpoint-store.js +3 -0
  252. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  253. package/dist/plugins/local-session-live-reference.js +39 -0
  254. package/dist/plugins/local-session-store.d.ts +71 -33
  255. package/dist/plugins/local-session-store.js +129 -27
  256. package/dist/plugins/memory-engine-pg.js +4 -0
  257. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  258. package/dist/plugins/memory-engine-tidb.js +4 -0
  259. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  260. package/dist/plugins/memory-run-store.js +1 -1
  261. package/dist/plugins/memory-sync-store-pg.js +5 -0
  262. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  263. package/dist/plugins/one-time-migrations.d.ts +80 -61
  264. package/dist/plugins/one-time-migrations.js +241 -104
  265. package/dist/plugins/org-delivery-quota-store.js +4 -1
  266. package/dist/plugins/outcome-ledger-sql.js +2 -0
  267. package/dist/plugins/permission-rule-store-sql.js +11 -4
  268. package/dist/plugins/pg-cost-quota.js +4 -0
  269. package/dist/plugins/pg-pool.js +28 -1
  270. package/dist/plugins/pg-rate-limiter.js +2 -0
  271. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  272. package/dist/plugins/posix-shell-fs.js +8 -3
  273. package/dist/plugins/remote-env-adb.d.ts +19 -15
  274. package/dist/plugins/remote-env-adb.js +44 -17
  275. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  276. package/dist/plugins/remote-env-connect-seat.js +72 -0
  277. package/dist/plugins/remote-env-device.d.ts +22 -5
  278. package/dist/plugins/remote-env-device.js +114 -14
  279. package/dist/plugins/remote-env-e2b.d.ts +18 -7
  280. package/dist/plugins/remote-env-e2b.js +53 -40
  281. package/dist/plugins/remote-env-file-error.js +6 -2
  282. package/dist/plugins/remote-env-host.d.ts +18 -1
  283. package/dist/plugins/remote-env-host.js +48 -47
  284. package/dist/plugins/remote-env-k8s.d.ts +41 -39
  285. package/dist/plugins/remote-env-k8s.js +56 -32
  286. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  287. package/dist/plugins/remote-env-local-docker.js +37 -43
  288. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  289. package/dist/plugins/remote-env-ssh.js +64 -62
  290. package/dist/plugins/remote-shell.d.ts +101 -2
  291. package/dist/plugins/remote-shell.js +221 -3
  292. package/dist/plugins/replica-registration.d.ts +35 -0
  293. package/dist/plugins/replica-registration.js +48 -0
  294. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  295. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  296. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  297. package/dist/plugins/retention-lane-store-sql.js +4 -0
  298. package/dist/plugins/retention-sql.d.ts +20 -2
  299. package/dist/plugins/retention-sql.js +11 -0
  300. package/dist/plugins/run-store-sql.d.ts +22 -3
  301. package/dist/plugins/run-store-sql.js +33 -9
  302. package/dist/plugins/scenario-prefix-switch-m42.d.ts +6 -12
  303. package/dist/plugins/scenario-prefix-switch-m42.js +7 -5
  304. package/dist/plugins/schema-consistency.d.ts +9 -5
  305. package/dist/plugins/schema-consistency.js +48 -18
  306. package/dist/plugins/schema-drift-sentence.js +1 -1
  307. package/dist/plugins/send-file-ledger.js +3 -0
  308. package/dist/plugins/serving-requirement.d.ts +68 -0
  309. package/dist/plugins/serving-requirement.js +60 -0
  310. package/dist/plugins/session-birth.d.ts +6 -2
  311. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  312. package/dist/plugins/session-ext-table.d.ts +14 -1
  313. package/dist/plugins/session-ext-table.js +5 -1
  314. package/dist/plugins/session-row-sql.d.ts +49 -2
  315. package/dist/plugins/session-row-sql.js +56 -13
  316. package/dist/plugins/session-store-sql.d.ts +34 -18
  317. package/dist/plugins/session-store-sql.js +98 -17
  318. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  319. package/dist/plugins/shared-memory-store-sql.js +6 -2
  320. package/dist/plugins/sql-driver.d.ts +2 -1
  321. package/dist/plugins/sql-driver.js +18 -3
  322. package/dist/plugins/sql-errors.d.ts +11 -6
  323. package/dist/plugins/sql-errors.js +5 -6
  324. package/dist/plugins/stop-then-start-sentence.d.ts +48 -3
  325. package/dist/plugins/stop-then-start-sentence.js +48 -0
  326. package/dist/plugins/stop-then-start.d.ts +109 -0
  327. package/dist/plugins/stop-then-start.js +104 -0
  328. package/dist/plugins/store-backend.d.ts +40 -13
  329. package/dist/plugins/store-backend.js +169 -27
  330. package/dist/plugins/store-boundary.d.ts +57 -0
  331. package/dist/plugins/store-boundary.js +139 -0
  332. package/dist/plugins/store-contracts.d.ts +29 -2
  333. package/dist/plugins/store-contracts.js +3 -0
  334. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  335. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  336. package/dist/plugins/store-fault.d.ts +26 -7
  337. package/dist/plugins/store-fault.js +18 -2
  338. package/dist/plugins/task-attachment-store.js +9 -0
  339. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  340. package/dist/plugins/task-list-store-sql.js +10 -8
  341. package/dist/plugins/tidb-cost-quota.js +4 -0
  342. package/dist/plugins/tidb-pool.js +6 -1
  343. package/dist/plugins/tidb-rate-limiter.js +4 -0
  344. package/dist/plugins/user-input-ledger-sql.js +4 -0
  345. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  346. package/dist/plugins/workflow-run-store-sql.js +64 -4
  347. package/dist/plugins/workspace-fill.d.ts +4 -7
  348. package/dist/plugins/workspace-fill.js +2 -1
  349. package/dist/plugins/write-behind-counter.js +5 -0
  350. package/dist/process-instance.d.ts +3 -0
  351. package/dist/process-instance.js +7 -0
  352. package/dist/project-context.d.ts +64 -0
  353. package/dist/project-context.js +66 -0
  354. package/dist/project-registry.d.ts +46 -0
  355. package/dist/project-registry.js +44 -0
  356. package/dist/question.d.ts +9 -21
  357. package/dist/question.js +28 -19
  358. package/dist/request-key-closure.js +1 -1
  359. package/dist/request-value-closure.d.ts +4 -0
  360. package/dist/request-value-closure.js +10 -0
  361. package/dist/run-acceptance.d.ts +3 -2
  362. package/dist/run-acceptance.js +4 -6
  363. package/dist/run-cancel-card.d.ts +1 -1
  364. package/dist/run-cancel-card.js +2 -1
  365. package/dist/run-local.js +1 -0
  366. package/dist/runs.d.ts +37 -8
  367. package/dist/runs.js +53 -12
  368. package/dist/runtime-governance.d.ts +5 -4
  369. package/dist/security.d.ts +31 -33
  370. package/dist/security.js +41 -7
  371. package/dist/server-secret-env.d.ts +12 -10
  372. package/dist/server-secret-env.js +24 -15
  373. package/dist/session-agent.d.ts +1 -10
  374. package/dist/session-agent.js +0 -6
  375. package/dist/session-bindings.d.ts +82 -0
  376. package/dist/session-bindings.js +18 -0
  377. package/dist/session-children-retire.d.ts +2 -2
  378. package/dist/session-children-retire.js +2 -6
  379. package/dist/session-deletion.d.ts +16 -1
  380. package/dist/session-error-wire.d.ts +3 -3
  381. package/dist/session-error-wire.js +3 -3
  382. package/dist/session-incarnation.d.ts +28 -4
  383. package/dist/session-incarnation.js +14 -2
  384. package/dist/session-kind.d.ts +57 -0
  385. package/dist/session-kind.js +22 -0
  386. package/dist/session-notify-door.d.ts +4 -4
  387. package/dist/session-notify-door.js +5 -6
  388. package/dist/session-row.d.ts +21 -1
  389. package/dist/session-row.js +7 -1
  390. package/dist/session-rules-seat.d.ts +4 -3
  391. package/dist/session-rules-seat.js +3 -11
  392. package/dist/session-sync-kernel.d.ts +24 -14
  393. package/dist/session-sync-kernel.js +3 -6
  394. package/dist/session-sync.d.ts +4 -98
  395. package/dist/session-sync.js +4 -74
  396. package/dist/session-titler.d.ts +10 -3
  397. package/dist/session-titler.js +19 -6
  398. package/dist/ssh-host-key.d.ts +5 -0
  399. package/dist/ssh-host-key.js +3 -0
  400. package/dist/stores-sql/checkpoint-store-sql.d.ts +21 -2
  401. package/dist/stores-sql/checkpoint-store-sql.js +106 -18
  402. package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
  403. package/dist/stores-sql/file-history-store-sql.js +108 -70
  404. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  405. package/dist/stores-sql/pg-session-storage.js +37 -13
  406. package/dist/stores-sql/retention-store-sql.d.ts +2 -10
  407. package/dist/stores-sql/retention-store-sql.js +2 -10
  408. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  409. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  410. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  411. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  412. package/dist/stores-sql/tidb-session-store.js +37 -13
  413. package/dist/submission-exit.d.ts +3 -2
  414. package/dist/submission-exit.js +9 -1
  415. package/dist/task-cwd.d.ts +6 -5
  416. package/dist/task-settings.d.ts +19 -5
  417. package/dist/task-settings.js +13 -7
  418. package/dist/terminal.d.ts +33 -2
  419. package/dist/terminal.js +139 -9
  420. package/dist/tool-approval.d.ts +147 -12
  421. package/dist/tool-approval.js +206 -109
  422. package/dist/trace/engine-notice-wire.d.ts +14 -0
  423. package/dist/trace/engine-notice-wire.js +18 -1
  424. package/dist/trace/project.d.ts +4 -3
  425. package/dist/trace/project.js +3 -2
  426. package/dist/trace/projection-table.d.ts +33 -2
  427. package/dist/trace/self-minted-wire.d.ts +422 -26
  428. package/dist/trace/self-minted-wire.js +149 -23
  429. package/dist/trace/tool-card.d.ts +3 -0
  430. package/dist/trace/tool-card.js +24 -0
  431. package/dist/trace/tool-output-media.d.ts +2 -1
  432. package/dist/unattended-permission-mode.d.ts +1 -1
  433. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  434. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  435. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  436. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +120 -41
  437. package/package.json +3 -3
  438. package/wire-changes/server@7.150.0.json +335 -0
  439. package/wire-changes/server@7.151.0.json +762 -0
  440. package/dist/http/session-inherit-env.d.ts +0 -36
  441. package/dist/http/session-inherit-env.js +0 -6
@@ -1,9 +1,9 @@
1
1
  import { parkedApprovalCard } from "../approval-content-kind.js";
2
2
  import { createHash } from "node:crypto";
3
3
  import { CHECKPOINT_FACET_SQL, CHECKPOINT_SWEEP_ORDER_SQL, CheckpointIndexRefusedError, checkpointSweepAfterSql, hostSessionOf, judgeCheckpointIndexCells } from "../plugins/checkpoint-index-columns.js";
4
- import { CheckpointError, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf, judgeCheckpointRow, CONTRACT_KIT_ENGINE_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, } from "@sema-agent/core";
4
+ import { CheckpointError, validatePendingSteer, appendPendingSteer, winnerFromOutcome, summarizeCheckpoint, checkpointRevOf, judgeCheckpointRow, CONTRACT_KIT_ENGINE_VERSION, ExecutionOutcomeConflictError, executionRecordDisposition, executionVerdict, readCheckpointSessionQuery, selectSessionPage, } from "@sema-agent/core";
5
5
  import { parkRowFacts, skillCardFacts } from "../approval-card.js";
6
- import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextSha256, readJsonArrayColumn, readJsonColumnTextSha256, readNonNullJsonValueColumn, StoreCorruptError, } from "../plugins/sql-json-column.js";
6
+ import { assertJsonColumnWritable, createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, isJsonObject, jsonColumnText, jsonColumnTextSha256, readJsonArrayColumn, readJsonColumnTextSha256, readNonNullJsonValueColumn, StoreCorruptError, } from "../plugins/sql-json-column.js";
7
7
  import { recordFailOpen } from "../observability/fail-open.js";
8
8
  import { durableRowIdentity, withoutCheckpointToken } from "../checkpoint-identity.js";
9
9
  import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "../plugins/sql-driver.js";
@@ -242,6 +242,12 @@ export function readPendingSteerQueueColumn(queueRaw) {
242
242
  }
243
243
  const STEER_CAS_ATTEMPTS = 8;
244
244
  const EXECUTION_RECORD_CAS_ATTEMPTS = 8;
245
+ const STORED_ROW_DAMAGE = {
246
+ body: "its stored body is not a checkpoint object",
247
+ decision: "its stored decision record cannot be read",
248
+ execution: "its stored execution record cannot be read",
249
+ filing: "its stored body does not name the session the row is filed under",
250
+ };
245
251
  export class SqlCheckpointStore {
246
252
  db;
247
253
  logger;
@@ -337,21 +343,62 @@ export class SqlCheckpointStore {
337
343
  throw rowTooLargeOrSelf(this.db.dialect, e, `the checkpoint row for session ${JSON.stringify(cp.sessionId)}`);
338
344
  }
339
345
  }
346
+ storedCheckpointColumns() {
347
+ const d = this.db.dialect;
348
+ return (`status, ${jsonColumnText(d, "checkpoint")}, ${jsonColumnText(d, "outcome")}, reopen_reason, rev, ` +
349
+ `${jsonColumnText(d, "pending_steer_queue")}, ${jsonColumnText(d, "execution_outcome")}, execution_at_ms`);
350
+ }
340
351
  async get(token) {
341
- const { rows } = await this.db.query(this.q(`SELECT status, ${jsonColumnText("mysql", "checkpoint")}, ${jsonColumnText("mysql", "outcome")}, reopen_reason, rev, ` +
342
- `${jsonColumnText("mysql", "pending_steer_queue")}, ${jsonColumnText("mysql", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = ?`, `SELECT status, ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnText("pg", "outcome")}, reopen_reason, rev, ` +
343
- `${jsonColumnText("pg", "pending_steer_queue")}, ${jsonColumnText("pg", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = $1`), [token]);
352
+ const { rows } = await this.db.query(`SELECT ${this.storedCheckpointColumns()} FROM checkpoint WHERE token = ${this.ph(1)}`, [token]);
344
353
  const r = rows[0];
345
354
  if (!r)
346
355
  return null;
347
- const cp = readCheckpointBodyOrRefuse(r.checkpoint, `for token ${tokenFingerprint(token)}`);
356
+ const read = this.readStoredCheckpoint(r, token);
357
+ if (read.kind === "damaged")
358
+ throw read.error;
359
+ const cp = read.checkpoint;
360
+ this.logger?.info?.("checkpoint_get", {
361
+ tokenFp: tokenFingerprint(token),
362
+ status: cp.status,
363
+ hasWorkspaceHandle: !!cp.state?.workspaceHandle,
364
+ snapshotId: cp.state?.workspaceHandle?.snapshotId,
365
+ });
366
+ return cp;
367
+ }
368
+ readStoredCheckpoint(r, token) {
369
+ const where = `for token ${tokenFingerprint(token)}`;
370
+ let cp;
371
+ try {
372
+ cp = readCheckpointBodyOrRefuse(r.checkpoint, where);
373
+ }
374
+ catch (e) {
375
+ if (e instanceof StoreCorruptError)
376
+ return { kind: "damaged", member: "body", error: e };
377
+ throw e;
378
+ }
348
379
  cp.status = r.status;
349
- const outcomeRaw = refusing("a decision record nobody can read", () => decodeOutcomeColumn(r.outcome, `for token ${tokenFingerprint(token)}`));
380
+ let outcomeRaw;
381
+ try {
382
+ outcomeRaw = refusing("a decision record nobody can read", () => decodeOutcomeColumn(r.outcome, where));
383
+ }
384
+ catch (e) {
385
+ if (e instanceof StoreCorruptError)
386
+ return { kind: "damaged", member: "decision", error: e, body: cp };
387
+ throw e;
388
+ }
350
389
  if (outcomeRaw)
351
390
  cp.resolvedOutcome = winnerFromOutcome(outcomeRaw);
352
391
  cp.reopenReason = r.reopen_reason ?? undefined;
353
392
  cp.rev = r.rev == null ? 0 : Number(r.rev);
354
- const executionOutcome = refusing("an execution record nobody can read", () => decodeExecutionOutcomeColumn(r.execution_outcome, `for token ${tokenFingerprint(token)}`));
393
+ let executionOutcome;
394
+ try {
395
+ executionOutcome = refusing("an execution record nobody can read", () => decodeExecutionOutcomeColumn(r.execution_outcome, where));
396
+ }
397
+ catch (e) {
398
+ if (e instanceof StoreCorruptError)
399
+ return { kind: "damaged", member: "execution", error: e, body: cp };
400
+ throw e;
401
+ }
355
402
  if (executionOutcome !== undefined) {
356
403
  cp.executionOutcome = executionOutcome;
357
404
  if (r.execution_at_ms != null)
@@ -362,17 +409,58 @@ export class SqlCheckpointStore {
362
409
  recordFailOpen("server.sql-json.checkpoint.pending-steer-queue-lossy");
363
410
  this.logger?.info?.("checkpoint_pending_steer_unreadable", { tokenFp: tokenFingerprint(token) });
364
411
  }
365
- if (cp.state) {
366
- if (steer.state.pendingSteerQueue !== undefined)
367
- cp.state.pendingSteerQueue = steer.state.pendingSteerQueue;
368
- }
369
- this.logger?.info?.("checkpoint_get", {
370
- tokenFp: tokenFingerprint(token),
371
- status: cp.status,
372
- hasWorkspaceHandle: !!cp.state?.workspaceHandle,
373
- snapshotId: cp.state?.workspaceHandle?.snapshotId,
412
+ if (isJsonObject(cp.state) && steer.state.pendingSteerQueue !== undefined)
413
+ cp.state.pendingSteerQueue = steer.state.pendingSteerQueue;
414
+ return { kind: "row", checkpoint: cp };
415
+ }
416
+ async listBySession(query) {
417
+ const read = readCheckpointSessionQuery(query);
418
+ const { rows } = await this.db.query(`SELECT token, scope, session_id, created_at_ms, ${this.storedCheckpointColumns()} FROM checkpoint WHERE session_id = ${this.ph(1)}`, [read.sessionId]);
419
+ const tokens = rows.map((r) => String(r.token));
420
+ const withoutTokens = (sentence) => tokens.reduce((out, t) => withoutCheckpointToken(out, t), sentence);
421
+ const damaged = new Map();
422
+ const candidates = [];
423
+ rows.forEach((r, i) => {
424
+ const token = tokens[i];
425
+ const got = this.readStoredCheckpoint(r, token);
426
+ const body = got.kind === "row" ? got.checkpoint : got.member === "body" ? undefined : got.body;
427
+ const filedHere = body !== undefined && body.sessionId === String(r.session_id);
428
+ if (got.kind === "row" && filedHere) {
429
+ candidates.push([token, body]);
430
+ return;
431
+ }
432
+ damaged.set(token, { problem: STORED_ROW_DAMAGE[got.kind === "damaged" ? got.member : "filing"], r, counted: filedHere });
433
+ if (filedHere) {
434
+ candidates.push([token, body]);
435
+ return;
436
+ }
437
+ const filing = { token: token, sessionId: String(r.session_id), scope: String(r.scope), status: String(r.status), createdAt: Number(r.created_at_ms) };
438
+ candidates.push([token, filing]);
374
439
  });
375
- return cp;
440
+ const page = selectSessionPage(candidates, read, (cp) => cp, (sentence) => recordFailOpen("server.approvals.park-row-blob-unreadable", withoutTokens(sentence)));
441
+ if (damaged.size === 0)
442
+ return page;
443
+ return {
444
+ ...page,
445
+ rows: page.rows.map((row) => {
446
+ const token = row.kind === "row" ? row.checkpoint.token : row.token;
447
+ const hit = damaged.get(token);
448
+ if (hit === undefined)
449
+ return row;
450
+ if (hit.counted && row.kind === "row")
451
+ recordFailOpen("server.approvals.park-row-blob-unreadable", `session listing, session ${JSON.stringify(String(hit.r.session_id))}: ${hit.problem} — listed as unreadable`);
452
+ const createdAt = Number(hit.r.created_at_ms);
453
+ return {
454
+ kind: "unreadable",
455
+ token: token,
456
+ scope: String(hit.r.scope),
457
+ sessionId: String(hit.r.session_id),
458
+ status: String(hit.r.status),
459
+ createdAt: Number.isFinite(createdAt) ? createdAt : null,
460
+ problem: hit.problem,
461
+ };
462
+ }),
463
+ };
376
464
  }
377
465
  async resolve(token, scope, outcome, expect) {
378
466
  const params = [this.json(outcome, "checkpoint outcome"), Date.now(), token, scope];
@@ -1,15 +1,26 @@
1
1
  import type { Pool as MySqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
- import { type FileHistoryRetentionPolicy, type ExecutionEnv, type FileHistoryStore, type FileHistoryResult, type FileHistoryTrackResult, type FileHistoryRestoreResult, type FileHistoryExport, type FileHistoryExportResult } from "@sema-agent/core";
3
+ import { type FileHistoryRetentionPolicy, type ExecutionEnv, type FileHistoryStore, type FileHistoryError, type FileHistoryResult, type FileHistoryTrackResult, type FileHistoryRestoreResult, type FileHistoryExport, type FileHistoryExportResult } from "@sema-agent/core";
4
4
  import type { BlobBackend } from "../plugins/blob-backend.js";
5
5
  import { SqlBlobIndex } from "./blob-index-sql.js";
6
6
  import type { ServiceBlobExchange } from "../plugins/store-backend.js";
7
- import { type SqlDriver, type SqlDialect } from "../plugins/sql-driver.js";
7
+ import { type SqlDriver, type SqlTxConn, type SqlDialect } from "../plugins/sql-driver.js";
8
8
  /** The file-history blob domain's SQL table (this store's blob index + the A7 assert). */
9
9
  export declare const FILE_HISTORY_BLOB_TABLE = "file_history_blob";
10
10
  /** MinIO object-key prefix for this domain (snapshot epoch used "blobs/"; a separate prefix keeps the
11
11
  * two domains' objects disjoint so neither sweep can see the other's keys). */
12
12
  export declare const FILE_HISTORY_BLOB_KEY_PREFIX = "file-history/";
13
+ /**
14
+ * {@link SqlFileHistoryStore.prepareImport} 的答:门 / 取值校验的拒(`ok: false`,店码),或这一份信封落进目标要做什么 —— `same`(目标已是这一张图:不写)/
15
+ * `publish`(目标空,或 `replace` 下目标是另一张图:发布步写)。发布步({@link SqlFileHistoryStore.publishImportOn})在调用方的事务里按它做。
16
+ */
17
+ export type FileHistoryImportPlan = {
18
+ readonly ok: false;
19
+ readonly error: FileHistoryError;
20
+ } | {
21
+ readonly ok: true;
22
+ readonly mode: "same" | "publish";
23
+ };
13
24
  /**
14
25
  * [ref] twin 保留条款(core 7.0.0 [ref] §片2 的 SQL 孪生)—— 构造期旋钮,键形与 core 内置后端
15
26
  * `FileFileHistoryStoreOptions.retention` / `FileStorageBackendOptions.fileHistory.retention` **同域同名**。
@@ -89,6 +100,13 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
89
100
  * The lock order itself stays single: sentinels first (sorted), then the scope's rows.
90
101
  */
91
102
  private lockScope;
103
+ /**
104
+ * The ensure-and-lock of {@link lockScope} **without** its restart — the statement source a transaction someone else opened runs (the
105
+ * landed section: its first statements lock the session's root row and judge the occupant, so a restart here would drop that lock and
106
+ * that judgement). A lost arbitration (a deadlock victim, or {@link ScopeSentinelVanishedError}, a `SqlArbitrationLostError`) propagates;
107
+ * the transaction's owner restarts from ITS top. Order: sentinels sorted, then the scope's rows (the one lock order).
108
+ */
109
+ lockScopeOn(conn: SqlTxConn, scopes: readonly string[]): Promise<void>;
92
110
  /** Returns the UPDATE's affected count — 0 = the record is gone (annulled) or was already observed;
93
111
  * the caller disambiguates by re-reading (codex R1-high: answering minted:false after the baseline
94
112
  * vanished would let a later touch mint already-modified bytes as pristine). */
@@ -164,6 +182,12 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
164
182
  * collected by the async grace-window sweep (never a synchronous external-store delete on the purge
165
183
  * path). Also the overwrite-dst session-sync wipe seam (import requires an EMPTY target graph). */
166
184
  deleteBySession(scope: string): Promise<number>;
185
+ /** The whole-scope removal as a statement source on the caller's transaction ({@link lockScopeOn} + the six tables, sentinel last):
186
+ * {@link deleteBySession}'s body, and the landed section's wipe (one transaction with the session's root-row lock). Returns rows removed. */
187
+ deleteScopeOn(conn: SqlTxConn, scope: string): Promise<number>;
188
+ /** Delete one scope's rows from `tables` (caller holds the scope's sentinel lock). 第六表 `file_history_reaped_entry`(spent-entry 台账)随 scope 一起走:
189
+ * E21 是删除权,台账不是例外。 */
190
+ private deleteScopeRowsOn;
167
191
  /** Async byte-GC: blobs referenced by NO version row (any scope), past the created_at grace window.
168
192
  * Domain-isolated by construction (this store's blob table only — [ref] D1). Reaper-driven. */
169
193
  sweepOrphanBlobs(): Promise<number>;
@@ -190,6 +214,32 @@ export declare class SqlFileHistoryStore implements FileHistoryStore {
190
214
  */
191
215
  private verifySourceBlobs;
192
216
  importHistory(scope: string, data: FileHistoryExport, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>): Promise<FileHistoryResult>;
217
+ /**
218
+ * 导入接住的一只故障的**唯一**折法(本店的图事务、准备步与落定段的发布步同读):core 7.55.0 —— `unsupported_format` 只说坏信封(或它的形驱动的活),
219
+ * 门之后撞上的故障(库拒了一份好图 / 重来用尽 / 连接断)是 `adopt_failed`,带接住的那只故障判得出的 `permanence`(判不出 ⇒ 不带)。
220
+ */
221
+ importFailureOf(err: unknown): FileHistoryError;
222
+ /**
223
+ * 导入的**事务外半**(`importHistory` 与落定段的重放同经它):门(core 的共享校验 + 本店的 mode 值域)→ 预读目标图(读不铸哨兵)判同图 / 异图 → 取值校验信封引用的每一枚 blob
224
+ * (首推 / 替换:校验过的字节续租进 blob 后端,发布前的孤儿清扫不收走它们;同图:只校验不回写)。`replace`(consented overwrite-dst 的落定重放)⇒ 异图不拒,按首推准备。
225
+ * 任何拒 ⇒ `ok: false`(店码);一个字节没发布。
226
+ */
227
+ prepareImport(scope: string, data: FileHistoryExport, srcGetBlob: (hash: string) => Promise<Uint8Array | undefined>, opts: {
228
+ readonly replace: boolean;
229
+ }): Promise<FileHistoryImportPlan>;
230
+ /**
231
+ * 导入的**发布步**,一只语句源跑在调用方的事务里(`importHistory` 自己的图事务 / 落定段):`same` ⇒ 不写;`publish` ⇒ scope 锁({@link lockScopeOn},不自己重开事务)→
232
+ * 事务内空目标复判(非破坏性目标律的 TOCTOU 关)→ 空 ⇒ 整图一步插入;已是这张图 ⇒ 不写;另一张图 ⇒ `replace` 下先删这一 scope 的图行(五表,哨兵留着 —— 本事务持着它)
233
+ * 再插(删与插同一事务:替换不会把目标留成空),否则 `conflict`、一个字节没写。
234
+ */
235
+ publishImportOn(conn: SqlTxConn, scope: string, data: FileHistoryExport, plan: Extract<FileHistoryImportPlan, {
236
+ ok: true;
237
+ }>, opts: {
238
+ readonly replace: boolean;
239
+ }): Promise<FileHistoryResult>;
240
+ /** The publish step's body under a held scope lock ({@link importHistory}: {@link lockScope} in the store's own transaction · {@link publishImportOn}:
241
+ * {@link lockScopeOn} in the landed section). */
242
+ private publishImportLockedOn;
193
243
  }
194
244
  /** MySQL-protocol (TiDB) binding. */
195
245
  export declare class TiDBFileHistoryStore extends SqlFileHistoryStore {
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { captureFileBackupViaEnv, applyFileRestoreViaEnv, fileHistoryModeProvenDifferent, worldStillMatchesMint, annulProofFailure, boundaryPublishVerdict, fileHistoryDescribeTrackKey, fileHistoryTrackKeyOf, fileHistoryResolveTrackKey, fileHistoryExportDigest, validateFileHistoryExport, resolveFileHistoryRetention, fileHistoryBoundariesToKeep, } from "@sema-agent/core";
3
3
  import { BlobTooLargeError, SqlBlobIndex } from "./blob-index-sql.js";
4
4
  import { mysqlDriver, pgDriver } from "../plugins/sql-driver.js";
5
- import { isMissingColumnError, retryOnDeadlock } from "../plugins/sql-errors.js";
5
+ import { isMissingColumnError, retryOnDeadlock, SqlArbitrationLostError } from "../plugins/sql-errors.js";
6
6
  import { probedMissingColumnRefusal } from "../plugins/schema-drift-sentence.js";
7
7
  import { recordFailOpen } from "../observability/fail-open.js";
8
8
  import { foldCaughtFailure } from "../file-history-disclosure.js";
@@ -12,8 +12,9 @@ const sha256hex = (s) => createHash("sha256").update(s).digest("hex");
12
12
  const truthy = (v) => v === true || v === 1 || v === "1";
13
13
  class StagedRefGoneError extends Error {
14
14
  }
15
- class ScopeSentinelVanishedError extends Error {
15
+ class ScopeSentinelVanishedError extends SqlArbitrationLostError {
16
16
  }
17
+ const FILE_HISTORY_SCOPED_TABLES = ["file_history_boundary_file", "file_history_boundary", "file_history_reaped_entry", "file_history_version", "file_history_tracked", "file_history_scope"];
17
18
  function reapedEntryRefusal(entryId) {
18
19
  return {
19
20
  ok: false,
@@ -67,14 +68,17 @@ export class SqlFileHistoryStore {
67
68
  await conn.begin();
68
69
  }
69
70
  restart = true;
70
- for (const scope of ordered) {
71
- await conn.query(this.q("INSERT INTO file_history_scope (scope, created_at_ms) VALUES (?,?) ON DUPLICATE KEY UPDATE scope = scope", "INSERT INTO file_history_scope (scope, created_at_ms) VALUES ($1,$2) ON CONFLICT (scope) DO NOTHING"), [scope, Date.now()]);
72
- const r = await conn.query(this.q("SELECT scope FROM file_history_scope WHERE scope = ? FOR UPDATE", "SELECT scope FROM file_history_scope WHERE scope = $1 FOR UPDATE"), [scope]);
73
- if (!r.rows[0])
74
- throw new ScopeSentinelVanishedError(`could not acquire the scope write lock for "${scope}" (sentinel row kept vanishing)`);
75
- }
71
+ await this.lockScopeOn(conn, ordered);
76
72
  }, (err) => err instanceof ScopeSentinelVanishedError);
77
73
  }
74
+ async lockScopeOn(conn, scopes) {
75
+ for (const scope of [...new Set(scopes)].sort()) {
76
+ await conn.query(this.q("INSERT INTO file_history_scope (scope, created_at_ms) VALUES (?,?) ON DUPLICATE KEY UPDATE scope = scope", "INSERT INTO file_history_scope (scope, created_at_ms) VALUES ($1,$2) ON CONFLICT (scope) DO NOTHING"), [scope, Date.now()]);
77
+ const r = await conn.query(this.q("SELECT scope FROM file_history_scope WHERE scope = ? FOR UPDATE", "SELECT scope FROM file_history_scope WHERE scope = $1 FOR UPDATE"), [scope]);
78
+ if (!r.rows[0])
79
+ throw new ScopeSentinelVanishedError(`could not acquire the scope write lock for "${scope}" (sentinel row kept vanishing)`);
80
+ }
81
+ }
78
82
  async markObserved(scope, keyHash) {
79
83
  const r = await this.db.query(this.q("UPDATE file_history_tracked SET observed_later = 1 WHERE scope = ? AND key_hash = ? AND observed_later = 0", "UPDATE file_history_tracked SET observed_later = TRUE WHERE scope = $1 AND key_hash = $2 AND observed_later = FALSE"), [scope, keyHash]);
80
84
  return r.affected;
@@ -134,7 +138,7 @@ export class SqlFileHistoryStore {
134
138
  return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} kept racing a concurrent retraction — retry` } };
135
139
  }
136
140
  catch (err) {
137
- const f = foldCaughtFailure(err);
141
+ const f = foldCaughtFailure(err, "sql-file-history-store.trackEdit");
138
142
  return { ok: false, error: { code: "track_failed", message: `first-touch record for ${absPath} could not be persisted: ${f.text}`, ...f.fault } };
139
143
  }
140
144
  }
@@ -201,7 +205,7 @@ export class SqlFileHistoryStore {
201
205
  return { ok: false, error: { code: "conflict", message: `first-touch record for ${absPath} was replaced while this retraction was being proven` } };
202
206
  }
203
207
  catch (err) {
204
- const f = foldCaughtFailure(err);
208
+ const f = foldCaughtFailure(err, "sql-file-history-store.annulTrack");
205
209
  return { ok: false, error: { code: "track_failed", message: `annulling the first-touch record for ${absPath} failed: ${f.text}`, ...f.fault } };
206
210
  }
207
211
  }
@@ -309,7 +313,7 @@ export class SqlFileHistoryStore {
309
313
  }
310
314
  }
311
315
  catch (err) {
312
- const f = foldCaughtFailure(err);
316
+ const f = foldCaughtFailure(err, "sql-file-history-store.snapshot");
313
317
  return { ok: false, error: { code: "snapshot_failed", message: f.text, ...f.fault } };
314
318
  }
315
319
  this.activeAttempts.add(lease);
@@ -412,7 +416,7 @@ export class SqlFileHistoryStore {
412
416
  catch (err) {
413
417
  await conn.rollback().catch(() => undefined);
414
418
  this.spentAttempts.add(lease);
415
- const f = foldCaughtFailure(err);
419
+ const f = foldCaughtFailure(err, "sql-file-history-store.snapshot");
416
420
  return { ok: false, error: { code: "snapshot_failed", message: `boundary for entry "${entryId}" could not be committed: ${f.text}`, ...f.fault } };
417
421
  }
418
422
  finally {
@@ -423,7 +427,7 @@ export class SqlFileHistoryStore {
423
427
  }
424
428
  catch (err) {
425
429
  this.spentAttempts.add(lease);
426
- const f = foldCaughtFailure(err);
430
+ const f = foldCaughtFailure(err, "sql-file-history-store.snapshot");
427
431
  return { ok: false, error: { code: "snapshot_failed", message: f.text, ...f.fault } };
428
432
  }
429
433
  finally {
@@ -491,7 +495,7 @@ export class SqlFileHistoryStore {
491
495
  });
492
496
  }
493
497
  catch (err) {
494
- applied = { outcome: "failed", reason: foldCaughtFailure(err).text };
498
+ applied = { outcome: "failed", reason: foldCaughtFailure(err, "sql-file-history-store.restore").text };
495
499
  }
496
500
  if (applied.outcome === "applied")
497
501
  ledger.filesChanged.push(t.trackKey);
@@ -508,7 +512,7 @@ export class SqlFileHistoryStore {
508
512
  }
509
513
  catch (err) {
510
514
  const touched = empty.filesChanged.length > 0 || empty.identicalSkipped.length > 0 || empty.refused.length > 0 || empty.failed.length > 0;
511
- const f = foldCaughtFailure(err);
515
+ const f = foldCaughtFailure(err, "sql-file-history-store.restore");
512
516
  return { outcome: touched ? "partial" : "failed", ...empty, error: { code: "restore_failed", message: f.text, ...f.fault } };
513
517
  }
514
518
  }
@@ -578,11 +582,7 @@ export class SqlFileHistoryStore {
578
582
  try {
579
583
  await conn.begin();
580
584
  await this.lockScope(conn, scope);
581
- let total = 0;
582
- for (const table of ["file_history_boundary_file", "file_history_boundary", "file_history_reaped_entry", "file_history_version", "file_history_tracked", "file_history_scope"]) {
583
- const r = await conn.query(this.q(`DELETE FROM ${table} WHERE scope = ?`, `DELETE FROM ${table} WHERE scope = $1`), [scope]);
584
- total += r.affected;
585
- }
585
+ const total = await this.deleteScopeRowsOn(conn, scope, FILE_HISTORY_SCOPED_TABLES);
586
586
  await conn.commit();
587
587
  return total;
588
588
  }
@@ -594,6 +594,18 @@ export class SqlFileHistoryStore {
594
594
  conn.release();
595
595
  }
596
596
  }
597
+ async deleteScopeOn(conn, scope) {
598
+ await this.lockScopeOn(conn, [scope]);
599
+ return this.deleteScopeRowsOn(conn, scope, FILE_HISTORY_SCOPED_TABLES);
600
+ }
601
+ async deleteScopeRowsOn(conn, scope, tables) {
602
+ let total = 0;
603
+ for (const table of tables) {
604
+ const r = await conn.query(this.q(`DELETE FROM ${table} WHERE scope = ?`, `DELETE FROM ${table} WHERE scope = $1`), [scope]);
605
+ total += r.affected;
606
+ }
607
+ return total;
608
+ }
597
609
  async sweepOrphanBlobs() {
598
610
  const { rows } = await this.db.query("SELECT blob_hash FROM file_history_blob WHERE blob_hash NOT IN (SELECT blob_hash FROM file_history_version WHERE blob_hash IS NOT NULL)");
599
611
  const orphans = rows.map((r) => String(r.blob_hash));
@@ -612,7 +624,7 @@ export class SqlFileHistoryStore {
612
624
  catch (err) {
613
625
  if (err instanceof BlobTooLargeError)
614
626
  return { ok: false, error: { code: "adopt_failed", message: err.message, permanence: "terminal", blobCap: { wallSource: err.cap.source } } };
615
- const f = foldCaughtFailure(err);
627
+ const f = foldCaughtFailure(err, "sql-file-history-store.putExchangedBlob");
616
628
  return { ok: false, error: { code: "snapshot_failed", message: `blob ${hash} write/upload failed: ${f.text}`, ...f.fault } };
617
629
  }
618
630
  }
@@ -650,7 +662,7 @@ export class SqlFileHistoryStore {
650
662
  }
651
663
  }
652
664
  catch (err) {
653
- const f = foldCaughtFailure(err);
665
+ const f = foldCaughtFailure(err, "sql-file-history-store.adoptScope");
654
666
  return { ok: false, error: { code: "adopt_failed", message: `adoptScope failed: ${f.text}`, ...f.fault } };
655
667
  }
656
668
  }
@@ -707,10 +719,10 @@ export class SqlFileHistoryStore {
707
719
  const conn = await this.db.connect();
708
720
  try {
709
721
  await conn.begin();
710
- await this.lockScope(conn, scope);
711
- const out = await this.exportOf(conn, scope, true);
722
+ const held = await conn.query(this.q("SELECT scope FROM file_history_scope WHERE scope = ? FOR UPDATE", "SELECT scope FROM file_history_scope WHERE scope = $1 FOR UPDATE"), [scope]);
723
+ const history = held.rows[0] === undefined ? null : await this.exportOf(conn, scope, true);
712
724
  await conn.commit();
713
- return { ok: true, history: out };
725
+ return { ok: true, history };
714
726
  }
715
727
  catch (err) {
716
728
  await conn.rollback().catch(() => undefined);
@@ -732,7 +744,7 @@ export class SqlFileHistoryStore {
732
744
  bytes = await srcGetBlob(hash);
733
745
  }
734
746
  catch (err) {
735
- const f = foldCaughtFailure(err);
747
+ const f = foldCaughtFailure(err, "sql-file-history-store.verifySourceBlobs");
736
748
  return { ok: false, error: { code: "adopt_failed", message: `source blob ${hash} fetch failed: ${f.text}`, ...f.fault } };
737
749
  }
738
750
  if (bytes === undefined)
@@ -745,6 +757,40 @@ export class SqlFileHistoryStore {
745
757
  return { ok: true };
746
758
  }
747
759
  async importHistory(scope, data, srcGetBlob) {
760
+ const plan = await this.prepareImport(scope, data, srcGetBlob, { replace: false });
761
+ if (!plan.ok)
762
+ return plan;
763
+ if (plan.mode === "same")
764
+ return { ok: true };
765
+ try {
766
+ const conn = await this.db.connect();
767
+ try {
768
+ await conn.begin();
769
+ await this.lockScope(conn, scope);
770
+ const out = await this.publishImportLockedOn(conn, scope, data, { replace: false });
771
+ if (out.ok)
772
+ await conn.commit();
773
+ else
774
+ await conn.rollback();
775
+ return out;
776
+ }
777
+ catch (err) {
778
+ await conn.rollback().catch(() => undefined);
779
+ throw err;
780
+ }
781
+ finally {
782
+ conn.release();
783
+ }
784
+ }
785
+ catch (err) {
786
+ return { ok: false, error: this.importFailureOf(err) };
787
+ }
788
+ }
789
+ importFailureOf(err) {
790
+ const f = foldCaughtFailure(err, "sql-file-history-store.importHistory");
791
+ return { code: "adopt_failed", message: `importHistory failed: ${f.text}`, ...f.fault };
792
+ }
793
+ async prepareImport(scope, data, srcGetBlob, opts) {
748
794
  try {
749
795
  const shaped = validateFileHistoryExport(data);
750
796
  if (!shaped.ok)
@@ -762,59 +808,51 @@ export class SqlFileHistoryStore {
762
808
  return exported;
763
809
  const existing = exported.history;
764
810
  const sameGraph = existing !== null && fileHistoryExportDigest(existing) === fileHistoryExportDigest(data);
765
- if (existing !== null && !sameGraph) {
811
+ if (existing !== null && !sameGraph && !opts.replace) {
766
812
  return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" already holds a DIFFERENT history — a first import requires an empty target and a retry is legal only for the identical envelope (atomic visibility prevents half-graphs; this law prevents publishing the wrong complete graph over locally-grown history)` } };
767
813
  }
768
814
  const verified = await this.verifySourceBlobs(data, srcGetBlob, sameGraph ? undefined : (hash, bytes) => this.blobs.put(hash, bytes));
769
815
  if (!verified.ok)
770
816
  return verified;
771
- if (sameGraph)
772
- return { ok: true };
773
- const conn = await this.db.connect();
774
- try {
775
- await conn.begin();
776
- await this.lockScope(conn, scope);
777
- if (!(await this.scopeEmpty(conn, scope, true))) {
778
- const now = await this.exportOf(conn, scope, true);
779
- if (now !== null && fileHistoryExportDigest(now) === fileHistoryExportDigest(data)) {
780
- await conn.commit();
781
- return { ok: true };
782
- }
783
- await conn.rollback();
784
- return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" gained a DIFFERENT history while this import was fetching blobs — nothing was published` } };
785
- }
786
- const now = Date.now();
787
- for (const t of data.tracked) {
788
- const keyHash = sha256hex(t.relOrAbsPath);
789
- await conn.query(this.q("INSERT INTO file_history_tracked (scope, key_hash, track_key, key_kind, key_family, base_available, observed_later, created_at_ms) VALUES (?,?,?,?,?,?,0,?)", "INSERT INTO file_history_tracked (scope, key_hash, track_key, key_kind, key_family, base_available, observed_later, created_at_ms) VALUES ($1,$2,$3,$4,$5,$6,FALSE,$7)"), [scope, keyHash, t.relOrAbsPath, t.keyKind, t.keyFamily, this.bool(t.baseAvailable), now]);
790
- for (const v of t.versions) {
791
- await conn.query(this.q("INSERT INTO file_history_version (scope, key_hash, version, blob_hash, mode, parent_ident, backup_time) VALUES (?,?,?,?,?,?,?)", "INSERT INTO file_history_version (scope, key_hash, version, blob_hash, mode, parent_ident, backup_time) VALUES ($1,$2,$3,$4,$5,$6,$7)"), [scope, keyHash, v.version, v.blobHash, v.mode ?? null, v.parentIdent ?? null, v.backupTime]);
792
- }
793
- }
794
- const seqBase = await conn.query(this.q("SELECT COALESCE(MAX(publish_seq), 0) AS s FROM file_history_boundary WHERE scope = ?", "SELECT COALESCE(MAX(publish_seq), 0) AS s FROM file_history_boundary WHERE scope = $1"), [scope]);
795
- let publishSeq = Number(seqBase.rows[0].s);
796
- for (const b of data.boundaries) {
797
- publishSeq += 1;
798
- await conn.query(this.q("INSERT INTO file_history_boundary (scope, entry_id, published_at_ms, publish_seq) VALUES (?,?,?,?)", "INSERT INTO file_history_boundary (scope, entry_id, published_at_ms, publish_seq) VALUES ($1,$2,$3,$4)"), [scope, b.entryId, now, publishSeq]);
799
- for (const f of b.files) {
800
- await conn.query(this.q("INSERT INTO file_history_boundary_file (scope, entry_id, key_hash, version) VALUES (?,?,?,?)", "INSERT INTO file_history_boundary_file (scope, entry_id, key_hash, version) VALUES ($1,$2,$3,$4)"), [scope, b.entryId, sha256hex(f.relOrAbsPath), f.version]);
801
- }
802
- }
803
- await conn.commit();
817
+ return { ok: true, mode: sameGraph ? "same" : "publish" };
818
+ }
819
+ catch (err) {
820
+ return { ok: false, error: this.importFailureOf(err) };
821
+ }
822
+ }
823
+ async publishImportOn(conn, scope, data, plan, opts) {
824
+ if (plan.mode === "same")
825
+ return { ok: true };
826
+ await this.lockScopeOn(conn, [scope]);
827
+ return this.publishImportLockedOn(conn, scope, data, opts);
828
+ }
829
+ async publishImportLockedOn(conn, scope, data, opts) {
830
+ if (!(await this.scopeEmpty(conn, scope, true))) {
831
+ const held = await this.exportOf(conn, scope, true);
832
+ if (held !== null && fileHistoryExportDigest(held) === fileHistoryExportDigest(data))
804
833
  return { ok: true };
805
- }
806
- catch (err) {
807
- await conn.rollback().catch(() => undefined);
808
- throw err;
809
- }
810
- finally {
811
- conn.release();
834
+ if (!opts.replace)
835
+ return { ok: false, error: { code: "conflict", message: `importHistory target "${scope}" gained a DIFFERENT history while this import was fetching blobs — nothing was published` } };
836
+ await this.deleteScopeRowsOn(conn, scope, FILE_HISTORY_SCOPED_TABLES.filter((t) => t !== "file_history_scope"));
837
+ }
838
+ const now = Date.now();
839
+ for (const t of data.tracked) {
840
+ const keyHash = sha256hex(t.relOrAbsPath);
841
+ await conn.query(this.q("INSERT INTO file_history_tracked (scope, key_hash, track_key, key_kind, key_family, base_available, observed_later, created_at_ms) VALUES (?,?,?,?,?,?,0,?)", "INSERT INTO file_history_tracked (scope, key_hash, track_key, key_kind, key_family, base_available, observed_later, created_at_ms) VALUES ($1,$2,$3,$4,$5,$6,FALSE,$7)"), [scope, keyHash, t.relOrAbsPath, t.keyKind, t.keyFamily, this.bool(t.baseAvailable), now]);
842
+ for (const v of t.versions) {
843
+ await conn.query(this.q("INSERT INTO file_history_version (scope, key_hash, version, blob_hash, mode, parent_ident, backup_time) VALUES (?,?,?,?,?,?,?)", "INSERT INTO file_history_version (scope, key_hash, version, blob_hash, mode, parent_ident, backup_time) VALUES ($1,$2,$3,$4,$5,$6,$7)"), [scope, keyHash, v.version, v.blobHash, v.mode ?? null, v.parentIdent ?? null, v.backupTime]);
812
844
  }
813
845
  }
814
- catch (err) {
815
- const f = foldCaughtFailure(err);
816
- return { ok: false, error: { code: "adopt_failed", message: `importHistory failed: ${f.text}`, ...f.fault } };
846
+ const seqBase = await conn.query(this.q("SELECT COALESCE(MAX(publish_seq), 0) AS s FROM file_history_boundary WHERE scope = ?", "SELECT COALESCE(MAX(publish_seq), 0) AS s FROM file_history_boundary WHERE scope = $1"), [scope]);
847
+ let publishSeq = Number(seqBase.rows[0].s);
848
+ for (const b of data.boundaries) {
849
+ publishSeq += 1;
850
+ await conn.query(this.q("INSERT INTO file_history_boundary (scope, entry_id, published_at_ms, publish_seq) VALUES (?,?,?,?)", "INSERT INTO file_history_boundary (scope, entry_id, published_at_ms, publish_seq) VALUES ($1,$2,$3,$4)"), [scope, b.entryId, now, publishSeq]);
851
+ for (const f of b.files) {
852
+ await conn.query(this.q("INSERT INTO file_history_boundary_file (scope, entry_id, key_hash, version) VALUES (?,?,?,?)", "INSERT INTO file_history_boundary_file (scope, entry_id, key_hash, version) VALUES ($1,$2,$3,$4)"), [scope, b.entryId, sha256hex(f.relOrAbsPath), f.version]);
853
+ }
817
854
  }
855
+ return { ok: true };
818
856
  }
819
857
  }
820
858
  export class TiDBFileHistoryStore extends SqlFileHistoryStore {