@sema-agent/server 7.148.0 → 7.150.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 (360) hide show
  1. package/MIGRATION.md +551 -9
  2. package/README.md +17 -2
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +126 -68
  5. package/dist/adoption/wire.d.ts +3 -3
  6. package/dist/approval-card.d.ts +33 -33
  7. package/dist/approval-gate-currency.d.ts +1 -1
  8. package/dist/approval-leg-origin.d.ts +33 -0
  9. package/dist/approval-leg-origin.js +4 -0
  10. package/dist/audit.d.ts +1 -1
  11. package/dist/audit.js +2 -2
  12. package/dist/auth-bridge.d.ts +9 -1
  13. package/dist/auth-bridge.js +4 -4
  14. package/dist/auth-keys.d.ts +10 -1
  15. package/dist/auth-keys.js +8 -0
  16. package/dist/background-session-reap.d.ts +1 -1
  17. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  18. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  19. package/dist/boot/execution-env.js +10 -11
  20. package/dist/boot/execution-lane.d.ts +6 -4
  21. package/dist/boot/execution-lane.js +3 -1
  22. package/dist/boot/org-delivery.d.ts +3 -2
  23. package/dist/boot/parked-revive-gate.d.ts +5 -5
  24. package/dist/boot/parked-revive-gate.js +2 -3
  25. package/dist/boot/resolve-spec.d.ts +4 -5
  26. package/dist/boot/resolve-spec.js +66 -28
  27. package/dist/boot/retention-lane.d.ts +1 -1
  28. package/dist/boot/runner-deps.js +3 -1
  29. package/dist/boot/session-faces.js +21 -21
  30. package/dist/boot/stage-01-config.d.ts +1 -0
  31. package/dist/boot/stage-01-config.js +4 -3
  32. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  33. package/dist/boot/stage-02-web-admin-pull.js +10 -2
  34. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  35. package/dist/boot/stage-03-store-backend.js +4 -4
  36. package/dist/boot/stage-04-stores.d.ts +1 -0
  37. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  38. package/dist/boot/stage-06-runners.d.ts +2 -1
  39. package/dist/boot/stage-06-runners.js +1 -2
  40. package/dist/boot/stage-07-capability-layer.d.ts +2 -1
  41. package/dist/boot/stage-07-capability-layer.js +1 -1
  42. package/dist/boot/stage-08-reapers.d.ts +2 -1
  43. package/dist/boot/stage-09-leader.d.ts +2 -1
  44. package/dist/boot/stage-10-http-server.d.ts +35 -2
  45. package/dist/boot/stage-10-http-server.js +9 -4
  46. package/dist/boot/stores.d.ts +3 -0
  47. package/dist/boot/stores.js +22 -19
  48. package/dist/boot/web-admin.d.ts +1 -1
  49. package/dist/capabilities/tool-defer.d.ts +1 -1
  50. package/dist/capability-facts.d.ts +70 -60
  51. package/dist/capability-facts.js +45 -49
  52. package/dist/card-settle-stored-scope.d.ts +56 -0
  53. package/dist/card-settle-stored-scope.js +27 -0
  54. package/dist/card-void.d.ts +7 -12
  55. package/dist/card-void.js +2 -7
  56. package/dist/config-catalog.d.ts +12 -0
  57. package/dist/config-catalog.js +37 -33
  58. package/dist/config-invariants.d.ts +5 -4
  59. package/dist/config-invariants.js +4 -4
  60. package/dist/config-types.d.ts +49 -16
  61. package/dist/config.d.ts +6 -5
  62. package/dist/config.js +35 -24
  63. package/dist/context-window-notice.d.ts +31 -0
  64. package/dist/context-window-notice.js +28 -0
  65. package/dist/deployment-governance.d.ts +7 -28
  66. package/dist/deployment-governance.js +1 -14
  67. package/dist/deployment-shape.d.ts +20 -11
  68. package/dist/deployment-shape.js +1 -0
  69. package/dist/device-ws-hub.d.ts +1 -0
  70. package/dist/device-ws-hub.js +64 -28
  71. package/dist/device-ws-protocol.d.ts +20 -6
  72. package/dist/device-ws-protocol.js +9 -2
  73. package/dist/engine-identity.d.ts +7 -1
  74. package/dist/engine-identity.js +5 -0
  75. package/dist/env-name-allowlist-knobs.d.ts +6 -5
  76. package/dist/env-name-allowlist-knobs.js +5 -0
  77. package/dist/execution-lane-caps.d.ts +20 -5
  78. package/dist/execution-lane-caps.js +18 -4
  79. package/dist/field-conflict.d.ts +2 -2
  80. package/dist/fleet/engine-signal.d.ts +14 -1
  81. package/dist/fleet/engine-signal.js +5 -1
  82. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  83. package/dist/fleet/serving-lease.d.ts +18 -3
  84. package/dist/fleet/serving-lease.js +81 -35
  85. package/dist/fleet-client.d.ts +13 -11
  86. package/dist/fleet-client.js +53 -33
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +1 -3
  89. package/dist/hitl-delivery.js +1 -1
  90. package/dist/hooks/command-spawn.d.ts +7 -2
  91. package/dist/hooks/command-spawn.js +13 -6
  92. package/dist/hooks/hook-runner.d.ts +5 -0
  93. package/dist/hooks/hook-runner.js +11 -7
  94. package/dist/http/active-run-conflict.d.ts +3 -7
  95. package/dist/http/active-run-conflict.js +2 -2
  96. package/dist/http/admission.js +5 -5
  97. package/dist/http/approval-sweeps.js +5 -3
  98. package/dist/http/caller-trust.d.ts +2 -2
  99. package/dist/http/caller-trust.js +3 -3
  100. package/dist/http/checkpoint-ctx.d.ts +18 -63
  101. package/dist/http/checkpoint-ctx.js +1 -24
  102. package/dist/http/claim-heartbeat.d.ts +6 -6
  103. package/dist/http/claim-heartbeat.js +6 -6
  104. package/dist/http/claimed-run.d.ts +16 -9
  105. package/dist/http/claimed-run.js +8 -0
  106. package/dist/http/dispatch.js +7 -5
  107. package/dist/http/durable-run-leg.d.ts +8 -1
  108. package/dist/http/durable-run-leg.js +34 -15
  109. package/dist/http/execution-context.d.ts +115 -14
  110. package/dist/http/execution-context.js +130 -40
  111. package/dist/http/owner-access.d.ts +246 -21
  112. package/dist/http/owner-access.js +186 -8
  113. package/dist/http/principal-gate.d.ts +18 -11
  114. package/dist/http/principal-gate.js +3 -1
  115. package/dist/http/readiness.d.ts +4 -2
  116. package/dist/http/resume-ctx-read.d.ts +14 -3
  117. package/dist/http/resume-ctx-read.js +24 -3
  118. package/dist/http/resume-legs.d.ts +35 -3
  119. package/dist/http/resume-legs.js +262 -106
  120. package/dist/http/route-ctx.d.ts +29 -11
  121. package/dist/http/routes/a2a-serve.js +17 -14
  122. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  123. package/dist/http/routes/approvals-assistant.js +11 -11
  124. package/dist/http/routes/capabilities.js +5 -4
  125. package/dist/http/routes/config-catalog.d.ts +1 -1
  126. package/dist/http/routes/diagnostics.d.ts +3 -1
  127. package/dist/http/routes/diagnostics.js +30 -5
  128. package/dist/http/routes/mcp-reconnect.js +2 -2
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/notify-wake.js +2 -3
  131. package/dist/http/routes/principal-inbox.d.ts +3 -1
  132. package/dist/http/routes/principal-inbox.js +3 -2
  133. package/dist/http/routes/projects.d.ts +1 -0
  134. package/dist/http/routes/projects.js +5 -3
  135. package/dist/http/routes/retention-ops.d.ts +1 -1
  136. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  137. package/dist/http/routes/run-permission-mode.js +2 -2
  138. package/dist/http/routes/runs.d.ts +1 -1
  139. package/dist/http/routes/runs.js +33 -37
  140. package/dist/http/routes/session-memory-sync.js +4 -5
  141. package/dist/http/routes/session-sync.d.ts +24 -5
  142. package/dist/http/routes/session-sync.js +45 -37
  143. package/dist/http/routes/sessions-list.d.ts +1 -1
  144. package/dist/http/routes/sessions.d.ts +1 -1
  145. package/dist/http/routes/sessions.js +18 -44
  146. package/dist/http/routes/tasks.js +136 -101
  147. package/dist/http/routes/trace-usage.js +14 -14
  148. package/dist/http/routes/workflows.d.ts +1 -0
  149. package/dist/http/routes/workflows.js +20 -38
  150. package/dist/http/server.d.ts +12 -9
  151. package/dist/http/server.js +35 -6
  152. package/dist/http/stored-body-replay.d.ts +3 -24
  153. package/dist/http/stored-body-replay.js +2 -12
  154. package/dist/http/submit-ctx.d.ts +2 -2
  155. package/dist/http/submit-ctx.js +5 -5
  156. package/dist/http/submit-replay.d.ts +15 -2
  157. package/dist/http/submit-replay.js +10 -4
  158. package/dist/http/wire-types.d.ts +6 -3
  159. package/dist/index.d.ts +3 -1
  160. package/dist/index.js +1 -0
  161. package/dist/leader/endpoint.js +9 -8
  162. package/dist/leg-facts.d.ts +13 -2
  163. package/dist/leg-facts.js +2 -0
  164. package/dist/local-store-declaration.d.ts +54 -0
  165. package/dist/local-store-declaration.js +59 -0
  166. package/dist/mcp-assertion-header.d.ts +1 -1
  167. package/dist/mcp-assertion-header.js +1 -1
  168. package/dist/mcp-authorization-face.d.ts +18 -14
  169. package/dist/mcp-authorization-face.js +3 -2
  170. package/dist/memory-project-layer.js +1 -1
  171. package/dist/memory-scope.d.ts +4 -4
  172. package/dist/memory-scope.js +4 -4
  173. package/dist/memory-store-refusal.d.ts +1 -1
  174. package/dist/memory-sync-client.js +2 -2
  175. package/dist/model-context-window.d.ts +50 -11
  176. package/dist/model-context-window.js +20 -1
  177. package/dist/model-entry-refusal.d.ts +5 -4
  178. package/dist/model-entry-refusal.js +17 -3
  179. package/dist/model-reach-scope.d.ts +2 -1
  180. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  181. package/dist/observability/fail-open.d.ts +19 -7
  182. package/dist/observability/fail-open.js +19 -7
  183. package/dist/observability/logger.d.ts +7 -0
  184. package/dist/observability/logger.js +3 -1
  185. package/dist/observability/metrics.js +1 -1
  186. package/dist/observability/run-terminal.d.ts +1 -1
  187. package/dist/observability/run-terminal.js +1 -0
  188. package/dist/observability/secret-env-scrub.d.ts +1 -1
  189. package/dist/observability/secret-env-scrub.js +1 -1
  190. package/dist/org-delivery-gate.d.ts +31 -6
  191. package/dist/org-delivery-gate.js +7 -4
  192. package/dist/owner-faces.d.ts +30 -5
  193. package/dist/owner-faces.js +2 -0
  194. package/dist/park-row-commit.d.ts +67 -0
  195. package/dist/park-row-commit.js +76 -0
  196. package/dist/parked-decide.d.ts +9 -7
  197. package/dist/parked-decide.js +25 -6
  198. package/dist/peer-directory.d.ts +1 -1
  199. package/dist/peer-seat.d.ts +1 -1
  200. package/dist/peer-seat.js +52 -59
  201. package/dist/plugins/adoption-log-sql.d.ts +3 -3
  202. package/dist/plugins/adoption-log-sql.js +19 -19
  203. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  204. package/dist/plugins/approval-ask-store-sql.js +3 -5
  205. package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
  206. package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
  207. package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
  208. package/dist/plugins/approval-exemption-skill-m46.js +6 -10
  209. package/dist/plugins/approval-exemption-store.d.ts +26 -1
  210. package/dist/plugins/approval-exemption-store.js +28 -19
  211. package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
  212. package/dist/plugins/ensure-schema-lock-policy.js +75 -9
  213. package/dist/plugins/exec-path-law.d.ts +34 -0
  214. package/dist/plugins/exec-path-law.js +13 -0
  215. package/dist/plugins/file-run-store.d.ts +22 -9
  216. package/dist/plugins/file-run-store.js +51 -23
  217. package/dist/plugins/local-checkpoint-store.d.ts +8 -1
  218. package/dist/plugins/local-checkpoint-store.js +5 -1
  219. package/dist/plugins/local-data-root-admission.d.ts +52 -0
  220. package/dist/plugins/local-data-root-admission.js +100 -0
  221. package/dist/plugins/local-engine-high-water.d.ts +28 -7
  222. package/dist/plugins/local-engine-high-water.js +27 -22
  223. package/dist/plugins/local-session-store.d.ts +134 -49
  224. package/dist/plugins/local-session-store.js +149 -108
  225. package/dist/plugins/memory-run-store.d.ts +14 -9
  226. package/dist/plugins/memory-run-store.js +48 -22
  227. package/dist/plugins/one-time-migration-marker.d.ts +19 -3
  228. package/dist/plugins/one-time-migration-marker.js +7 -1
  229. package/dist/plugins/one-time-migrations.d.ts +110 -59
  230. package/dist/plugins/one-time-migrations.js +224 -136
  231. package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
  232. package/dist/plugins/org-delivery-quota-store.js +29 -14
  233. package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
  234. package/dist/plugins/permission-rule-store-sql.js +5 -9
  235. package/dist/plugins/pg-pool.d.ts +1 -1
  236. package/dist/plugins/pg-pool.js +14 -12
  237. package/dist/plugins/posix-shell-fs.js +4 -2
  238. package/dist/plugins/remote-env-adb.js +3 -2
  239. package/dist/plugins/remote-env-device.d.ts +3 -2
  240. package/dist/plugins/remote-env-device.js +8 -6
  241. package/dist/plugins/remote-env-e2b.d.ts +4 -8
  242. package/dist/plugins/remote-env-e2b.js +21 -15
  243. package/dist/plugins/remote-env-host.d.ts +51 -10
  244. package/dist/plugins/remote-env-host.js +97 -31
  245. package/dist/plugins/remote-env-k8s.js +7 -4
  246. package/dist/plugins/remote-env-local-docker.d.ts +6 -3
  247. package/dist/plugins/remote-env-local-docker.js +11 -25
  248. package/dist/plugins/remote-env-ssh.js +4 -3
  249. package/dist/plugins/remote-shell.d.ts +3 -1
  250. package/dist/plugins/remote-shell.js +3 -3
  251. package/dist/plugins/replica-registration.d.ts +35 -0
  252. package/dist/plugins/replica-registration.js +48 -0
  253. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  254. package/dist/plugins/run-store-sql.d.ts +27 -8
  255. package/dist/plugins/run-store-sql.js +46 -18
  256. package/dist/plugins/scenario-prefix-switch-m42.d.ts +8 -12
  257. package/dist/plugins/scenario-prefix-switch-m42.js +9 -6
  258. package/dist/plugins/schema-consistency.d.ts +91 -5
  259. package/dist/plugins/schema-consistency.js +189 -7
  260. package/dist/plugins/schema-drift-sentence.d.ts +6 -5
  261. package/dist/plugins/schema-drift-sentence.js +13 -5
  262. package/dist/plugins/serving-requirement.d.ts +49 -0
  263. package/dist/plugins/serving-requirement.js +53 -0
  264. package/dist/plugins/session-placement.d.ts +5 -4
  265. package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
  266. package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
  267. package/dist/plugins/stop-then-start-sentence.d.ts +55 -0
  268. package/dist/plugins/stop-then-start-sentence.js +51 -0
  269. package/dist/plugins/store-backend.d.ts +23 -9
  270. package/dist/plugins/store-backend.js +58 -24
  271. package/dist/plugins/store-contracts.d.ts +45 -5
  272. package/dist/plugins/store-contracts.js +19 -0
  273. package/dist/plugins/tidb-pool.d.ts +11 -6
  274. package/dist/plugins/tidb-pool.js +22 -12
  275. package/dist/plugins/workspace-custody.d.ts +44 -0
  276. package/dist/plugins/workspace-custody.js +47 -0
  277. package/dist/process-instance.d.ts +3 -0
  278. package/dist/process-instance.js +7 -0
  279. package/dist/project-context.d.ts +66 -0
  280. package/dist/project-context.js +70 -0
  281. package/dist/project-identity.d.ts +8 -7
  282. package/dist/project-identity.js +40 -17
  283. package/dist/project-layer-key.d.ts +13 -2
  284. package/dist/project-layer-key.js +9 -1
  285. package/dist/question.d.ts +9 -21
  286. package/dist/question.js +28 -19
  287. package/dist/read-deny-floor-notice.d.ts +36 -0
  288. package/dist/read-deny-floor-notice.js +40 -0
  289. package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
  290. package/dist/request-declaration-disposition.js +39 -0
  291. package/dist/run-cancel-card.d.ts +28 -0
  292. package/dist/run-cancel-card.js +20 -0
  293. package/dist/run-cancel-context.d.ts +10 -0
  294. package/dist/run-cancel-context.js +1 -0
  295. package/dist/run-local.js +28 -13
  296. package/dist/runs.d.ts +31 -7
  297. package/dist/runs.js +34 -16
  298. package/dist/security.d.ts +3 -3
  299. package/dist/server-secret-env.d.ts +34 -20
  300. package/dist/server-secret-env.js +61 -18
  301. package/dist/session-agent.d.ts +4 -12
  302. package/dist/session-agent.js +0 -6
  303. package/dist/session-children-retire.d.ts +6 -3
  304. package/dist/session-children-retire.js +2 -5
  305. package/dist/session-incarnation.d.ts +18 -4
  306. package/dist/session-incarnation.js +4 -1
  307. package/dist/session-notify-door.d.ts +4 -4
  308. package/dist/session-notify-door.js +5 -6
  309. package/dist/session-rules-seat.d.ts +4 -3
  310. package/dist/session-rules-seat.js +3 -11
  311. package/dist/session-sync.js +3 -3
  312. package/dist/session-workspace.d.ts +3 -3
  313. package/dist/store-admission.d.ts +33 -0
  314. package/dist/store-admission.js +49 -0
  315. package/dist/store-posture.d.ts +5 -0
  316. package/dist/store-posture.js +18 -1
  317. package/dist/stores-sql/checkpoint-store-sql.d.ts +22 -3
  318. package/dist/stores-sql/checkpoint-store-sql.js +109 -23
  319. package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
  320. package/dist/stores-sql/file-history-store-sql.js +3 -5
  321. package/dist/stores-sql/retention-store-sql.d.ts +1 -1
  322. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  323. package/dist/stores-sql/tool-result-store-sql.js +4 -6
  324. package/dist/task-a2a.d.ts +3 -8
  325. package/dist/task-a2a.js +25 -27
  326. package/dist/task-cwd.d.ts +27 -10
  327. package/dist/task-cwd.js +3 -0
  328. package/dist/task-mcp.d.ts +3 -3
  329. package/dist/task-mcp.js +1 -1
  330. package/dist/task-settings.d.ts +13 -0
  331. package/dist/task-settings.js +14 -7
  332. package/dist/terminal.d.ts +2 -0
  333. package/dist/terminal.js +21 -6
  334. package/dist/tool-approval.d.ts +50 -41
  335. package/dist/tool-approval.js +96 -73
  336. package/dist/trace/engine-notice-wire.d.ts +38 -21
  337. package/dist/trace/engine-notice-wire.js +25 -4
  338. package/dist/trace/ledger-sink.d.ts +3 -1
  339. package/dist/trace/project.d.ts +8 -5
  340. package/dist/trace/project.js +11 -9
  341. package/dist/trace/projection-table.d.ts +33 -2
  342. package/dist/trace/redact.d.ts +12 -0
  343. package/dist/trace/redact.js +5 -1
  344. package/dist/trace/self-minted-wire.d.ts +268 -56
  345. package/dist/trace/self-minted-wire.js +110 -40
  346. package/dist/trace/tool-card.d.ts +3 -0
  347. package/dist/trace/tool-card.js +24 -0
  348. package/dist/trace/tool-output-media.d.ts +2 -1
  349. package/dist/unattended-permission-mode.d.ts +1 -1
  350. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  351. package/dist/web-admin-client/apply-effective.js +22 -9
  352. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  353. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  354. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
  355. package/docs/error-text-lexicon.json +317 -0
  356. package/package.json +4 -2
  357. package/scripts/embed-minimal.mjs +183 -0
  358. package/wire-changes/server@7.149.0.json +372 -0
  359. package/wire-changes/server@7.150.0.json +335 -0
  360. package/dist/plain-string-record.js +0 -70
@@ -8,7 +8,7 @@ import { IS_WIN32 } from "./host-platform.js";
8
8
  import { atomicWriteFileAsync, atomicWriteFileSync, proveDurableSync } from "./owner-only-file-mode.js";
9
9
  import { assertFirstOwnerWritable, forkOwnerOf, importerOwnerOf, placedSessionOwnerOf } from "./session-ownership.js";
10
10
  import { SessionOwnerUnreadableError, StampedSessionArchiveError, sessionUnreadableOf } from "../session-error-wire.js";
11
- import { SHELL_SENTINEL_OWNER_M48 } from "./shell-sentinel-owner-m48.js";
11
+ import { isShellSentinelOwnerM48 } from "./shell-sentinel-owner-m48.js";
12
12
  import { FileSessionRepo, SessionError, StoredSession, admitSessionPlacement, atomicWriteFile, ensureDir, forkRowCells, sessionLogMarkOf, settledForkOptions, uuidv7, validateEntriesForImport } from "@sema-agent/core";
13
13
  import { placementCapabilityOf } from "./session-placement.js";
14
14
  import { z } from "zod";
@@ -19,8 +19,9 @@ import { judgeSessionRowWrite, sessionRowTagKey, settleSessionRowWrite, } from "
19
19
  import { quarantineUnreadableSidecar } from "./local-sidecar-quarantine.js";
20
20
  import { createZodColumnReader, decodeJsonColumn, isJsonColumnBodyProblem, isJsonObject, readJsonObjectColumn, readStringRecordColumn, SqlJsonColumnError } from "./sql-json-column.js";
21
21
  import { RENAMED_EXECUTION_LANE_M40, RETIRED_EXECUTION_LANE_WORD_M40 } from "./execution-lane-rename-m40.js";
22
- import { ExecutionLaneRenameWriteRefusedError, ONE_TIME_MIGRATIONS } from "./one-time-migrations.js";
22
+ import { ExecutionLaneRenameWriteRefusedError, migrationCarried } from "./one-time-migrations.js";
23
23
  import { compareReleaseTriples, releaseText, releaseTriple } from "./release-version.js";
24
+ import { PRE_CUTOFF_SKILL_EXEMPTION_CONSEQUENCE, SKILL_EXEMPTION_TOOL_NAME } from "./approval-exemption-skill-cutoff.js";
24
25
  import { recordFailOpen } from "../observability/fail-open.js";
25
26
  import { createLogger, writeLogText } from "../observability/logger.js";
26
27
  import { UNMANAGED_RETENTION } from "./retention-declaration.js";
@@ -78,6 +79,15 @@ function readSessionMetaCell(cell) {
78
79
  ? { incarnation: cell, owner: undefined, execution: undefined, forkKey: undefined, createdAt: undefined, forkedFrom: undefined, forkedFromIncarnation: undefined, dropping: false, deleting: undefined, clientWrites: undefined }
79
80
  : { 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 };
80
81
  }
82
+ function shellSentinelCellIdsM48(cells) {
83
+ const ids = [];
84
+ for (const [sessionId, cell] of cells) {
85
+ if (typeof cell === "string" || !isShellSentinelOwnerM48(cell.owner) || readSessionMetaCell(cell).dropping)
86
+ continue;
87
+ ids.push(sessionId);
88
+ }
89
+ return ids;
90
+ }
81
91
  const ownerRecorded = (cell) => cell !== undefined && readSessionMetaCell(cell).owner !== undefined;
82
92
  const cellObjectOf = (cell) => (typeof cell === "string" ? { incarnation: cell } : cell);
83
93
  function cellWith(before, bits) {
@@ -162,6 +172,27 @@ export const SESSION_META_CELL_OBJECT = z
162
172
  .strict();
163
173
  const SESSION_META_SIDECAR = z.record(z.string(), z.union([z.string(), SESSION_META_CELL_OBJECT]));
164
174
  const readSessionMetaSidecar = createZodColumnReader(SESSION_META_SIDECAR);
175
+ function renameRetiredExecutionLaneWordsInCellsM40(entries) {
176
+ if (!isJsonObject(entries))
177
+ return 0;
178
+ let renamed = 0;
179
+ for (const cell of Object.values(entries)) {
180
+ if (!isJsonObject(cell))
181
+ continue;
182
+ const execution = cell["execution"];
183
+ if (!isJsonObject(execution) || execution["role"] !== "writer" || execution["lane"] !== RETIRED_EXECUTION_LANE_WORD_M40)
184
+ continue;
185
+ execution["lane"] = RENAMED_EXECUTION_LANE_M40;
186
+ renamed += 1;
187
+ }
188
+ return renamed;
189
+ }
190
+ const STRICT_SIDECARS = [{ file: SESSION_META_SIDECAR_FILE, which: "session-meta" }];
191
+ const readSessionMetaSidecarInWindow = (body) => {
192
+ renameRetiredExecutionLaneWordsInCellsM40(body);
193
+ return readSessionMetaSidecar(body);
194
+ };
195
+ const STRICT_SIDECAR_FILES = new Set(STRICT_SIDECARS.map((s) => s.file));
165
196
  const SIDECAR_MAP_STATE_BY_PATH = new Map();
166
197
  const SESSION_WRITE_LOCKS_BY_ROOT = new Map();
167
198
  const quotedFieldNames = (names) => `${names.slice(0, 8).map((name) => JSON.stringify(name.slice(0, 64))).join(", ")}${names.length > 8 ? `, … (${names.length} in all)` : ""}`;
@@ -174,24 +205,40 @@ function describeDataRootFinding(f) {
174
205
  if (f.problem === "newer-engine") {
175
206
  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}).`;
176
207
  }
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
- }
208
+ if (f.problem === "skipped-required-migration")
209
+ return describeSkippedMigration(f);
191
210
  return f.unknownFields.length > 0
192
211
  ? ` ${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).`
193
212
  : ` ${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.`;
194
213
  }
214
+ function describeSkippedMigration(f) {
215
+ const m = f.skippedMigration;
216
+ switch (m.residue) {
217
+ case "archives-without-cell":
218
+ return describeArchivesWithoutCell(f, m);
219
+ case "pre-cutoff-skill-exemptions": {
220
+ const atLeast = m.highWater !== null && releaseAtOrAbove(m.highWater, m.requiredSince) ? m.highWater : m.requiredSince;
221
+ const cutoff = m.cutoff === null ? "no cutoff is recorded in the data root (no release that records it has fully started on this root)" : `the data root's cutoff is ${m.cutoff}`;
222
+ return (` ${f.label} at ${f.path}: ${m.sessions} session(s) hold a \`${SKILL_EXEMPTION_TOOL_NAME}\` session approval exemption first granted before the Skill exemption cutoff (${cutoff}). ` +
223
+ `${PRE_CUTOFF_SKILL_EXEMPTION_CONSEQUENCE} This build no longer carries the one-time migration (introduced in ${m.requiredSince}) that removes such rows. ` +
224
+ `Start the root once with a release at or above ${atLeast} that still carries that migration (server or run-local) and let that start finish, then start this build.`);
225
+ }
226
+ }
227
+ }
228
+ function describeArchivesWithoutCell(f, m) {
229
+ const { highWater, requiredSince, sessions } = m;
230
+ 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.";
231
+ if (highWater !== null && releaseAtOrAbove(highWater, requiredSince)) {
232
+ 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 ` +
233
+ `(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 ` +
234
+ `(${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} is still in the data root). ${consequence} ` +
235
+ `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.`);
236
+ }
237
+ 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}`;
238
+ 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}, ` +
239
+ `whose one-time migration gives such an archive its row cell under the occupant token the archive already carries. ${consequence} ` +
240
+ `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.`);
241
+ }
195
242
  function describeLocalDataRootMismatch(findings, when) {
196
243
  const lines = [
197
244
  when === "boot"
@@ -200,7 +247,9 @@ function describeLocalDataRootMismatch(findings, when) {
200
247
  ];
201
248
  for (const f of findings)
202
249
  lines.push(describeDataRootFinding(f));
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.");
250
+ 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.", when === "boot"
251
+ ? "This refusal comes from the data-root admission, which only reads and runs before this start writes anything to the data root: the data root is exactly as it was — no file, directory, key or lock was created, changed or removed."
252
+ : "The check behind this refusal only reads: the file(s) named above were left in place, unchanged.");
204
253
  return lines.join("\n");
205
254
  }
206
255
  export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
@@ -215,14 +264,11 @@ export class LocalDataRootSchemaMismatchError extends StoreBootRefusalError {
215
264
  }
216
265
  }
217
266
  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
267
  const ARCHIVE_DIR = "sessions";
222
268
  const ARCHIVE_SUFFIX = ".jsonl";
223
269
  const ARCHIVE_TOKEN_ADOPTION_PENDING_FILE = "archive-token-adoption.pending";
224
270
  const archiveTokenAdoptionPendingPath = (root) => resolve(join(root, ARCHIVE_TOKEN_ADOPTION_PENDING_FILE));
225
- function archiveTokenAdoptionPendingPresent(root) {
271
+ export function archiveTokenAdoptionPendingPresent(root) {
226
272
  try {
227
273
  lstatSync(archiveTokenAdoptionPendingPath(root));
228
274
  return true;
@@ -258,7 +304,7 @@ function dischargeArchiveTokenAdoptionPending(root) {
258
304
  if (!IS_WIN32)
259
305
  fsyncPaths(dirname(path));
260
306
  }
261
- function archiveIdsInSync(root) {
307
+ export function archiveIdsInSync(root) {
262
308
  let names;
263
309
  try {
264
310
  names = readdirSync(join(root, ARCHIVE_DIR));
@@ -270,54 +316,78 @@ function archiveIdsInSync(root) {
270
316
  }
271
317
  return names.filter((name) => name.endsWith(ARCHIVE_SUFFIX)).map((name) => name.slice(0, -ARCHIVE_SUFFIX.length));
272
318
  }
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);
319
+ function strictSidecarSite(root, entry) {
320
+ return { path: resolve(join(root, entry.file)), label: `${entry.file} (the local ${entry.which} sidecar)` };
321
+ }
322
+ export function readStrictSidecarTextsSync(root) {
323
+ return STRICT_SIDECARS.map((entry) => readDataRootFileTextSync(strictSidecarSite(root, entry).path));
324
+ }
325
+ function decodeStrictSidecar(root, entry, raw) {
326
+ const { path, label } = strictSidecarSite(root, entry);
327
+ if (raw.state === "absent")
328
+ return raw;
277
329
  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
- }
330
+ return { state: "unreadable", finding: dataRootMismatchOf(path, label, raw.fault) };
331
+ try {
332
+ return { state: "read", entries: decodeJsonColumn(raw.text, readSessionMetaSidecarInWindow, label) };
287
333
  }
288
- return archiveIdsInSync(root)
289
- .filter((id) => !Object.hasOwn(cells, id))
290
- .sort();
334
+ catch (e) {
335
+ return { state: "unreadable", finding: dataRootMismatchOf(path, label, e) };
336
+ }
337
+ }
338
+ export function readSessionMetaCellsSync(root) {
339
+ return decodeStrictSidecar(root, STRICT_SIDECARS[0], readDataRootFileTextSync(strictSidecarSite(root, STRICT_SIDECARS[0]).path));
291
340
  }
292
- export function holdArchiveTokenAdoptionDuty(root, priorHighWater, adoptionCarried) {
341
+ export function judgeStrictSidecars(root, texts) {
342
+ const findings = [];
343
+ let sessionMeta = {};
344
+ STRICT_SIDECARS.forEach((entry, i) => {
345
+ const reading = decodeStrictSidecar(root, entry, texts[i] ?? { state: "absent" });
346
+ if (reading.state === "unreadable")
347
+ findings.push(reading.finding);
348
+ else if (reading.state === "read")
349
+ sessionMeta = reading.entries;
350
+ });
351
+ if (findings.length > 0)
352
+ throw new LocalDataRootSchemaMismatchError(findings, "boot");
353
+ return sessionMeta;
354
+ }
355
+ function cellLessArchiveIds(archiveIds, cells) {
356
+ return archiveIds.filter((id) => !Object.hasOwn(cells, id)).sort();
357
+ }
358
+ export function judgeArchiveTokenAdoptionDuty(root, priorHighWater, adoptionCarried, observed) {
293
359
  const since = releaseTriple(ARCHIVE_TOKEN_ADOPTION_SINCE);
294
360
  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);
361
+ if (adoptionCarried)
362
+ return neverOpenedByAdoptingRelease && observed.archiveIds.length > 0 ? "record" : "none";
363
+ const owed = observed.adoptionPending;
301
364
  if (!neverOpenedByAdoptingRelease && !owed)
302
- return;
303
- const cellLess = archiveIdsWithoutCellSync(root);
304
- if (cellLess.length === 0) {
305
- if (owed)
306
- dischargeArchiveTokenAdoptionPending(root);
307
- return;
308
- }
365
+ return "none";
366
+ const cellLess = cellLessArchiveIds(observed.archiveIds, observed.cells);
367
+ if (cellLess.length === 0)
368
+ return owed ? "discharge" : "none";
309
369
  throw new LocalDataRootSchemaMismatchError([
310
370
  {
311
371
  path: resolve(join(root, ARCHIVE_DIR)),
312
372
  label: `${ARCHIVE_DIR} (the local session archives)`,
313
373
  problem: "skipped-required-migration",
314
374
  unknownFields: [],
315
- skippedMigration: { highWater: priorHighWater === undefined ? null : releaseText(priorHighWater), requiredSince: ARCHIVE_TOKEN_ADOPTION_SINCE, sessions: cellLess.length },
375
+ skippedMigration: { residue: "archives-without-cell", highWater: priorHighWater === undefined ? null : releaseText(priorHighWater), requiredSince: ARCHIVE_TOKEN_ADOPTION_SINCE, sessions: cellLess.length },
316
376
  },
317
377
  ], "boot");
318
378
  }
379
+ export function settleArchiveTokenAdoptionDuty(physicalRoot, priorHighWater, action) {
380
+ switch (action) {
381
+ case "none":
382
+ return;
383
+ case "record":
384
+ return recordArchiveTokenAdoptionPending(physicalRoot, priorHighWater);
385
+ case "discharge":
386
+ return dischargeArchiveTokenAdoptionPending(physicalRoot);
387
+ }
388
+ }
319
389
  function firstCellIncarnationOf(sessionId, archiveToken) {
320
- const token = archiveTokenAdoptionCarried() ? incarnationFromCell(sessionId, archiveToken, "absent") : undefined;
390
+ const token = migrationCarried("M47") ? incarnationFromCell(sessionId, archiveToken, "absent") : undefined;
321
391
  return token !== undefined ? { incarnation: token, adopted: true } : { incarnation: mintSessionIncarnation(), adopted: false };
322
392
  }
323
393
  function cellChangedUnderLock(sessionId) {
@@ -423,20 +493,6 @@ class SidecarMap {
423
493
  }
424
494
  return st.loaded;
425
495
  }
426
- async reconcile() {
427
- const path = this.path;
428
- if (!path)
429
- return;
430
- const st = this.state;
431
- if (st.refused !== undefined)
432
- throw st.refused;
433
- if (!st.loaded)
434
- return this.ensureLoaded();
435
- await st.loaded;
436
- const disk = await this.readDisk(path);
437
- if (disk.state === "unreadable")
438
- throw this.refuse(path, disk);
439
- }
440
496
  async landed(path, changed, draft) {
441
497
  const disk = await this.readDisk(path);
442
498
  switch (disk.state) {
@@ -656,7 +712,6 @@ export class LocalSessionStore {
656
712
  instructionSources;
657
713
  workspaces;
658
714
  meta;
659
- strictSidecars = [];
660
715
  log;
661
716
  sessionWriteLocks;
662
717
  executionPlacement;
@@ -698,17 +753,15 @@ export class LocalSessionStore {
698
753
  }
699
754
  this.durability = opts.durability;
700
755
  }
701
- const sidecar = (file, which, read, strict = false) => {
702
- const map = new SidecarMap(dataDir ? resolve(join(dataDir, file)) : undefined, `${file} (the local ${which} sidecar)`, read, (path, e) => this.refuseSidecar(path, `LocalSessionStore ${which} sidecar`, e, which), (e) => this.warnSidecar("write", e, which), strict);
703
- if (strict)
704
- this.strictSidecars.push(map);
705
- return map;
756
+ const sidecar = (file, which, read) => {
757
+ const strict = STRICT_SIDECAR_FILES.has(file);
758
+ return new SidecarMap(dataDir ? resolve(join(dataDir, file)) : undefined, `${file} (the local ${which} sidecar)`, read, (path, e) => this.refuseSidecar(path, `LocalSessionStore ${which} sidecar`, e, which), (e) => this.warnSidecar("write", e, which), strict);
706
759
  };
707
760
  this.titles = sidecar("session-titles.json", "title", readStringRecordColumn);
708
761
  this.pinned = sidecar("session-pinned.json", "pinned", readPinnedSidecar);
709
762
  this.instructionSources = sidecar("session-instruction-sources.json", "instruction-source", readStringRecordColumn);
710
763
  this.workspaces = sidecar("session-workspaces.json", "workspace", readStringRecordColumn);
711
- this.meta = sidecar(SESSION_META_SIDECAR_FILE, "session-meta", readSessionMetaSidecar, true);
764
+ this.meta = sidecar(SESSION_META_SIDECAR_FILE, "session-meta", readSessionMetaSidecar);
712
765
  const root = dataDir ? resolve(dataDir) : undefined;
713
766
  this.dataRoot = root;
714
767
  if (root !== undefined && !SESSION_WRITE_LOCKS_BY_ROOT.has(root))
@@ -738,16 +791,7 @@ export class LocalSessionStore {
738
791
  const disk = await readSessionMetaForM40(path);
739
792
  if (disk.state !== "read")
740
793
  return 0;
741
- let renamed = 0;
742
- for (const cell of Object.values(disk.entries)) {
743
- if (!isJsonObject(cell))
744
- continue;
745
- const execution = cell["execution"];
746
- if (!isJsonObject(execution) || execution["role"] !== "writer" || execution["lane"] !== RETIRED_EXECUTION_LANE_WORD_M40)
747
- continue;
748
- execution["lane"] = RENAMED_EXECUTION_LANE_M40;
749
- renamed += 1;
750
- }
794
+ const renamed = renameRetiredExecutionLaneWordsInCellsM40(disk.entries);
751
795
  if (renamed === 0)
752
796
  return 0;
753
797
  try {
@@ -758,19 +802,6 @@ export class LocalSessionStore {
758
802
  }
759
803
  return renamed;
760
804
  }
761
- async assertStrictSidecarsReadable() {
762
- const loads = await Promise.allSettled(this.strictSidecars.map((map) => map.reconcile()));
763
- const findings = [];
764
- for (const load of loads) {
765
- if (load.status === "fulfilled")
766
- continue;
767
- if (!(load.reason instanceof LocalDataRootSchemaMismatchError))
768
- throw load.reason;
769
- findings.push(...load.reason.findings);
770
- }
771
- if (findings.length > 0)
772
- throw new LocalDataRootSchemaMismatchError(findings, "boot");
773
- }
774
805
  async forkingOf(sessionId, meta) {
775
806
  if (!isForkCreatedCell(meta))
776
807
  return false;
@@ -1027,7 +1058,7 @@ export class LocalSessionStore {
1027
1058
  return meta;
1028
1059
  }
1029
1060
  async firstCellIncarnationHeld(sessionId) {
1030
- const head = archiveTokenAdoptionCarried() ? await this.archiveHeadOf(sessionId) : undefined;
1061
+ const head = migrationCarried("M47") ? await this.archiveHeadOf(sessionId) : undefined;
1031
1062
  return firstCellIncarnationOf(sessionId, head?.incarnation).incarnation;
1032
1063
  }
1033
1064
  async headOf(sessionId) {
@@ -2040,20 +2071,25 @@ export class LocalSessionStore {
2040
2071
  await this.meta.ensureLoaded();
2041
2072
  let cleared = 0;
2042
2073
  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
- }
2074
+ const hits = shellSentinelCellIdsM48(draft);
2075
+ for (const sessionId of hits)
2076
+ draft.set(sessionId, { ...cellObjectOf(draft.get(sessionId)), owner: null });
2077
+ cleared = hits.length;
2051
2078
  return cleared > 0;
2052
2079
  }, "best-effort");
2053
2080
  if (!committed)
2054
2081
  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
2082
  return cleared;
2056
2083
  }
2084
+ async countShellSentinelOwnersM48() {
2085
+ await this.meta.ensureLoaded();
2086
+ let count = 0;
2087
+ await this.meta.commit((draft) => {
2088
+ count = shellSentinelCellIdsM48(draft).length;
2089
+ return false;
2090
+ }, "best-effort");
2091
+ return count;
2092
+ }
2057
2093
  async adoptArchiveTokensM47() {
2058
2094
  const root = this.dataRoot;
2059
2095
  if (root === undefined)
@@ -2087,8 +2123,13 @@ export class LocalSessionStore {
2087
2123
  return { adopted, minted };
2088
2124
  }
2089
2125
  async assertArchiveTokensAdoptedM47(root) {
2126
+ const reading = readSessionMetaCellsSync(root);
2127
+ if (reading.state === "unreadable") {
2128
+ throw new SessionError("storage", `LocalSessionStore: the one-time migration M47 cannot re-read ${reading.finding.label} to prove the adoption finished (${reading.finding.problem}) — refusing to start. ` +
2129
+ `${ARCHIVE_TOKEN_ADOPTION_PENDING_FILE} stays in the data root; the next start of a release carrying this migration tries again.`);
2130
+ }
2090
2131
  let left = 0;
2091
- for (const sessionId of archiveIdsWithoutCellSync(root))
2132
+ for (const sessionId of cellLessArchiveIds(archiveIdsInSync(root), reading.state === "read" ? reading.entries : {}))
2092
2133
  if ((await this.adoptionScopeOf(sessionId)) === "in")
2093
2134
  left += 1;
2094
2135
  if (left > 0) {
@@ -66,11 +66,15 @@ export declare class MemoryRunStore implements InProcessClaimFence {
66
66
  withdrawRunsOf(sessionId: string, ownTaskId: string | undefined): "proceed" | {
67
67
  readonly keep: "runs";
68
68
  };
69
+ /** 撤回本请求铸的这一条 run(SQL / 文件孪生同判,律见 SQL 孪生头注):行上的持有腿不是 `legId` ⇒ 不是本腿的行,一个字节不动;是 ⇒ 行仍在跑 ⇒ 删事件、幂等键、行,放本行持有的认领;已终局 ⇒ 行零删、只放它上面的提交幂等键;停驻 ⇒ 一个字节不动。交回删没删。 */
70
+ withdrawMintedRun(taskId: string, legId: string): Promise<boolean>;
69
71
  /** Undo THIS submission's binding (key + body fingerprint → null) — called only where the submit surface refuses the submission
70
72
  * before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, an execution-placement reject):
71
73
  * the error answer invites a retry, and a retry with the same key must run, not replay the refusal. The row stays (it
72
74
  * is the refusal's record); only the binding is freed. */
73
75
  releaseIdemKey(taskId: string): Promise<void>;
76
+ /** 放一条行上的提交幂等键绑定(`releaseIdemKey` 与 `withdrawMintedRun` 的非在跑臂同读)。 */
77
+ private dropIdemBinding;
74
78
  /** The body fingerprint this row's binding of `idem.key` holds — binding `idem.bodyFingerprint` first when it holds none (first
75
79
  * arrival binds, everyone after reads; the twin of the SQL conditional UPDATE). `null` = no such row, or the row is not bound to
76
80
  * this key (never written). */
@@ -95,15 +99,16 @@ export declare class MemoryRunStore implements InProcessClaimFence {
95
99
  /** The terminal write's one synchronous section (SQL twin: one transaction): the k8s space's pending commit row (when
96
100
  * offered) → the terminal row → the active-run slot release. Returns whether the pending row is now this run's. */
97
101
  private writeTerminal;
98
- /** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS on running/suspended (reaper-revert guard).
99
- * [ref]:同时落 `result` —— 封口脱敏后的投影(`sealRunRowResult`,凭据不落;`null` = 没观察到 park 结果),SQL 孪生同注。 */
100
- setSuspended(taskId: string, result: PersistedTaskResult | null): Promise<void>;
101
- /** [ref] D-B: park NON-terminal `needs_review`, KEEP the claim. CAS on running/needs_review.
102
- * [ref]:同 {@link setSuspended} 落封口脱敏后的 `result`。 */
103
- setNeedsReview(taskId: string, result: PersistedTaskResult | null): Promise<void>;
104
- /** Durable F4 resume: flip suspended/needs_review → running (CAS) + reset cancel/preempt flags.
105
- * [ref](codex R1 [medium]):认领 = 接管 `instanceId`(= 当前持有副本;SQL 孪生同注)。CAS 输不改。 */
106
- markResuming(taskId: string, instanceId: string): Promise<boolean>;
102
+ /** Durable F4: park NON-terminal `suspended`, KEEP the task_active claim. CAS = 行仍归 `legId` ∧ running / suspended ∧ 没挂取消请求(三件的理由见 SQL 孪生)。
103
+ * [ref]:同时落 `result` —— 封口脱敏后的投影(`sealRunRowResult`,凭据不落;`null` = 没观察到 park 结果),SQL 孪生同注。
104
+ * 交回 CAS 的答:`true` ⇔ 这一写之后行停在这个词上;`false` ⇔ 三件有一件不成立(或行不在)—— 写不动它。判与写同一个同步段。 */
105
+ setSuspended(taskId: string, result: PersistedTaskResult | null, legId: string): Promise<boolean>;
106
+ /** [ref] D-B: park NON-terminal `needs_review`, KEEP the claim. CAS = {@link setSuspended} 的三件(词换成 needs_review)。
107
+ * [ref]:同 {@link setSuspended} 落封口脱敏后的 `result`;CAS 的答同 {@link setSuspended}。 */
108
+ setNeedsReview(taskId: string, result: PersistedTaskResult | null, legId: string): Promise<boolean>;
109
+ /** Durable F4 resume: flip suspended/needs_review → running (CAS);认领 = 换持有腿(铸新的 `legId` 交回)+ 两面请求旗随旧腿清零(SQL 孪生同注)。
110
+ * [ref](codex R1 [medium]):认领 = 接管 `instanceId`(= 当前持有副本;SQL 孪生同注)。CAS 输不改、交回 `undefined`。 */
111
+ markResuming(taskId: string, instanceId: string): Promise<string | undefined>;
107
112
  getActiveTaskId(sessionId: string): Promise<string | undefined>;
108
113
  /** 认领当写栅栏用(契约 = `store-contracts.ts` 的 `InProcessClaimFence`):判、刷新活性与 `write` 是同一个同步段。 */
109
114
  whileHoldingClaim(sessionId: string, taskId: string, write: (run: FencedRun) => FenceLanded): boolean;
@@ -1,7 +1,7 @@
1
1
  import { redactErrorMessage, redactLedgerEventData, runRowErrorCodeOf, sealRunRowResult } from "../observability/run-terminal.js";
2
2
  import { outwardPlane } from "../terminal.js";
3
3
  import { projectUsageStats, USAGE_SCAN_LIMIT } from "../usage-analytics.js";
4
- import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, RUN_STALL_DEFAULT_WIRING, isTerminalRunStatus, runLivenessOf, settledLegObservationOf } from "./store-contracts.js";
4
+ import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, RUN_STALL_DEFAULT_WIRING, isTerminalRunStatus, mintRunLegId, runLivenessOf, settledLegObservationOf } from "./store-contracts.js";
5
5
  import { buildSessionRunRollups } from "./session-run-rollup.js";
6
6
  import { notifyRunTerminal } from "../observability/run-terminal.js";
7
7
  import { createInProcessSpaceCommitTable, fenceSpaceCommitAtTerminal } from "../workspace-space-commit.js";
@@ -52,6 +52,7 @@ export class MemoryRunStore {
52
52
  source: r.source,
53
53
  identity: r.identity,
54
54
  instanceId: r.instanceId,
55
+ legId: r.legId,
55
56
  cancelRequested: r.cancelRequested,
56
57
  objectivePreview: r.objectivePreview,
57
58
  createdAt: r.createdAt.toISOString(),
@@ -74,6 +75,7 @@ export class MemoryRunStore {
74
75
  if (claimed !== undefined && !this.settleDueHolder(claimed))
75
76
  return { ok: false, activeTaskId: claimed };
76
77
  const now = new Date();
78
+ const legId = mintRunLegId();
77
79
  this.active.set(sessionId, taskId);
78
80
  this.runs.set(taskId, {
79
81
  taskId,
@@ -86,6 +88,7 @@ export class MemoryRunStore {
86
88
  jobId: meta.jobId ?? null,
87
89
  source: meta.source ?? null,
88
90
  instanceId,
91
+ legId,
89
92
  objectivePreview: meta.objectivePreview ?? null,
90
93
  idemKey,
91
94
  idemBodyFingerprint: meta.idem?.bodyFingerprint ?? null,
@@ -97,7 +100,7 @@ export class MemoryRunStore {
97
100
  });
98
101
  if (idemKey !== null)
99
102
  this.byIdem.set(idemKey, taskId);
100
- return { ok: true };
103
+ return { ok: true, legId };
101
104
  }
102
105
  withdrawRunsOf(sessionId, ownTaskId) {
103
106
  for (const r of this.runs.values())
@@ -114,14 +117,33 @@ export class MemoryRunStore {
114
117
  this.runs.delete(own.taskId);
115
118
  return "proceed";
116
119
  }
120
+ async withdrawMintedRun(taskId, legId) {
121
+ const r = this.runs.get(taskId);
122
+ if (r === undefined || r.legId !== legId)
123
+ return false;
124
+ if (r.status !== "running") {
125
+ if (isTerminalRunStatus(r.status))
126
+ this.dropIdemBinding(r);
127
+ return false;
128
+ }
129
+ this.events.delete(taskId);
130
+ if (r.idemKey !== null && this.byIdem.get(r.idemKey) === taskId)
131
+ this.byIdem.delete(r.idemKey);
132
+ this.runs.delete(taskId);
133
+ if (this.active.get(r.sessionId) === taskId)
134
+ this.active.delete(r.sessionId);
135
+ return true;
136
+ }
117
137
  async releaseIdemKey(taskId) {
118
138
  const r = this.runs.get(taskId);
119
- if (!r)
120
- return;
139
+ if (r)
140
+ this.dropIdemBinding(r);
141
+ }
142
+ dropIdemBinding(r) {
121
143
  r.idemBodyFingerprint = null;
122
144
  if (r.idemKey === null)
123
145
  return;
124
- if (this.byIdem.get(r.idemKey) === taskId)
146
+ if (this.byIdem.get(r.idemKey) === r.taskId)
125
147
  this.byIdem.delete(r.idemKey);
126
148
  r.idemKey = null;
127
149
  }
@@ -235,38 +257,42 @@ export class MemoryRunStore {
235
257
  notifyRunTerminal(pending);
236
258
  return fenced;
237
259
  }
238
- async setSuspended(taskId, result) {
260
+ async setSuspended(taskId, result, legId) {
239
261
  const sealed = sealRunRowResult(result);
240
262
  const r = this.runs.get(taskId);
241
- if (r && (r.status === "running" || r.status === "suspended")) {
242
- r.status = "suspended";
243
- r.result = sealed;
244
- r.errorCode = runRowErrorCodeOf(sealed);
245
- r.updatedAt = new Date();
246
- }
263
+ if (!r || r.legId !== legId || (r.status !== "running" && r.status !== "suspended") || r.cancelRequested)
264
+ return false;
265
+ r.status = "suspended";
266
+ r.result = sealed;
267
+ r.errorCode = runRowErrorCodeOf(sealed);
268
+ r.updatedAt = new Date();
269
+ return true;
247
270
  }
248
- async setNeedsReview(taskId, result) {
271
+ async setNeedsReview(taskId, result, legId) {
249
272
  const sealed = sealRunRowResult(result);
250
273
  const r = this.runs.get(taskId);
251
- if (r && (r.status === "running" || r.status === "needs_review")) {
252
- r.status = "needs_review";
253
- r.result = sealed;
254
- r.errorCode = runRowErrorCodeOf(sealed);
255
- r.updatedAt = new Date();
256
- }
274
+ if (!r || r.legId !== legId || (r.status !== "running" && r.status !== "needs_review") || r.cancelRequested)
275
+ return false;
276
+ r.status = "needs_review";
277
+ r.result = sealed;
278
+ r.errorCode = runRowErrorCodeOf(sealed);
279
+ r.updatedAt = new Date();
280
+ return true;
257
281
  }
258
282
  async markResuming(taskId, instanceId) {
259
283
  const r = this.runs.get(taskId);
260
284
  if (!r || (r.status !== "suspended" && r.status !== "needs_review"))
261
- return false;
285
+ return undefined;
286
+ const legId = mintRunLegId();
262
287
  r.status = "running";
263
288
  r.result = null;
264
289
  r.errorCode = null;
265
290
  r.instanceId = instanceId;
291
+ r.legId = legId;
266
292
  r.cancelRequested = false;
267
293
  r.preemptRequested = false;
268
294
  r.updatedAt = new Date();
269
- return true;
295
+ return legId;
270
296
  }
271
297
  async getActiveTaskId(sessionId) {
272
298
  return this.active.get(sessionId);
@@ -276,7 +302,7 @@ export class MemoryRunStore {
276
302
  if (this.active.get(sessionId) !== taskId || r === undefined || r.status !== "running")
277
303
  return false;
278
304
  r.updatedAt = new Date();
279
- write({ instanceId: r.instanceId });
305
+ write({ instanceId: r.instanceId, legId: r.legId });
280
306
  return true;
281
307
  }
282
308
  async getRun(taskId) {
@@ -1,10 +1,26 @@
1
1
  /**
2
- * **一次性迁移的标记表**(SQL 两方言)—— 需要「这一库上这一条已经判过 / 落过」这一事实的迁移各在这里记一行(键 = 登记表里的那一条名,`plugins/one-time-migrations.ts`)。
3
- * 表由用到它的那一步自己建(不进中央建表语句:它随最后一条读它的迁移删码,库里留下的空表无人读,起服列册对账只核本树声明的表)。最后一条读它的迁移删码时,本文件一起删。
2
+ * **标记表**(SQL 两方言)—— 一库一行一事实,键 = 落它的那一条一次性迁移的名(`plugins/one-time-migrations.ts` 登记表)。两类行:
3
+ * · **一次性的标记**:需要「这一库上这一条已经判过 / 落过」的迁移各记一行(M42 / M43 / M51),随那一条删码后无人再读(行留着无害);
4
+ * · **常设的一行 —— `Skill` 豁免截点**({@link SKILL_EXEMPTION_CUTOFF_ID}):T0 = 一只按今天的意思读会话豁免行的构建第一次接管这只库的时刻。
5
+ * 首次放行早于它的引擎家 `Skill` 豁免行是更早的意思写下的(批准 = 激活某技能),读者 = 跳版拒启臂 `approval-exemption-skill-cutoff.ts`(无窗)与一次性迁移 M46(窗内删那些行)。
6
+ * 表是常设的:中央建表语句建它(`tidb-pool.ts` / `pg-pool.ts`),`ensureSchema()` 在建表之后落截点那一行(只在缺席时落 —— 「此刻这只库由知道今天读法的构建接管」)。
4
7
  */
5
8
  import type { SqlDialect } from "./sql-driver.js";
6
9
  /** 标记表名。 */
7
10
  export declare const MIGRATION_MARKER_TABLE = "one_time_migration_marker";
8
- /** 标记表的建表句(两方言;幂等)。 */
11
+ /**
12
+ * 常设的那一行的键:`Skill` 豁免截点 T0(`marked_at_ms` = T0 的 epoch 毫秒)。键沿用第一个落它的一次性迁移 M46 的名 —— 7.148.0 起的库里已经是这一行,
13
+ * 改名就要再迁一次;M46 删码之后这一行照旧是截点的家。
14
+ */
15
+ export declare const SKILL_EXEMPTION_CUTOFF_ID = "M46";
16
+ /**
17
+ * 标记表的建表句(两方言;幂等)。中央建表语句与一次性迁移的步骤读同一只。键列与中央各表同形钉二进制排序(MySQL 协议表级 `COLLATE utf8mb4_bin`、PG 列级 `"C"`),
18
+ * 不继承库的缺省排序;7.148.0 由迁移建下的那一张(MySQL 协议上没钉表级排序)`IF NOT EXISTS` 不动它 —— 键是固定的 ASCII 迁移名,列册对账不看排序规则。
19
+ */
9
20
  export declare function migrationMarkerDdl(dialect: SqlDialect): string;
21
+ /**
22
+ * 落一行标记、只在缺席时落(两方言;参数 `[键, 毫秒]`;已在 ⇒ 零改动,先落的那一只赢 —— 并发起服收敛到同一个值)。本表唯一的写句:
23
+ * `ensureSchema()` 的截点种子行与一次性迁移的各步同读这一只。
24
+ */
25
+ export declare function markIfAbsentSql(dialect: SqlDialect): string;
10
26
  //# sourceMappingURL=one-time-migration-marker.d.ts.map