@sema-agent/server 7.147.0 → 7.148.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 (725) hide show
  1. package/MIGRATION.md +1039 -24
  2. package/README.md +20 -7
  3. package/README.zh-CN.md +14 -8
  4. package/USAGE.md +380 -163
  5. package/deploy/sema-up/README.md +16 -5
  6. package/deploy/sema-up/chart/templates/NOTES.txt +1 -1
  7. package/deploy/sema-up/chart/templates/_helpers.tpl +20 -4
  8. package/deploy/sema-up/chart/values.yaml +3 -2
  9. package/deploy/sema-up/cluster-up.sh +74 -176
  10. package/deploy/sema-up/kube-up.sh +29 -49
  11. package/deploy/sema-up/lib/chart-run.sh +23 -0
  12. package/deploy/sema-up/lib/dataroot.sh +3 -3
  13. package/deploy/sema-up/lib/health.sh +62 -13
  14. package/deploy/sema-up/lib/helm-values-cluster.py +18 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +35 -0
  16. package/deploy/sema-up/lib/regcred.py +8 -0
  17. package/deploy/sema-up/lib/remote/helm-install.sh +3 -0
  18. package/deploy/sema-up/lib/remote/image-import.sh +6 -0
  19. package/deploy/sema-up/lib/remote/machine-precheck.sh +11 -0
  20. package/deploy/sema-up/lib/remote/region-probe.sh +3 -0
  21. package/deploy/sema-up/lib/remote/stack-ready.sh +26 -0
  22. package/deploy/sema-up/lib/remote/vxlan-guard.sh +8 -0
  23. package/deploy/sema-up/lib/render-stack.py +17 -0
  24. package/deploy/sema-up/lib/shell-text.sh +87 -0
  25. package/deploy/sema-up/lib/usage-flags.txt +10 -0
  26. package/deploy/sema-up/lib/usage-head.txt +1 -0
  27. package/deploy/sema-up/lib/usage-tail.txt +13 -0
  28. package/deploy/sema-up/preflight.sh +10 -8
  29. package/deploy/sema-up/sema-up.sh +14 -39
  30. package/deploy/sema-up/smoke.sh +7 -6
  31. package/deploy/sema-up/stack/compose.dataroot.yaml +3 -0
  32. package/deploy/sema-up/stack/compose.yaml +13 -1
  33. package/deploy/sema-up/stack/sema-stack-cluster.yaml +21 -0
  34. package/dist/adoption/plan.d.ts +24 -0
  35. package/dist/adoption/plan.js +53 -1
  36. package/dist/adoption/runner.d.ts +10 -1
  37. package/dist/adoption/runner.js +36 -16
  38. package/dist/adoption/wire.d.ts +3 -3
  39. package/dist/approval-card.d.ts +28 -30
  40. package/dist/approval-gate-currency.js +3 -2
  41. package/dist/approval-reconciler.d.ts +4 -13
  42. package/dist/approval-reconciler.js +2 -2
  43. package/dist/approval.d.ts +13 -11
  44. package/dist/approval.js +3 -3
  45. package/dist/audit.d.ts +10 -3
  46. package/dist/audit.js +15 -7
  47. package/dist/auth-bridge.d.ts +0 -14
  48. package/dist/auth-bridge.js +0 -7
  49. package/dist/auto-mode-face.d.ts +10 -7
  50. package/dist/auto-mode-face.js +7 -4
  51. package/dist/background-agent-name.d.ts +19 -1
  52. package/dist/background-agent-name.js +12 -2
  53. package/dist/background-session-reap.d.ts +1 -1
  54. package/dist/bake-runner/main.js +9 -6
  55. package/dist/bench/s1/arms.js +6 -5
  56. package/dist/bench/s1/live-deps.js +5 -2
  57. package/dist/bench/s1/run-firm.js +6 -5
  58. package/dist/bind-host.d.ts +25 -0
  59. package/dist/bind-host.js +25 -0
  60. package/dist/boot/adoption.d.ts +3 -0
  61. package/dist/boot/adoption.js +1 -0
  62. package/dist/boot/deployment-mods.d.ts +1 -1
  63. package/dist/boot/execution-env.d.ts +17 -7
  64. package/dist/boot/execution-env.js +37 -31
  65. package/dist/boot/exit-boundary.d.ts +2 -2
  66. package/dist/boot/exit-boundary.js +6 -5
  67. package/dist/boot/fatal-exit.d.ts +3 -5
  68. package/dist/boot/fatal-exit.js +2 -2
  69. package/dist/boot/leader.d.ts +15 -0
  70. package/dist/boot/leader.js +17 -15
  71. package/dist/boot/memory-consolidation.d.ts +37 -29
  72. package/dist/boot/memory-consolidation.js +11 -5
  73. package/dist/boot/org-memory.js +1 -1
  74. package/dist/boot/parked-revive-gate.js +2 -2
  75. package/dist/boot/reapers.d.ts +4 -3
  76. package/dist/boot/reapers.js +19 -16
  77. package/dist/boot/resolve-spec.d.ts +5 -3
  78. package/dist/boot/resolve-spec.js +55 -67
  79. package/dist/boot/retention-lane.d.ts +4 -4
  80. package/dist/boot/retention-lane.js +2 -2
  81. package/dist/boot/runner-deps.d.ts +28 -7
  82. package/dist/boot/runner-deps.js +11 -4
  83. package/dist/boot/runtime-caps.d.ts +2 -2
  84. package/dist/boot/runtime-caps.js +16 -2
  85. package/dist/boot/session-faces.d.ts +16 -7
  86. package/dist/boot/session-faces.js +114 -94
  87. package/dist/boot/shutdown.d.ts +5 -4
  88. package/dist/boot/shutdown.js +6 -4
  89. package/dist/boot/stage-01-config.js +9 -1
  90. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  91. package/dist/boot/stage-02-web-admin-pull.js +6 -1
  92. package/dist/boot/stage-03-store-backend.d.ts +3 -0
  93. package/dist/boot/stage-03-store-backend.js +4 -3
  94. package/dist/boot/stage-04-stores.d.ts +2 -0
  95. package/dist/boot/stage-04-stores.js +2 -2
  96. package/dist/boot/stage-05-execution-env.d.ts +7 -3
  97. package/dist/boot/stage-05-execution-env.js +2 -2
  98. package/dist/boot/stage-06-runners.d.ts +10 -12
  99. package/dist/boot/stage-06-runners.js +23 -15
  100. package/dist/boot/stage-07-capability-layer.d.ts +11 -8
  101. package/dist/boot/stage-07-capability-layer.js +5 -3
  102. package/dist/boot/stage-08-reapers.d.ts +11 -8
  103. package/dist/boot/stage-08-reapers.js +3 -3
  104. package/dist/boot/stage-09-leader.d.ts +12 -15
  105. package/dist/boot/stage-09-leader.js +12 -4
  106. package/dist/boot/stage-10-http-server.d.ts +12 -15
  107. package/dist/boot/stage-10-http-server.js +7 -4
  108. package/dist/boot/store-evidence.d.ts +20 -0
  109. package/dist/boot/store-evidence.js +13 -0
  110. package/dist/boot/stores.d.ts +3 -0
  111. package/dist/boot/stores.js +21 -10
  112. package/dist/boot/task-list-lane.d.ts +4 -4
  113. package/dist/boot/web-admin.d.ts +4 -2
  114. package/dist/boot/web-admin.js +55 -35
  115. package/dist/boot/webfetch-summarize-lane.d.ts +1 -1
  116. package/dist/boot-reclaim.d.ts +3 -1
  117. package/dist/boot-reclaim.js +1 -1
  118. package/dist/brain.d.ts +7 -18
  119. package/dist/brain.js +5 -17
  120. package/dist/budget.js +6 -6
  121. package/dist/capabilities/center-plugins.d.ts +7 -1
  122. package/dist/capabilities/center-plugins.js +32 -10
  123. package/dist/capabilities/center-wiring.js +1 -1
  124. package/dist/capabilities/composite-members.d.ts +2 -2
  125. package/dist/capabilities/composite-members.js +2 -2
  126. package/dist/capabilities/repo-tools.d.ts +2 -0
  127. package/dist/capabilities/repo-tools.js +14 -12
  128. package/dist/capabilities/scenarios.d.ts +6 -15
  129. package/dist/capabilities/scenarios.js +11 -16
  130. package/dist/capabilities/skills.d.ts +1 -1
  131. package/dist/capabilities/tool-defer.d.ts +1 -1
  132. package/dist/capability-facts.d.ts +38 -11
  133. package/dist/capability-facts.js +47 -8
  134. package/dist/card-void.d.ts +4 -4
  135. package/dist/card-void.js +1 -1
  136. package/dist/center-credential-renewer.d.ts +6 -2
  137. package/dist/center-credential-renewer.js +7 -3
  138. package/dist/config-catalog.d.ts +16 -1
  139. package/dist/config-catalog.js +62 -56
  140. package/dist/config-dir-trust.d.ts +37 -4
  141. package/dist/config-dir-trust.js +88 -44
  142. package/dist/config-invariants.d.ts +42 -3
  143. package/dist/config-invariants.js +69 -5
  144. package/dist/config-lkg.d.ts +23 -1
  145. package/dist/config-lkg.js +17 -3
  146. package/dist/config-provider.d.ts +9 -9
  147. package/dist/config-provider.js +25 -110
  148. package/dist/config-pull-failure.d.ts +50 -0
  149. package/dist/config-pull-failure.js +48 -0
  150. package/dist/config-types.d.ts +26 -17
  151. package/dist/config.d.ts +8 -16
  152. package/dist/config.js +113 -47
  153. package/dist/delegated-session-owner.d.ts +1 -1
  154. package/dist/deleted-session-ids.d.ts +3 -2
  155. package/dist/deployment-governance.d.ts +3 -3
  156. package/dist/deployment-shape.d.ts +30 -4
  157. package/dist/deployment-shape.js +7 -0
  158. package/dist/egress-redirect.d.ts +25 -7
  159. package/dist/egress-redirect.js +20 -2
  160. package/dist/elicitation.d.ts +6 -5
  161. package/dist/elicitation.js +23 -22
  162. package/dist/engine-identity.d.ts +28 -0
  163. package/dist/engine-identity.js +10 -0
  164. package/dist/entry-problems.d.ts +5 -7
  165. package/dist/entry-problems.js +1 -1
  166. package/dist/env-facts.js +2 -1
  167. package/dist/env-witness.d.ts +7 -0
  168. package/dist/env-witness.js +16 -0
  169. package/dist/execution-lane-caps.d.ts +5 -18
  170. package/dist/execution-lane-caps.js +0 -22
  171. package/dist/execution-placement.d.ts +58 -18
  172. package/dist/execution-placement.js +41 -2
  173. package/dist/field-conflict.d.ts +7 -11
  174. package/dist/field-conflict.js +3 -3
  175. package/dist/file-history-capture.d.ts +3 -3
  176. package/dist/file-history-disclosure.js +1 -0
  177. package/dist/fleet/fleet-bus.d.ts +21 -3
  178. package/dist/fleet/fleet-bus.js +35 -25
  179. package/dist/fleet/fleet-reconciler.js +2 -2
  180. package/dist/fleet/serving-lease.js +3 -3
  181. package/dist/fleet-client.js +3 -3
  182. package/dist/fleet-lease.d.ts +0 -1
  183. package/dist/fleet-lease.js +4 -5
  184. package/dist/history-anchor.d.ts +23 -3
  185. package/dist/history-anchor.js +24 -4
  186. package/dist/hitl-delivery.d.ts +90 -0
  187. package/dist/hitl-delivery.js +98 -0
  188. package/dist/hooks/classic-stations.d.ts +8 -11
  189. package/dist/hooks/classic-stations.js +0 -7
  190. package/dist/hooks/hook-llm.d.ts +5 -1
  191. package/dist/hooks/hook-llm.js +24 -15
  192. package/dist/hooks/hook-runner.d.ts +67 -57
  193. package/dist/hooks/hook-runner.js +260 -186
  194. package/dist/host-lsp-manager.d.ts +22 -8
  195. package/dist/host-lsp-manager.js +4 -1
  196. package/dist/http/active-run-conflict.d.ts +2 -43
  197. package/dist/http/active-run-conflict.js +3 -7
  198. package/dist/http/admission.d.ts +4 -18
  199. package/dist/http/admission.js +75 -78
  200. package/dist/http/approval-sweeps.d.ts +3 -3
  201. package/dist/http/approval-sweeps.js +54 -30
  202. package/dist/http/caller-trust.d.ts +8 -10
  203. package/dist/http/caller-trust.js +1 -2
  204. package/dist/http/claim-heartbeat.d.ts +49 -0
  205. package/dist/http/claim-heartbeat.js +46 -0
  206. package/dist/http/claimed-run.d.ts +4 -3
  207. package/dist/http/dispatch.d.ts +8 -1
  208. package/dist/http/dispatch.js +76 -30
  209. package/dist/http/durable-run-leg.d.ts +3 -3
  210. package/dist/http/durable-run-leg.js +17 -12
  211. package/dist/http/execution-context.d.ts +17 -43
  212. package/dist/http/execution-context.js +7 -5
  213. package/dist/http/health-reader.d.ts +36 -0
  214. package/dist/http/health-reader.js +21 -0
  215. package/dist/http/health-self-report.d.ts +9 -9
  216. package/dist/http/health-self-report.js +4 -4
  217. package/dist/http/idempotency.d.ts +9 -5
  218. package/dist/http/idempotency.js +4 -4
  219. package/dist/http/memory-compliance-failure-reply.d.ts +15 -16
  220. package/dist/http/memory-compliance-failure-reply.js +4 -2
  221. package/dist/http/memory-face-fault-reply.d.ts +67 -0
  222. package/dist/http/memory-face-fault-reply.js +71 -0
  223. package/dist/http/memory-store-refusal-reply.d.ts +1 -1
  224. package/dist/http/memory-store-refusal-reply.js +3 -0
  225. package/dist/http/owner-access.d.ts +391 -67
  226. package/dist/http/owner-access.js +371 -35
  227. package/dist/http/principal-gate.d.ts +24 -81
  228. package/dist/http/principal-gate.js +24 -68
  229. package/dist/http/refusal-body.d.ts +35 -2
  230. package/dist/http/refusal-body.js +34 -0
  231. package/dist/http/request-guards.d.ts +9 -13
  232. package/dist/http/request-guards.js +34 -68
  233. package/dist/http/resume-composition.d.ts +37 -0
  234. package/dist/http/resume-composition.js +18 -0
  235. package/dist/http/resume-ctx-read.d.ts +35 -12
  236. package/dist/http/resume-ctx-read.js +25 -6
  237. package/dist/http/resume-legs.d.ts +21 -18
  238. package/dist/http/resume-legs.js +189 -129
  239. package/dist/http/retired-skill-manifest.d.ts +1 -1
  240. package/dist/http/route-ctx.d.ts +25 -32
  241. package/dist/http/routes/a2a-serve.d.ts +43 -5
  242. package/dist/http/routes/a2a-serve.js +58 -37
  243. package/dist/http/routes/admin-config-refresh.js +2 -2
  244. package/dist/http/routes/admin-drain.js +2 -2
  245. package/dist/http/routes/admin-memory-optout.js +4 -3
  246. package/dist/http/routes/adoption.d.ts +3 -0
  247. package/dist/http/routes/adoption.js +17 -10
  248. package/dist/http/routes/agents-roster.js +13 -7
  249. package/dist/http/routes/approvals-assistant.js +70 -65
  250. package/dist/http/routes/attachments.js +2 -2
  251. package/dist/http/routes/background.d.ts +3 -3
  252. package/dist/http/routes/background.js +9 -17
  253. package/dist/http/routes/capabilities.js +15 -18
  254. package/dist/http/routes/config-catalog.js +2 -2
  255. package/dist/http/routes/devices.d.ts +3 -3
  256. package/dist/http/routes/devices.js +4 -4
  257. package/dist/http/routes/diagnostics.d.ts +27 -3
  258. package/dist/http/routes/diagnostics.js +27 -21
  259. package/dist/http/routes/engine.js +2 -2
  260. package/dist/http/routes/fleet.js +5 -3
  261. package/dist/http/routes/images.js +10 -6
  262. package/dist/http/routes/leader.d.ts +7 -2
  263. package/dist/http/routes/leader.js +12 -3
  264. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  265. package/dist/http/routes/mcp-reconnect.js +7 -15
  266. package/dist/http/routes/memory-bundle.js +4 -7
  267. package/dist/http/routes/memory-compliance.js +4 -4
  268. package/dist/http/routes/memory-consolidation.js +6 -9
  269. package/dist/http/routes/memory-origin.d.ts +2 -0
  270. package/dist/http/routes/memory-origin.js +7 -8
  271. package/dist/http/routes/memory-policy.js +17 -12
  272. package/dist/http/routes/notify-wake.js +10 -19
  273. package/dist/http/routes/observability.js +3 -3
  274. package/dist/http/routes/principal-inbox.d.ts +1 -1
  275. package/dist/http/routes/principal-inbox.js +2 -2
  276. package/dist/http/routes/projects.d.ts +1 -1
  277. package/dist/http/routes/projects.js +3 -2
  278. package/dist/http/routes/retention-ops.js +6 -9
  279. package/dist/http/routes/rules.js +20 -5
  280. package/dist/http/routes/run-memory-capture.d.ts +2 -2
  281. package/dist/http/routes/run-memory-capture.js +6 -12
  282. package/dist/http/routes/run-permission-mode.d.ts +2 -2
  283. package/dist/http/routes/run-permission-mode.js +10 -17
  284. package/dist/http/routes/runs.js +146 -231
  285. package/dist/http/routes/session-memory-sync.d.ts +2 -2
  286. package/dist/http/routes/session-memory-sync.js +7 -13
  287. package/dist/http/routes/session-sync.d.ts +4 -2
  288. package/dist/http/routes/session-sync.js +21 -20
  289. package/dist/http/routes/sessions-list.d.ts +3 -3
  290. package/dist/http/routes/sessions-list.js +9 -9
  291. package/dist/http/routes/sessions.js +87 -124
  292. package/dist/http/routes/shared-memory.js +6 -6
  293. package/dist/http/routes/side-query.js +2 -2
  294. package/dist/http/routes/tasks.js +136 -120
  295. package/dist/http/routes/trace-usage.d.ts +0 -12
  296. package/dist/http/routes/trace-usage.js +40 -46
  297. package/dist/http/routes/workflows.d.ts +39 -4
  298. package/dist/http/routes/workflows.js +42 -14
  299. package/dist/http/run-concurrency-cap.d.ts +20 -12
  300. package/dist/http/run-concurrency-cap.js +39 -26
  301. package/dist/http/run-read-face.d.ts +3 -5
  302. package/dist/http/run-read-face.js +8 -13
  303. package/dist/http/send.d.ts +12 -3
  304. package/dist/http/send.js +42 -6
  305. package/dist/http/server.d.ts +39 -40
  306. package/dist/http/server.js +36 -11
  307. package/dist/http/sse-log.d.ts +17 -10
  308. package/dist/http/sse-log.js +7 -7
  309. package/dist/http/stored-body-replay.d.ts +12 -0
  310. package/dist/http/stored-body-replay.js +2 -2
  311. package/dist/http/submit-ctx.d.ts +16 -8
  312. package/dist/http/submit-ctx.js +6 -2
  313. package/dist/http/submit-replay.d.ts +19 -6
  314. package/dist/http/submit-replay.js +18 -6
  315. package/dist/http/sync-submit-body.d.ts +3 -4
  316. package/dist/http/sync-submit-body.js +4 -5
  317. package/dist/http/wire-types.d.ts +4 -3
  318. package/dist/identity-source.d.ts +36 -5
  319. package/dist/identity-source.js +27 -0
  320. package/dist/index.d.ts +1 -0
  321. package/dist/index.js +1 -0
  322. package/dist/key-resolver.d.ts +32 -0
  323. package/dist/key-resolver.js +23 -9
  324. package/dist/leader/diffup.js +2 -1
  325. package/dist/leader/endpoint.d.ts +26 -5
  326. package/dist/leader/endpoint.js +154 -14
  327. package/dist/leader/fanout.d.ts +3 -0
  328. package/dist/leader/fanout.js +78 -63
  329. package/dist/leader/leader.d.ts +16 -0
  330. package/dist/leader/leader.js +43 -3
  331. package/dist/leader/merge.d.ts +9 -1
  332. package/dist/leader/merge.js +16 -1
  333. package/dist/leader/repair-wire.js +1 -1
  334. package/dist/leader/wire.d.ts +36 -3
  335. package/dist/leader/wire.js +59 -18
  336. package/dist/lsp-evict.d.ts +6 -4
  337. package/dist/lsp-evict.js +2 -3
  338. package/dist/memory-operator-faces.d.ts +5 -4
  339. package/dist/memory-operator-faces.js +1 -1
  340. package/dist/memory-scope.d.ts +29 -2
  341. package/dist/memory-scope.js +64 -15
  342. package/dist/memory-store-refusal.d.ts +5 -2
  343. package/dist/memory-sync-client.d.ts +3 -2
  344. package/dist/memory-sync-client.js +6 -3
  345. package/dist/model-compat.d.ts +17 -8
  346. package/dist/model-compat.js +6 -2
  347. package/dist/model-context-window.d.ts +18 -0
  348. package/dist/model-context-window.js +2 -0
  349. package/dist/model-entry-headers.d.ts +46 -4
  350. package/dist/model-entry-headers.js +2 -2
  351. package/dist/model-entry-refusal.d.ts +35 -16
  352. package/dist/model-entry-refusal.js +40 -27
  353. package/dist/model-plane-face.d.ts +19 -11
  354. package/dist/model-plane-face.js +9 -1
  355. package/dist/model-plane-probe.d.ts +2 -0
  356. package/dist/model-plane-probe.js +17 -0
  357. package/dist/model-provider.d.ts +3 -3
  358. package/dist/model-route-endpoint.d.ts +9 -5
  359. package/dist/model-route-endpoint.js +29 -7
  360. package/dist/model-select.d.ts +16 -0
  361. package/dist/model-select.js +4 -0
  362. package/dist/mountinfo.d.ts +37 -0
  363. package/dist/mountinfo.js +47 -0
  364. package/dist/namespaced-rule-name.d.ts +23 -14
  365. package/dist/namespaced-rule-name.js +8 -11
  366. package/dist/net-address.d.ts +1 -1
  367. package/dist/observability/corrupt-read-seat.d.ts +4 -4
  368. package/dist/observability/corrupt-read-seat.js +5 -3
  369. package/dist/observability/fail-open.d.ts +58 -38
  370. package/dist/observability/fail-open.js +60 -38
  371. package/dist/observability/logger.d.ts +22 -1
  372. package/dist/observability/logger.js +89 -12
  373. package/dist/observability/metrics.js +3 -4
  374. package/dist/observability/otel-exporter.js +2 -1
  375. package/dist/observability/principal-context.d.ts +14 -7
  376. package/dist/observability/principal-context.js +8 -3
  377. package/dist/observability/run-terminal.d.ts +7 -1
  378. package/dist/observability/run-terminal.js +5 -2
  379. package/dist/orchestration/hardened-vm-runner.d.ts +1 -0
  380. package/dist/orchestration/hardened-vm-runner.js +11 -5
  381. package/dist/orchestration/hardened-vm-worker-runner.d.ts +0 -14
  382. package/dist/orchestration/hardened-vm-worker-runner.js +5 -0
  383. package/dist/orchestration/hardened-vm-worker.js +2 -1
  384. package/dist/orchestration/workflow-completion-inbox.d.ts +3 -3
  385. package/dist/orchestration/workflow-completion-inbox.js +3 -2
  386. package/dist/org-delivery-gate.d.ts +5 -4
  387. package/dist/org-delivery-gate.js +7 -6
  388. package/dist/outbound-dispatcher.d.ts +1 -1
  389. package/dist/outbound-dispatcher.js +6 -12
  390. package/dist/owner-brands.d.ts +81 -0
  391. package/dist/owner-brands.js +42 -0
  392. package/dist/owner-faces.d.ts +531 -0
  393. package/dist/owner-faces.js +126 -0
  394. package/dist/parked-decide.d.ts +67 -5
  395. package/dist/parked-decide.js +127 -5
  396. package/dist/parked-revive-surface.d.ts +14 -4
  397. package/dist/parked-revive-surface.js +5 -4
  398. package/dist/peer-directory.d.ts +6 -8
  399. package/dist/peer-directory.js +3 -2
  400. package/dist/permission-rule-vocab.d.ts +53 -8
  401. package/dist/permission-rule-vocab.js +30 -2
  402. package/dist/plugins/adoption-log-sql.d.ts +0 -22
  403. package/dist/plugins/adoption-log-sql.js +5 -3
  404. package/dist/plugins/approval-ask-retired-delete-by-task-m50.d.ts +7 -0
  405. package/dist/plugins/approval-ask-retired-delete-by-task-m50.js +17 -0
  406. package/dist/plugins/approval-ask-store-file.d.ts +18 -7
  407. package/dist/plugins/approval-ask-store-file.js +37 -14
  408. package/dist/plugins/approval-ask-store-memory.d.ts +12 -4
  409. package/dist/plugins/approval-ask-store-memory.js +24 -10
  410. package/dist/plugins/approval-ask-store-sql.d.ts +39 -20
  411. package/dist/plugins/approval-ask-store-sql.js +41 -10
  412. package/dist/plugins/approval-exemption-skill-m46.d.ts +20 -0
  413. package/dist/plugins/approval-exemption-skill-m46.js +25 -0
  414. package/dist/plugins/approval-exemption-store.d.ts +6 -1
  415. package/dist/plugins/approval-exemption-store.js +14 -0
  416. package/dist/plugins/approval-nonce-store.d.ts +39 -12
  417. package/dist/plugins/approval-nonce-store.js +72 -23
  418. package/dist/plugins/blob-backend.d.ts +2 -0
  419. package/dist/plugins/blob-backend.js +9 -6
  420. package/dist/plugins/caching-session-store.d.ts +13 -1
  421. package/dist/plugins/caching-session-store.js +43 -4
  422. package/dist/plugins/checkpoint-index-columns.d.ts +32 -5
  423. package/dist/plugins/checkpoint-index-columns.js +36 -1
  424. package/dist/plugins/checkpoint-store-face.d.ts +83 -10
  425. package/dist/plugins/checkpoint-store-face.js +18 -1
  426. package/dist/plugins/device-store-sql.js +1 -0
  427. package/dist/plugins/ensure-index.d.ts +13 -1
  428. package/dist/plugins/ensure-index.js +154 -21
  429. package/dist/plugins/ensure-schema-lock-policy.d.ts +0 -15
  430. package/dist/plugins/ensure-schema-lock-policy.js +3 -2
  431. package/dist/plugins/file-run-store.d.ts +63 -23
  432. package/dist/plugins/file-run-store.js +138 -83
  433. package/dist/plugins/host-platform.d.ts +24 -3
  434. package/dist/plugins/host-platform.js +15 -1
  435. package/dist/plugins/image-bake-store-sql.js +2 -0
  436. package/dist/plugins/imported-layer-root-retirement.d.ts +2 -2
  437. package/dist/plugins/k8s-pod-reaper.d.ts +1 -3
  438. package/dist/plugins/k8s-pod-reaper.js +0 -1
  439. package/dist/plugins/leader-run-store-sql.d.ts +22 -0
  440. package/dist/plugins/leader-run-store-sql.js +34 -4
  441. package/dist/plugins/local-checkpoint-store.d.ts +58 -23
  442. package/dist/plugins/local-checkpoint-store.js +195 -76
  443. package/dist/plugins/local-data-dir-modes-m39.js +2 -1
  444. package/dist/plugins/local-docker-capacity.d.ts +93 -0
  445. package/dist/plugins/local-docker-capacity.js +194 -0
  446. package/dist/plugins/local-docker-reaper.d.ts +92 -0
  447. package/dist/plugins/local-docker-reaper.js +102 -0
  448. package/dist/plugins/local-engine-high-water.d.ts +4 -2
  449. package/dist/plugins/local-engine-high-water.js +4 -1
  450. package/dist/plugins/local-session-store.d.ts +358 -125
  451. package/dist/plugins/local-session-store.js +824 -251
  452. package/dist/plugins/local-session-window.d.ts +3 -2
  453. package/dist/plugins/local-session-window.js +3 -2
  454. package/dist/plugins/local-sidecar-quarantine.js +3 -2
  455. package/dist/plugins/local-task-attachment-store.d.ts +18 -1
  456. package/dist/plugins/local-task-attachment-store.js +22 -1
  457. package/dist/plugins/memory-embedder.js +9 -7
  458. package/dist/plugins/memory-engine-pg.d.ts +3 -2
  459. package/dist/plugins/memory-engine-pg.js +14 -5
  460. package/dist/plugins/memory-engine-sql-apply.d.ts +8 -4
  461. package/dist/plugins/memory-engine-sql-apply.js +6 -8
  462. package/dist/plugins/memory-engine-tidb.d.ts +4 -2
  463. package/dist/plugins/memory-engine-tidb.js +11 -2
  464. package/dist/plugins/memory-optout-grant-store-sql.d.ts +1 -1
  465. package/dist/plugins/memory-optout-grant-store-sql.js +3 -3
  466. package/dist/plugins/memory-run-store.d.ts +39 -27
  467. package/dist/plugins/memory-run-store.js +72 -70
  468. package/dist/plugins/one-time-migration-marker.d.ts +10 -0
  469. package/dist/plugins/one-time-migration-marker.js +7 -0
  470. package/dist/plugins/one-time-migrations.d.ts +186 -45
  471. package/dist/plugins/one-time-migrations.js +442 -119
  472. package/dist/plugins/org-delivery-quota-store.js +2 -1
  473. package/dist/plugins/permission-rule-store-file.d.ts +3 -3
  474. package/dist/plugins/permission-rule-store-sql.d.ts +4 -63
  475. package/dist/plugins/permission-rule-store-sql.js +6 -86
  476. package/dist/plugins/pg-pool.d.ts +1 -31
  477. package/dist/plugins/pg-pool.js +16 -7
  478. package/dist/plugins/remote-env-e2b.d.ts +7 -25
  479. package/dist/plugins/remote-env-e2b.js +34 -25
  480. package/dist/plugins/remote-env-host.d.ts +2 -9
  481. package/dist/plugins/remote-env-host.js +34 -22
  482. package/dist/plugins/remote-env-k8s.d.ts +40 -45
  483. package/dist/plugins/remote-env-k8s.js +107 -58
  484. package/dist/plugins/remote-env-local-docker.d.ts +148 -14
  485. package/dist/plugins/remote-env-local-docker.js +367 -72
  486. package/dist/plugins/remote-scratchpad.js +4 -0
  487. package/dist/plugins/retention-deployment.d.ts +49 -17
  488. package/dist/plugins/retention-deployment.js +39 -8
  489. package/dist/plugins/run-preview-m45.d.ts +11 -0
  490. package/dist/plugins/run-preview-m45.js +19 -0
  491. package/dist/plugins/run-store-sql.d.ts +56 -68
  492. package/dist/plugins/run-store-sql.js +127 -116
  493. package/dist/plugins/s3-answer.d.ts +2 -1
  494. package/dist/plugins/sandbox-reaper.d.ts +26 -0
  495. package/dist/plugins/sandbox-reaper.js +2 -0
  496. package/dist/plugins/scenario-prefix-switch-m42.d.ts +54 -0
  497. package/dist/plugins/scenario-prefix-switch-m42.js +55 -0
  498. package/dist/plugins/scheduler-support.d.ts +16 -1
  499. package/dist/plugins/scheduler-support.js +22 -2
  500. package/dist/plugins/schema-consistency.d.ts +42 -9
  501. package/dist/plugins/schema-consistency.js +212 -11
  502. package/dist/plugins/schema-drift-sentence.d.ts +4 -1
  503. package/dist/plugins/schema-drift-sentence.js +4 -4
  504. package/dist/plugins/schema-ensure-legs.d.ts +9 -0
  505. package/dist/plugins/schema-ensure-legs.js +1 -1
  506. package/dist/plugins/schema-indexes.js +3 -0
  507. package/dist/plugins/schema-live-columns.d.ts +14 -5
  508. package/dist/plugins/schema-live-columns.js +10 -10
  509. package/dist/plugins/send-user-file.js +2 -1
  510. package/dist/plugins/session-birth.d.ts +37 -0
  511. package/dist/plugins/session-birth.js +38 -0
  512. package/dist/plugins/session-epoch-anchor.d.ts +40 -12
  513. package/dist/plugins/session-epoch-anchor.js +33 -11
  514. package/dist/plugins/session-ext-table.d.ts +27 -3
  515. package/dist/plugins/session-ext-table.js +19 -6
  516. package/dist/plugins/session-fork.d.ts +15 -1
  517. package/dist/plugins/session-fork.js +3 -0
  518. package/dist/plugins/session-ownership.d.ts +1 -1
  519. package/dist/plugins/session-placement.d.ts +22 -16
  520. package/dist/plugins/session-placement.js +15 -12
  521. package/dist/plugins/session-row-sql.d.ts +12 -0
  522. package/dist/plugins/session-row-sql.js +3 -3
  523. package/dist/plugins/session-run-rollup.d.ts +29 -0
  524. package/dist/plugins/session-run-rollup.js +40 -0
  525. package/dist/plugins/session-store-sql.d.ts +38 -12
  526. package/dist/plugins/session-store-sql.js +130 -30
  527. package/dist/plugins/session-withdraw-sql.d.ts +36 -0
  528. package/dist/plugins/session-withdraw-sql.js +42 -0
  529. package/dist/plugins/shared-memory-store-sql.d.ts +0 -69
  530. package/dist/plugins/shared-memory-store-sql.js +2 -1
  531. package/dist/plugins/shell-sentinel-owner-m48.d.ts +20 -0
  532. package/dist/plugins/shell-sentinel-owner-m48.js +3 -0
  533. package/dist/plugins/snapshot-bucket-presence.d.ts +2 -1
  534. package/dist/plugins/sql-driver.d.ts +1 -1
  535. package/dist/plugins/sql-driver.js +2 -1
  536. package/dist/plugins/sql-errors.d.ts +32 -5
  537. package/dist/plugins/sql-errors.js +55 -7
  538. package/dist/plugins/sql-json-column.d.ts +8 -7
  539. package/dist/plugins/sql-json-column.js +10 -6
  540. package/dist/plugins/store-backend.d.ts +84 -15
  541. package/dist/plugins/store-backend.js +186 -55
  542. package/dist/plugins/store-contracts.d.ts +90 -30
  543. package/dist/plugins/store-contracts.js +22 -1
  544. package/dist/plugins/store-fault.d.ts +28 -2
  545. package/dist/plugins/store-fault.js +20 -2
  546. package/dist/plugins/task-attachment-store.d.ts +15 -3
  547. package/dist/plugins/task-attachment-store.js +22 -9
  548. package/dist/plugins/tidb-pool.js +23 -8
  549. package/dist/plugins/web-search.js +5 -3
  550. package/dist/plugins/workflow-run-store-sql.d.ts +6 -1
  551. package/dist/plugins/workflow-run-store-sql.js +11 -5
  552. package/dist/principal-jwt.d.ts +4 -4
  553. package/dist/principal-jwt.js +20 -5
  554. package/dist/principal-shape.d.ts +41 -0
  555. package/dist/principal-shape.js +19 -0
  556. package/dist/question.d.ts +13 -23
  557. package/dist/question.js +21 -39
  558. package/dist/request-declaration-refusal.d.ts +35 -12
  559. package/dist/request-declaration-refusal.js +21 -9
  560. package/dist/request-form.d.ts +58 -0
  561. package/dist/request-form.js +72 -0
  562. package/dist/request-image-urls.d.ts +8 -2
  563. package/dist/request-image-urls.js +2 -1
  564. package/dist/request-images.d.ts +20 -0
  565. package/dist/request-images.js +59 -0
  566. package/dist/request-key-closure.d.ts +6 -7
  567. package/dist/request-key-closure.js +15 -2
  568. package/dist/resource-suspend.d.ts +7 -16
  569. package/dist/resource-suspend.js +1 -1
  570. package/dist/retired-name-host-notice.d.ts +21 -0
  571. package/dist/retired-name-host-notice.js +12 -0
  572. package/dist/rule-entry-list.d.ts +2 -2
  573. package/dist/rule-entry-list.js +3 -4
  574. package/dist/rule-issue-reading.d.ts +58 -0
  575. package/dist/rule-issue-reading.js +14 -0
  576. package/dist/rules-consent.d.ts +5 -3
  577. package/dist/rules-consent.js +1 -0
  578. package/dist/run-acceptance.d.ts +3 -1
  579. package/dist/run-acceptance.js +3 -0
  580. package/dist/run-local.d.ts +14 -6
  581. package/dist/run-local.js +92 -80
  582. package/dist/run-stale-default.d.ts +7 -0
  583. package/dist/run-stale-default.js +2 -0
  584. package/dist/runs.d.ts +26 -15
  585. package/dist/runs.js +39 -27
  586. package/dist/runtime-caps-resolver.d.ts +21 -6
  587. package/dist/runtime-caps-resolver.js +17 -4
  588. package/dist/runtime-governance.d.ts +2 -2
  589. package/dist/runtime-governance.js +5 -3
  590. package/dist/sealed-key-layout.d.ts +12 -5
  591. package/dist/sealed-key-layout.js +5 -0
  592. package/dist/sealed-key.d.ts +12 -2
  593. package/dist/sealed-key.js +115 -27
  594. package/dist/security.d.ts +128 -158
  595. package/dist/security.js +163 -59
  596. package/dist/sema-config-dir.d.ts +20 -0
  597. package/dist/sema-config-dir.js +25 -0
  598. package/dist/server-secret-env.d.ts +21 -5
  599. package/dist/server-secret-env.js +14 -4
  600. package/dist/session-children-retire.d.ts +7 -5
  601. package/dist/session-children-retire.js +7 -2
  602. package/dist/session-deletion.d.ts +88 -0
  603. package/dist/session-deletion.js +8 -0
  604. package/dist/session-erase.d.ts +145 -0
  605. package/dist/session-erase.js +115 -0
  606. package/dist/session-error-wire.d.ts +5 -5
  607. package/dist/session-error-wire.js +1 -1
  608. package/dist/session-incarnation.d.ts +52 -5
  609. package/dist/session-incarnation.js +19 -2
  610. package/dist/session-policy-wire.d.ts +6 -3
  611. package/dist/session-policy-wire.js +11 -1
  612. package/dist/session-resident-background.d.ts +0 -71
  613. package/dist/session-resident-background.js +4 -3
  614. package/dist/session-row.d.ts +2 -1
  615. package/dist/session-rules-seat.d.ts +12 -0
  616. package/dist/session-rules-seat.js +19 -0
  617. package/dist/session-titler.d.ts +7 -15
  618. package/dist/session-titler.js +1 -1
  619. package/dist/spec-fields.d.ts +57 -3
  620. package/dist/spec-fields.js +72 -51
  621. package/dist/store-posture.d.ts +136 -20
  622. package/dist/store-posture.js +188 -3
  623. package/dist/stored-request-hooks-m20.js +2 -2
  624. package/dist/stores-sql/background-agent-store-sql.d.ts +11 -2
  625. package/dist/stores-sql/background-agent-store-sql.js +73 -20
  626. package/dist/stores-sql/checkpoint-store-sql.d.ts +33 -28
  627. package/dist/stores-sql/checkpoint-store-sql.js +75 -44
  628. package/dist/stores-sql/mailbox-store-sql.d.ts +7 -4
  629. package/dist/stores-sql/mailbox-store-sql.js +31 -32
  630. package/dist/stores-sql/pg-session-storage.d.ts +7 -5
  631. package/dist/stores-sql/pg-session-storage.js +54 -46
  632. package/dist/stores-sql/retention-store-sql.d.ts +87 -19
  633. package/dist/stores-sql/retention-store-sql.js +92 -27
  634. package/dist/stores-sql/session-policy-store-sql.d.ts +32 -10
  635. package/dist/stores-sql/session-policy-store-sql.js +44 -36
  636. package/dist/stores-sql/session-write-hooks.d.ts +12 -6
  637. package/dist/stores-sql/tidb-session-storage.d.ts +4 -4
  638. package/dist/stores-sql/tidb-session-storage.js +13 -8
  639. package/dist/stores-sql/tidb-session-store.d.ts +36 -17
  640. package/dist/stores-sql/tidb-session-store.js +69 -39
  641. package/dist/stores-sql/windowed-session-storage.d.ts +5 -0
  642. package/dist/stores-sql/windowed-session-storage.js +9 -0
  643. package/dist/submission-exit.d.ts +50 -0
  644. package/dist/submission-exit.js +108 -0
  645. package/dist/task-a2a.d.ts +1 -1
  646. package/dist/task-a2a.js +3 -3
  647. package/dist/task-cwd.d.ts +20 -0
  648. package/dist/task-cwd.js +8 -0
  649. package/dist/task-mcp.d.ts +1 -1
  650. package/dist/task-mcp.js +3 -3
  651. package/dist/task-settings.d.ts +14 -10
  652. package/dist/task-settings.js +18 -7
  653. package/dist/terminal.d.ts +25 -43
  654. package/dist/terminal.js +31 -11
  655. package/dist/tool-approval.d.ts +131 -129
  656. package/dist/tool-approval.js +189 -193
  657. package/dist/trace/core-keyset-guard.d.ts +13 -6
  658. package/dist/trace/core-vocab-wire.d.ts +95 -0
  659. package/dist/trace/core-vocab-wire.js +15 -0
  660. package/dist/trace/exit-seat.d.ts +30 -4
  661. package/dist/trace/exit-seat.js +40 -36
  662. package/dist/trace/extension-call-face.d.ts +3 -2
  663. package/dist/trace/extension-call-face.js +3 -1
  664. package/dist/trace/ledger-events.d.ts +4 -7
  665. package/dist/trace/ledger-sink.d.ts +1 -30
  666. package/dist/trace/ledger-sink.js +0 -11
  667. package/dist/trace/model-segment.d.ts +5 -5
  668. package/dist/trace/model-segment.js +5 -5
  669. package/dist/trace/project.d.ts +55 -42
  670. package/dist/trace/project.js +52 -19
  671. package/dist/trace/redact.d.ts +123 -27
  672. package/dist/trace/redact.js +880 -260
  673. package/dist/trace/self-minted-wire.d.ts +487 -19
  674. package/dist/trace/self-minted-wire.js +172 -23
  675. package/dist/trace/wire-projection-faces.d.ts +5 -6
  676. package/dist/trace/wire-projection-faces.js +2 -2
  677. package/dist/upstream-body.d.ts +23 -0
  678. package/dist/upstream-body.js +16 -0
  679. package/dist/url-refusal.d.ts +3 -3
  680. package/dist/url-refusal.js +11 -4
  681. package/dist/user-authored-text.d.ts +1 -1
  682. package/dist/user-input-ledger.d.ts +7 -8
  683. package/dist/user-input-ledger.js +1 -1
  684. package/dist/web-admin-client/apply-effective.d.ts +6 -3
  685. package/dist/web-admin-client/apply-effective.js +13 -16
  686. package/dist/web-admin-client/apply-ledger.d.ts +16 -7
  687. package/dist/web-admin-client/apply-ledger.js +5 -2
  688. package/dist/web-admin-client/center-request.d.ts +2 -2
  689. package/dist/web-admin-client/center-request.js +15 -15
  690. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  691. package/dist/web-admin-client/effective-keys.js +1 -1
  692. package/dist/web-admin-client/entry-metadata.d.ts +8 -0
  693. package/dist/web-admin-client/entry-metadata.js +13 -0
  694. package/dist/web-admin-client/http-client.d.ts +1 -1
  695. package/dist/web-admin-client/http-client.js +23 -4
  696. package/dist/web-admin-client/mcp-revocation.d.ts +1 -1
  697. package/dist/web-admin-client/mcp-revocation.js +2 -1
  698. package/dist/web-admin-client/read-warnings.d.ts +1 -0
  699. package/dist/web-admin-client/read-warnings.js +1 -0
  700. package/dist/web-admin-client/restart-signal.js +8 -4
  701. package/dist/web-admin-client/skills-mcp.d.ts +2 -2
  702. package/dist/web-admin-client/types.d.ts +1 -2
  703. package/dist/workspace-space.d.ts +3 -2
  704. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +4 -4
  705. package/package.json +6 -3
  706. package/scripts/drain-check-local.mjs +118 -147
  707. package/scripts/drain-check.mysql.sql +22 -2
  708. package/scripts/drain-check.pg.sql +22 -2
  709. package/wire-changes/server@7.148.0.json +1517 -0
  710. package/dist/http/memory-control-plane-corrupt-reply.d.ts +0 -25
  711. package/dist/http/memory-control-plane-corrupt-reply.js +0 -19
  712. package/dist/http/memory-layer-reply.d.ts +0 -30
  713. package/dist/http/memory-layer-reply.js +0 -24
  714. package/dist/lsp/e2b-bridge.d.ts +0 -57
  715. package/dist/lsp/e2b-bridge.js +0 -139
  716. package/dist/lsp/e2b-manager.d.ts +0 -93
  717. package/dist/lsp/e2b-manager.js +0 -178
  718. package/dist/lsp/lsp-frames.d.ts +0 -14
  719. package/dist/lsp/lsp-frames.js +0 -25
  720. package/dist/lsp/manager.d.ts +0 -129
  721. package/dist/lsp/manager.js +0 -127
  722. package/dist/lsp/types.d.ts +0 -12
  723. package/dist/lsp/types.js +0 -2
  724. package/dist/lsp/ws-transport.d.ts +0 -40
  725. package/dist/lsp/ws-transport.js +0 -210
@@ -1,32 +1,36 @@
1
+ import { SCENARIO_SUBJECT_PREFIX } from "../owner-brands.js";
1
2
  import { lstat, readFile } from "node:fs/promises";
2
3
  import { closeSync, existsSync, fsyncSync, lstatSync, openSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
3
4
  import { AsyncLocalStorage } from "node:async_hooks";
4
5
  import { isDeepStrictEqual } from "node:util";
5
6
  import { dirname, join, resolve } from "node:path";
6
- import { atomicWriteFileAsync } from "./owner-only-file-mode.js";
7
+ import { IS_WIN32 } from "./host-platform.js";
8
+ import { atomicWriteFileAsync, atomicWriteFileSync, proveDurableSync } from "./owner-only-file-mode.js";
7
9
  import { assertFirstOwnerWritable, forkOwnerOf, importerOwnerOf, placedSessionOwnerOf } from "./session-ownership.js";
8
10
  import { SessionOwnerUnreadableError, StampedSessionArchiveError, sessionUnreadableOf } from "../session-error-wire.js";
9
- import { FileSessionRepo, SessionError, StoredSession, admitSessionPlacement, atomicWriteFile, ensureDir, sessionLogMarkOf, settledForkOptions, uuidv7, validateEntriesForImport } from "@sema-agent/core";
11
+ import { SHELL_SENTINEL_OWNER_M48 } from "./shell-sentinel-owner-m48.js";
12
+ import { FileSessionRepo, SessionError, StoredSession, admitSessionPlacement, atomicWriteFile, ensureDir, forkRowCells, sessionLogMarkOf, settledForkOptions, uuidv7, validateEntriesForImport } from "@sema-agent/core";
10
13
  import { placementCapabilityOf } from "./session-placement.js";
11
14
  import { z } from "zod";
12
15
  import { contentForkRelation, fastForwardSharedContentDiverged, identicalIdsAlsoIdenticalContent } from "../session-sync-content.js";
13
16
  import { SCHEMA_MISMATCH_ERROR_CODE, StoreBootRefusalError } from "./store-contracts.js";
17
+ import { entryTypeWithdrawable } from "./session-birth.js";
14
18
  import { judgeSessionRowWrite, sessionRowTagKey, settleSessionRowWrite, } from "../session-row.js";
15
19
  import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
16
20
  import { createZodColumnReader, decodeJsonColumn, isJsonColumnBodyProblem, isJsonObject, readJsonObjectColumn, readStringRecordColumn, SqlJsonColumnError } from "./sql-json-column.js";
17
21
  import { RENAMED_EXECUTION_LANE_M40, RETIRED_EXECUTION_LANE_WORD_M40 } from "./execution-lane-rename-m40.js";
18
- import { ExecutionLaneRenameWriteRefusedError, ONE_TIME_MIGRATIONS, migrationWindowOpen } from "./one-time-migrations.js";
22
+ import { ExecutionLaneRenameWriteRefusedError, ONE_TIME_MIGRATIONS } from "./one-time-migrations.js";
23
+ import { compareReleaseTriples, releaseText, releaseTriple } from "./release-version.js";
19
24
  import { recordFailOpen } from "../observability/fail-open.js";
20
- import { createLogger } from "../observability/logger.js";
25
+ import { createLogger, writeLogText } from "../observability/logger.js";
21
26
  import { UNMANAGED_RETENTION } from "./retention-declaration.js";
22
- import { UnreadableLeaf, carriedRowCells, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
27
+ import { UnreadableLeaf, carriedRowCells, forkLineageOfRow, headFromRow, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
23
28
  import { isExecutionLane } from "../execution-lane-caps.js";
24
- import { SessionForkInProgressError, WRITABLE, assertPlaceableLane, isUnusableArchive, judgeImportWrite, settledMirrorOutcome, settledPlaceOutcome, } from "../execution-placement.js";
29
+ import { SessionForkInProgressError, WRITABLE, assertPlaceableLane, judgeSessionWrite, settledMirrorOutcome, settledPlaceOutcome, SessionDeletingError, } from "../execution-placement.js";
25
30
  import { classifyForkTarget, forkByFreshId } from "./session-fork.js";
26
31
  import { decodeInstructionSource, encodeInstructionSource, sameInstructionSourceBaseline } from "../instruction-source-notice.js";
27
32
  import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
28
33
  import { createCorruptReadFailOpenSeat } from "../observability/corrupt-read-seat.js";
29
- import { buildObjectivePreview, isAttachmentFrame } from "../user-authored-text.js";
30
34
  import { classifySyncRelationshipByIds, ownerCellIs, SyncConflictError, StagedCommitOwnerChangedError, stagingIdFor, } from "../session-sync-kernel.js";
31
35
  const ownerEq = (a, b) => (a ?? null) === (b ?? null);
32
36
  const isNotFound = (e) => e instanceof SessionError && e.code === "not_found";
@@ -50,46 +54,29 @@ function archiveFaultWord(code) {
50
54
  }
51
55
  }
52
56
  }
53
- function unusableArchiveOf(word, fault) {
54
- const code = fault instanceof SessionError ? undefined : fault?.code;
57
+ function unusableArchiveOfFault(fault) {
58
+ const engine = fault instanceof SessionOwnerUnreadableError ? "corrupt_session" : fault instanceof SessionError ? archiveFaultWord(fault.code) : "storage";
59
+ const word = engine === "absent" ? "storage" : engine;
60
+ const code = fault instanceof SessionError || fault instanceof SessionOwnerUnreadableError ? undefined : fault?.code;
55
61
  return { state: "unusable", word, fault, ...(word === "storage" && typeof code === "string" ? { errno: code } : {}) };
56
62
  }
57
- function ownerUnreadableArchive(probe) {
58
- const { fault } = probe;
59
- if (probe.word === "storage" || !(fault instanceof SessionError))
60
- return probe;
61
- return { ...probe, fault: new SessionOwnerUnreadableError(fault) };
63
+ function ownerUnreadableFault(fault) {
64
+ return fault instanceof SessionError ? new SessionOwnerUnreadableError(fault) : fault instanceof Error ? fault : new Error(String(fault));
62
65
  }
66
+ const ABSENT_ROW = { row: "absent" };
67
+ const NO_CELL = { state: "none" };
63
68
  function importRowRefused(sessionId, landing) {
64
69
  return new SessionError("conflict", landing === "create"
65
70
  ? `session ${sessionId} already exists — importEntries only creates a session (replaceEntries rewrites one); nothing was written`
66
71
  : `session ${sessionId} is not the importer's row — an import replaces only a log whose row the importer owns (or an anonymous row, by an anonymous import); nothing was written`);
67
72
  }
68
- const SESSION_OBJECTIVE_PREVIEW_CHARS = 200;
69
- function userText(entry) {
70
- if (entry.type !== "message")
71
- return null;
72
- const msg = entry.message;
73
- if (msg.role !== "user" || isAttachmentFrame(msg))
74
- return null;
75
- const c = msg.content;
76
- if (typeof c === "string")
77
- return c;
78
- if (Array.isArray(c)) {
79
- return c
80
- .filter((p) => !!p && typeof p === "object" && p.type === "text")
81
- .map((p) => p.text)
82
- .join(" ");
83
- }
84
- return null;
85
- }
86
73
  const SIDECAR_DEGRADES = { lastActivity: "recency", title: "title", pinned: "pin state", "instruction-source": "instruction-source baseline", workspace: "each session's last recorded place and project (the next frame records it again)", "session-meta": "session owner + incarnation (the write that needed it failed loudly)" };
87
74
  const readPinnedSidecar = createZodColumnReader(z.record(z.string(), z.literal(true)));
88
75
  const MIRROR_CELL = { role: "mirror" };
89
76
  function readSessionMetaCell(cell) {
90
77
  return typeof cell === "string"
91
- ? { incarnation: cell, owner: undefined, execution: undefined, forkKey: undefined, createdAt: undefined, forkedFrom: undefined }
92
- : { incarnation: cell.incarnation, owner: cell.owner, execution: cell.execution, forkKey: cell.forkKey, createdAt: cell.createdAt, forkedFrom: cell.forkedFrom };
78
+ ? { incarnation: cell, owner: undefined, execution: undefined, forkKey: undefined, createdAt: undefined, forkedFrom: undefined, forkedFromIncarnation: undefined, dropping: false, deleting: undefined, clientWrites: undefined }
79
+ : { incarnation: cell.incarnation, owner: cell.owner, execution: cell.execution, forkKey: cell.forkKey, createdAt: cell.createdAt, forkedFrom: cell.forkedFrom, forkedFromIncarnation: cell.forkedFromIncarnation, dropping: cell.dropping === true, deleting: cell.deleting, clientWrites: cell.clientWrites };
93
80
  }
94
81
  const ownerRecorded = (cell) => cell !== undefined && readSessionMetaCell(cell).owner !== undefined;
95
82
  const cellObjectOf = (cell) => (typeof cell === "string" ? { incarnation: cell } : cell);
@@ -105,9 +92,9 @@ function archivedRowFactsOf(head) {
105
92
  ...(typeof forkedFrom === "string" && forkedFrom !== "" ? { forkedFrom } : {}),
106
93
  };
107
94
  }
108
- function rowFactsFilled(before, facts) {
95
+ function rowFactsFilled(sessionId, before, facts, archiveToken) {
109
96
  if (before === undefined)
110
- return { incarnation: mintSessionIncarnation(), createdAt: facts.createdAt, ...(facts.forkedFrom !== undefined ? { forkedFrom: facts.forkedFrom } : {}) };
97
+ return { incarnation: firstCellIncarnationOf(sessionId, archiveToken).incarnation, createdAt: facts.createdAt, ...(facts.forkedFrom !== undefined ? { forkedFrom: facts.forkedFrom } : {}) };
111
98
  const held = cellObjectOf(before);
112
99
  const add = {
113
100
  ...(held.createdAt === undefined ? { createdAt: facts.createdAt } : {}),
@@ -124,7 +111,7 @@ function rowCreatedAtOf(sessionId, meta) {
124
111
  }
125
112
  function rowMetadataOf(sessionId, meta) {
126
113
  const face = sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation);
127
- return { id: sessionId, createdAt: rowCreatedAtOf(sessionId, meta), owner: face.owner, incarnation: face.incarnation, ...(meta.forkedFrom !== undefined ? { forkedFrom: meta.forkedFrom } : {}) };
114
+ return { id: sessionId, createdAt: rowCreatedAtOf(sessionId, meta), owner: face.owner, incarnation: face.incarnation, ...forkLineageOfRow(sessionId, meta.forkedFrom, meta.forkedFromIncarnation) };
128
115
  }
129
116
  const isForkCreatedCell = (meta) => meta !== undefined && meta.forkKey !== undefined && meta.execution === undefined;
130
117
  function placementCellOf(meta, forking) {
@@ -133,6 +120,27 @@ function placementCellOf(meta, forking) {
133
120
  function readingOfCell(sessionId, meta, forking) {
134
121
  return { occupant: sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation), cell: placementCellOf(meta, forking) };
135
122
  }
123
+ function legacyNeedsMint(seen) {
124
+ return seen.row === "legacy" && seen.placed === undefined && seen.unreadable === undefined;
125
+ }
126
+ function executionReadingOf(sessionId, seen) {
127
+ switch (seen.row) {
128
+ case "absent":
129
+ return undefined;
130
+ case "live":
131
+ return seen.placed !== undefined ? undefined : readingOfCell(sessionId, seen.meta, seen.forking);
132
+ case "legacy":
133
+ if (seen.placed !== undefined)
134
+ return undefined;
135
+ if (seen.unreadable !== undefined)
136
+ throw ownerUnreadableFault(seen.unreadable);
137
+ throw new Error(`LocalSessionStore: session ${sessionId} is a legacy row — its cell is minted under its write lock before an execution reading is given`);
138
+ default: {
139
+ const unknownRow = seen;
140
+ throw new Error(`LocalSessionStore: no row observation ${JSON.stringify(unknownRow)}`);
141
+ }
142
+ }
143
+ }
136
144
  const SESSION_META_SIDECAR_FILE = "session-incarnations.json";
137
145
  const LOCAL_EXECUTION_CELL = z.discriminatedUnion("role", [
138
146
  z.object({ role: z.literal("writer"), lane: z.custom((v) => typeof v === "string" && isExecutionLane(v), { message: "not an execution lane" }) }).strict(),
@@ -146,23 +154,40 @@ export const SESSION_META_CELL_OBJECT = z
146
154
  forkKey: z.string().optional(),
147
155
  createdAt: z.string().optional(),
148
156
  forkedFrom: z.string().optional(),
157
+ forkedFromIncarnation: z.string().optional(),
158
+ dropping: z.literal(true).optional(),
159
+ deleting: z.string().min(1).optional(),
160
+ clientWrites: z.number().int().nonnegative().optional(),
149
161
  })
150
162
  .strict();
151
163
  const SESSION_META_SIDECAR = z.record(z.string(), z.union([z.string(), SESSION_META_CELL_OBJECT]));
152
164
  const readSessionMetaSidecar = createZodColumnReader(SESSION_META_SIDECAR);
153
- function ownerClaimWindowOpen() {
154
- const window = ONE_TIME_MIGRATIONS.find((w) => w.id === "M10");
155
- if (window === undefined)
156
- throw new Error("LocalSessionStore: the one-time migration registry has no M10 row (the local session-owner claim window) — the claim arm outlived its registry row; delete it with that row");
157
- return migrationWindowOpen(window, new Date().toISOString().slice(0, 10));
158
- }
159
165
  const SIDECAR_MAP_STATE_BY_PATH = new Map();
160
166
  const SESSION_WRITE_LOCKS_BY_ROOT = new Map();
161
167
  const quotedFieldNames = (names) => `${names.slice(0, 8).map((name) => JSON.stringify(name.slice(0, 64))).join(", ")}${names.length > 8 ? `, … (${names.length} in all)` : ""}`;
168
+ function releaseAtOrAbove(a, b) {
169
+ const ta = releaseTriple(a);
170
+ const tb = releaseTriple(b);
171
+ return ta !== undefined && tb !== undefined && compareReleaseTriples(ta, tb) >= 0;
172
+ }
162
173
  function describeDataRootFinding(f) {
163
174
  if (f.problem === "newer-engine") {
164
175
  return ` ${f.label} at ${f.path}: engine high-water ${f.engineHighWater.highWater} — the data root has been opened by server ${f.engineHighWater.highWater}, NEWER than this build (${f.engineHighWater.engine}).`;
165
176
  }
177
+ if (f.problem === "skipped-required-migration") {
178
+ const { highWater, requiredSince, sessions } = f.skippedMigration;
179
+ const consequence = "This build no longer carries that migration: opening the root would give those sessions a new occupant token, and memory records kept under the old one (capture opt-outs, pollution marks) would stop applying to them.";
180
+ if (highWater !== null && releaseAtOrAbove(highWater, requiredSince)) {
181
+ return (` ${f.label} at ${f.path}: ${sessions} session archive(s) have no session-meta cell, and the data root was last opened by server ${highWater}, which carries the one-time migration ` +
182
+ `(introduced in ${requiredSince}) that gives such an archive its row cell under the occupant token the archive already carries — but that start did not finish it ` +
183
+ `(${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} is still in the data root). ${consequence} ` +
184
+ `Start the root once with a release at or above ${highWater} that still carries that migration (server or run-local) and let that start finish, then start this build.`);
185
+ }
186
+ const lastOpened = highWater === null ? "no engine that records a high-water (run-local before 7.148.0, or a server before 7.144.0)" : `server ${highWater}`;
187
+ return (` ${f.label} at ${f.path}: ${sessions} session archive(s) have no session-meta cell, and the data root was last opened by ${lastOpened} — older than ${requiredSince}, ` +
188
+ `whose one-time migration gives such an archive its row cell under the occupant token the archive already carries. ${consequence} ` +
189
+ `Start the root once with a release at or above ${requiredSince} that still carries that migration (server or run-local) and let that start finish, then start this build.`);
190
+ }
166
191
  return f.unknownFields.length > 0
167
192
  ? ` ${f.label} at ${f.path}: UNKNOWN field(s) ${quotedFieldNames(f.unknownFields)} — the data root is NEWER than this build (a newer server engine wrote it).`
168
193
  : ` ${f.label} at ${f.path}: unreadable (${f.problem}) — this build cannot read it, and an unreadable file is treated as a mismatch, not as empty.`;
@@ -175,7 +200,7 @@ function describeLocalDataRootMismatch(findings, when) {
175
200
  ];
176
201
  for (const f of findings)
177
202
  lines.push(describeDataRootFinding(f));
178
- lines.push("This build never reads such a file as empty and never writes over it: either would silently change what the file records.", "", " Ways out (these three and no others):", " (1) the data root is NEWER than this build (unknown fields, or an engine high-water above this build): deploy the build that wrote it (for a high-water: server at or above it) — to run an older build on this machine, give it its own data root (LOCAL_DATA_ROOT);", " (2) roll back: restore the data-root backup taken before the upgrade, then start this build — also the way out when the file's bytes are damaged (not-json / malformed);", " (3) the file could not be read at all (an I/O error such as EACCES): repair access to it (permissions, mount, disk) and restart — nothing in the file needs changing.", "The check behind this refusal only reads: the file(s) named above were left in place, unchanged.");
203
+ lines.push("This build never reads such a file as empty and never writes over it: either would silently change what the file records.", "", " Ways out (these four and no others):", " (1) the data root is NEWER than this build (unknown fields, or an engine high-water above this build): deploy the build that wrote it (for a high-water: server at or above it) — to run an older build on this machine, give it its own data root (LOCAL_DATA_ROOT);", " (2) roll back: restore the data-root backup taken before the upgrade, then start this build — also the way out when the file's bytes are damaged (not-json / malformed);", " (3) the file could not be read at all (an I/O error such as EACCES): repair access to it (permissions, mount, disk) and restart — nothing in the file needs changing;", " (4) the data root skipped a required one-time migration, or a release carrying it opened the root and did not finish it (the finding names the release to start with): start the root once with a release at or above that one that still carries the migration (server or run-local), let that start finish, then start this build.", "The check behind this refusal only reads: the file(s) named above were left in place, unchanged.");
179
204
  return lines.join("\n");
180
205
  }
181
206
  export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
@@ -189,6 +214,115 @@ export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
189
214
  this.name = "LocalDataRootSchemaMismatchError";
190
215
  }
191
216
  }
217
+ export const ARCHIVE_TOKEN_ADOPTION_SINCE = "7.148.0";
218
+ export function archiveTokenAdoptionCarried() {
219
+ return ONE_TIME_MIGRATIONS.some((w) => w.id === "M47");
220
+ }
221
+ const ARCHIVE_DIR = "sessions";
222
+ const ARCHIVE_SUFFIX = ".jsonl";
223
+ const ARCHIVE_TOKEN_ADOPTION_PENDING_FILE = "archive-token-adoption.pending";
224
+ const archiveTokenAdoptionPendingPath = (root) => resolve(join(root, ARCHIVE_TOKEN_ADOPTION_PENDING_FILE));
225
+ function archiveTokenAdoptionPendingPresent(root) {
226
+ try {
227
+ lstatSync(archiveTokenAdoptionPendingPath(root));
228
+ return true;
229
+ }
230
+ catch (e) {
231
+ if (e?.code === "ENOENT")
232
+ return false;
233
+ throw e;
234
+ }
235
+ }
236
+ function recordArchiveTokenAdoptionPending(root, priorHighWater) {
237
+ const path = archiveTokenAdoptionPendingPath(root);
238
+ const prior = priorHighWater === undefined ? "none" : releaseText(priorHighWater);
239
+ try {
240
+ atomicWriteFileSync(path, `opened ${new Date().toISOString()} by a release that adopts archive tokens; engine high-water before that start: ${prior}\n`, { durability: "required" });
241
+ }
242
+ catch (e) {
243
+ throw new Error(`DB_BACKEND=local cannot record ${path} durably: ${e instanceof Error ? e.message : String(e)} — refusing to open the data root: ` +
244
+ "this start raises the engine high-water past the release that adopts the occupant tokens of session archives without a session-meta cell, and without this record a later build could not tell that the adoption has not finished. " +
245
+ "Repair write access to the data root and restart.", { cause: e });
246
+ }
247
+ }
248
+ function dischargeArchiveTokenAdoptionPending(root) {
249
+ const path = archiveTokenAdoptionPendingPath(root);
250
+ try {
251
+ unlinkSync(path);
252
+ }
253
+ catch (e) {
254
+ if (e?.code !== "ENOENT")
255
+ throw e;
256
+ return;
257
+ }
258
+ if (!IS_WIN32)
259
+ fsyncPaths(dirname(path));
260
+ }
261
+ function archiveIdsInSync(root) {
262
+ let names;
263
+ try {
264
+ names = readdirSync(join(root, ARCHIVE_DIR));
265
+ }
266
+ catch (e) {
267
+ if (e?.code === "ENOENT")
268
+ return [];
269
+ throw e;
270
+ }
271
+ return names.filter((name) => name.endsWith(ARCHIVE_SUFFIX)).map((name) => name.slice(0, -ARCHIVE_SUFFIX.length));
272
+ }
273
+ export function archiveIdsWithoutCellSync(root) {
274
+ const sidecarPath = resolve(join(root, SESSION_META_SIDECAR_FILE));
275
+ const label = `${SESSION_META_SIDECAR_FILE} (the local session-meta sidecar)`;
276
+ const raw = readDataRootFileTextSync(sidecarPath);
277
+ if (raw.state === "unreadable")
278
+ throw new LocalDataRootSchemaMismatchError([dataRootMismatchOf(sidecarPath, label, raw.fault)], "boot");
279
+ let cells = {};
280
+ if (raw.state === "text") {
281
+ try {
282
+ cells = decodeJsonColumn(raw.text, readSessionMetaSidecar, label);
283
+ }
284
+ catch (e) {
285
+ throw new LocalDataRootSchemaMismatchError([dataRootMismatchOf(sidecarPath, label, e)], "boot");
286
+ }
287
+ }
288
+ return archiveIdsInSync(root)
289
+ .filter((id) => !Object.hasOwn(cells, id))
290
+ .sort();
291
+ }
292
+ export function holdArchiveTokenAdoptionDuty(root, priorHighWater, adoptionCarried) {
293
+ const since = releaseTriple(ARCHIVE_TOKEN_ADOPTION_SINCE);
294
+ const neverOpenedByAdoptingRelease = priorHighWater === undefined || compareReleaseTriples(priorHighWater, since) < 0;
295
+ if (adoptionCarried) {
296
+ if (neverOpenedByAdoptingRelease && archiveIdsInSync(root).length > 0)
297
+ recordArchiveTokenAdoptionPending(root, priorHighWater);
298
+ return;
299
+ }
300
+ const owed = archiveTokenAdoptionPendingPresent(root);
301
+ if (!neverOpenedByAdoptingRelease && !owed)
302
+ return;
303
+ const cellLess = archiveIdsWithoutCellSync(root);
304
+ if (cellLess.length === 0) {
305
+ if (owed)
306
+ dischargeArchiveTokenAdoptionPending(root);
307
+ return;
308
+ }
309
+ throw new LocalDataRootSchemaMismatchError([
310
+ {
311
+ path: resolve(join(root, ARCHIVE_DIR)),
312
+ label: `${ARCHIVE_DIR} (the local session archives)`,
313
+ problem: "skipped-required-migration",
314
+ unknownFields: [],
315
+ skippedMigration: { highWater: priorHighWater === undefined ? null : releaseText(priorHighWater), requiredSince: ARCHIVE_TOKEN_ADOPTION_SINCE, sessions: cellLess.length },
316
+ },
317
+ ], "boot");
318
+ }
319
+ function firstCellIncarnationOf(sessionId, archiveToken) {
320
+ const token = archiveTokenAdoptionCarried() ? incarnationFromCell(sessionId, archiveToken, "absent") : undefined;
321
+ return token !== undefined ? { incarnation: token, adopted: true } : { incarnation: mintSessionIncarnation(), adopted: false };
322
+ }
323
+ function cellChangedUnderLock(sessionId) {
324
+ throw new SessionError("storage", `LocalSessionStore: session ${sessionId}'s session-meta cell changed under its own write lock between the observation and the write — refusing to guess its occupant`);
325
+ }
192
326
  export async function readDataRootFileText(path) {
193
327
  try {
194
328
  return { state: "text", text: await readFile(path, "utf8") };
@@ -303,6 +437,21 @@ class SidecarMap {
303
437
  if (disk.state === "unreadable")
304
438
  throw this.refuse(path, disk);
305
439
  }
440
+ async landed(path, changed, draft) {
441
+ const disk = await this.readDisk(path);
442
+ switch (disk.state) {
443
+ case "absent":
444
+ return "not-landed";
445
+ case "unreadable":
446
+ return "unknown";
447
+ case "read":
448
+ return changed.every((key) => (draft.has(key) ? Object.hasOwn(disk.entries, key) && isDeepStrictEqual(disk.entries[key], draft.get(key)) : !Object.hasOwn(disk.entries, key))) ? "landed" : "not-landed";
449
+ default: {
450
+ const unknownRead = disk;
451
+ throw new Error(`SidecarMap: no sidecar read state ${JSON.stringify(unknownRead)}`);
452
+ }
453
+ }
454
+ }
306
455
  async readDisk(path) {
307
456
  const raw = await readDataRootFileText(path);
308
457
  if (raw.state !== "text")
@@ -322,7 +471,7 @@ class SidecarMap {
322
471
  return refusal;
323
472
  }
324
473
  persist() {
325
- return this.commit(() => true);
474
+ return this.commit(() => true, "best-effort");
326
475
  }
327
476
  syncToDisk() {
328
477
  const path = this.path;
@@ -330,10 +479,10 @@ class SidecarMap {
330
479
  return;
331
480
  fsyncPaths(path, dirname(path));
332
481
  }
333
- deleteKey(key) {
334
- return this.commit((draft) => draft.delete(key));
482
+ deleteKey(key, durability) {
483
+ return this.commit((draft) => draft.delete(key), durability);
335
484
  }
336
- commit(change) {
485
+ commit(change, durability) {
337
486
  const path = this.path;
338
487
  const st = this.state;
339
488
  const link = st.writeChain.then(async () => {
@@ -341,8 +490,27 @@ class SidecarMap {
341
490
  throw st.refused;
342
491
  const base = new Map(st.entries);
343
492
  const draft = new Map(base);
344
- if (!change(draft))
345
- return true;
493
+ if (!change(draft)) {
494
+ if (durability !== "required" || !path || !existsSync(path))
495
+ return true;
496
+ try {
497
+ proveDurableSync(path);
498
+ return true;
499
+ }
500
+ catch (e) {
501
+ this.onWriteFailed(e);
502
+ return false;
503
+ }
504
+ }
505
+ const changed = [...new Set([...base.keys(), ...draft.keys()])].filter((key) => !(draft.has(key) === base.has(key) && draft.get(key) === base.get(key)));
506
+ const publish = () => {
507
+ for (const key of changed) {
508
+ if (draft.has(key))
509
+ st.entries.set(key, draft.get(key));
510
+ else
511
+ st.entries.delete(key);
512
+ }
513
+ };
346
514
  if (path) {
347
515
  if (this.strict) {
348
516
  const disk = await this.readDisk(path);
@@ -354,21 +522,16 @@ class SidecarMap {
354
522
  }
355
523
  }
356
524
  try {
357
- await atomicWriteFileAsync(path, JSON.stringify(Object.fromEntries(draft)), { durability: "best-effort" });
525
+ await atomicWriteFileAsync(path, JSON.stringify(Object.fromEntries(draft)), { durability });
358
526
  }
359
527
  catch (e) {
360
528
  this.onWriteFailed(e);
529
+ if (durability === "required" && (await this.landed(path, changed, draft)) !== "not-landed")
530
+ publish();
361
531
  return false;
362
532
  }
363
533
  }
364
- for (const key of new Set([...base.keys(), ...draft.keys()])) {
365
- if (draft.has(key) === base.has(key) && draft.get(key) === base.get(key))
366
- continue;
367
- if (draft.has(key))
368
- st.entries.set(key, draft.get(key));
369
- else
370
- st.entries.delete(key);
371
- }
534
+ publish();
372
535
  return true;
373
536
  });
374
537
  st.writeChain = link.then(() => undefined, () => undefined);
@@ -480,7 +643,6 @@ export class LocalSessionStore {
480
643
  listPlaced;
481
644
  recordedPlacementOf;
482
645
  pending = new Map();
483
- rowsDropped = new Set();
484
646
  lastActivity = new Map();
485
647
  stagings = new Map();
486
648
  loaded;
@@ -501,10 +663,12 @@ export class LocalSessionStore {
501
663
  executionPlacementBackfill;
502
664
  dataRoot;
503
665
  archiveSnapshotsSettled = 0;
666
+ withdrawCheckpointCtx;
504
667
  constructor(repo, runs, dataDir, opts) {
505
668
  this.repo = repo;
506
669
  this.runs = runs;
507
670
  this.sidecarPath = dataDir ? join(dataDir, "session-last-activity.json") : undefined;
671
+ this.withdrawCheckpointCtx = opts?.withdrawCheckpointCtx;
508
672
  this.log = opts?.log ?? createLogger();
509
673
  const repoPlacementOf = repo.placementOf;
510
674
  const repoListPlaced = repo.listPlaced;
@@ -555,10 +719,17 @@ export class LocalSessionStore {
555
719
  place: (sessionId, occupant, cell) => this.placeExecution(sessionId, occupant, cell),
556
720
  markMirror: (sessionId, occupant) => this.markMirrorExecution(sessionId, occupant),
557
721
  };
558
- this.executionPlacementBackfill = {
559
- read: (sessionId) => this.placementRead(sessionId),
560
- place: (sessionId, occupant, cell) => this.placeExecution(sessionId, occupant, cell),
561
- };
722
+ this.executionPlacementBackfill = { backfill: (sessionId, lanes) => this.backfillPlacement(sessionId, lanes) };
723
+ }
724
+ async scenarioPrefixOwnerRowsM42() {
725
+ await this.meta.ensureLoaded();
726
+ let n = 0;
727
+ for (const id of [...this.liveCellIds(), ...this.droppingIds(), ...this.deletingIds()]) {
728
+ const cell = this.cellStateOf(id);
729
+ if (cell.state !== "none" && typeof cell.meta.owner === "string" && cell.meta.owner.startsWith(SCENARIO_SUBJECT_PREFIX))
730
+ n += 1;
731
+ }
732
+ return n;
562
733
  }
563
734
  async renameRetiredExecutionLaneWordM40() {
564
735
  if (this.dataRoot === undefined)
@@ -600,39 +771,67 @@ export class LocalSessionStore {
600
771
  if (findings.length > 0)
601
772
  throw new LocalDataRootSchemaMismatchError(findings, "boot");
602
773
  }
603
- async forkingRead(sessionId, meta) {
774
+ async forkingOf(sessionId, meta) {
604
775
  if (!isForkCreatedCell(meta))
605
776
  return false;
606
- const probe = await this.probeArchive(sessionId);
607
- if (probe.state === "unusable" && probe.word === "storage")
608
- return probe;
609
- return probe.state === "absent";
777
+ return (await this.probeArchive(sessionId)).state === "absent";
610
778
  }
611
- async forkingOf(sessionId, meta) {
612
- const forking = await this.forkingRead(sessionId, meta);
613
- if (typeof forking !== "boolean")
614
- throw forking.fault;
615
- return forking;
616
- }
617
- async placementRead(sessionId) {
618
- const read = await this.metaRead(sessionId);
619
- if (read.state === "unusable")
620
- return read;
621
- if (read.state === "absent")
622
- return undefined;
623
- const partition = await this.placementProbe(sessionId);
624
- if (partition.state === "placed")
625
- return undefined;
626
- if (partition.state === "unusable" && partition.word === "storage")
627
- return partition;
628
- const forking = await this.forkingRead(sessionId, read.meta);
629
- return typeof forking === "boolean" ? readingOfCell(sessionId, read.meta, forking) : forking;
779
+ async observeRow(sessionId, want = {}) {
780
+ await this.meta.ensureLoaded();
781
+ const cell = this.cellStateOf(sessionId);
782
+ if (cell.state === "dropping" || cell.state === "deleting")
783
+ return ABSENT_ROW;
784
+ if (cell.state === "none") {
785
+ const probe = await this.probeArchive(sessionId);
786
+ if (probe.state === "absent")
787
+ return ABSENT_ROW;
788
+ const placed = want.placement === true ? await this.placedRecordOf(sessionId) : undefined;
789
+ return { row: "legacy", placed, ...(probe.state === "corrupt" ? { unreadable: probe.fault } : {}) };
790
+ }
791
+ const placed = want.placement === true ? await this.placedRecordOf(sessionId) : undefined;
792
+ const forking = want.forking === true && placed === undefined ? await this.forkingOf(sessionId, cell.meta) : false;
793
+ return { row: "live", meta: cell.meta, placed, forking };
630
794
  }
631
795
  async readExecutionPlacement(sessionId) {
632
- const reading = await this.placementRead(sessionId);
633
- if (isUnusableArchive(reading))
634
- throw reading.fault;
635
- return reading;
796
+ const seen = await this.observeRow(sessionId, { placement: true, forking: true });
797
+ if (!legacyNeedsMint(seen))
798
+ return executionReadingOf(sessionId, seen);
799
+ return this.withSessionWriteLock(sessionId, async () => {
800
+ const held = await this.observeRow(sessionId, { placement: true, forking: true });
801
+ return legacyNeedsMint(held) ? readingOfCell(sessionId, await this.mintHeld(sessionId), false) : executionReadingOf(sessionId, held);
802
+ });
803
+ }
804
+ async backfillPlacement(sessionId, lanes) {
805
+ return this.withSessionWriteLock(sessionId, async () => {
806
+ let seen;
807
+ try {
808
+ seen = await this.observeRow(sessionId, { placement: true, forking: true });
809
+ }
810
+ catch (fault) {
811
+ return unusableArchiveOfFault(fault);
812
+ }
813
+ if (seen.row === "absent" || seen.placed !== undefined)
814
+ return { outcome: "skipped" };
815
+ if (seen.row === "legacy" && seen.unreadable !== undefined)
816
+ return unusableArchiveOfFault(ownerUnreadableFault(seen.unreadable));
817
+ if (seen.row === "live" && (seen.meta.execution !== undefined || seen.forking))
818
+ return { outcome: "skipped" };
819
+ if (lanes.length !== 1)
820
+ return { outcome: "ambiguous" };
821
+ const writer = { role: "writer", lane: lanes[0] };
822
+ assertPlaceableLane(sessionId, writer);
823
+ let first;
824
+ if (seen.row === "legacy") {
825
+ try {
826
+ first = await this.firstCellIncarnationHeld(sessionId);
827
+ }
828
+ catch (fault) {
829
+ return unusableArchiveOfFault(fault);
830
+ }
831
+ }
832
+ await this.writeMeta(sessionId, (before) => before === undefined ? { incarnation: first ?? cellChangedUnderLock(sessionId), execution: writer } : readSessionMetaCell(before).execution === undefined ? cellWith(before, { execution: writer }) : undefined);
833
+ return { outcome: "placed" };
834
+ });
636
835
  }
637
836
  async placeExecution(sessionId, occupant, cell) {
638
837
  assertPlaceableLane(sessionId, cell);
@@ -644,7 +843,7 @@ export class LocalSessionStore {
644
843
  async markMirrorExecution(sessionId, occupant) {
645
844
  return this.withSessionWriteLock(sessionId, async () => {
646
845
  await this.meta.ensureLoaded();
647
- const claim = this.metaCellOf(sessionId) === undefined ? undefined : await this.runs.getActiveTaskId(sessionId);
846
+ const claim = this.liveCellOf(sessionId) === undefined ? undefined : await this.runs.getActiveTaskId(sessionId);
648
847
  return this.settleExecutionHeld(sessionId, (reading) => {
649
848
  const settled = settledMirrorOutcome(sessionId, reading, occupant);
650
849
  if (settled !== WRITABLE)
@@ -654,9 +853,9 @@ export class LocalSessionStore {
654
853
  });
655
854
  }
656
855
  async settleExecutionHeld(sessionId, judge) {
657
- await this.meta.ensureLoaded();
658
- const placed = await this.isPlacedRow(sessionId);
659
- const forking = !placed && (await this.forkingOf(sessionId, this.metaCellOf(sessionId)));
856
+ const seen = await this.observeRow(sessionId, { placement: true, forking: true });
857
+ const placed = seen.row !== "absent" && seen.placed !== undefined;
858
+ const forking = seen.row === "live" && seen.forking;
660
859
  let settled;
661
860
  let fault;
662
861
  await this.writeMeta(sessionId, (before) => {
@@ -673,33 +872,75 @@ export class LocalSessionStore {
673
872
  throw fault;
674
873
  return settled.outcome;
675
874
  }
676
- metaCellOf(sessionId) {
875
+ cellStateOf(sessionId) {
677
876
  const cell = this.meta.state.entries.get(sessionId);
678
- return cell === undefined ? undefined : readSessionMetaCell(cell);
877
+ if (cell === undefined)
878
+ return NO_CELL;
879
+ const meta = readSessionMetaCell(cell);
880
+ return { state: meta.deleting !== undefined ? "deleting" : meta.dropping ? "dropping" : "live", meta };
881
+ }
882
+ async rowDropping(sessionId) {
883
+ await this.meta.ensureLoaded();
884
+ const state = this.cellStateOf(sessionId).state;
885
+ return state === "dropping" || state === "deleting";
886
+ }
887
+ rowGoneSince(sessionId, hadLiveCell) {
888
+ const state = this.cellStateOf(sessionId).state;
889
+ return state === "dropping" || state === "deleting" || (state === "none" && hadLiveCell);
890
+ }
891
+ liveCellOf(sessionId) {
892
+ const cell = this.cellStateOf(sessionId);
893
+ return cell.state === "live" ? cell.meta : undefined;
894
+ }
895
+ liveCellIds() {
896
+ return [...this.meta.state.entries]
897
+ .filter(([, cell]) => {
898
+ const meta = readSessionMetaCell(cell);
899
+ return !meta.dropping && meta.deleting === undefined;
900
+ })
901
+ .map(([id]) => id)
902
+ .sort();
903
+ }
904
+ deletingIds() {
905
+ return [...this.meta.state.entries].filter(([id]) => this.cellStateOf(id).state === "deleting").map(([id]) => id).sort();
906
+ }
907
+ droppingIds() {
908
+ return [...this.meta.state.entries].filter(([id]) => this.cellStateOf(id).state === "dropping").map(([id]) => id).sort();
679
909
  }
680
910
  async writeMeta(sessionId, next) {
681
911
  await this.meta.ensureLoaded();
682
912
  let changed = false;
683
913
  const committed = await this.meta.commit((draft) => {
684
- const value = next(draft.get(sessionId));
914
+ const before = draft.get(sessionId);
915
+ if (before !== undefined && readSessionMetaCell(before).deleting !== undefined)
916
+ throw new SessionError("not_found", `LocalSessionStore: session ${sessionId} is being deleted — it reads as gone; this write was not made`);
917
+ if (before !== undefined && readSessionMetaCell(before).dropping)
918
+ throw new Error(`LocalSessionStore: session ${sessionId}'s cell carries the drop mark while another writer reached it — every section finishes a pending drop first; refusing to write`);
919
+ const value = next(before);
685
920
  if (value === undefined)
686
921
  return false;
687
922
  draft.set(sessionId, value);
688
923
  changed = true;
689
924
  return true;
690
- });
925
+ }, "best-effort");
691
926
  if (!committed)
692
927
  throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed for session ${sessionId} — nothing was recorded`);
693
928
  return changed;
694
929
  }
695
930
  async dropMeta(sessionId) {
696
931
  await this.meta.ensureLoaded();
697
- if (!(await this.meta.deleteKey(sessionId)))
698
- throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed while deleting session ${sessionId} — its owner/incarnation cell is still on disk`);
932
+ if (!(await this.meta.deleteKey(sessionId, "best-effort")))
933
+ throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed while withdrawing session ${sessionId}'s cell — the cell is still on disk`);
934
+ }
935
+ async withRootRow(sessionId, body) {
936
+ return this.withSessionWriteLock(sessionId, async () => body(await this.rowPresentHeld(sessionId)));
937
+ }
938
+ async rowPresentHeld(sessionId) {
939
+ return (await this.observeRow(sessionId)).row !== "absent";
699
940
  }
700
941
  async withSessionWriteLock(sessionId, body) {
701
942
  const locks = this.sessionWriteLocks;
702
- const link = (locks.get(sessionId) ?? Promise.resolve()).then(body);
943
+ const link = (locks.get(sessionId) ?? Promise.resolve()).then(async () => body(await this.finishPendingDropHeld(sessionId)));
703
944
  const tail = link.then(() => undefined, () => undefined);
704
945
  locks.set(sessionId, tail);
705
946
  try {
@@ -717,7 +958,9 @@ export class LocalSessionStore {
717
958
  }
718
959
  catch (e) {
719
960
  const word = e instanceof SessionError ? archiveFaultWord(e.code) : "storage";
720
- return word === "absent" ? { state: "absent" } : unusableArchiveOf(word, e);
961
+ if (word === "storage")
962
+ throw e;
963
+ return word === "absent" ? { state: "absent" } : { state: "corrupt", fault: e };
721
964
  }
722
965
  }
723
966
  async placementProbe(sessionId) {
@@ -727,76 +970,88 @@ export class LocalSessionStore {
727
970
  }
728
971
  catch (e) {
729
972
  const word = e instanceof SessionError ? archiveFaultWord(e.code) : "storage";
730
- return word === "absent" ? { state: "ordinary" } : unusableArchiveOf(word, e);
973
+ if (word === "storage")
974
+ throw e;
975
+ return word === "absent" ? { state: "ordinary" } : { state: "corrupt", fault: e };
731
976
  }
732
977
  }
733
978
  async placedRecordOf(sessionId) {
734
979
  const probe = await this.placementProbe(sessionId);
735
- if (probe.state === "unusable") {
736
- if (probe.word === "storage")
737
- throw probe.fault;
738
- return undefined;
739
- }
740
980
  return probe.state === "placed" ? probe.record : undefined;
741
981
  }
742
- async isPlacedRow(sessionId) {
743
- return (await this.placedRecordOf(sessionId)) !== undefined;
744
- }
745
982
  async placedRecordOrThrow(sessionId) {
746
983
  const probe = await this.placementProbe(sessionId);
747
- if (probe.state === "unusable")
984
+ if (probe.state === "corrupt")
748
985
  throw probe.fault;
749
986
  return probe.state === "placed" ? probe.record : undefined;
750
987
  }
751
988
  async existsInRepo(sessionId) {
752
989
  const probe = await this.probeArchive(sessionId);
753
- if (probe.state === "unusable")
990
+ if (probe.state === "corrupt")
754
991
  throw probe.fault;
755
992
  return probe.state === "readable";
756
993
  }
757
994
  async metaOf(sessionId) {
758
- const read = await this.metaRead(sessionId);
759
- if (read.state === "unusable")
760
- throw read.fault;
761
- return read.state === "found" ? read.meta : undefined;
995
+ const seen = await this.observeRow(sessionId);
996
+ switch (seen.row) {
997
+ case "absent":
998
+ return undefined;
999
+ case "live":
1000
+ return seen.meta;
1001
+ case "legacy":
1002
+ if (seen.unreadable !== undefined)
1003
+ throw ownerUnreadableFault(seen.unreadable);
1004
+ return this.withSessionWriteLock(sessionId, () => this.metaHeld(sessionId));
1005
+ default: {
1006
+ const unknownRow = seen;
1007
+ throw new Error(`LocalSessionStore: no row observation ${JSON.stringify(unknownRow)}`);
1008
+ }
1009
+ }
762
1010
  }
763
- async metaRead(sessionId) {
764
- await this.meta.ensureLoaded();
765
- const cell = this.meta.state.entries.get(sessionId);
766
- if (cell !== undefined)
767
- return { state: "found", meta: readSessionMetaCell(cell) };
768
- const probe = await this.probeArchive(sessionId);
769
- if (probe.state === "unusable")
770
- return ownerUnreadableArchive(probe);
771
- if (probe.state === "absent")
772
- return { state: "absent" };
773
- const meta = await this.cellOfPresent(sessionId);
774
- return meta === undefined ? { state: "absent" } : { state: "found", meta };
775
- }
776
- async cellOfPresent(sessionId) {
777
- if (!this.meta.state.entries.has(sessionId))
778
- await this.writeMeta(sessionId, (before) => (before === undefined && !this.rowsDropped.has(sessionId) ? { incarnation: mintSessionIncarnation() } : undefined));
779
- const cell = this.meta.state.entries.get(sessionId);
780
- return cell === undefined ? undefined : readSessionMetaCell(cell);
1011
+ async metaHeld(sessionId) {
1012
+ const seen = await this.observeRow(sessionId);
1013
+ if (seen.row === "absent")
1014
+ return undefined;
1015
+ if (seen.row === "live")
1016
+ return seen.meta;
1017
+ if (seen.unreadable !== undefined)
1018
+ throw ownerUnreadableFault(seen.unreadable);
1019
+ return this.mintHeld(sessionId);
1020
+ }
1021
+ async mintHeld(sessionId) {
1022
+ const incarnation = await this.firstCellIncarnationHeld(sessionId);
1023
+ await this.writeMeta(sessionId, (before) => (before === undefined ? { incarnation } : undefined));
1024
+ const meta = this.liveCellOf(sessionId);
1025
+ if (meta === undefined)
1026
+ throw new SessionError("storage", `LocalSessionStore: session ${sessionId} has no live session-meta cell right after its cell was minted under its own write lock — refusing to guess`);
1027
+ return meta;
1028
+ }
1029
+ async firstCellIncarnationHeld(sessionId) {
1030
+ const head = archiveTokenAdoptionCarried() ? await this.archiveHeadOf(sessionId) : undefined;
1031
+ return firstCellIncarnationOf(sessionId, head?.incarnation).incarnation;
781
1032
  }
782
1033
  async headOf(sessionId) {
783
1034
  const before = await this.metaOf(sessionId);
784
1035
  if (before === undefined)
785
1036
  return undefined;
786
1037
  const leaf = await this.archiveHeadOf(sessionId).then((archive) => archive?.leafId ?? null, (fault) => new UnreadableLeaf(fault));
787
- const cell = this.meta.state.entries.get(sessionId);
788
- const after = cell === undefined ? undefined : readSessionMetaCell(cell);
1038
+ const after = this.liveCellOf(sessionId);
789
1039
  if (after === undefined || after.incarnation !== before.incarnation || (after.owner ?? null) !== (before.owner ?? null))
790
1040
  return undefined;
791
1041
  return headFromRow(sessionId, before.owner ?? null, before.incarnation, leaf);
792
1042
  }
1043
+ async deletionHeadOf(sessionId) {
1044
+ await this.meta.ensureLoaded();
1045
+ const cell = this.cellStateOf(sessionId);
1046
+ if (cell.state === "dropping" || cell.state === "deleting")
1047
+ return headFromRow(sessionId, cell.meta.owner ?? null, cell.meta.incarnation, null);
1048
+ return this.headOf(sessionId);
1049
+ }
793
1050
  archiveHeadOf(sessionId) {
794
1051
  return this.repo.headOf(sessionId);
795
1052
  }
796
1053
  async handOut(sessionId, session) {
797
- const meta = await this.cellOfPresent(sessionId);
798
- if (meta === undefined)
799
- throw new SessionError("storage", `LocalSessionStore: session ${sessionId} lost its session-meta cell while it was being handed out`);
1054
+ const meta = this.liveCellOf(sessionId) ?? (await this.mintHeld(sessionId));
800
1055
  return { session: occupiedBy(session, rowMetadataOf(sessionId, meta)), sessionId };
801
1056
  }
802
1057
  async sessionMetadataOf(sessionId) {
@@ -804,11 +1059,15 @@ export class LocalSessionStore {
804
1059
  return meta === undefined ? undefined : rowMetadataOf(sessionId, meta);
805
1060
  }
806
1061
  async probeTitle(sessionId) {
1062
+ if (await this.rowDropping(sessionId))
1063
+ return "none";
807
1064
  await this.titles.ensureLoaded();
808
1065
  return this.titles.state.entries.has(sessionId) ? "titled" : "untitled";
809
1066
  }
810
1067
  async setTitleIfNull(sessionId, title) {
811
1068
  return this.withSessionWriteLock(sessionId, async () => {
1069
+ if (!(await this.rowPresentHeld(sessionId)))
1070
+ return false;
812
1071
  await this.titles.ensureLoaded();
813
1072
  const entries = this.titles.state.entries;
814
1073
  if (entries.has(sessionId))
@@ -834,6 +1093,7 @@ export class LocalSessionStore {
834
1093
  case "mirror":
835
1094
  return { kind: "mirror" };
836
1095
  case "write":
1096
+ await this.bumpClientWritesHeld(sessionId);
837
1097
  await this.writeRowCellsHeld(sessionId, cells);
838
1098
  return settleSessionRowWrite(how, verdict, await this.listRowOf(sessionId));
839
1099
  case "changed":
@@ -846,7 +1106,7 @@ export class LocalSessionStore {
846
1106
  });
847
1107
  }
848
1108
  async lockedRowOf(sessionId) {
849
- const meta = this.metaCellOf(sessionId);
1109
+ const meta = this.liveCellOf(sessionId);
850
1110
  if (meta === undefined || (await this.rowHeaderOf(sessionId)) === undefined)
851
1111
  return undefined;
852
1112
  return { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent"), placed: false, mirror: meta.execution?.role === "mirror", cells: this.rowCellsOf(sessionId) };
@@ -864,7 +1124,7 @@ export class LocalSessionStore {
864
1124
  return false;
865
1125
  draft.set(sessionId, title);
866
1126
  return true;
867
- });
1127
+ }, "best-effort");
868
1128
  if (!ok)
869
1129
  throw new SessionError("storage", `LocalSessionStore: title sidecar write failed for session ${sessionId} — the title was not recorded`);
870
1130
  }
@@ -876,7 +1136,7 @@ export class LocalSessionStore {
876
1136
  return false;
877
1137
  draft.set(sessionId, true);
878
1138
  return true;
879
- });
1139
+ }, "best-effort");
880
1140
  if (!ok)
881
1141
  throw new SessionError("storage", `LocalSessionStore: pin sidecar write failed for session ${sessionId} — the pin was not recorded`);
882
1142
  }
@@ -884,23 +1144,28 @@ export class LocalSessionStore {
884
1144
  async listRowOf(sessionId) {
885
1145
  await this.loadRowSidecars();
886
1146
  const header = await this.rowHeaderOf(sessionId);
887
- const meta = this.metaCellOf(sessionId);
1147
+ const meta = this.liveCellOf(sessionId);
888
1148
  if (header === undefined || meta === undefined)
889
1149
  return undefined;
890
- return { row: await this.summaryOf(await this.listedRowOf(header), await this.runRollups()), occupant: { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent") } };
1150
+ return { row: this.summaryOf(await this.listedRowOf(header), await this.runRollups()), occupant: { owner: meta.owner ?? null, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent") } };
891
1151
  }
892
1152
  async sessionSummaryOf(sessionId) {
893
1153
  await this.loadRowSidecars();
894
1154
  const header = await this.rowHeaderOf(sessionId);
895
- return header === undefined ? undefined : this.summaryOf(await this.listedRowOf(header), await this.runRollups());
1155
+ if (header === undefined)
1156
+ return undefined;
1157
+ const hadLiveCell = this.cellStateOf(sessionId).state === "live";
1158
+ const listed = await this.listedRowOf(header);
1159
+ const rollups = await this.runRollups();
1160
+ return this.rowGoneSince(sessionId, hadLiveCell) ? undefined : this.summaryOf(listed, rollups);
896
1161
  }
897
1162
  async rowHeaders() {
898
1163
  await this.meta.ensureLoaded();
899
- const listed = await this.repo.list();
1164
+ const listed = (await this.repo.list()).filter((row) => this.cellStateOf(row.id).state !== "dropping");
900
1165
  const listedIds = new Set(listed.map((row) => row.id));
901
1166
  const placed = this.createPlacedIdsReader();
902
1167
  const rows = listed.map((header) => ({ sessionId: header.id, header }));
903
- for (const id of [...this.meta.state.entries.keys()].sort()) {
1168
+ for (const id of this.liveCellIds()) {
904
1169
  if (listedIds.has(id))
905
1170
  continue;
906
1171
  const row = await this.headerUnreadableRowOf(id, placed);
@@ -911,10 +1176,13 @@ export class LocalSessionStore {
911
1176
  }
912
1177
  async rowHeaderOf(sessionId) {
913
1178
  await this.meta.ensureLoaded();
1179
+ const cell = this.cellStateOf(sessionId);
1180
+ if (cell.state === "dropping" || cell.state === "deleting")
1181
+ return undefined;
914
1182
  const header = (await this.repo.list()).find((m) => m.id === sessionId);
915
1183
  if (header !== undefined)
916
1184
  return { sessionId, header };
917
- return this.meta.state.entries.has(sessionId) ? this.headerUnreadableRowOf(sessionId, this.createPlacedIdsReader()) : undefined;
1185
+ return cell.state === "live" ? this.headerUnreadableRowOf(sessionId, this.createPlacedIdsReader()) : undefined;
918
1186
  }
919
1187
  async headerUnreadableRowOf(sessionId, placed) {
920
1188
  try {
@@ -934,35 +1202,43 @@ export class LocalSessionStore {
934
1202
  return { sessionId: row.sessionId, unreadable: row.headerFault };
935
1203
  try {
936
1204
  await this.archiveHeadOf(row.sessionId);
937
- return { sessionId: row.sessionId, readable: row.header };
1205
+ return { sessionId: row.sessionId, readable: true };
938
1206
  }
939
1207
  catch (fault) {
940
1208
  return { sessionId: row.sessionId, unreadable: fault };
941
1209
  }
942
1210
  }
943
1211
  async readInstructionSource(sessionId) {
1212
+ if (await this.rowDropping(sessionId))
1213
+ return decodeInstructionSource(undefined);
944
1214
  await this.instructionSources.ensureLoaded();
945
1215
  return decodeInstructionSource(this.instructionSources.state.entries.get(sessionId));
946
1216
  }
947
1217
  async recordInstructionSource(sessionId, expected, source) {
948
- await this.instructionSources.ensureLoaded();
949
- const committed = await this.instructionSources.commit((draft) => {
950
- if (!sameInstructionSourceBaseline(decodeInstructionSource(draft.get(sessionId)), expected))
951
- return false;
952
- draft.set(sessionId, encodeInstructionSource(source));
953
- return true;
1218
+ await this.withSessionWriteLock(sessionId, async () => {
1219
+ if (!(await this.rowPresentHeld(sessionId)))
1220
+ return;
1221
+ await this.instructionSources.ensureLoaded();
1222
+ const committed = await this.instructionSources.commit((draft) => {
1223
+ if (!sameInstructionSourceBaseline(decodeInstructionSource(draft.get(sessionId)), expected))
1224
+ return false;
1225
+ draft.set(sessionId, encodeInstructionSource(source));
1226
+ return true;
1227
+ }, "best-effort");
1228
+ if (!committed)
1229
+ throw new Error("LocalSessionStore: instruction-source sidecar write failed — baseline not recorded");
954
1230
  });
955
- if (!committed)
956
- throw new Error("LocalSessionStore: instruction-source sidecar write failed — baseline not recorded");
957
1231
  }
958
1232
  async readSessionWorkspace(sessionId) {
1233
+ if (await this.rowDropping(sessionId))
1234
+ return undefined;
959
1235
  await this.workspaces.ensureLoaded();
960
1236
  return decodeSessionWorkspaceCell(this.workspaces.state.entries.get(sessionId) ?? null, sessionId);
961
1237
  }
962
1238
  async recordSessionWorkspace(sessionId, incarnation, workspace) {
963
1239
  const text = encodeSessionWorkspaceCell(workspace, JSON.stringify, sessionId);
964
1240
  await this.withSessionWriteLock(sessionId, async () => {
965
- if ((await this.metaOf(sessionId))?.incarnation !== incarnation)
1241
+ if ((await this.metaHeld(sessionId))?.incarnation !== incarnation)
966
1242
  return;
967
1243
  await this.workspaces.ensureLoaded();
968
1244
  const committed = await this.workspaces.commit((draft) => {
@@ -970,7 +1246,7 @@ export class LocalSessionStore {
970
1246
  return false;
971
1247
  draft.set(sessionId, text);
972
1248
  return true;
973
- });
1249
+ }, "best-effort");
974
1250
  if (!committed)
975
1251
  throw new SessionError("storage", `LocalSessionStore: workspace sidecar write failed for session ${sessionId} — its place was not recorded`);
976
1252
  });
@@ -1039,7 +1315,7 @@ export class LocalSessionStore {
1039
1315
  if (now - this.lastWarnAt < 60_000)
1040
1316
  return;
1041
1317
  this.lastWarnAt = now;
1042
- console.warn(`LocalSessionStore: ${which} sidecar ${op} failed (${SIDECAR_DEGRADES[which]} persistence degraded): ${e instanceof Error ? e.message : String(e)}`);
1318
+ writeLogText("stderr", `LocalSessionStore: ${which} sidecar ${op} failed (${SIDECAR_DEGRADES[which]} persistence degraded): ${e instanceof Error ? e.message : String(e)}`);
1043
1319
  }
1044
1320
  async acquire(sessionId, opts) {
1045
1321
  if (!sessionId && this.declaredPlacement(opts) !== undefined)
@@ -1068,6 +1344,9 @@ export class LocalSessionStore {
1068
1344
  }
1069
1345
  async load(sessionId, requireExisting = false, opts) {
1070
1346
  return this.withSessionWriteLock(sessionId, async () => {
1347
+ await this.meta.ensureLoaded();
1348
+ if (this.cellStateOf(sessionId).state === "deleting")
1349
+ throw new SessionError("not_found", `session ${sessionId} not found (it is being deleted)`);
1071
1350
  try {
1072
1351
  return await this.openForItselfHeld(sessionId, opts);
1073
1352
  }
@@ -1078,7 +1357,7 @@ export class LocalSessionStore {
1078
1357
  throw e;
1079
1358
  }
1080
1359
  await this.meta.ensureLoaded();
1081
- if (isForkCreatedCell(this.metaCellOf(sessionId)))
1360
+ if (isForkCreatedCell(this.liveCellOf(sessionId)))
1082
1361
  throw new SessionForkInProgressError(sessionId);
1083
1362
  const placement = this.declaredPlacement(opts);
1084
1363
  if (placement !== undefined)
@@ -1095,7 +1374,7 @@ export class LocalSessionStore {
1095
1374
  return this.handOut(sessionId, session);
1096
1375
  }
1097
1376
  async createPlacedHeld(sessionId, placement, opts) {
1098
- if (this.metaCellOf(sessionId) !== undefined) {
1377
+ if (this.liveCellOf(sessionId) !== undefined) {
1099
1378
  throw new SessionError("placement_refused", `session ${sessionId} is already registered as an ordinary session — refusing to create a subagent transcript on its id`);
1100
1379
  }
1101
1380
  const { owner: treeOwner, ...tuple } = placement;
@@ -1122,8 +1401,8 @@ export class LocalSessionStore {
1122
1401
  return this.handOut(sessionId, session);
1123
1402
  }
1124
1403
  async holdsThisPlacement(sessionId, opts) {
1125
- const probe = await this.placementProbe(sessionId);
1126
- if (probe.state === "unusable")
1404
+ const probe = await this.placementProbe(sessionId).then((answer) => answer, (fault) => ({ state: "storage", fault }));
1405
+ if (probe.state === "corrupt" || probe.state === "storage")
1127
1406
  return true;
1128
1407
  try {
1129
1408
  admitSessionPlacement(sessionId, probe.state === "placed" ? probe.record : undefined, opts, placementCapabilityOf(this));
@@ -1144,28 +1423,31 @@ export class LocalSessionStore {
1144
1423
  }
1145
1424
  }
1146
1425
  async register(sessionId, owner) {
1147
- await this.withSessionWriteLock(sessionId, async () => {
1426
+ await this.registerWithReceipt(sessionId, owner);
1427
+ }
1428
+ async registerWithReceipt(sessionId, owner) {
1429
+ return this.withSessionWriteLock(sessionId, async () => {
1148
1430
  await this.meta.ensureLoaded();
1149
- if (ownerRecorded(this.meta.state.entries.get(sessionId)))
1150
- return;
1431
+ if (this.cellStateOf(sessionId).state === "deleting")
1432
+ return {};
1433
+ if (this.liveCellOf(sessionId)?.owner !== undefined)
1434
+ return {};
1151
1435
  assertFirstOwnerWritable(sessionId, owner);
1152
1436
  const existed = await this.existsInRepo(sessionId);
1153
- if (!existed && isForkCreatedCell(this.metaCellOf(sessionId)))
1437
+ if (!existed && isForkCreatedCell(this.liveCellOf(sessionId)))
1154
1438
  throw new SessionForkInProgressError(sessionId);
1155
- const claim = existed && owner !== null && ownerClaimWindowOpen() && !(await this.isPlacedRow(sessionId));
1439
+ const born = mintSessionIncarnation();
1440
+ const first = existed && this.liveCellOf(sessionId) === undefined ? await this.firstCellIncarnationHeld(sessionId) : undefined;
1156
1441
  const wrote = await this.writeMeta(sessionId, (before) => {
1157
1442
  if (ownerRecorded(before))
1158
1443
  return undefined;
1159
- if (claim)
1160
- return before === undefined ? { incarnation: mintSessionIncarnation(), owner } : cellWith(before, { owner });
1161
1444
  if (!existed)
1162
- return { incarnation: mintSessionIncarnation(), owner, createdAt: rowBornNow() };
1163
- return before === undefined ? { incarnation: mintSessionIncarnation() } : undefined;
1445
+ return { incarnation: born, owner, createdAt: rowBornNow(), clientWrites: 0 };
1446
+ return before === undefined ? { incarnation: first ?? cellChangedUnderLock(sessionId) } : undefined;
1164
1447
  });
1165
1448
  if (!existed && wrote)
1166
1449
  await this.repo.create({ id: sessionId });
1167
- if (claim && wrote)
1168
- this.log.info("session_owner_claimed", { sessionId });
1450
+ return !existed && wrote ? { minted: { sessionId, owner, incarnation: born } } : {};
1169
1451
  });
1170
1452
  }
1171
1453
  async touch(sessionId) {
@@ -1177,8 +1459,10 @@ export class LocalSessionStore {
1177
1459
  await this.withSessionWriteLock(sessionId, async () => {
1178
1460
  if ((await this.placedRecordOrThrow(sessionId)) === undefined)
1179
1461
  return;
1462
+ const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
1463
+ if (verdict !== "proceed")
1464
+ throw verdict.refuse;
1180
1465
  await this.dropRowHeld(sessionId);
1181
- await this.repo.delete({ id: sessionId, createdAt: "" });
1182
1466
  });
1183
1467
  }
1184
1468
  async listSessions(opts) {
@@ -1186,28 +1470,31 @@ export class LocalSessionStore {
1186
1470
  }
1187
1471
  async listSessionRows(opts) {
1188
1472
  await this.loadRowSidecars();
1473
+ const hadLiveCell = new Set(this.liveCellIds());
1189
1474
  const rows = [];
1190
1475
  for (const header of await this.rowHeaders())
1191
1476
  rows.push(await this.listedRowOf(header));
1192
- const rollups = await this.runRollups();
1477
+ const rollups = await this.runRollups(opts.q);
1193
1478
  const all = [];
1194
1479
  for (const row of rows) {
1480
+ if (opts.q && !rollups.has(row.sessionId))
1481
+ continue;
1482
+ if (this.rowGoneSince(row.sessionId, hadLiveCell.has(row.sessionId)))
1483
+ continue;
1195
1484
  const owner = this.ownerCellOf(row.sessionId);
1196
1485
  if (opts.owner !== undefined && owner !== opts.owner && !(opts.includeUnowned === true && owner === null))
1197
1486
  continue;
1198
- const meta = this.metaCellOf(row.sessionId);
1487
+ const meta = this.liveCellOf(row.sessionId);
1199
1488
  const incarnation = meta === undefined ? undefined : incarnationFromCell(row.sessionId, meta.incarnation, "absent");
1200
- all.push({ row: await this.summaryOf(row, rollups), occupant: { owner, incarnation } });
1489
+ all.push({ row: this.summaryOf(row, rollups), occupant: { owner, incarnation } });
1201
1490
  }
1202
- const qlc = opts.q?.toLowerCase();
1203
- const filtered = qlc ? all.filter(({ row: r }) => r.objectivePreview?.toLowerCase().includes(qlc)) : all;
1204
- filtered.sort(({ row: a }, { row: b }) => a.lastActivityAt !== b.lastActivityAt
1491
+ all.sort(({ row: a }, { row: b }) => a.lastActivityAt !== b.lastActivityAt
1205
1492
  ? (a.lastActivityAt < b.lastActivityAt ? 1 : -1)
1206
1493
  : (a.sessionId < b.sessionId ? 1 : -1));
1207
1494
  const c = opts.cursor;
1208
1495
  const after = c
1209
- ? filtered.filter(({ row: r }) => r.lastActivityAt < c.lastActivityAt || (r.lastActivityAt === c.lastActivityAt && r.sessionId < c.sessionId))
1210
- : filtered;
1496
+ ? all.filter(({ row: r }) => r.lastActivityAt < c.lastActivityAt || (r.lastActivityAt === c.lastActivityAt && r.sessionId < c.sessionId))
1497
+ : all;
1211
1498
  return after.slice(0, opts.limit);
1212
1499
  }
1213
1500
  async loadRowSidecars() {
@@ -1217,58 +1504,43 @@ export class LocalSessionStore {
1217
1504
  await this.meta.ensureLoaded();
1218
1505
  }
1219
1506
  ownerCellOf(sessionId) {
1220
- const cell = this.meta.state.entries.get(sessionId);
1221
- return cell === undefined ? null : (readSessionMetaCell(cell).owner ?? null);
1507
+ return this.liveCellOf(sessionId)?.owner ?? null;
1222
1508
  }
1223
- async summaryOf(listed, rollups) {
1509
+ summaryOf(listed, rollups) {
1224
1510
  const { sessionId } = listed;
1225
- const firstActivityAt = rowCreatedAtOf(sessionId, this.metaCellOf(sessionId));
1511
+ const firstActivityAt = rowCreatedAtOf(sessionId, this.liveCellOf(sessionId));
1226
1512
  const owner = this.ownerCellOf(sessionId);
1227
1513
  const cells = this.rowCellsOf(sessionId);
1228
1514
  const rowTag = this.rowTagKeyOf(sessionId, owner, cells);
1229
1515
  const lastActivityAt = this.lastActivity.get(sessionId) ?? firstActivityAt;
1230
- const objectivePreview = "readable" in listed ? await this.previewOf(listed.readable) : null;
1231
1516
  return {
1232
1517
  sessionId,
1233
1518
  owner,
1234
1519
  lastActivityAt,
1235
1520
  firstActivityAt,
1236
- objectivePreview,
1237
- ...this.runProjectionOf(sessionId, rollups),
1521
+ ...this.runRollupOf(sessionId, rollups),
1238
1522
  title: cells.title,
1239
1523
  pinned: cells.pinned,
1240
1524
  ...rowTag,
1241
1525
  ...("unreadable" in listed ? { unreadable: sessionUnreadableOf(listed.unreadable) } : {}),
1526
+ ...this.listedLineageOf(sessionId),
1242
1527
  };
1243
1528
  }
1529
+ listedLineageOf(sessionId) {
1530
+ const lineage = forkLineageOfRow(sessionId, this.liveCellOf(sessionId)?.forkedFrom, undefined);
1531
+ return lineage.forkedFrom !== undefined ? { forkedFrom: lineage.forkedFrom } : {};
1532
+ }
1244
1533
  rowTagKeyOf(sessionId, owner, cells) {
1245
- const meta = this.metaCellOf(sessionId);
1534
+ const meta = this.liveCellOf(sessionId);
1246
1535
  if (meta === undefined)
1247
1536
  return {};
1248
1537
  return sessionRowTagKey({ sessionId, owner, incarnation: incarnationFromCell(sessionId, meta.incarnation, "absent"), cells });
1249
1538
  }
1250
- async runRollups() {
1251
- const rows = await this.runs.listSessions({ limit: Number.MAX_SAFE_INTEGER });
1252
- return new Map(rows.map((r) => [r.sessionId, { runCount: r.runCount, lastStatus: r.lastStatus, lastRunId: r.lastRunId, ...(r.lastTaskRunId !== undefined ? { lastTaskRunId: r.lastTaskRunId } : {}) }]));
1539
+ runRollups(q) {
1540
+ return this.runs.sessionRunRollups(q ? { q } : {});
1253
1541
  }
1254
- runProjectionOf(sessionId, rollups) {
1255
- return rollups.get(sessionId) ?? { runCount: 0, lastStatus: "", lastRunId: null };
1256
- }
1257
- async previewOf(meta) {
1258
- try {
1259
- const session = await this.repo.open({ id: meta.id, createdAt: meta.createdAt });
1260
- const entries = await session.getEntries();
1261
- for (let i = entries.length - 1; i >= 0; i--) {
1262
- const text = userText(entries[i]);
1263
- const preview = text === null ? "" : buildObjectivePreview(text, SESSION_OBJECTIVE_PREVIEW_CHARS);
1264
- if (preview !== "")
1265
- return preview;
1266
- }
1267
- return null;
1268
- }
1269
- catch {
1270
- return null;
1271
- }
1542
+ runRollupOf(sessionId, rollups) {
1543
+ return rollups.get(sessionId) ?? { runCount: 0, lastStatus: "", lastRunId: null, objectivePreview: null };
1272
1544
  }
1273
1545
  async fork(sourceId, owner, opts) {
1274
1546
  return forkByFreshId((s, o, t) => this.forkTo(s, o, t), sourceId, owner, opts);
@@ -1281,6 +1553,9 @@ export class LocalSessionStore {
1281
1553
  return this.withSessionWriteLock(first, () => this.withSessionWriteLock(second, () => this.forkToHeld(sourceId, owner, target)));
1282
1554
  }
1283
1555
  async forkToHeld(sourceId, owner, target) {
1556
+ await this.meta.ensureLoaded();
1557
+ if (this.cellStateOf(sourceId).state === "deleting")
1558
+ return null;
1284
1559
  const sourceMeta = { id: sourceId, createdAt: "" };
1285
1560
  let source;
1286
1561
  try {
@@ -1293,15 +1568,25 @@ export class LocalSessionStore {
1293
1568
  }
1294
1569
  await this.assertArchiveAnonymous(sourceId);
1295
1570
  const forkOptions = await settledForkOptions(source);
1296
- const forkOwner = forkOwnerOf(owner, this.metaCellOf(sourceId)?.owner);
1297
- const existing = this.metaCellOf(target.id);
1571
+ const sourceCell = this.liveCellOf(sourceId);
1572
+ const forkOwner = forkOwnerOf(owner, sourceCell?.owner);
1573
+ const sourceToken = sourceCell === undefined ? undefined : incarnationFromCell(sourceId, sourceCell.incarnation, "absent");
1574
+ const lineage = forkRowCells({ ...sourceMeta, ...(sourceToken !== undefined ? { incarnation: sourceToken } : {}) });
1575
+ const existing = this.liveCellOf(target.id);
1298
1576
  const targetArchived = await this.existsInRepo(target.id);
1299
1577
  const placement = this.placements !== undefined ? target.placement : undefined;
1300
1578
  const copy = () => this.repo.fork(sourceMeta, { ...forkOptions, id: target.id, ...(placement !== undefined ? { placement } : {}) });
1301
1579
  if (existing === undefined && !targetArchived) {
1302
1580
  const created = await this.writeMeta(target.id, (before) => before !== undefined
1303
1581
  ? undefined
1304
- : { incarnation: mintSessionIncarnation(), ...(forkOwner !== undefined ? { owner: forkOwner } : {}), ...(target.key !== undefined ? { forkKey: target.key } : {}), createdAt: rowBornNow(), forkedFrom: sourceId });
1582
+ : {
1583
+ incarnation: mintSessionIncarnation(),
1584
+ ...(forkOwner !== undefined ? { owner: forkOwner } : {}),
1585
+ ...(target.key !== undefined ? { forkKey: target.key } : {}),
1586
+ createdAt: rowBornNow(),
1587
+ ...(lineage.forkedFrom !== undefined ? { forkedFrom: lineage.forkedFrom } : {}),
1588
+ ...(lineage.forkedFromIncarnation !== undefined ? { forkedFromIncarnation: lineage.forkedFromIncarnation } : {}),
1589
+ });
1305
1590
  if (!created)
1306
1591
  throw new SessionError("storage", `LocalSessionStore: fork target ${target.id} gained a session-meta cell under its own write lock — refusing to guess`);
1307
1592
  let forked;
@@ -1317,8 +1602,12 @@ export class LocalSessionStore {
1317
1602
  return { outcome: "created", id: target.id };
1318
1603
  }
1319
1604
  const out = classifyForkTarget(target, { owner: existing?.owner, forkKey: existing?.forkKey }, forkOwner);
1320
- if (out.outcome === "replayed" && !targetArchived)
1605
+ if (out.outcome === "replayed" && isForkCreatedCell(existing) && !targetArchived) {
1606
+ const recorded = existing?.forkedFromIncarnation;
1607
+ if (recorded !== undefined && recorded !== lineage.forkedFromIncarnation)
1608
+ return null;
1321
1609
  await copy();
1610
+ }
1322
1611
  return out;
1323
1612
  }
1324
1613
  async admitForkedPlacement(sessionId, forked, placement) {
@@ -1339,6 +1628,9 @@ export class LocalSessionStore {
1339
1628
  async exportEntries(sessionId) {
1340
1629
  if (!this.repo.exportEntries)
1341
1630
  return null;
1631
+ await this.meta.ensureLoaded();
1632
+ if (this.cellStateOf(sessionId).state !== "live" && this.cellStateOf(sessionId).state !== "none")
1633
+ return null;
1342
1634
  try {
1343
1635
  return await this.repo.exportEntries(sessionId);
1344
1636
  }
@@ -1381,7 +1673,7 @@ export class LocalSessionStore {
1381
1673
  if (landing === "replace")
1382
1674
  await this.assertNotPlacedHeld(sessionId);
1383
1675
  validateEntriesForImport(entries);
1384
- const current = this.metaCellOf(sessionId);
1676
+ const current = this.liveCellOf(sessionId);
1385
1677
  if (landing !== "judged")
1386
1678
  await this.admitImportHeld(sessionId, current);
1387
1679
  if (!(await this.importMayLand(sessionId, current, owner, landing)))
@@ -1391,8 +1683,8 @@ export class LocalSessionStore {
1391
1683
  case "create":
1392
1684
  case "judged":
1393
1685
  try {
1394
- const archive = (await this.archiveHeadOf(sessionId)) ?? null;
1395
- await this.repo.importEntries(sessionId, undefined, entries, { expect: { row: archive } });
1686
+ const expectRow = landing === "create" ? null : ((await this.archiveHeadOf(sessionId)) ?? null);
1687
+ await this.repo.importEntries(sessionId, undefined, entries, { expect: { row: expectRow } });
1396
1688
  }
1397
1689
  catch (err) {
1398
1690
  await this.withdrawImportCell(sessionId, prior, err);
@@ -1423,9 +1715,10 @@ export class LocalSessionStore {
1423
1715
  }
1424
1716
  }
1425
1717
  async admitImportHeld(sessionId, current) {
1426
- const verdict = await judgeImportWrite(sessionId, {
1718
+ const verdict = await judgeSessionWrite({ op: "import", rootSessionId: sessionId }, {
1427
1719
  claim: () => this.runs.getActiveTaskId(sessionId),
1428
1720
  cell: async () => placementCellOf(current, await this.forkingOf(sessionId, current)),
1721
+ deleting: async () => this.deletingTokenOf(sessionId),
1429
1722
  });
1430
1723
  if (verdict !== "proceed")
1431
1724
  throw verdict.refuse;
@@ -1451,7 +1744,7 @@ export class LocalSessionStore {
1451
1744
  const cells = carriedRowCells(sessionId, before === undefined ? undefined : readSessionMetaCell(before));
1452
1745
  const base = before === undefined ? { createdAt: rowBornNow() } : cellObjectOf(before);
1453
1746
  const { forkKey: _carriedByTheRule, ...kept } = base;
1454
- return { ...kept, incarnation: cells.incarnation, owner, execution: MIRROR_CELL, ...(cells.forkKey !== null ? { forkKey: cells.forkKey } : {}) };
1747
+ return { ...kept, incarnation: cells.incarnation, owner, execution: MIRROR_CELL, ...(cells.forkKey !== null ? { forkKey: cells.forkKey } : {}), clientWrites: (kept.clientWrites ?? 0) + 1 };
1455
1748
  });
1456
1749
  return prior;
1457
1750
  }
@@ -1483,7 +1776,7 @@ export class LocalSessionStore {
1483
1776
  }
1484
1777
  async commitStagedImport(sessionId, owner, staged, opts) {
1485
1778
  return this.withImportLock(sessionId, owner, async (importer) => {
1486
- const current = await this.metaOf(sessionId);
1779
+ const current = await this.metaHeld(sessionId);
1487
1780
  if (!ownerCellIs(opts.expectedOwner, current === undefined ? undefined : (current.owner ?? null)))
1488
1781
  throw new StagedCommitOwnerChangedError(sessionId);
1489
1782
  await this.assertNotPlacedHeld(sessionId);
@@ -1509,46 +1802,326 @@ export class LocalSessionStore {
1509
1802
  await this.landImportCell(sessionId, importer);
1510
1803
  await this.touch(sessionId);
1511
1804
  }
1512
- const meta = await this.metaOf(sessionId);
1805
+ const meta = await this.metaHeld(sessionId);
1513
1806
  if (meta === undefined)
1514
1807
  throw new SessionError("storage", `LocalSessionStore: staged commit for session ${sessionId} left no session face — nothing was settled`);
1515
1808
  const face = sessionFaceFromRow(sessionId, meta.owner ?? null, meta.incarnation);
1516
1809
  return { relation: rel.relation, face };
1517
1810
  });
1518
1811
  }
1519
- async deleteSession(sessionId, owner) {
1520
- return this.withSessionWriteLock(sessionId, () => this.deleteSessionHeld(sessionId, owner));
1812
+ async deleteSession(sessionId, owner, token, legs) {
1813
+ return this.withSessionWriteLock(sessionId, (dropped) => this.deleteSessionHeld(sessionId, owner, token, legs, dropped));
1521
1814
  }
1522
- async deleteSessionHeld(sessionId, owner) {
1523
- const current = await this.metaOf(sessionId);
1524
- if (current === undefined || !ownerEq(current.owner ?? null, owner))
1525
- return false;
1815
+ async deleteSessionHeld(sessionId, owner, token, legs, dropped) {
1816
+ if (dropped !== undefined)
1817
+ return undefined;
1818
+ await this.meta.ensureLoaded();
1819
+ const cell = this.cellStateOf(sessionId);
1820
+ if (cell.state === "none" || !ownerEq(cell.meta.owner ?? null, owner))
1821
+ return undefined;
1822
+ const verdict = await judgeSessionWrite({ op: "erase", rootSessionId: sessionId, token }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
1823
+ if (verdict !== "proceed")
1824
+ throw verdict.refuse;
1825
+ const occupant = { owner: cell.meta.owner ?? null, incarnation: cell.meta.incarnation };
1826
+ await legs(occupant);
1827
+ await this.dropRowHeld(sessionId);
1828
+ return occupant;
1829
+ }
1830
+ deletingTokenOf(sessionId) {
1831
+ const cell = this.cellStateOf(sessionId);
1832
+ return cell.state === "none" ? null : (cell.meta.deleting ?? null);
1833
+ }
1834
+ sessionDeletion = {
1835
+ fence: (sessionId, owner, opts) => this.withSessionWriteLock(sessionId, (dropped) => this.fenceDeletionHeld(sessionId, owner, opts, dropped)),
1836
+ holds: async (sessionId, token) => {
1837
+ await this.meta.ensureLoaded();
1838
+ return this.deletingTokenOf(sessionId) === token;
1839
+ },
1840
+ pending: async () => {
1841
+ await this.meta.ensureLoaded();
1842
+ return this.deletingIds().map((sessionId) => ({ sessionId, owner: this.deletionOwnerOf(sessionId), token: this.deletingTokenOf(sessionId) }));
1843
+ },
1844
+ };
1845
+ deletionOwnerOf(sessionId) {
1846
+ const cell = this.cellStateOf(sessionId);
1847
+ return cell.state === "none" ? null : (cell.meta.owner ?? null);
1848
+ }
1849
+ async fenceDeletionHeld(sessionId, owner, opts, dropped) {
1850
+ if (dropped !== undefined)
1851
+ return undefined;
1852
+ await this.meta.ensureLoaded();
1853
+ const state = this.cellStateOf(sessionId);
1854
+ let current;
1855
+ if (state.state === "deleting")
1856
+ current = state.meta;
1857
+ else {
1858
+ if (opts.expect !== undefined)
1859
+ return undefined;
1860
+ const seen = await this.observeRow(sessionId);
1861
+ if (seen.row === "absent")
1862
+ return undefined;
1863
+ current = seen.row === "live" ? seen.meta : undefined;
1864
+ }
1865
+ if (!ownerEq(current?.owner ?? null, owner))
1866
+ return undefined;
1867
+ if (state.state === "deleting") {
1868
+ const held = state.meta.deleting;
1869
+ if (opts.expect !== undefined && held !== opts.expect)
1870
+ return undefined;
1871
+ if (opts.holder(held) === "running")
1872
+ return { inProgress: true };
1873
+ }
1874
+ else {
1875
+ const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => ({ role: "absent" }), deleting: async () => this.deletingTokenOf(sessionId) });
1876
+ if (verdict !== "proceed")
1877
+ throw verdict.refuse;
1878
+ }
1879
+ const token = uuidv7();
1880
+ let incarnation;
1881
+ const committed = await this.meta.commit((draft) => {
1882
+ const before = draft.get(sessionId);
1883
+ const base = before === undefined ? { incarnation: mintSessionIncarnation() } : cellObjectOf(before);
1884
+ incarnation = before === undefined ? undefined : base.incarnation;
1885
+ draft.set(sessionId, { ...base, deleting: token });
1886
+ return true;
1887
+ }, "required");
1888
+ if (!committed)
1889
+ throw new SessionError("storage", `LocalSessionStore: the deletion fence of session ${sessionId} could not be made durable — nothing of the session was removed; retry the delete`);
1890
+ return { token, incarnation };
1891
+ }
1892
+ async bumpClientWritesHeld(sessionId) {
1893
+ await this.meta.ensureLoaded();
1894
+ if (this.cellStateOf(sessionId).state === "deleting")
1895
+ throw new SessionDeletingError(sessionId);
1896
+ if (this.liveCellOf(sessionId) === undefined)
1897
+ return;
1898
+ const committed = await this.meta.commit((draft) => {
1899
+ const before = draft.get(sessionId);
1900
+ if (before === undefined)
1901
+ return false;
1902
+ const cell = readSessionMetaCell(before);
1903
+ if (cell.dropping)
1904
+ throw new Error(`LocalSessionStore: session ${sessionId}'s cell carries the drop mark while a client write reached it — every section finishes a pending drop first; refusing to write`);
1905
+ draft.set(sessionId, { ...cellObjectOf(before), clientWrites: (cell.clientWrites ?? 0) + 1 });
1906
+ return true;
1907
+ }, "required");
1908
+ if (!committed)
1909
+ throw new SessionError("storage", `LocalSessionStore: the client-write count of session ${sessionId} could not be made durable — the write was not made; retry it`);
1910
+ }
1911
+ async withClientWrite(sessionId, write) {
1912
+ return this.withSessionWriteLock(sessionId, async () => {
1913
+ let open = true;
1914
+ try {
1915
+ return await write(async () => {
1916
+ if (!open)
1917
+ throw new Error(`LocalSessionStore.withClientWrite: session ${sessionId}'s client-write count was taken after its lock section ended`);
1918
+ await this.bumpClientWritesHeld(sessionId);
1919
+ });
1920
+ }
1921
+ finally {
1922
+ open = false;
1923
+ }
1924
+ });
1925
+ }
1926
+ async withdrawBirth(birth, ownTaskId) {
1927
+ const runs = this.runs;
1928
+ return this.withSessionWriteLock(birth.sessionId, async (dropped) => {
1929
+ const sessionId = birth.sessionId;
1930
+ const kept = (word) => ({ withdrawn: false, kept: word });
1931
+ if (dropped !== undefined)
1932
+ return kept("absent");
1933
+ await this.meta.ensureLoaded();
1934
+ if (this.cellStateOf(sessionId).state === "deleting")
1935
+ return kept("claim");
1936
+ const seen = await this.observeRow(sessionId);
1937
+ if (seen.row === "absent")
1938
+ return kept("absent");
1939
+ if (seen.row !== "live")
1940
+ return kept("incarnation");
1941
+ const cell = seen.meta;
1942
+ if (cell.incarnation !== birth.incarnation || cell.owner === undefined || cell.owner !== birth.owner)
1943
+ return kept("incarnation");
1944
+ if (cell.clientWrites === undefined)
1945
+ return kept("upgrade_pending");
1946
+ if (await this.archiveHasContentHeld(sessionId))
1947
+ return kept("content");
1948
+ const verdict = await judgeSessionWrite({ op: "delete", rootSessionId: sessionId }, { claim: () => this.runs.getActiveTaskId(sessionId), cell: async () => placementCellOf(cell, false), deleting: async () => this.deletingTokenOf(sessionId) });
1949
+ if (verdict !== "proceed")
1950
+ return kept("claim");
1951
+ if (cell.clientWrites > 0)
1952
+ return kept("client_writes");
1953
+ if (typeof runs.withdrawRunsOf !== "function") {
1954
+ throw new Error(`LocalSessionStore.withdrawBirth: the run store wired into this session store cannot answer the withdraw's run half (withdrawRunsOf) — cannot tell whether session ${sessionId} has other runs; refusing to guess`);
1955
+ }
1956
+ const runsVerdict = runs.withdrawRunsOf.call(this.runs, sessionId, ownTaskId);
1957
+ if (runsVerdict !== "proceed")
1958
+ return kept(runsVerdict.keep);
1959
+ this.withdrawCheckpointCtx?.(sessionId);
1960
+ await this.dropRowHeld(sessionId);
1961
+ return { withdrawn: true };
1962
+ });
1963
+ }
1964
+ async archiveHasContentHeld(sessionId) {
1965
+ if (!this.repo.exportEntries)
1966
+ return true;
1967
+ let entries;
1526
1968
  try {
1527
- await this.repo.delete({ id: sessionId, createdAt: "" });
1969
+ entries = await this.repo.exportEntries(sessionId);
1528
1970
  }
1529
1971
  catch (e) {
1530
- if (!isNotFound(e))
1531
- throw e;
1972
+ if (isNotFound(e))
1973
+ return false;
1974
+ throw e;
1532
1975
  }
1533
- await this.dropRowHeld(sessionId);
1534
- return true;
1976
+ return entries.some((e) => !entryTypeWithdrawable(e.type));
1535
1977
  }
1536
1978
  async dropRowHeld(sessionId) {
1537
- this.rowsDropped.add(sessionId);
1979
+ await this.meta.ensureLoaded();
1980
+ let marked;
1981
+ const s0 = await this.meta.commit((draft) => {
1982
+ const before = draft.get(sessionId);
1983
+ if (before !== undefined && readSessionMetaCell(before).dropping) {
1984
+ marked = cellObjectOf(before);
1985
+ return false;
1986
+ }
1987
+ marked = { ...(before === undefined ? { incarnation: mintSessionIncarnation() } : cellObjectOf(before)), dropping: true };
1988
+ draft.set(sessionId, marked);
1989
+ return true;
1990
+ }, "required");
1991
+ if (!s0)
1992
+ throw new SessionError("storage", `LocalSessionStore: the drop mark of session ${sessionId} could not be made durable — nothing of the session was removed; retry the delete`);
1538
1993
  for (const [map, which] of [[this.titles, "title"], [this.pinned, "pinned"], [this.instructionSources, "instruction-source"], [this.workspaces, "workspace"]]) {
1539
1994
  await map.ensureLoaded();
1540
- if (!(await map.deleteKey(sessionId)))
1541
- throw new SessionError("storage", `LocalSessionStore: ${which} sidecar write failed while deleting session ${sessionId} — its ${which} is still on disk; retry the delete`);
1995
+ if (!(await map.deleteKey(sessionId, "required")))
1996
+ throw new SessionError("storage", `LocalSessionStore: ${which} sidecar write failed while deleting session ${sessionId} — the session already reads as deleted; retry the delete (or restart) to finish it`);
1542
1997
  }
1543
1998
  await this.ensureLoaded();
1544
1999
  this.lastActivity.delete(sessionId);
1545
2000
  this.scheduleFlush();
1546
- await this.dropMeta(sessionId);
2001
+ try {
2002
+ await this.repo.delete({ id: sessionId, createdAt: "" });
2003
+ }
2004
+ catch (e) {
2005
+ if (!isNotFound(e))
2006
+ throw e;
2007
+ }
2008
+ this.syncArchiveDirectory();
2009
+ if (!(await this.meta.deleteKey(sessionId, "required")))
2010
+ throw new SessionError("storage", `LocalSessionStore: session-meta sidecar write failed while deleting session ${sessionId} — the session already reads as deleted; retry the delete (or restart) to finish it`);
2011
+ return readSessionMetaCell(marked);
1547
2012
  }
1548
2013
  syncArchiveDirectory() {
1549
- if (this.dataRoot === undefined)
2014
+ if (this.dataRoot === undefined || !(this.repo instanceof FileSessionRepo) || IS_WIN32)
1550
2015
  return;
1551
- fsyncPaths(join(this.dataRoot, "sessions"));
2016
+ fsyncPaths(join(this.dataRoot, ARCHIVE_DIR));
2017
+ }
2018
+ async finishPendingDropHeld(sessionId) {
2019
+ await this.meta.ensureLoaded();
2020
+ if (this.cellStateOf(sessionId).state !== "dropping")
2021
+ return undefined;
2022
+ return this.dropRowHeld(sessionId);
2023
+ }
2024
+ async finishPendingDrops() {
2025
+ await this.meta.ensureLoaded();
2026
+ for (const sessionId of this.droppingIds()) {
2027
+ try {
2028
+ await this.withSessionWriteLock(sessionId, async () => undefined);
2029
+ }
2030
+ catch (e) {
2031
+ this.log.warn("session_drop_unfinished", {
2032
+ sessionId,
2033
+ error: e instanceof Error ? e.message : String(e),
2034
+ effect: "the session reads as deleted everywhere; its remaining bytes are removed the next time anything touches this session id, or at the next start",
2035
+ });
2036
+ }
2037
+ }
2038
+ }
2039
+ async clearShellSentinelOwnersM48() {
2040
+ await this.meta.ensureLoaded();
2041
+ let cleared = 0;
2042
+ const committed = await this.meta.commit((draft) => {
2043
+ cleared = 0;
2044
+ for (const sessionId of [...draft.keys()]) {
2045
+ const cell = draft.get(sessionId);
2046
+ if (cell === undefined || typeof cell === "string" || cell.owner !== SHELL_SENTINEL_OWNER_M48 || readSessionMetaCell(cell).dropping)
2047
+ continue;
2048
+ draft.set(sessionId, { ...cell, owner: null });
2049
+ cleared += 1;
2050
+ }
2051
+ return cleared > 0;
2052
+ }, "best-effort");
2053
+ if (!committed)
2054
+ throw new SessionError("storage", "LocalSessionStore: the one-time migration M48 could not write the session-meta cells it cleared of the shell sentinel owner durably — nothing was changed");
2055
+ return cleared;
2056
+ }
2057
+ async adoptArchiveTokensM47() {
2058
+ const root = this.dataRoot;
2059
+ if (root === undefined)
2060
+ return { adopted: 0, minted: 0 };
2061
+ const owed = archiveTokenAdoptionPendingPresent(root);
2062
+ const rows = await this.rowHeaders();
2063
+ let adopted = 0;
2064
+ let minted = 0;
2065
+ const committed = await this.meta.commit((draft) => {
2066
+ adopted = 0;
2067
+ minted = 0;
2068
+ for (const row of rows) {
2069
+ if (!("header" in row) || draft.get(row.sessionId) !== undefined)
2070
+ continue;
2071
+ const first = firstCellIncarnationOf(row.sessionId, row.header.incarnation);
2072
+ const facts = archivedRowFactsOf(row.header);
2073
+ draft.set(row.sessionId, { incarnation: first.incarnation, createdAt: facts.createdAt, ...(facts.forkedFrom !== undefined ? { forkedFrom: facts.forkedFrom } : {}) });
2074
+ if (first.adopted)
2075
+ adopted += 1;
2076
+ else
2077
+ minted += 1;
2078
+ }
2079
+ return adopted + minted > 0;
2080
+ }, owed ? "required" : "best-effort");
2081
+ if (!committed)
2082
+ throw new SessionError("storage", "LocalSessionStore: the one-time migration M47 could not write the session-meta cells it adopted into the sidecar durably — the adoption stays owed");
2083
+ if (owed) {
2084
+ await this.assertArchiveTokensAdoptedM47(root);
2085
+ dischargeArchiveTokenAdoptionPending(root);
2086
+ }
2087
+ return { adopted, minted };
2088
+ }
2089
+ async assertArchiveTokensAdoptedM47(root) {
2090
+ let left = 0;
2091
+ for (const sessionId of archiveIdsWithoutCellSync(root))
2092
+ if ((await this.adoptionScopeOf(sessionId)) === "in")
2093
+ left += 1;
2094
+ if (left > 0) {
2095
+ throw new SessionError("storage", `LocalSessionStore: the one-time migration M47 did not adopt ${left} session archive(s) without a session-meta cell whose header it can read (the session listing it read did not list them) — refusing to start. ` +
2096
+ `${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} stays in the data root; the next start of a release carrying this migration tries again.`);
2097
+ }
2098
+ }
2099
+ async adoptionScopeOf(sessionId) {
2100
+ let placed;
2101
+ try {
2102
+ placed = await this.recordedPlacementOf(sessionId);
2103
+ }
2104
+ catch (fault) {
2105
+ return this.skippedFromAdoption(sessionId, fault);
2106
+ }
2107
+ if (placed !== undefined)
2108
+ return "out";
2109
+ try {
2110
+ return (await this.archiveHeadOf(sessionId)) === undefined ? "out" : "in";
2111
+ }
2112
+ catch (fault) {
2113
+ if (fault instanceof SessionError && archiveFaultWord(fault.code) === "corrupt_session")
2114
+ return this.skippedFromAdoption(sessionId, fault);
2115
+ return "in";
2116
+ }
2117
+ }
2118
+ skippedFromAdoption(sessionId, fault) {
2119
+ this.log.warn("archive_token_adoption_skipped", {
2120
+ sessionId,
2121
+ error: fault instanceof Error ? fault.message : String(fault),
2122
+ effect: "this session archive's header cannot be read, so the one-time migration M47 cannot keep its occupant token; if it becomes readable only after the migration's window, its first read mints a new token and memory records kept under the old one stop applying",
2123
+ });
2124
+ return "out";
1552
2125
  }
1553
2126
  archiveSnapshotDir() {
1554
2127
  return this.dataRoot === undefined ? undefined : join(this.dataRoot, LOCAL_ARCHIVE_SNAPSHOT_DIR);
@@ -1617,10 +2190,10 @@ export class LocalSessionStore {
1617
2190
  catch (e) {
1618
2191
  throw refuse("restoring the archive from the snapshot failed", e);
1619
2192
  }
1620
- await this.fillRowFacts(id, record.rowFacts);
2193
+ await this.fillRowFacts(id, record.rowFacts, record.archiveIncarnation);
1621
2194
  }
1622
2195
  else if (head.owner === null) {
1623
- await this.fillRowFacts(id, record.rowFacts);
2196
+ await this.fillRowFacts(id, record.rowFacts, record.archiveIncarnation);
1624
2197
  }
1625
2198
  else if (head.incarnation !== (record.archiveIncarnation ?? undefined)) {
1626
2199
  throw refuse("the session's archive is a different generation from the one the snapshot was taken of");
@@ -1629,8 +2202,8 @@ export class LocalSessionStore {
1629
2202
  fsyncPaths(dir);
1630
2203
  });
1631
2204
  }
1632
- async fillRowFacts(sessionId, facts) {
1633
- await this.writeMeta(sessionId, (before) => rowFactsFilled(before, facts));
2205
+ async fillRowFacts(sessionId, facts, archiveToken) {
2206
+ await this.writeMeta(sessionId, (before) => rowFactsFilled(sessionId, before, facts, archiveToken));
1634
2207
  }
1635
2208
  async migrateLocalArchives() {
1636
2209
  const dir = this.archiveSnapshotDir();
@@ -1655,14 +2228,14 @@ export class LocalSessionStore {
1655
2228
  const committed = await this.meta.commit((draft) => {
1656
2229
  rowFactsBackfilled = 0;
1657
2230
  for (const row of listed) {
1658
- const next = rowFactsFilled(draft.get(row.id), archivedRowFactsOf(row));
2231
+ const next = rowFactsFilled(row.id, draft.get(row.id), archivedRowFactsOf(row), row.incarnation);
1659
2232
  if (next === undefined)
1660
2233
  continue;
1661
2234
  draft.set(row.id, next);
1662
2235
  rowFactsBackfilled += 1;
1663
2236
  }
1664
2237
  return rowFactsBackfilled > 0;
1665
- });
2238
+ }, "best-effort");
1666
2239
  if (!committed)
1667
2240
  throw new SessionError("storage", "LocalSessionStore: the one-time migration M33 could not write the row facts it backfilled into the session-meta sidecar — no archive was touched");
1668
2241
  this.meta.syncToDisk();
@@ -1720,7 +2293,7 @@ export class LocalSessionStore {
1720
2293
  }
1721
2294
  if (!sameLog(validated, row.entries))
1722
2295
  return { outcome: "kept", reason: "normalizes" };
1723
- const cell = this.metaCellOf(sessionId);
2296
+ const cell = this.liveCellOf(sessionId);
1724
2297
  if (cell?.createdAt === undefined)
1725
2298
  throw new Error(`LocalSessionStore: the one-time migration M33 reached session ${sessionId}'s archive before its row facts were backfilled — refusing to rewrite it`);
1726
2299
  const record = {