@sema-agent/server 7.132.0 → 7.133.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 (402) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +8 -9
  3. package/README.zh-CN.md +7 -6
  4. package/USAGE.md +174 -55
  5. package/deploy/sema-up/README.md +19 -1
  6. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
  7. package/deploy/sema-up/chart/templates/server.yaml +5 -2
  8. package/deploy/sema-up/stack/compose.yaml +3 -0
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  10. package/dist/adoption/plan.d.ts +3 -2
  11. package/dist/adoption/plan.js +7 -5
  12. package/dist/adoption/runner.js +3 -10
  13. package/dist/adoption/sql.d.ts +3 -2
  14. package/dist/adoption/sql.js +3 -5
  15. package/dist/adoption/wire.d.ts +4 -1
  16. package/dist/adoption/wire.js +1 -0
  17. package/dist/approval-ask-audit-store.d.ts +31 -13
  18. package/dist/approval-ask-audit-store.js +272 -74
  19. package/dist/approval-ask-machine.d.ts +15 -0
  20. package/dist/approval-ask-machine.js +6 -0
  21. package/dist/approval-ask-retention.d.ts +136 -0
  22. package/dist/approval-ask-retention.js +102 -0
  23. package/dist/approval-card.d.ts +79 -19
  24. package/dist/approval-card.js +44 -13
  25. package/dist/approval-deny-reasons.d.ts +1 -1
  26. package/dist/approval.d.ts +9 -0
  27. package/dist/approval.js +24 -3
  28. package/dist/audit.d.ts +27 -10
  29. package/dist/audit.js +12 -16
  30. package/dist/bake-runner/main.js +3 -2
  31. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  32. package/dist/boot/execution-env.js +9 -7
  33. package/dist/boot/execution-lane.js +11 -0
  34. package/dist/boot/leader.js +2 -0
  35. package/dist/boot/memory-boundary.js +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +5 -4
  37. package/dist/boot/memory-consolidation.js +7 -6
  38. package/dist/boot/org-delivery.d.ts +28 -0
  39. package/dist/boot/org-delivery.js +20 -0
  40. package/dist/boot/reapers.d.ts +3 -0
  41. package/dist/boot/reapers.js +21 -1
  42. package/dist/boot/resolve-spec.d.ts +3 -0
  43. package/dist/boot/resolve-spec.js +60 -81
  44. package/dist/boot/runner-deps.js +1 -0
  45. package/dist/boot/session-faces.d.ts +1 -0
  46. package/dist/boot/session-faces.js +4 -2
  47. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  48. package/dist/boot/stage-06-runners.d.ts +2 -0
  49. package/dist/boot/stage-06-runners.js +6 -3
  50. package/dist/boot/stage-07-capability-layer.d.ts +2 -0
  51. package/dist/boot/stage-07-capability-layer.js +24 -3
  52. package/dist/boot/stage-08-reapers.d.ts +2 -0
  53. package/dist/boot/stage-08-reapers.js +2 -2
  54. package/dist/boot/stage-09-leader.d.ts +3 -0
  55. package/dist/boot/stage-10-http-server.d.ts +3 -0
  56. package/dist/boot/stage-10-http-server.js +5 -6
  57. package/dist/boot/stores.js +2 -2
  58. package/dist/boot/web-admin.d.ts +4 -0
  59. package/dist/boot/web-admin.js +151 -63
  60. package/dist/boot/workflow-orchestration.d.ts +2 -0
  61. package/dist/boot/workflow-orchestration.js +2 -1
  62. package/dist/brain.js +8 -3
  63. package/dist/breaker-key.d.ts +6 -0
  64. package/dist/breaker-key.js +29 -0
  65. package/dist/caller-text-echo.d.ts +9 -6
  66. package/dist/caller-text-echo.js +8 -11
  67. package/dist/capabilities/center-plugins.d.ts +7 -0
  68. package/dist/capabilities/center-plugins.js +1 -1
  69. package/dist/capabilities/composite-members.d.ts +1 -1
  70. package/dist/capabilities/composite-members.js +1 -1
  71. package/dist/capabilities/execution-lane.js +2 -2
  72. package/dist/capabilities/hands-lane.d.ts +21 -0
  73. package/dist/capabilities/hands-lane.js +10 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -4
  75. package/dist/capabilities/mcp-probe.js +27 -4
  76. package/dist/capabilities/scenarios.d.ts +63 -7
  77. package/dist/capabilities/scenarios.js +39 -7
  78. package/dist/capability-facts.d.ts +34 -4
  79. package/dist/capability-facts.js +28 -2
  80. package/dist/center-config-tier.d.ts +69 -0
  81. package/dist/center-config-tier.js +42 -0
  82. package/dist/checkpoint-identity.d.ts +16 -0
  83. package/dist/checkpoint-identity.js +19 -0
  84. package/dist/config-catalog.d.ts +17 -10
  85. package/dist/config-catalog.js +43 -28
  86. package/dist/config-d-migration.d.ts +3 -1
  87. package/dist/config-d-migration.js +2 -2
  88. package/dist/config-dir-trust.d.ts +114 -0
  89. package/dist/config-dir-trust.js +388 -0
  90. package/dist/config-invariants.d.ts +18 -5
  91. package/dist/config-invariants.js +35 -3
  92. package/dist/config-provider.d.ts +6 -1
  93. package/dist/config-provider.js +6 -2
  94. package/dist/config-types.d.ts +71 -14
  95. package/dist/config-types.js +8 -0
  96. package/dist/config.d.ts +13 -0
  97. package/dist/config.js +121 -59
  98. package/dist/delegated-session-owner.d.ts +185 -0
  99. package/dist/delegated-session-owner.js +119 -0
  100. package/dist/device-protocol.d.ts +5 -3
  101. package/dist/device-protocol.js +1 -1
  102. package/dist/device-ws-hub.js +21 -2
  103. package/dist/device-ws-protocol.d.ts +70 -6
  104. package/dist/device-ws-protocol.js +50 -23
  105. package/dist/elicitation.js +3 -2
  106. package/dist/entry-problems.d.ts +34 -0
  107. package/dist/entry-problems.js +14 -0
  108. package/dist/execution-lane-caps.d.ts +49 -10
  109. package/dist/execution-lane-caps.js +43 -4
  110. package/dist/field-conflict.d.ts +43 -0
  111. package/dist/field-conflict.js +20 -0
  112. package/dist/fleet/fleet-bus.d.ts +80 -6
  113. package/dist/fleet/fleet-bus.js +41 -9
  114. package/dist/hooks/classic-stations.d.ts +10 -5
  115. package/dist/hooks/classic-stations.js +4 -4
  116. package/dist/hooks/command-spawn.d.ts +57 -0
  117. package/dist/hooks/command-spawn.js +139 -0
  118. package/dist/hooks/hook-llm.js +2 -2
  119. package/dist/hooks/hook-runner.d.ts +127 -25
  120. package/dist/hooks/hook-runner.js +668 -287
  121. package/dist/hooks/hook-source.d.ts +44 -0
  122. package/dist/hooks/hook-source.js +40 -0
  123. package/dist/http/active-run-conflict.d.ts +5 -13
  124. package/dist/http/active-run-conflict.js +17 -27
  125. package/dist/http/admission.js +103 -60
  126. package/dist/http/approval-sweeps.js +55 -18
  127. package/dist/http/caller-trust.d.ts +77 -0
  128. package/dist/http/caller-trust.js +143 -0
  129. package/dist/http/checkpoint-ctx.d.ts +16 -16
  130. package/dist/http/checkpoint-ctx.js +13 -10
  131. package/dist/http/claimed-run.d.ts +4 -3
  132. package/dist/http/device-placement.d.ts +2 -4
  133. package/dist/http/device-placement.js +2 -1
  134. package/dist/http/dispatch.js +10 -4
  135. package/dist/http/durable-run-leg.d.ts +4 -32
  136. package/dist/http/durable-run-leg.js +34 -10
  137. package/dist/http/owner-access.d.ts +56 -0
  138. package/dist/http/owner-access.js +42 -0
  139. package/dist/http/principal-gate.d.ts +48 -23
  140. package/dist/http/principal-gate.js +39 -12
  141. package/dist/http/readiness.d.ts +11 -5
  142. package/dist/http/readiness.js +10 -3
  143. package/dist/http/request-guards.d.ts +16 -23
  144. package/dist/http/request-guards.js +30 -15
  145. package/dist/http/resume-ctx-read.d.ts +2 -5
  146. package/dist/http/resume-ctx-read.js +7 -4
  147. package/dist/http/resume-legs.d.ts +3 -4
  148. package/dist/http/resume-legs.js +126 -69
  149. package/dist/http/retired-service-deps.d.ts +43 -0
  150. package/dist/http/retired-service-deps.js +18 -0
  151. package/dist/http/route-ctx.d.ts +44 -28
  152. package/dist/http/route-ctx.js +4 -0
  153. package/dist/http/route-table.js +2 -0
  154. package/dist/http/routes/a2a-serve.js +3 -4
  155. package/dist/http/routes/admin-config-refresh.js +1 -1
  156. package/dist/http/routes/admin-drain.js +1 -1
  157. package/dist/http/routes/admin-memory-optout.js +5 -1
  158. package/dist/http/routes/adoption.js +2 -9
  159. package/dist/http/routes/approvals-assistant.js +49 -48
  160. package/dist/http/routes/background.js +3 -2
  161. package/dist/http/routes/capabilities.js +20 -4
  162. package/dist/http/routes/config-catalog.d.ts +3 -3
  163. package/dist/http/routes/config-catalog.js +1 -1
  164. package/dist/http/routes/devices.js +3 -17
  165. package/dist/http/routes/diagnostics.js +5 -1
  166. package/dist/http/routes/images.js +6 -7
  167. package/dist/http/routes/leader.js +1 -8
  168. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  169. package/dist/http/routes/mcp-reconnect.js +3 -2
  170. package/dist/http/routes/memory-bundle.js +1 -1
  171. package/dist/http/routes/memory-compliance.js +1 -1
  172. package/dist/http/routes/memory-consolidation.js +1 -1
  173. package/dist/http/routes/memory-origin.js +1 -1
  174. package/dist/http/routes/memory-policy.d.ts +1 -1
  175. package/dist/http/routes/memory-policy.js +3 -2
  176. package/dist/http/routes/notify-wake.js +8 -21
  177. package/dist/http/routes/observability.js +2 -2
  178. package/dist/http/routes/principal-inbox.d.ts +39 -0
  179. package/dist/http/routes/principal-inbox.js +76 -0
  180. package/dist/http/routes/retention-ops.js +2 -9
  181. package/dist/http/routes/rules.js +5 -18
  182. package/dist/http/routes/runs.d.ts +5 -0
  183. package/dist/http/routes/runs.js +33 -106
  184. package/dist/http/routes/session-sync.js +15 -28
  185. package/dist/http/routes/sessions.js +30 -40
  186. package/dist/http/routes/side-query.js +6 -4
  187. package/dist/http/routes/tasks.js +50 -14
  188. package/dist/http/routes/workflows.d.ts +351 -0
  189. package/dist/http/routes/workflows.js +153 -119
  190. package/dist/http/send.d.ts +24 -3
  191. package/dist/http/send.js +14 -2
  192. package/dist/http/server.d.ts +17 -18
  193. package/dist/http/server.js +15 -13
  194. package/dist/http/stored-body-replay.js +17 -4
  195. package/dist/http/submit-ctx.js +4 -3
  196. package/dist/http/submit-replay.d.ts +4 -3
  197. package/dist/http/submit-replay.js +5 -4
  198. package/dist/http/wire-types.d.ts +10 -7
  199. package/dist/index.d.ts +1 -1
  200. package/dist/index.js +0 -1
  201. package/dist/lane-home-probe.d.ts +78 -9
  202. package/dist/lane-home-probe.js +78 -12
  203. package/dist/leader/endpoint.d.ts +4 -1
  204. package/dist/leader/endpoint.js +11 -10
  205. package/dist/limit-refusal.d.ts +17 -5
  206. package/dist/limit-refusal.js +4 -1
  207. package/dist/mcp-assertion-header.d.ts +31 -10
  208. package/dist/mcp-assertion-header.js +10 -3
  209. package/dist/mcp-tool-faces.js +1 -3
  210. package/dist/memory-project-layer.d.ts +37 -0
  211. package/dist/memory-project-layer.js +46 -0
  212. package/dist/memory-scope.d.ts +23 -16
  213. package/dist/memory-scope.js +24 -8
  214. package/dist/memory-sync-client.d.ts +27 -13
  215. package/dist/memory-sync-client.js +107 -18
  216. package/dist/model-entry-refusal.d.ts +9 -1
  217. package/dist/model-entry-refusal.js +6 -1
  218. package/dist/model-plane-face.d.ts +51 -0
  219. package/dist/model-plane-face.js +24 -0
  220. package/dist/model-provider.d.ts +5 -0
  221. package/dist/model-provider.js +4 -2
  222. package/dist/model-route-endpoint.d.ts +8 -11
  223. package/dist/model-route-endpoint.js +4 -7
  224. package/dist/model-route-env.d.ts +22 -0
  225. package/dist/model-route-env.js +2 -0
  226. package/dist/model-route-gap.d.ts +60 -30
  227. package/dist/model-route-gap.js +21 -15
  228. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  229. package/dist/observability/corrupt-read-seat.js +9 -0
  230. package/dist/observability/err-text.d.ts +6 -0
  231. package/dist/observability/err-text.js +18 -4
  232. package/dist/observability/fail-open.d.ts +35 -9
  233. package/dist/observability/fail-open.js +35 -9
  234. package/dist/observability/metrics.js +1 -0
  235. package/dist/observability/prompt-manifest.d.ts +3 -17
  236. package/dist/observability/prompt-manifest.js +45 -42
  237. package/dist/observability/tool-trace.js +9 -9
  238. package/dist/operator-ask.d.ts +10 -0
  239. package/dist/operator-ask.js +3 -0
  240. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  241. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  242. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  243. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  244. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  245. package/dist/orchestration/workflow-notify-journal.js +27 -34
  246. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  247. package/dist/orchestration/workflow-park-decision.js +118 -0
  248. package/dist/org-delivery-gate.d.ts +81 -0
  249. package/dist/org-delivery-gate.js +59 -0
  250. package/dist/panel-tool-names.d.ts +23 -0
  251. package/dist/panel-tool-names.js +8 -0
  252. package/dist/parked-decide.d.ts +39 -6
  253. package/dist/parked-decide.js +98 -51
  254. package/dist/parked-revive-surface.d.ts +9 -2
  255. package/dist/parked-revive-surface.js +11 -8
  256. package/dist/permission-rule-list.d.ts +1 -1
  257. package/dist/permission-rule-list.js +2 -2
  258. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  259. package/dist/plugins/approval-ask-store-file.js +21 -0
  260. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  261. package/dist/plugins/approval-ask-store-memory.js +34 -2
  262. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  263. package/dist/plugins/approval-ask-store-sql.js +37 -1
  264. package/dist/plugins/blob-backend.d.ts +38 -25
  265. package/dist/plugins/blob-backend.js +29 -19
  266. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  267. package/dist/plugins/breaker-state-sql.js +4 -4
  268. package/dist/plugins/caching-session-store.d.ts +3 -0
  269. package/dist/plugins/caching-session-store.js +3 -0
  270. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  271. package/dist/plugins/checkpoint-store-sql.js +20 -2
  272. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  273. package/dist/plugins/file-history-store-sql.js +3 -1
  274. package/dist/plugins/file-run-store.d.ts +5 -13
  275. package/dist/plugins/file-run-store.js +9 -0
  276. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  277. package/dist/plugins/fork-routing-session-store.js +12 -1
  278. package/dist/plugins/host-platform.d.ts +2 -0
  279. package/dist/plugins/host-platform.js +2 -1
  280. package/dist/plugins/local-checkpoint-store.d.ts +2 -0
  281. package/dist/plugins/local-checkpoint-store.js +6 -1
  282. package/dist/plugins/local-session-store.d.ts +1 -1
  283. package/dist/plugins/local-session-window.d.ts +39 -0
  284. package/dist/plugins/local-session-window.js +54 -0
  285. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  286. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  287. package/dist/plugins/mailbox-store-sql.js +77 -52
  288. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  289. package/dist/plugins/memory-run-store.d.ts +5 -13
  290. package/dist/plugins/memory-run-store.js +8 -0
  291. package/dist/plugins/one-time-migrations.d.ts +110 -5
  292. package/dist/plugins/one-time-migrations.js +320 -8
  293. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  294. package/dist/plugins/org-delivery-quota-store.js +228 -0
  295. package/dist/plugins/pg-pool.js +39 -7
  296. package/dist/plugins/pg-session-storage.d.ts +17 -4
  297. package/dist/plugins/pg-session-storage.js +29 -6
  298. package/dist/plugins/remote-env-adb.d.ts +6 -12
  299. package/dist/plugins/remote-env-adb.js +12 -2
  300. package/dist/plugins/remote-env-k8s.d.ts +8 -12
  301. package/dist/plugins/remote-env-k8s.js +23 -5
  302. package/dist/plugins/remote-env-local-docker.d.ts +10 -15
  303. package/dist/plugins/remote-env-local-docker.js +12 -9
  304. package/dist/plugins/remote-env-ssh.d.ts +10 -16
  305. package/dist/plugins/remote-env-ssh.js +17 -11
  306. package/dist/plugins/run-store-sql.d.ts +16 -14
  307. package/dist/plugins/run-store-sql.js +22 -2
  308. package/dist/plugins/schema-indexes.js +2 -0
  309. package/dist/plugins/send-user-file.js +2 -2
  310. package/dist/plugins/session-epoch-anchor.d.ts +32 -6
  311. package/dist/plugins/session-epoch-anchor.js +2 -3
  312. package/dist/plugins/session-store.d.ts +18 -8
  313. package/dist/plugins/session-store.js +15 -4
  314. package/dist/plugins/sql-driver.d.ts +74 -11
  315. package/dist/plugins/sql-driver.js +210 -12
  316. package/dist/plugins/sql-errors.d.ts +23 -0
  317. package/dist/plugins/sql-errors.js +21 -0
  318. package/dist/plugins/store-backend.d.ts +36 -1
  319. package/dist/plugins/store-backend.js +40 -14
  320. package/dist/plugins/store-contracts.d.ts +42 -0
  321. package/dist/plugins/store-contracts.js +1 -0
  322. package/dist/plugins/tidb-pool.d.ts +6 -1
  323. package/dist/plugins/tidb-pool.js +44 -10
  324. package/dist/plugins/tidb-session-storage.d.ts +3 -2
  325. package/dist/plugins/tidb-session-storage.js +2 -2
  326. package/dist/plugins/tidb-session-store.d.ts +24 -4
  327. package/dist/plugins/tidb-session-store.js +28 -4
  328. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  329. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  330. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  331. package/dist/plugins/workflow-run-store-sql.js +50 -9
  332. package/dist/posture-source.d.ts +18 -10
  333. package/dist/posture-source.js +4 -1
  334. package/dist/project-identity.d.ts +45 -6
  335. package/dist/project-identity.js +162 -8
  336. package/dist/project-layer-key.d.ts +28 -0
  337. package/dist/project-layer-key.js +29 -0
  338. package/dist/question.js +3 -2
  339. package/dist/read-deny-entry.d.ts +25 -0
  340. package/dist/read-deny-entry.js +12 -0
  341. package/dist/read-face-posture.d.ts +37 -11
  342. package/dist/read-face-posture.js +32 -3
  343. package/dist/request-key-closure.d.ts +7 -0
  344. package/dist/request-key-closure.js +9 -1
  345. package/dist/request-value-closure.d.ts +5 -0
  346. package/dist/request-value-closure.js +11 -3
  347. package/dist/rule-entry-list.d.ts +26 -2
  348. package/dist/rule-entry-list.js +72 -12
  349. package/dist/rules-consent.js +3 -4
  350. package/dist/run-acceptance.d.ts +19 -3
  351. package/dist/run-acceptance.js +13 -1
  352. package/dist/run-local.js +36 -6
  353. package/dist/runs.d.ts +10 -9
  354. package/dist/runs.js +21 -3
  355. package/dist/runtime-caps-resolver.d.ts +16 -6
  356. package/dist/runtime-caps-resolver.js +18 -5
  357. package/dist/sandbox-pkg-source.d.ts +19 -1
  358. package/dist/sandbox-pkg-source.js +29 -7
  359. package/dist/security.d.ts +87 -9
  360. package/dist/security.js +52 -9
  361. package/dist/session-error-wire.d.ts +19 -2
  362. package/dist/session-error-wire.js +5 -1
  363. package/dist/session-titled-frame.d.ts +33 -0
  364. package/dist/session-titled-frame.js +4 -0
  365. package/dist/session-titler.d.ts +9 -1
  366. package/dist/session-titler.js +15 -4
  367. package/dist/spec-fields.js +21 -2
  368. package/dist/sql-engine-posture.d.ts +3 -1
  369. package/dist/task-cwd.d.ts +1 -1
  370. package/dist/task-mcp.d.ts +3 -3
  371. package/dist/task-mcp.js +7 -66
  372. package/dist/task-settings.d.ts +89 -88
  373. package/dist/task-settings.js +71 -63
  374. package/dist/text-bidi.d.ts +0 -4
  375. package/dist/text-bidi.js +1 -3
  376. package/dist/tighten-only-seats.d.ts +150 -0
  377. package/dist/tighten-only-seats.js +196 -0
  378. package/dist/tool-approval.d.ts +303 -87
  379. package/dist/tool-approval.js +623 -239
  380. package/dist/trace/core-keyset-guard.d.ts +104 -22
  381. package/dist/trace/exit-seat.d.ts +28 -0
  382. package/dist/trace/exit-seat.js +76 -0
  383. package/dist/trace/ledger-events.d.ts +3 -2
  384. package/dist/trace/project.d.ts +80 -12
  385. package/dist/trace/project.js +41 -28
  386. package/dist/trace/projection-table.d.ts +100 -0
  387. package/dist/trace/projection-table.js +130 -0
  388. package/dist/trace/redact.d.ts +54 -0
  389. package/dist/trace/redact.js +778 -152
  390. package/dist/trace/self-minted-wire.d.ts +345 -1
  391. package/dist/trace/self-minted-wire.js +117 -1
  392. package/dist/trace/wire-projection-faces.d.ts +298 -2
  393. package/dist/trace/wire-projection-faces.js +154 -13
  394. package/dist/url-refusal.js +2 -0
  395. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  396. package/dist/web-admin-client/apply-effective.js +56 -5
  397. package/dist/web-admin-client/facade.d.ts +1 -1
  398. package/dist/web-admin-client/facade.js +1 -1
  399. package/dist/web-admin-client/read-face.js +5 -9
  400. package/dist/web-admin-client/skills-mcp.js +4 -5
  401. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
  402. package/package.json +7 -3
@@ -200,6 +200,62 @@
200
200
  * 4 及更早的载荷若带 prompts,那是 13.x 的两轴 wire(引擎当期不读)。`config-lkg.ts` `loadLkg` 读回 4(及迁到 4 的 2 / 3)时:载荷带 prompts ⇒ 不读回
201
201
  * (拒因点名版本与键);不带 ⇒ 载荷零改动读回,读回方按 5 写回原位,启动日志 `config_lkg_migrated` 一行。规则在 `config-lkg.ts` 顶注 formatVersion 5 一段与
202
202
  * `migrateV4Lkg`。没有读数键(它不写库)。
203
+ * · **M23 存量 placed 子代行的属主**(7.133.0)—— 7.132.x 及以前两只 SQL 店建委派子代的 placed 行时属主恒 NULL,会话面的属主谓词对 NULL 放行 ⇒ 谁拿到子代的会话 id
204
+ * (父客户端在子代帧的 `transcriptId` 上收到)谁就读得到整段转录。7.133.0 起建行那一笔按委派继承律写属主(`src/delegated-session-owner.ts`,律的唯一属主),
205
+ * 属主为 NULL 的 placed 行只对 fleet 调用方放行(无主放行臂的唯一判据 `security.ts` 的 `ownerlessRowOpen`,常设的读面律,不随本条删);存量行 ⇒ **每次起服的幂等回填**,
206
+ * 判据与建行逐字同一只(`judgeDelegatedOwner`,属主 = 判断时见证到的):根会话的行在 ⇒ 它的属主(根无主 ⇒ 子代仍无主,不写);根行不在 ⇒ 父会话的行;都不在(孤行)⇒
207
+ * 哨兵属主 `_orphan:<id>`(保留主体前缀,任何已验身份都不等于它 —— 不留 NULL,宁可没人能读),计数 + 起服一行 warn `placed_session_owners_orphaned` 点名条数。记下的根 / 父是空串 ⇒ 读作没有那一格。
208
+ * **单趟**:按 `session_id` 键集分页扫 `placement_kind IS NOT NULL ∧ owner IS NULL` 的行,每一页一条语句连读根 / 父两行(属主、住客、是不是 placed 行;连接键套
209
+ * `NULLIF(…, '')`);回写 = 认领句(`CLAIM_OWNERLESS_PLACED_SQL`:只 CAS 子行自己 —— 扫描读到的化身 + `owner IS NULL` + `placement_kind IS NOT NULL`;扫描与回写之间根 / 父被删、
210
+ * 同 id 重建、换住客,写进去的仍是扫描那一读见证到的属主;这一行自己被删、同 id 换了住客或已被别的写者写过 ⇒ 零命中,下一次起服按那时的律重判);
211
+ * 化身为空的行(子代或来源行)⇒ 响亮拒(全仓「读到空化身」同一条律)。读数三格:写成的(`placedSessionOwnersInherited` / `placedSessionOwnersOrphaned`)与
212
+ * **残余** `placedSessionOwnersRemaining` = 律这一读判不出、因为它读到的来源本身还是一只无主的委派子代行的行(根已删、父是同样待回填的行,且排在它之后或在同一页里)。
213
+ * 残余非零 ⇒ 读数行 warn;**不拒启、不重扫**:本文件的每一条在窗内每次起服都跑(「窗」一节),残余行只对 fleet 调用方放行(读面律),由之后起服的本条(一次至少再定下一层)
214
+ * 或这一行的下一次停驻(停驻挂点按同一条律认领,`delegated-session-owner.ts` 的 `settleDelegatedOwner`)定下。根无主的子代(律就让它无主)不是残余,每次起服照样被扫到、照样不写。
215
+ * **够不到的一格(如实登记)**:local 车道没有 placed 行(引擎 File 店不实现 placement,子代住私店),本条不碰 local 的会话边档;local 店也不记委派树、
216
+ * 不认领 ⇒ 经晋升腿进 local 宿主店的升级前子代行仍无主,它们再次停驻时停驻挂点照「属主不是律给的那一只 ⇒ 拒」处置(收口 = local 的 placement 协议;随包文字的补偿:
217
+ * local 带身份的部署升级前先把停驻待批的审批处理完)。窗 = 发布日 + 30 天(与 M19 / M21 同一条论证:
218
+ * 窗关时缺省部署上升级前的子代转录已被联合收割按 `BG_AGENT_RETENTION_MS` 收走;调长了留存的部署、滚动升级里旧副本在窗内新写的行如实登记,不为它们加长窗)。
219
+ * · **M24 workflow journal 的属主 scope 搬家**(7.133.0)—— 7.132.x 及以前 `workflow_journal` 的 scope 是**每一行**上的一列:读面按它判,写面(按
220
+ * `(run_id, ordinal)` 的 upsert)从不判它 ⇒ 别的 scope 的 append 落新序数插进一条 run、落既有序数覆盖属主的结果。7.133.0 起一条 run 的属主 scope 只记在
221
+ * `workflow_journal_owner` 一行(主键 `run_id`,带认领时新铸的 `incarnation`),店的写面与读面经同一只谓词判;journal 行上不再有 scope 列,改带写下它的那只
222
+ * 属主记录的 `incarnation`,且它是主键的一段:`(run_id, incarnation, ordinal)`(一行只属于写下它的那只属主记录,`workflow-journal-store-sql.ts` 顶注)。
223
+ * 主键换形 ⇒ 与 M9 同一种做法 —— **复制 + 原子换名**。存量库(正表上 scope 列在场 = 没经过本条)⇒ ① 上一次换名之前被打断留下的暂存表删掉重来(正表在换名
224
+ * 之前一个字节不动,暂存表只是它的不完整拷贝)② **回填属主**:每条还没有属主记录的 run 一行 —— scope = 该 run **最小序数**那一行的 scope(主键
225
+ * `(run_id, ordinal)` 保证恰一行;引擎按序铸序数,最小序数就是这条 run 最先记下的那一步),`incarnation` = 常量 `M24_OWNER_INCARNATION`(令牌只需在同一
226
+ * run_id 的先后几只记录之间不同:回填的是它已知的第一只,之后的认领铸随机令牌),`appended_at_ms` = 该 run 最晚的 `created_at_ms`(属主记录自己的 GC 时钟,
227
+ * 不早于它的任何一行);带 `NOT EXISTS`,续跑只补缺的 ③ 数跨 scope 行(scope ≠ 属主的行 —— 旧写面放进来的别的 scope 的写;新形里不可表示,不搬)
228
+ * ④ 照**中央 DDL 的同一条语句**换表名建暂存表 `workflow_journal_m24`,按旧主键键集分批(每批 `result` 字节有界、各自提交)搬与属主同 scope 的行(带属主记录的令牌)⑤ **原子换名**:TiDB 一条
229
+ * `RENAME TABLE 正 TO 待删, 暂存 TO 正` 后删待删旧表;PG 两条换名、删旧表与主键约束名改回常规名在同一个事务里 —— 换名提交那一刻跨 scope 行随旧表一起消失,计数进读数 +
230
+ * 起服一行 warn `workflow_journal_foreign_scope_rows_removed`。正表上 scope 列不在 ⇒ `current`(新库 / 第二跑;TiDB 上一次换名之后被打断 ⇒ 只补删待删旧表)。写它们的 scope 失去那几条 resume 重放,退化为活跑。破坏性的换名排在「破坏性步骤」一节。滚动升级:换名之后仍在跑的 ≤7.132.x 副本的 journal 语句点名
231
+ * 不存在的列、响亮失败(先停光旧副本再起新,`docs/DEPLOY-PREREQS.md` 的 7.133.0 SQL 后端一节);复制之后、换名之前旧副本写进旧表的行随旧表删掉(那几步续跑时活跑)。
232
+ * local 车道的店是 core 的 File 店(属主 = 每条 run 的 journal 文件首行)⇒ `file-store` / 0 / 0。
233
+ *
234
+ * · **M25 单用户记忆项目层换键形**(7.133.0)—— 不在店里、不在起服里(与 M10 同为**窗内才存在的行为臂**,但它是改写):项目层键从 `repo:<sha256(项目根)[:24]>`
235
+ * (7.98.0–7.132.x)换成带主体、跨设备的 `userrepo:<属主段>/<档>.<摘要>`(文法 `project-layer-key.ts`)。退役键是路径哈希、反推不出路径 ⇒ 起服没法枚举改写;
236
+ * 改为**每个项目第一次跑任务时**由项目层准备一步(`memory-project-layer.ts` `createMemoryProjectLayers`)把该 checkout 的 `repo:` 层经 core `migrateScope`
237
+ * 折进新键(File 与两只 SQL 孪生同一条;id 保留、CAS,冲突留原地下次再试)。同一窗同删的另一半:同步客户端腿的旧游标文件 `sync-cursor-<peer>.json`(只按 peer 分区)
238
+ * 第一轮收编为同步层的新游标(`memory-sync-client.ts` `adoptLegacyCursor`)。本条不写起服读数(`reportKeys` 空,与 M7 / M10 同形);折了什么由运行期 info
239
+ * `memory_project_layer_folded` / `memory_sync_cursor_adopted` 说。常设的另一条律(`r` 档时本 checkout 的 `p` 档折进当前档)不是迁移,不随本条删。
240
+ * · **M26 删掉退役的共享断路器表 `circuit_breaker`**(7.133.0)—— 断路器键的路由段从 baseUrl **原串**换成路由身份的摘要(铸点与形 `breaker-key.ts`),
241
+ * 新一代键写**新表** `breaker_state`(中央 DDL 建)。旧表 `circuit_breaker` 的行是旧键(`<provider>:<id>@<baseUrl>`,网关根原串 query 可带凭据),
242
+ * 新代码不读不写它、它也没有 TTL 腿 ⇒ 不删就永远在共享库里。**整只删表**(`DROP TABLE IF EXISTS circuit_breaker`,不看任何一行的内容):
243
+ * 不从键的形去推断哪一行是旧的,也不碰新表里的活状态。断路器状态是秒级的建议性活数据,所以不做「旧键改写成新键」;新表从空开始、按阈值重新计数。
244
+ * 幂等与本段每一条同:第二次起服表已不在 ⇒ 零动作。滚动升级窗里还在跑的旧副本写旧表会失败(建议性写穿,吞掉并计数 —— 它们退回各自进程内的
245
+ * 断路器);窗里重启的旧副本按自己的 DDL 重建空的旧表,下一次新副本起服再删。读数 = 删掉的退役表数(0 / 1),一行键都不进日志。
246
+ * local 车道没有这张表(断路器是进程内的)⇒ 恒 0。
247
+ * · **M27 收件箱里判官拒的键的行**(7.133.0)—— 收件箱的键列本版只有一只判官 `mailboxKeyProblem`(`mailbox-store-sql.ts`:良构 Unicode ∧ 无控制字符 ∧
248
+ * UTF-8 ≤ 190 字节 ∧ scope 非空),律是「判官拒的键没有箱」:两方言每一只入口对它答「箱不在」(`reap` 对候选扫描交回的被拒句柄同样跳过),会话删除的打标 / 丢箱
249
+ * 两步也不再碰它。7.132.x 及以前的 PostgreSQL 孪生只拒 NUL 与孤立代理 ⇒ 带别的控制字符(HTAB …)的主体 / 会话 handle 写下过箱(可信主体头收 HTAB、
250
+ * JWT `sub` 收任何控制字符);MySQL 协议孪生 7.132.x 已拒控制字符与超宽键(更早版本写下的同删)。那样的行在新律下谁都够不着、正文永久留库 ⇒ **一次性删除**:
251
+ * 三张表(`mailbox` / `mailbox_message` / `mailbox_tombstone`,在场的才扫 —— 收件箱表由店在本段之后建,新库上三张都还不在)按 `(scope_key, handle)` 键集分页扫键
252
+ * (只取两只键列,正文不出库),键读回进程问**同一只判官**(`storedMailboxKeyless`:TiDB 的原字节先无损读成串,不是 UTF-8 的字节 —— 没有任何串编码成它 —— 判不良构;
253
+ * SQL 里不重述判据,两份判据迟早分叉),判官拒的那只键按 `drop` 的
254
+ * 锁序与语句逐表删(`MAILBOX_KEYED_TABLES` / `mailboxKeyDeleteSql`),参数是读回的原字节。删掉的行数进读数 `mailboxRejectedKeyRowsRemoved`(一只键都不进日志)。
255
+ * 幂等:只按判官挑,第二次起服零命中;每句自动提交、不包事务 —— 被打断就由下一次起服接着删剩下的。**不在本条之内(如实登记)**:① lease owner 列 —— 它不是
256
+ * 箱的键;判官拒的 owner 持不了租(`ack` / `releaseLease` 照「不是持租者」答、`claimLease` 抛),存量那一格随租约到期失效;② MySQL 协议上带孤立代理的主体
257
+ * 在升级前写下的箱 —— 驱动把孤立代理换成 U+FFFD 的三字节,库里的键与字面带 U+FFFD 的那只主体的键逐字节相同,判官收这只键 ⇒ 不删(挑出来就得删掉一只律认为
258
+ * 存在的箱)。local 车道用引擎自己的 File 信箱(盘上格式归引擎)⇒ 恒 0。
203
259
  *
204
260
  * ## 收据账(一次起服一只,每一条迁移同一只)
205
261
  * 每一步把它**已提交**的改写 / 删除与点名读数当场记进同一只收据账({@link MigrationReceipts}):计数随每次提交累加、行随每次提交追加、形读数在那一步落定时写。走完 ⇒
@@ -210,7 +266,9 @@
210
266
  *
211
267
  * ## 幂等
212
268
  * 改写的几条都可重复跑(M4:`detail IS NOT NULL AND … = 'null'`;M5:列在场探针 + `WHERE incarnation = ''`;M6:`WHERE error_code IS NULL`;
213
- * M8 / M18:字样扫描 + CAS 回写 —— 写过的行不再命中;M17 的改写只在改名那一拍跑,第二跑列名已是新名 ⇒ 零读零写;续判时已带戳的行零动作)。并发起动由 {@link runSqlOneTimeMigrations} 的 advisory 锁 + 每步的后置条件判据兜住(见那里)。
269
+ * M8 / M18:字样扫描 + CAS 回写 —— 写过的行不再命中;M17 的改写只在改名那一拍跑,第二跑列名已是新名 ⇒ 零读零写;续判时已带戳的行零动作;
270
+ * M24:正表 scope 列在场探针 —— 换名之后只补收尾(删待删旧表),换名之前被打断则暂存表删掉重建、回填只补缺的属主记录(`NOT EXISTS`);
271
+ * M27:只按判官挑 —— 删过的键不再被扫到)。并发起动由 {@link runSqlOneTimeMigrations} 的 advisory 锁 + 每步的后置条件判据兜住(见那里)。
214
272
  */
215
273
  import type { Pool as MysqlPool } from "mysql2/promise";
216
274
  import type { Pool as PgPool } from "pg";
@@ -268,6 +326,25 @@ export interface OneTimeMigrationReport {
268
326
  readonly parkedRowGateKindsBackfilled: number;
269
327
  /** M21:`gate_kind` 为 NULL、门子树里读不出一只 core 登记门型的停驻行 —— 引擎续不了(`pauseOf` 拒)⇒ 置 expired 的行数(SQL 独有;local 恒 0)。非零 = 真改了存量行。 */
270
328
  readonly parkedRowsUnresumableExpired: number;
329
+ /** M23:本次给属主为 NULL 的存量 placed 子代行写上委派树属主(根的,根行不在取父的)的行数(SQL 独有;local 恒 0)。非零 = 真写了存量行。 */
330
+ readonly placedSessionOwnersInherited: number;
331
+ /** M23:写上了谁都不匹配的属主的存量 placed 子代行 —— 根 / 父两行都已不在(孤行哨兵),或继承自这样一只祖先(同一只哨兵);没有任何调用方能读它们,运维可手工改属主。
332
+ * 行数(SQL 独有;local 恒 0)。非零 ⇒ 另一行 warn 点名条数。 */
333
+ readonly placedSessionOwnersOrphaned: number;
334
+ /** M23:这一次起服留着无主的存量 placed 子代行 —— 律这一读判不出,因为它读到的来源本身还是一只无主的委派子代行(根已删、父待回填)。它们只对 fleet 调用方
335
+ * 放行(读面律),由之后起服的本条或它的下一次停驻定下。根无主的子代(律就让它无主)不算。行数(SQL 独有;local 恒 0)。非零 ⇒ 读数行 warn(不拒启)。 */
336
+ readonly placedSessionOwnersRemaining: number;
337
+ /** M24:workflow journal 的属主 scope —— `moved` = 本次把行上的 scope 列搬进 `workflow_journal_owner`(回填 + 删跨 scope 行 + 删列);`current` = 早已是新形
338
+ * (新库 / 第二跑:`workflow_journal` 上没有 scope 列);`file-store` = local 车道(core 的 File 店,属主 = 每条 run 的 journal 文件首行)。 */
339
+ readonly workflowJournalScopeColumn: "moved" | "current" | "file-store";
340
+ /** M24:本次回填的属主行数(每条还没有属主行的 run 一行)。非零 = 这次迁移真写了存量库。 */
341
+ readonly workflowJournalOwnersBackfilled: number;
342
+ /** M24:本次删掉的跨 scope 行数(scope ≠ 那条 run 属主的 journal 行 —— 旧写面放进来的别的 scope 的写)。非零 ⇒ 另一行 warn 点名条数。 */
343
+ readonly workflowJournalForeignScopeRowsRemoved: number;
344
+ /** M26:本次删掉的退役断路器表数(`circuit_breaker`;0 / 1,SQL 独有,local 恒 0)。只有计数 —— 旧形键带着 URL,一行都不读。 */
345
+ readonly retiredBreakerTablesDropped: number;
346
+ /** M27:本次从收件箱三张表(盒 / 消息 / 墓碑)删掉的、键(scope / handle)过不了收件箱键判官的行数(SQL 独有,local 恒 0)。只有计数 —— 键是主体与会话 handle,一只都不进日志。 */
347
+ readonly mailboxRejectedKeyRowsRemoved: number;
271
348
  }
272
349
  /** 退役的两只命令桶(引擎 7.51.0 起不是桶;只给 M17 点名认)。 */
273
350
  export type RetiredSessionRuleBucket = "commandAllow" | "commandDeny";
@@ -301,7 +378,7 @@ export interface SessionRuleRowUnreadableDeny {
301
378
  readonly retiredBuckets?: readonly RetiredSessionRuleBucket[];
302
379
  }
303
380
  /** 三个形读数(M5 列 / M9 主键 / M17 列名)—— 那一步**落定**之前收据账里缺席(读数行要它们齐;被打断的那一行只带已落定的)。 */
304
- type ShapeReadingKey = "sessionIncarnationColumn" | "workflowParkIndexKey" | "sessionRuleColumn";
381
+ type ShapeReadingKey = "sessionIncarnationColumn" | "workflowParkIndexKey" | "sessionRuleColumn" | "workflowJournalScopeColumn";
305
382
  /**
306
383
  * 读数的「已记下的那些」形:形读数可缺席(那一步还没落定),其余每一键与 {@link OneTimeMigrationReport} 同名同形。读数行({@link logOneTimeMigrations})与
307
384
  * 被打断的那一行({@link logOneTimeMigrationsInterrupted})读同一只投影({@link readingFields}),拒因({@link OneTimeMigrationInterruptedError})带的也是这一形。
@@ -364,7 +441,7 @@ export declare const ONE_TIME_MIGRATIONS: readonly OneTimeMigrationWindow[];
364
441
  * 只给**窗内才生效的行为臂**用(M10 的认领、M19 / M20 的续跑重读):那种臂过了窗就停,不等删码车 —— 删码因此是零行为变化,而窗只写在本表这一处。
365
442
  */
366
443
  export declare function migrationWindowOpen(window: Pick<OneTimeMigrationWindow, "removableAfter">, today: string): boolean;
367
- /** local 车道上 SQL 独有的几条(M4 / M5 / M8 / M9 / M11 / M17 / M18 / M21)的读数(M6 两车道都有,local 腿的读数由 `LocalBackend.runOneTimeMigrations` 现算):那几张表 / 列在文件底座上从来不存在 ⇒ 如实答 absent / 0。 */
444
+ /** local 车道上 SQL 独有的几条(M4 / M5 / M8 / M9 / M11 / M17 / M18 / M21 / M23 / M24 / M26 / M27)的读数(M6 两车道都有,local 腿的读数由 `LocalBackend.runOneTimeMigrations` 现算):那几张表 / 列在文件底座上从来不存在 ⇒ 如实答 absent / 0。 */
368
445
  export declare const NO_SQL_MIGRATIONS: Omit<OneTimeMigrationReport, "holderDeathCodeRowsBackfilled">;
369
446
  /** 启动期的日志口(logger 的子集)。两级各有其用,见 {@link logOneTimeMigrations}。 */
370
447
  export interface MigrationLogSink {
@@ -392,13 +469,30 @@ export interface MigrationDb {
392
469
  * 每条改表 DDL 的成功判据仍是它的**后置条件**({@link execUntil}),「另一个副本抢先把同一张表换了形」不会把滚动升级变成启动失败循环。
393
470
  *
394
471
  * ## 次序(codex r1 [medium] 同轮)
395
- * 非破坏改写(M4–M9、M18:改值不丢信息 / 加法 DDL / 复制 + 换名)在前,破坏性步骤(M11 的删行)排在最后;
396
- * M11 自己半途失败时,拒因自带已删的行(`purgeUndurableScopeRows`)—— 否则重试看不见已删的行,证据永久消失。
472
+ * 非破坏改写(M4–M9、M18:改值不丢信息 / 加法 DDL / 复制 + 换名)在前,破坏性步骤(M11 的删行、M24 丢下跨 scope 行的换名)排在最后;
473
+ * M11 自己半途失败时,拒因自带已删的行(`purgeUndurableScopeRows`)—— 否则重试看不见已删的行,证据永久消失;M24 的丢行计数在换名提交之后进收据账。
397
474
  *
398
475
  * 本函数**不问**退役 schema:带着它的库在更早处就被 `ensureSchema()` 自身拒掉了(起服对账一族 `schema-consistency.ts` 的探针口,先于建表);走公开序列
399
476
  * `createStoreBackend()` → `ensureSchema()` → `runOneTimeMigrations()` 的嵌入方与本服务的起服序列一样,有这一问、零动作。
400
477
  */
401
478
  export declare function runSqlOneTimeMigrations(dialect: "tidb" | "pg", pool: MysqlPool | PgPool): Promise<OneTimeMigrationReport>;
479
+ /**
480
+ * M24(7.133.0)—— workflow journal 的属主 scope 从逐行列搬进 `workflow_journal_owner` 一行,journal 行改按 `(run_id, incarnation, ordinal)` 记。规则与理由在
481
+ * 头注 M24 段;这里只写各步为什么这样写。
482
+ *
483
+ * **判据 = 正表上 scope 列在不在**(旧形的标志;新形里没有这一列)。不在 ⇒ 新形:只补收尾(上一次在 TiDB 换名之后、删旧表之前被打断留下的待删旧表),读数 `current`。
484
+ * 在 ⇒ 旧形,**正表在换名之前一个字节不动**:
485
+ * ① 残留的暂存表(上一次换名之前被打断)删掉重来 —— 它只是正表的一份不完整拷贝。
486
+ * ② **回填属主**(`INSERT … SELECT`,两方言同一句):每条 run 取**最小序数**那一行 —— 旧主键 `(run_id, ordinal)` 保证每条 run 恰一行,于是一条语句里不会对同一个
487
+ * run_id 插两次;`appended_at_ms` 取该 run 最晚的 `created_at_ms`(属主记录的 GC 时钟因此不早于它的任何一行)。`NOT EXISTS`:续跑只补缺的,已在的属主记录一个字节不改。
488
+ * ③ 数跨 scope 行(scope ≠ 属主):它们不搬 —— 新形里一条 run 只有一个 scope;它们只可能是旧写面放进来的别的 scope 的写(读它们的只有写它们的那个 scope 的
489
+ * resume,丢掉 = 那几步退化为活跑)。计数在换名提交之后才进收据账(那一刻它们才真的随旧表消失)。
490
+ * ④ 照**中央 DDL 的同一条语句**换表名建暂存表(不手抄第二份),按旧主键键集分批 `INSERT … SELECT` 搬与属主同 scope 的行、带属主记录的令牌(新主键下不会撞键:旧主键唯一 ×
491
+ * 每条 run 一只属主;每批字节有界、各自提交 —— 整表一条语句会撞 TiDB 的事务总量上限,见 {@link M24_COPY_BATCH_BYTES})。
492
+ * ⑤ **原子换名**:TiDB 一条 `RENAME TABLE 正 TO 待删, 暂存 TO 正`,之后删待删旧表;PG 两条换名、删旧表与主键约束名改回常规名在**同一个事务**里({@link execAtomically})
493
+ * —— 中途死 = 整段回滚,盘上回到「正表旧形 + 暂存表」,下一次起动从 ① 重来。每一条 DDL 按后置条件收敛({@link execUntil} / {@link execAtomically})。
494
+ */
495
+ export declare function moveWorkflowJournalScope(db: MigrationDb, dialect: "tidb" | "pg", receipts: MigrationReceipts): Promise<void>;
402
496
  /**
403
497
  * M17 —— 一份存量规则体带哪几只退役命令桶(纯函数,点名判据):只认**对象上自有**的 `commandAllow` / `commandDeny` 键(不论值形 —— 坏形的退役桶照样是退役桶);
404
498
  * 不是对象 / 没有这两个键 / 条目文字里出现同名字样 ⇒ 空。不改写:新文法写不出旧命令桶的拒(头注 M17 段 ③)。
@@ -493,6 +587,17 @@ export declare function parkedRowRepairOf(gateKindCell: unknown, gate: unknown,
493
587
  * 每一次提交当场记进收据账。
494
588
  */
495
589
  export declare function repairParkedRows(db: MigrationDb, dialect: "tidb" | "pg", receipts: MigrationReceipts): Promise<void>;
590
+ /**
591
+ * M23 —— 存量 placed 子代行的属主回填,**每次起服单趟、幂等**(规则、为什么、够不到的一格见头注 M23 段):键集分页扫完 placed ∧ 属主 NULL 的行,每一行按唯一判据判
592
+ * (`judgeDelegatedOwner`,建行那一笔与停驻挂点同一只:来源顺序根、父;属主 = 这一读见证到的):
593
+ * · 判出属主 ⇒ {@link CLAIM_OWNERLESS_PLACED_SQL}(只 CAS 子行自己,绑扫描读到的化身;与停驻挂点的认领**同一句、同一份绑参**;第二跑零命中);写成的按属主计进
594
+ * `placedSessionOwnersOrphaned`(谁都不匹配:孤行哨兵,或继承自孤行祖先的同一只哨兵)或 `placedSessionOwnersInherited`,每一次提交当场记进收据账;零命中(这一行在扫描之后
595
+ * 被写过 / 删过 / 换了住客)⇒ 不计,下一次起服按那时的律重判;
596
+ * · 判出无主:读到的来源本身是一只 placed 行(还没定下的委派子代行)⇒ 计进 `placedSessionOwnersRemaining`(残余);来源是普通会话(根无主)⇒ 律就让它无主,不计;
597
+ * · 扫到的任何一行化身为空 ⇒ 响亮拒(不论判不判得出属主;来源行同一条律)。
598
+ * 不重扫、不拒启:残余由下一次起服或那一行的下一次停驻定下,期间只对 fleet 调用方放行(头注 M23 段)。
599
+ */
600
+ export declare function inheritPlacedSessionOwners(db: MigrationDb, dialect: "tidb" | "pg", receipts: MigrationReceipts): Promise<void>;
496
601
  /**
497
602
  * 启动日志的读数行。零命中(第二跑起的常态)也照发 —— 「跑过了、什么都没改」与「压根没跑」必须可分辨。
498
603
  *
@@ -6,13 +6,15 @@ import { PG_SCHEMA_STATEMENTS, acquirePgEnsureSchemaLock, releasePgEnsureSchemaL
6
6
  import { CENTRAL_SCHEMA_INDEXES } from "./schema-indexes.js";
7
7
  import { ensureIndexes } from "./ensure-index.js";
8
8
  import { createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, isJsonColumnBodyProblem, jsonColumnText, jsonColumnTextExpr, readNonNullJsonValueColumn, readOpaqueJsonValueColumn, } from "./sql-json-column.js";
9
- import { SESSION_META_INCARNATION_COLUMN, mintSessionIncarnation } from "../session-incarnation.js";
9
+ import { SESSION_META_INCARNATION_COLUMN, incarnationFromCell, mintSessionIncarnation, sessionFaceFromRow } from "../session-incarnation.js";
10
10
  import { RUN_HOLDER_DEATH_CODES, RUN_ORPHANED_ERROR, RUN_STALLED_ERROR } from "./store-contracts.js";
11
11
  import { PERMISSION_RULE_TABLE, purgeUndurableScopeRows } from "./permission-rule-store-sql.js";
12
12
  import { SESSION_RULES_COLUMN } from "./session-rules-column.js";
13
13
  import { readJsonObjectColumn } from "./sql-json-column.js";
14
14
  import { retireStoredCardWords } from "../retired-card-words.js";
15
15
  import { redactSecrets } from "../trace/redact.js";
16
+ import { CLAIM_OWNERLESS_PLACED_SQL, claimOwnerlessParams, isOrphanSessionOwner, judgeDelegatedOwner } from "../delegated-session-owner.js";
17
+ import { MAILBOX_KEYED_TABLES, mailboxKeyDeleteSql, storedMailboxKeyless } from "./mailbox-store-sql.js";
16
18
  const RETIRED_SESSION_RULE_BUCKETS = ["commandAllow", "commandDeny"];
17
19
  export function buildMigrationReceipts() {
18
20
  return {
@@ -34,14 +36,21 @@ export function buildMigrationReceipts() {
34
36
  parkedQuestionRowsLeftAsWritten: 0,
35
37
  parkedRowGateKindsBackfilled: 0,
36
38
  parkedRowsUnresumableExpired: 0,
39
+ placedSessionOwnersInherited: 0,
40
+ placedSessionOwnersOrphaned: 0,
41
+ placedSessionOwnersRemaining: 0,
42
+ workflowJournalOwnersBackfilled: 0,
43
+ workflowJournalForeignScopeRowsRemoved: 0,
44
+ retiredBreakerTablesDropped: 0,
45
+ mailboxRejectedKeyRowsRemoved: 0,
37
46
  };
38
47
  }
39
48
  function completedReport(r) {
40
- const { sessionIncarnationColumn, workflowParkIndexKey, sessionRuleColumn } = r;
41
- if (sessionIncarnationColumn === undefined || workflowParkIndexKey === undefined || sessionRuleColumn === undefined) {
42
- throw new Error(`one-time migrations finished without settling a shape reading (M5 ${String(sessionIncarnationColumn)} / M9 ${String(workflowParkIndexKey)} / M17 ${String(sessionRuleColumn)}) — a step did not record it`);
49
+ const { sessionIncarnationColumn, workflowParkIndexKey, sessionRuleColumn, workflowJournalScopeColumn } = r;
50
+ if (sessionIncarnationColumn === undefined || workflowParkIndexKey === undefined || sessionRuleColumn === undefined || workflowJournalScopeColumn === undefined) {
51
+ throw new Error(`one-time migrations finished without settling a shape reading (M5 ${String(sessionIncarnationColumn)} / M9 ${String(workflowParkIndexKey)} / M17 ${String(sessionRuleColumn)} / M24 ${String(workflowJournalScopeColumn)}) — a step did not record it`);
43
52
  }
44
- return { ...r, sessionIncarnationColumn, workflowParkIndexKey, sessionRuleColumn };
53
+ return { ...r, sessionIncarnationColumn, workflowParkIndexKey, sessionRuleColumn, workflowJournalScopeColumn };
45
54
  }
46
55
  function changedAnything(r) {
47
56
  return (r.auditDetailJsonNullRows > 0 ||
@@ -64,7 +73,15 @@ function changedAnything(r) {
64
73
  r.parkedQuestionCardsWritten > 0 ||
65
74
  r.parkedQuestionRowsLeftAsWritten > 0 ||
66
75
  r.parkedRowGateKindsBackfilled > 0 ||
67
- r.parkedRowsUnresumableExpired > 0);
76
+ r.parkedRowsUnresumableExpired > 0 ||
77
+ r.placedSessionOwnersInherited > 0 ||
78
+ r.placedSessionOwnersOrphaned > 0 ||
79
+ r.placedSessionOwnersRemaining > 0 ||
80
+ r.workflowJournalScopeColumn === "moved" ||
81
+ r.workflowJournalOwnersBackfilled > 0 ||
82
+ r.workflowJournalForeignScopeRowsRemoved > 0 ||
83
+ r.retiredBreakerTablesDropped > 0 ||
84
+ r.mailboxRejectedKeyRowsRemoved > 0);
68
85
  }
69
86
  function readingsSnapshot(r) {
70
87
  return {
@@ -364,6 +381,75 @@ export const ONE_TIME_MIGRATIONS = [
364
381
  "与回滚到 7.131.x 读 5 同一种处置",
365
382
  reportKeys: [],
366
383
  },
384
+ {
385
+ id: "M23",
386
+ introducedIn: "7.133.0",
387
+ releasedOn: "2026-10-21",
388
+ removableAfter: "2026-11-20",
389
+ removal: "删 migrateOn 的 M23 步(`inheritPlacedSessionOwners`:placed ∧ 属主 NULL 的行按 session_id 键集分页单趟扫描 + 同一条语句连读根 / 父两行 + 按认领句回写属主)与 `PLACED_OWNER_SCAN_PAGE` / `placedOwnerScanSql` / `scannedPlacement` / `scannedFaces` / `scannedPlacedSources`(`isOrphanSessionOwner` 若再无别的读者一并删);" +
390
+ "**同窗同删的另一半**(认领:按律给升级前写下的无主子代行写属主 —— 停驻挂点对宿主无主行做的就是本条对那一行的单行步):`delegated-session-owner.ts` 的 `CLAIM_OWNERLESS_PLACED_SQL` / `claimOwnerlessParams` / `OwnerlessClaim` / " +
391
+ "`settleDelegatedOwner` 的认领一步(删后它只剩树比对与属主比对)/ `DelegationTreeStore.claimOwnerless`," +
392
+ "两只 SQL 店(`tidb-session-store.ts` / `pg-session-storage.ts`)的 `claimOwnerless`、`CachingSessionStore` 的转发、`OwnerAwareSessionStore.claimOwnerless`、`delegatedOwnerHostOf` 对它的「与 `placementOf` 同在或同缺」;" +
393
+ "报告、日志行与 `NO_SQL_MIGRATIONS` 的 `placedSessionOwnersInherited` / `placedSessionOwnersOrphaned` / `placedSessionOwnersRemaining` 与 `placed_session_owners_orphaned` 那一行 warn;" +
394
+ "`test/one-time-migrations.test.ts` 的 M23 组与 `test/one-time-migrations-db-integration.test.ts` 的 M23 格。" +
395
+ "**不随本条删**:属主为 NULL 的 placed 行只对 fleet 调用方放行(`security.ts` 的 `ownerlessRowOpen` / `sessionOwnerCellOf` 与各门读 `placementOf` 的那一次)—— 那是常设的读面律",
396
+ afterRemoval: "会话面的读面律(常设,不随本条删)接住:跳过了带本条的版本直接升的库里,属主为 NULL 的存量 placed 子代行只对 fleet 调用方放行(出示 service 凭据、不报主体;dev-open)," +
397
+ "其余调用方答 404(与别人的行同体)—— 出路是升级时先过一次带本条的版本,或运维按 `placement_root_session_id`(缺则 `placement_parent_session_id`)那一行的属主手工回填、" +
398
+ "推不出的写一个任何主体都不匹配的值;停驻挂点读到无主的宿主子代行 ⇒ 回到比对的既有臂:无主 ≠ 律给的属主 ⇒ 停驻否决(fail-closed,响亮;出路同上);" +
399
+ "建行那一笔的继承律不随本条删(那是常设的写法,不是迁移)",
400
+ reportKeys: ["placedSessionOwnersInherited", "placedSessionOwnersOrphaned", "placedSessionOwnersRemaining"],
401
+ },
402
+ {
403
+ id: "M24",
404
+ introducedIn: "7.133.0",
405
+ releasedOn: "2026-10-21",
406
+ removableAfter: "2026-11-20",
407
+ removal: "删 migrateOn 的 M24 步(`moveWorkflowJournalScope`:scope 列在场探针 + 删残留暂存表 / 待删旧表 + 回填属主记录 + 数跨 scope 行 + 照中央 DDL 建暂存表 + 整表复制 + 原子换名 + 删旧表 + PG 主键约束名)与 " +
408
+ "`copyJournalInBatches` / `journalKeyBound` / `WORKFLOW_JOURNAL_TABLE` / `WORKFLOW_JOURNAL_OWNER_TABLE` / `WORKFLOW_JOURNAL_STAGING` / `WORKFLOW_JOURNAL_RETIRED` / `RETIRED_JOURNAL_SCOPE_COLUMN` / " +
409
+ "`M24_OWNER_INCARNATION` / `M24_COPY_BATCH_BYTES` / `M24_COPY_SCAN_PAGE`;" +
410
+ "报告、日志行与 `NO_SQL_MIGRATIONS` 的 `workflowJournalScopeColumn` / `workflowJournalOwnersBackfilled` / `workflowJournalForeignScopeRowsRemoved` 与 `workflow_journal_foreign_scope_rows_removed` 那一行 warn;" +
411
+ "`test/one-time-migrations.test.ts` 的 M24 组与 `test/one-time-migrations-db-integration.test.ts` 的 M24 格。**不随本条删**:属主表、行主键里的令牌与店的写面 / 读面墙(那是常设的数据形)",
412
+ afterRemoval: "起服对账一族的列集探针接住(`schema-consistency.ts` 的 `assertSchemaMatchesCode`,常设):跳过了带本条的版本直接升的库,`workflow_journal` 上仍带 scope 列、没有 incarnation 列 = 库与代码列集不符 ⇒ " +
413
+ "响亮拒启(`config.schema_mismatch`),拒句点名两列;出路是先在带本条的版本(7.133.x)起服一次让它走完,再升",
414
+ reportKeys: ["workflowJournalScopeColumn", "workflowJournalOwnersBackfilled", "workflowJournalForeignScopeRowsRemoved"],
415
+ },
416
+ {
417
+ id: "M25",
418
+ introducedIn: "7.133.0",
419
+ releasedOn: "2026-10-21",
420
+ removableAfter: "2026-11-20",
421
+ removal: "删 `project-identity.ts` 的 `mintRepoScopeKey`(与它的窗注;`RETIRED_PROJECT_KEY_PREFIX` 留给 `isServerMintedProjectKey` 的起服拒)与 `projectLayerForTaskRoot` 的 `foldFrom` 里它那一员" +
422
+ "(`p` 档那一员留 —— 常设律);`memory-sync-client.ts` 的 `legacyMemorySyncCursorPath` / `adoptLegacyCursor` 与 runner 里调它的那一段(`legacyCursorChecked`);" +
423
+ "`test/memory-project-layer-fold.test.ts` 的 ① 格与 `test/memory-project-layer-fold-db-integration.test.ts` 整份、`test/memory-sync-client.test.ts` 的旧游标收编格、" +
424
+ "`test/project-identity.test.ts` 的 `repo:` 金样格(公式金样改钉 `p` 档摘要)",
425
+ afterRemoval: "没有新的臂(如实):窗内一次都没跑过任务的项目,它的 `repo:` 层留在库里、不再被任何读列表包含(不读、不删;起服拒只管运维点名这一族);出路 = 升级时先过一次带本条的版本并在每个项目跑一次任务。" +
426
+ "同步腿的旧游标文件不再收编:同步层从空基线首轮重收敛(两边都改过的条目按冲突上报,不静默丢)",
427
+ reportKeys: [],
428
+ },
429
+ {
430
+ id: "M26",
431
+ introducedIn: "7.133.0",
432
+ releasedOn: "2026-10-21",
433
+ removableAfter: "2026-11-20",
434
+ removal: "删 migrateOn 的 M26 步(`dropRetiredBreakerTable`)与 `RETIRED_BREAKER_TABLE`;报告、日志行与 `NO_SQL_MIGRATIONS` 的 `retiredBreakerTablesDropped`;" +
435
+ "`test/one-time-migrations.test.ts` 的 S-491 组与 `test/one-time-migrations-db-integration.test.ts` 的 M26 格。" +
436
+ "**不随本条删**:键的铸点 `breakerKeyOf` / `routeDigestOf`(常设的键形)与新表 `breaker_state`",
437
+ afterRemoval: "没有新的臂(如实):跳过了带本条的版本直接升的库,退役表 `circuit_breaker` 留在库里、没有读者也没有写者(断路器只读写 `breaker_state`)—— " +
438
+ "出路 = 升级时先过一次带本条的版本,或手工 `DROP TABLE circuit_breaker`(建议性状态,删表安全)",
439
+ reportKeys: ["retiredBreakerTablesDropped"],
440
+ },
441
+ {
442
+ id: "M27",
443
+ introducedIn: "7.133.0",
444
+ releasedOn: "2026-10-21",
445
+ removableAfter: "2026-11-20",
446
+ removal: "删 migrateOn 的 M27 步(`dropRejectedMailboxKeys`)与 `MAILBOX_KEY_SCAN_PAGE`;报告、日志行与 `NO_SQL_MIGRATIONS` 的 `mailboxRejectedKeyRowsRemoved`;" +
447
+ "`test/one-time-migrations.test.ts` 的 M27 组。**不随本条删**:判官 `mailboxKeyProblem`、存储键读回判 `storedMailboxKeyless`(常设 `reap` 读候选句柄也经它)与「判官拒的键没有箱」那条入口律(含 `reap` 跳过候选扫描交回的被拒句柄)、" +
448
+ "`MAILBOX_KEYED_TABLES` / `mailboxKeyDeleteSql`(`drop` 的常设语句)",
449
+ afterRemoval: "没有新的臂(如实):跳过了带本条的版本直接升的库,判官拒的键的行留在库里,任何入口对它都按「箱不在」答(不读、不投、不清)—— 出路 = 升级时先过一次带本条的版本," +
450
+ "或照 `docs/DEPLOY-PREREQS.md` 7.133.0 SQL 节的查法按键手工删",
451
+ reportKeys: ["mailboxRejectedKeyRowsRemoved"],
452
+ },
367
453
  ];
368
454
  export function migrationWindowOpen(window, today) {
369
455
  return window.removableAfter === null || today <= window.removableAfter;
@@ -389,6 +475,14 @@ export const NO_SQL_MIGRATIONS = {
389
475
  parkedQuestionRowsLeftAsWritten: 0,
390
476
  parkedRowGateKindsBackfilled: 0,
391
477
  parkedRowsUnresumableExpired: 0,
478
+ placedSessionOwnersInherited: 0,
479
+ placedSessionOwnersOrphaned: 0,
480
+ placedSessionOwnersRemaining: 0,
481
+ workflowJournalScopeColumn: "file-store",
482
+ workflowJournalOwnersBackfilled: 0,
483
+ workflowJournalForeignScopeRowsRemoved: 0,
484
+ retiredBreakerTablesDropped: 0,
485
+ mailboxRejectedKeyRowsRemoved: 0,
392
486
  };
393
487
  const AUDIT_TABLE = "device_audit";
394
488
  const AUDIT_DETAIL_COLUMN = "detail";
@@ -493,8 +587,125 @@ async function migrateStepsOn(db, dialect, receipts) {
493
587
  await reshapeWorkflowParkIndex(db, dialect, receipts);
494
588
  await migrateSessionRuleGrammar(db, dialect, receipts);
495
589
  await repairParkedRows(db, dialect, receipts);
590
+ if (await tableExists(db, dialect, "session_meta"))
591
+ await inheritPlacedSessionOwners(db, dialect, receipts);
496
592
  if (await tableExists(db, dialect, PERMISSION_RULE_TABLE))
497
593
  await purgeUndurableScopeRows(db, dialect, receipts);
594
+ await moveWorkflowJournalScope(db, dialect, receipts);
595
+ if (await tableExists(db, dialect, RETIRED_BREAKER_TABLE))
596
+ await dropRetiredBreakerTable(db, dialect, receipts);
597
+ await dropRejectedMailboxKeys(db, dialect, receipts);
598
+ }
599
+ const MAILBOX_KEY_SCAN_PAGE = 1000;
600
+ async function dropRejectedMailboxKeys(db, dialect, receipts) {
601
+ const present = [];
602
+ for (const table of MAILBOX_KEYED_TABLES)
603
+ if (await tableExists(db, dialect, table))
604
+ present.push(table);
605
+ const p = (i) => (dialect === "tidb" ? "?" : `$${i}`);
606
+ for (const table of present) {
607
+ let after;
608
+ for (;;) {
609
+ const page = await db.rows(`SELECT DISTINCT scope_key, handle FROM ${table}` +
610
+ (after === undefined ? "" : ` WHERE scope_key > ${p(1)} OR (scope_key = ${p(2)} AND handle > ${p(3)})`) +
611
+ ` ORDER BY scope_key, handle LIMIT ${MAILBOX_KEY_SCAN_PAGE}`, after === undefined ? undefined : [after[0], after[0], after[1]]);
612
+ for (const row of page) {
613
+ if (!storedMailboxKeyless(row.scope_key, row.handle))
614
+ continue;
615
+ for (const t of present)
616
+ receipts.mailboxRejectedKeyRowsRemoved += await db.exec(mailboxKeyDeleteSql(dialect, t), [row.scope_key, row.handle]);
617
+ }
618
+ if (page.length < MAILBOX_KEY_SCAN_PAGE)
619
+ break;
620
+ const last = page[page.length - 1];
621
+ after = [last.scope_key, last.handle];
622
+ }
623
+ }
624
+ }
625
+ const RETIRED_BREAKER_TABLE = "circuit_breaker";
626
+ async function dropRetiredBreakerTable(db, dialect, receipts) {
627
+ await execUntil(db, `DROP TABLE IF EXISTS ${RETIRED_BREAKER_TABLE}`, async () => !(await tableExists(db, dialect, RETIRED_BREAKER_TABLE)));
628
+ receipts.retiredBreakerTablesDropped += 1;
629
+ }
630
+ const WORKFLOW_JOURNAL_TABLE = "workflow_journal";
631
+ const WORKFLOW_JOURNAL_OWNER_TABLE = "workflow_journal_owner";
632
+ const WORKFLOW_JOURNAL_STAGING = `${WORKFLOW_JOURNAL_TABLE}_m24`;
633
+ const WORKFLOW_JOURNAL_RETIRED = `${WORKFLOW_JOURNAL_TABLE}_m24_old`;
634
+ const RETIRED_JOURNAL_SCOPE_COLUMN = "scope";
635
+ const M24_OWNER_INCARNATION = "m24-backfill";
636
+ const M24_COPY_BATCH_BYTES = 32 * 1024 * 1024;
637
+ const M24_COPY_SCAN_PAGE = 1000;
638
+ function journalKeyBound(dialect, op, first) {
639
+ const p = (i) => (dialect === "tidb" ? "?" : `$${first + i}`);
640
+ return op === "after"
641
+ ? `(${WORKFLOW_JOURNAL_TABLE}.run_id > ${p(0)} OR (${WORKFLOW_JOURNAL_TABLE}.run_id = ${p(1)} AND ordinal > ${p(2)}))`
642
+ : `(${WORKFLOW_JOURNAL_TABLE}.run_id < ${p(0)} OR (${WORKFLOW_JOURNAL_TABLE}.run_id = ${p(1)} AND ordinal <= ${p(2)}))`;
643
+ }
644
+ async function copyJournalInBatches(db, dialect) {
645
+ const T = WORKFLOW_JOURNAL_TABLE;
646
+ const bytes = dialect === "tidb" ? "LENGTH(result)" : "OCTET_LENGTH(result)";
647
+ const copyHead = `INSERT INTO ${WORKFLOW_JOURNAL_STAGING} (run_id, incarnation, ordinal, call_key, result, created_at_ms) ` +
648
+ `SELECT ${T}.run_id, o.incarnation, ordinal, call_key, ${jsonColumnText(dialect, "result")}, created_at_ms FROM ${T} ` +
649
+ `JOIN ${WORKFLOW_JOURNAL_OWNER_TABLE} o ON o.run_id = ${T}.run_id AND o.scope = ${T}.${RETIRED_JOURNAL_SCOPE_COLUMN} WHERE `;
650
+ let after;
651
+ for (;;) {
652
+ const page = await db.rows(`SELECT ${T}.run_id, ordinal, ${bytes} AS n FROM ${T}` + (after === undefined ? "" : ` WHERE ${journalKeyBound(dialect, "after", 1)}`) + ` ORDER BY run_id, ordinal LIMIT ${M24_COPY_SCAN_PAGE}`, after === undefined ? [] : [after[0], after[0], after[1]]);
653
+ if (page.length === 0)
654
+ return;
655
+ let end = 0;
656
+ let total = 0;
657
+ while (end < page.length) {
658
+ const n = Number(page[end].n);
659
+ if (end > 0 && total + n > M24_COPY_BATCH_BYTES)
660
+ break;
661
+ total += n;
662
+ end += 1;
663
+ }
664
+ const last = page[end - 1];
665
+ const upTo = [String(last.run_id), Number(last.ordinal)];
666
+ if (after === undefined)
667
+ await db.exec(copyHead + journalKeyBound(dialect, "upTo", 1), [upTo[0], upTo[0], upTo[1]]);
668
+ else
669
+ await db.exec(`${copyHead}${journalKeyBound(dialect, "after", 1)} AND ${journalKeyBound(dialect, "upTo", 4)}`, [after[0], after[0], after[1], upTo[0], upTo[0], upTo[1]]);
670
+ after = upTo;
671
+ }
672
+ }
673
+ export async function moveWorkflowJournalScope(db, dialect, receipts) {
674
+ const T = WORKFLOW_JOURNAL_TABLE;
675
+ const S = WORKFLOW_JOURNAL_STAGING;
676
+ const O = WORKFLOW_JOURNAL_RETIRED;
677
+ const exists = (t) => tableExists(db, dialect, t);
678
+ const legacy = () => columnExists(db, dialect, T, RETIRED_JOURNAL_SCOPE_COLUMN);
679
+ if (!(await legacy())) {
680
+ if (await exists(O))
681
+ await execUntil(db, `DROP TABLE ${O}`, async () => !(await exists(O)));
682
+ receipts.workflowJournalScopeColumn = "current";
683
+ return;
684
+ }
685
+ const ddl = (dialect === "tidb" ? SCHEMA_STATEMENTS : PG_SCHEMA_STATEMENTS).find((stmt) => stmt.includes(`CREATE TABLE IF NOT EXISTS ${T} (`));
686
+ if (ddl === undefined)
687
+ throw new Error(`one-time migration M24: the central schema carries no definition of ${T} — refusing to rebuild a table it cannot define`);
688
+ if (await exists(S))
689
+ await execUntil(db, `DROP TABLE ${S}`, async () => !(await exists(S)));
690
+ receipts.workflowJournalOwnersBackfilled += await db.exec(`INSERT INTO ${WORKFLOW_JOURNAL_OWNER_TABLE} (run_id, scope, incarnation, appended_at_ms) ` +
691
+ `SELECT j.run_id, j.${RETIRED_JOURNAL_SCOPE_COLUMN}, '${M24_OWNER_INCARNATION}', m.last_ms FROM ${T} j ` +
692
+ `JOIN (SELECT run_id, MIN(ordinal) AS first_ordinal, MAX(created_at_ms) AS last_ms FROM ${T} GROUP BY run_id) m ` +
693
+ `ON m.run_id = j.run_id AND m.first_ordinal = j.ordinal ` +
694
+ `WHERE NOT EXISTS (SELECT 1 FROM ${WORKFLOW_JOURNAL_OWNER_TABLE} o WHERE o.run_id = j.run_id)`);
695
+ const foreign = Number((await db.rows(`SELECT COUNT(*) AS n FROM ${T} j JOIN ${WORKFLOW_JOURNAL_OWNER_TABLE} o ON o.run_id = j.run_id WHERE j.${RETIRED_JOURNAL_SCOPE_COLUMN} <> o.scope`))[0]?.n ?? 0);
696
+ await db.exec(ddl.replace(`CREATE TABLE IF NOT EXISTS ${T} (`, `CREATE TABLE IF NOT EXISTS ${S} (`));
697
+ await copyJournalInBatches(db, dialect);
698
+ const swapped = async () => !(await exists(S)) && (await exists(T)) && !(await legacy());
699
+ if (dialect === "tidb") {
700
+ await execUntil(db, `RENAME TABLE ${T} TO ${O}, ${S} TO ${T}`, swapped);
701
+ receipts.workflowJournalForeignScopeRowsRemoved += foreign;
702
+ await execUntil(db, `DROP TABLE IF EXISTS ${O}`, async () => !(await exists(O)));
703
+ }
704
+ else {
705
+ await execAtomically(db, [`ALTER TABLE ${T} RENAME TO ${O}`, `ALTER TABLE ${S} RENAME TO ${T}`, `DROP TABLE ${O}`, `ALTER TABLE ${T} RENAME CONSTRAINT ${S}_pkey TO ${T}_pkey`], async () => (await swapped()) && !(await exists(O)));
706
+ receipts.workflowJournalForeignScopeRowsRemoved += foreign;
707
+ }
708
+ receipts.workflowJournalScopeColumn = "moved";
498
709
  }
499
710
  const SESSION_POLICY_TABLE = "session_policy";
500
711
  const RETIRED_SESSION_RULES_COLUMN = "rules";
@@ -867,6 +1078,66 @@ export async function repairParkedRows(db, dialect, receipts) {
867
1078
  return;
868
1079
  }
869
1080
  }
1081
+ const PLACED_OWNER_SCAN_PAGE = 500;
1082
+ function placedOwnerScanSql(dialect) {
1083
+ return ("SELECT c.session_id, c.incarnation, r.session_id AS root_row, r.owner AS root_owner, r.incarnation AS root_incarnation, r.placement_kind AS root_kind, " +
1084
+ "p.session_id AS parent_row, p.owner AS parent_owner, p.incarnation AS parent_incarnation, p.placement_kind AS parent_kind, " +
1085
+ "c.placement_root_session_id AS root_id, c.placement_parent_session_id AS parent_id " +
1086
+ "FROM session_meta c LEFT JOIN session_meta r ON r.session_id = NULLIF(c.placement_root_session_id, '') LEFT JOIN session_meta p ON p.session_id = NULLIF(c.placement_parent_session_id, '') " +
1087
+ `WHERE c.placement_kind IS NOT NULL AND c.owner IS NULL AND c.session_id > ${dialect === "tidb" ? "?" : "$1"} ORDER BY c.session_id LIMIT ${PLACED_OWNER_SCAN_PAGE}`);
1088
+ }
1089
+ function scannedPlacement(r) {
1090
+ return {
1091
+ ...(typeof r.root_id === "string" ? { rootSessionId: r.root_id } : {}),
1092
+ ...(typeof r.parent_id === "string" ? { parentSessionId: r.parent_id } : {}),
1093
+ };
1094
+ }
1095
+ function scannedFaces(r) {
1096
+ const faces = new Map();
1097
+ const put = (id, row, owner, incarnation) => {
1098
+ if (typeof id !== "string" || row === null || row === undefined)
1099
+ return;
1100
+ faces.set(id, sessionFaceFromRow(id, typeof owner === "string" ? owner : null, incarnation));
1101
+ };
1102
+ put(r.root_id, r.root_row, r.root_owner, r.root_incarnation);
1103
+ put(r.parent_id, r.parent_row, r.parent_owner, r.parent_incarnation);
1104
+ return faces;
1105
+ }
1106
+ function scannedPlacedSources(r) {
1107
+ const placed = new Set();
1108
+ if (typeof r.root_id === "string" && r.root_row !== null && r.root_row !== undefined && r.root_kind !== null && r.root_kind !== undefined)
1109
+ placed.add(r.root_id);
1110
+ if (typeof r.parent_id === "string" && r.parent_row !== null && r.parent_row !== undefined && r.parent_kind !== null && r.parent_kind !== undefined)
1111
+ placed.add(r.parent_id);
1112
+ return placed;
1113
+ }
1114
+ export async function inheritPlacedSessionOwners(db, dialect, receipts) {
1115
+ const scan = placedOwnerScanSql(dialect);
1116
+ const cas = CLAIM_OWNERLESS_PLACED_SQL[dialect];
1117
+ let after = "";
1118
+ for (;;) {
1119
+ const page = await db.rows(scan, [after]);
1120
+ for (const r of page) {
1121
+ const sessionId = String(r.session_id);
1122
+ after = sessionId;
1123
+ const incarnation = incarnationFromCell(sessionId, r.incarnation);
1124
+ const judged = judgeDelegatedOwner(sessionId, scannedPlacement(r), scannedFaces(r));
1125
+ const owner = judged.inherited.owner;
1126
+ if (owner === null) {
1127
+ if (judged.inherited.kind === "inherited" && scannedPlacedSources(r).has(judged.inherited.from))
1128
+ receipts.placedSessionOwnersRemaining += 1;
1129
+ continue;
1130
+ }
1131
+ const landed = await db.exec(cas, [...claimOwnerlessParams(sessionId, incarnation, owner)]);
1132
+ if (isOrphanSessionOwner(owner))
1133
+ receipts.placedSessionOwnersOrphaned += landed;
1134
+ else
1135
+ receipts.placedSessionOwnersInherited += landed;
1136
+ }
1137
+ if (page.length < PLACED_OWNER_SCAN_PAGE)
1138
+ return;
1139
+ }
1140
+ }
870
1141
  const INCARNATION_BACKFILL_BATCH = 500;
871
1142
  async function migrateSessionIncarnations(db, dialect, receipts) {
872
1143
  if (!(await tableExists(db, dialect, "session_meta"))) {
@@ -948,6 +1219,14 @@ function readingFields(r) {
948
1219
  parkedQuestionRowsLeftAsWritten: r.parkedQuestionRowsLeftAsWritten,
949
1220
  parkedRowGateKindsBackfilled: r.parkedRowGateKindsBackfilled,
950
1221
  parkedRowsUnresumableExpired: r.parkedRowsUnresumableExpired,
1222
+ placedSessionOwnersInherited: r.placedSessionOwnersInherited,
1223
+ placedSessionOwnersOrphaned: r.placedSessionOwnersOrphaned,
1224
+ placedSessionOwnersRemaining: r.placedSessionOwnersRemaining,
1225
+ ...(r.workflowJournalScopeColumn !== undefined ? { workflowJournalScopeColumn: r.workflowJournalScopeColumn } : {}),
1226
+ workflowJournalOwnersBackfilled: r.workflowJournalOwnersBackfilled,
1227
+ workflowJournalForeignScopeRowsRemoved: r.workflowJournalForeignScopeRowsRemoved,
1228
+ retiredBreakerTablesDropped: r.retiredBreakerTablesDropped,
1229
+ mailboxRejectedKeyRowsRemoved: r.mailboxRejectedKeyRowsRemoved,
951
1230
  };
952
1231
  }
953
1232
  export function logOneTimeMigrations(log, backend, report) {
@@ -979,7 +1258,19 @@ export function logOneTimeMigrations(log, backend, report) {
979
1258
  "the cards this step cannot reach are read the same way, counted under server.approval-card.retired-card-word-read; M21: suspended rows parked by an engine " +
980
1259
  "before the question card existed and still pending — a question approval whose gate carries no card — get the card word question (rows the engine sealed " +
981
1260
  "with its row digest are left as written and counted), and suspended rows with no gate kind on the row get it from the gate (a row whose gate names no " +
982
- "registered kind cannot be resumed and is expired)). Each step " +
1261
+ "registered kind cannot be resumed and is expired); M23: delegated (placed) session rows written ownerless by an earlier version get the owner of their " +
1262
+ "delegation tree as this boot reads it — the root session's owner, or the parent's when the root row is gone; a row under an ownerless root stays ownerless; a row whose root and parent " +
1263
+ "are both gone gets an owner no caller can match and the rows below it inherit that owner, all counted and named by a placed_session_owners_orphaned line; " +
1264
+ "placedSessionOwnersRemaining counts the rows this boot could not settle because the session they inherit from was itself still ownerless — until a later boot or " +
1265
+ "their next park settles them, only fleet callers can read them; M24: the scope that owns a workflow run's resume journal moved from a column on every journal row " +
1266
+ "into one row per run in workflow_journal_owner — each run got the scope of its lowest-ordinal row, and the journal table was rebuilt (copy + atomic rename) " +
1267
+ "keyed by (run, owner-record incarnation, ordinal) without the scope column; rows another scope had written into the run were not carried " +
1268
+ "(counted, one workflow_journal_foreign_scope_rows_removed line); M26: the retired shared circuit-breaker table circuit_breaker dropped — its keys " +
1269
+ "carried a route's base URL, which could carry a credential from the URL's query, and the whole table is dropped rather than judged row by row; " +
1270
+ "the breakers live in the new table breaker_state, which this step never touches; only the count is logged, never a key; M27: rows of the three " +
1271
+ "mailbox tables (mailbox / mailbox_message / mailbox_tombstone) whose key — scope or handle — the mailbox key judge refuses (control characters, " +
1272
+ "malformed Unicode, wider than 190 UTF-8 bytes, empty scope) removed, a whole box at a time: no entry point reaches such a key any more, so neither " +
1273
+ "delivery, reading nor session deletion would ever have touched them again; only the count is logged, never a key). Each step " +
983
1274
  "runs on every boot until its window closes (the release that introduced it + 30 days; the per-step date is in " +
984
1275
  "removableAfter, null while that release is not published yet) and is deleted in the first minor after that — start a " +
985
1276
  "version that still carries a step at least once before upgrading past its deletion.",
@@ -987,6 +1278,27 @@ export function logOneTimeMigrations(log, backend, report) {
987
1278
  logReceiptRows(log, backend, report);
988
1279
  }
989
1280
  function logReceiptRows(log, backend, r) {
1281
+ if (r.workflowJournalForeignScopeRowsRemoved > 0) {
1282
+ log.warn("workflow_journal_foreign_scope_rows_removed", {
1283
+ backend,
1284
+ rows: r.workflowJournalForeignScopeRowsRemoved,
1285
+ note: "workflow journal rows whose scope differed from the scope that owns their run were removed while the run's owning scope moved into workflow_journal_owner. " +
1286
+ "Earlier versions accepted an append from another scope into a run (a new step was inserted, an existing step overwritten under the owner's scope); the current " +
1287
+ "schema keeps one scope per run, so such rows have nowhere to live. Only the scope that wrote them could read them, and only to replay those steps when resuming " +
1288
+ "that run — such a resume now runs those steps live. No action needed",
1289
+ });
1290
+ }
1291
+ if (r.placedSessionOwnersOrphaned > 0) {
1292
+ log.warn("placed_session_owners_orphaned", {
1293
+ backend,
1294
+ rows: r.placedSessionOwnersOrphaned,
1295
+ note: "delegated (placed) session rows that an earlier version wrote without an owner were given an owner no caller can match, in one of two forms: a row whose root and parent " +
1296
+ "sessions are both gone gets `_orphan:<its own session id>`; a row whose parent is such a row inherits that same value, `_orphan:<that ancestor's session id>` (rows below it " +
1297
+ "inherit it in turn). Their transcripts are no longer readable through the session faces by anyone but fleet-wide and operator callers. They are the rows whose owner " +
1298
+ "starts with `_orphan:` (an owner naming the row's own id had no surviving ancestor; one naming another session came down from that orphaned ancestor); set a real owner " +
1299
+ "in the session store by hand if someone should read one, or leave them to the transcript retention",
1300
+ });
1301
+ }
990
1302
  for (const row of r.undurableScopeRuleRows) {
991
1303
  log.warn("permission_rule_row_removed", {
992
1304
  backend,
@@ -1058,7 +1370,7 @@ function logOneTimeMigrationsInterrupted(log, backend, error) {
1058
1370
  ...readingFields(error.receipts),
1059
1371
  note: "the one-time migrations failed after committing changes: the readings on this line and the lines that follow are what this boot already committed — a retried " +
1060
1372
  "boot finds that work done and does not report it again, so these lines are the only record of them, written once by this boot — save them. The failure itself follows " +
1061
- "as this boot's refusal. A shape reading absent from this line (sessionIncarnationColumn / workflowParkIndexKey / sessionRuleColumn) belongs to a step that did not " +
1373
+ "as this boot's refusal. A shape reading absent from this line (sessionIncarnationColumn / workflowParkIndexKey / sessionRuleColumn / workflowJournalScopeColumn) belongs to a step that did not " +
1062
1374
  "finish; sessionRuleColumn absent after session_rule_row_old_grammar_entries lines means the column still carries its in-progress name — the next boot rewrites the " +
1063
1375
  "remaining rows (rows already rewritten are left alone) and publishes the final name",
1064
1376
  });