@sema-agent/server 7.139.0 → 7.141.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 (328) hide show
  1. package/MIGRATION.md +471 -0
  2. package/README.md +1 -1
  3. package/README.zh-CN.md +1 -1
  4. package/USAGE.md +94 -26
  5. package/dist/adoption/runner.js +1 -1
  6. package/dist/adoption/sql.js +1 -1
  7. package/dist/approval-batch-census.d.ts +27 -0
  8. package/dist/approval-batch-census.js +27 -0
  9. package/dist/approval-card.d.ts +37 -22
  10. package/dist/approval-card.js +6 -3
  11. package/dist/approval.d.ts +15 -0
  12. package/dist/approval.js +10 -0
  13. package/dist/audit.d.ts +4 -2
  14. package/dist/audit.js +2 -2
  15. package/dist/bench/s1/live-deps.js +3 -2
  16. package/dist/bench/s1/row.d.ts +5 -2
  17. package/dist/bench/s1/row.js +2 -22
  18. package/dist/boot/adoption.js +1 -1
  19. package/dist/boot/device-lane.d.ts +11 -0
  20. package/dist/boot/device-lane.js +16 -0
  21. package/dist/boot/engine-lease.d.ts +24 -3
  22. package/dist/boot/engine-lease.js +9 -2
  23. package/dist/boot/execution-env.js +18 -12
  24. package/dist/boot/execution-lane.d.ts +8 -0
  25. package/dist/boot/execution-lane.js +6 -1
  26. package/dist/boot/memory-boundary.d.ts +16 -53
  27. package/dist/boot/memory-boundary.js +10 -20
  28. package/dist/boot/memory-consolidation.d.ts +16 -12
  29. package/dist/boot/memory-consolidation.js +24 -13
  30. package/dist/boot/reapers.d.ts +2 -2
  31. package/dist/boot/resolve-spec.d.ts +3 -5
  32. package/dist/boot/resolve-spec.js +9 -8
  33. package/dist/boot/runner-deps.d.ts +3 -3
  34. package/dist/boot/runner-deps.js +1 -0
  35. package/dist/boot/session-faces.d.ts +1 -1
  36. package/dist/boot/session-faces.js +4 -3
  37. package/dist/boot/shutdown.js +5 -0
  38. package/dist/boot/stage-03-store-backend.d.ts +2 -6
  39. package/dist/boot/stage-03-store-backend.js +2 -3
  40. package/dist/boot/stage-04-stores.d.ts +2 -6
  41. package/dist/boot/stage-04-stores.js +5 -5
  42. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  43. package/dist/boot/stage-05-execution-env.js +3 -1
  44. package/dist/boot/stage-06-runners.d.ts +2 -8
  45. package/dist/boot/stage-06-runners.js +3 -3
  46. package/dist/boot/stage-07-capability-layer.d.ts +2 -7
  47. package/dist/boot/stage-07-capability-layer.js +8 -10
  48. package/dist/boot/stage-08-reapers.d.ts +2 -7
  49. package/dist/boot/stage-09-leader.d.ts +2 -7
  50. package/dist/boot/stage-10-http-server.d.ts +2 -7
  51. package/dist/boot/stage-10-http-server.js +6 -4
  52. package/dist/boot/stores.d.ts +3 -6
  53. package/dist/boot/stores.js +37 -44
  54. package/dist/boot/task-list-lane.d.ts +1 -1
  55. package/dist/boot/task-list-lane.js +1 -1
  56. package/dist/boot/web-admin.js +36 -9
  57. package/dist/capabilities/execution-lane.d.ts +2 -2
  58. package/dist/capabilities/scenarios.d.ts +1 -1
  59. package/dist/capability-facts.d.ts +9 -0
  60. package/dist/capability-facts.js +9 -0
  61. package/dist/card-void.d.ts +38 -0
  62. package/dist/card-void.js +23 -0
  63. package/dist/config-catalog.js +12 -8
  64. package/dist/config-provider.d.ts +18 -17
  65. package/dist/config-provider.js +28 -36
  66. package/dist/config-types.d.ts +36 -9
  67. package/dist/config.js +56 -46
  68. package/dist/delegated-session-owner.d.ts +23 -20
  69. package/dist/delegated-session-owner.js +2 -2
  70. package/dist/deployment-shape.d.ts +9 -8
  71. package/dist/deployment-shape.js +4 -4
  72. package/dist/device-store.d.ts +7 -0
  73. package/dist/device-store.js +3 -0
  74. package/dist/execution-config-root.d.ts +22 -0
  75. package/dist/execution-config-root.js +35 -0
  76. package/dist/execution-lane-caps.d.ts +145 -107
  77. package/dist/execution-lane-caps.js +144 -83
  78. package/dist/execution-placement.d.ts +3 -0
  79. package/dist/execution-placement.js +7 -0
  80. package/dist/fleet/drain-entry.d.ts +3 -0
  81. package/dist/hosted-posture.d.ts +8 -0
  82. package/dist/hosted-posture.js +4 -0
  83. package/dist/http/admission.js +3 -3
  84. package/dist/http/caller-trust.d.ts +3 -2
  85. package/dist/http/caller-trust.js +2 -1
  86. package/dist/http/dispatch.js +4 -0
  87. package/dist/http/live-frame.d.ts +24 -0
  88. package/dist/http/live-frame.js +16 -0
  89. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  90. package/dist/http/memory-compliance-failure-reply.js +5 -0
  91. package/dist/http/memory-layer-reply.d.ts +30 -0
  92. package/dist/http/memory-layer-reply.js +24 -0
  93. package/dist/http/resume-legs.js +49 -35
  94. package/dist/http/retired-service-deps.d.ts +1 -1
  95. package/dist/http/retired-service-deps.js +1 -1
  96. package/dist/http/route-table.js +2 -0
  97. package/dist/http/routes/adoption.js +1 -1
  98. package/dist/http/routes/approvals-assistant.js +1 -0
  99. package/dist/http/routes/capabilities.js +5 -4
  100. package/dist/http/routes/diagnostics.d.ts +0 -19
  101. package/dist/http/routes/diagnostics.js +16 -2
  102. package/dist/http/routes/memory-bundle.d.ts +2 -2
  103. package/dist/http/routes/memory-bundle.js +4 -1
  104. package/dist/http/routes/memory-compliance.js +9 -2
  105. package/dist/http/routes/memory-consolidation.js +12 -1
  106. package/dist/http/routes/memory-origin.js +14 -2
  107. package/dist/http/routes/memory-policy.js +7 -2
  108. package/dist/http/routes/run-permission-mode.d.ts +75 -0
  109. package/dist/http/routes/run-permission-mode.js +170 -0
  110. package/dist/http/routes/session-sync.d.ts +3 -6
  111. package/dist/http/routes/session-sync.js +7 -2
  112. package/dist/http/routes/sessions.js +33 -15
  113. package/dist/http/routes/tasks.js +67 -68
  114. package/dist/http/run-read-face.js +3 -3
  115. package/dist/http/send.d.ts +1 -1
  116. package/dist/http/send.js +2 -2
  117. package/dist/http/server.d.ts +14 -27
  118. package/dist/http/server.js +2 -2
  119. package/dist/http/sse-log.d.ts +3 -8
  120. package/dist/http/stored-body-replay.d.ts +3 -3
  121. package/dist/http/stored-body-replay.js +3 -1
  122. package/dist/identity-source.d.ts +1 -1
  123. package/dist/identity-source.js +1 -1
  124. package/dist/instruction-source-notice.d.ts +7 -0
  125. package/dist/instruction-source-notice.js +6 -6
  126. package/dist/lane-home-probe.d.ts +68 -42
  127. package/dist/lane-home-probe.js +53 -30
  128. package/dist/leader/wire.js +5 -4
  129. package/dist/mcp-assertion-header.d.ts +4 -0
  130. package/dist/mcp-assertion-header.js +5 -0
  131. package/dist/memory-bundle-engine.d.ts +16 -9
  132. package/dist/memory-bundle-engine.js +29 -6
  133. package/dist/memory-operator-faces.d.ts +85 -30
  134. package/dist/memory-operator-faces.js +136 -30
  135. package/dist/memory-posture.d.ts +1 -1
  136. package/dist/memory-project-layer.d.ts +3 -21
  137. package/dist/memory-project-layer.js +11 -8
  138. package/dist/memory-scope-stores.d.ts +73 -0
  139. package/dist/memory-scope-stores.js +86 -0
  140. package/dist/memory-scope.d.ts +33 -9
  141. package/dist/memory-scope.js +17 -5
  142. package/dist/memory-sync-client.d.ts +34 -9
  143. package/dist/memory-sync-client.js +108 -28
  144. package/dist/observability/fail-open.d.ts +30 -2
  145. package/dist/observability/fail-open.js +30 -2
  146. package/dist/observability/metrics.js +0 -1
  147. package/dist/observability/run-terminal.d.ts +6 -2
  148. package/dist/observability/run-terminal.js +25 -26
  149. package/dist/observability/secret-env-scrub.d.ts +20 -1
  150. package/dist/observability/secret-env-scrub.js +14 -0
  151. package/dist/orchestration/workflow-completion-inbox.d.ts +5 -2
  152. package/dist/orchestration/workflow-completion-inbox.js +13 -5
  153. package/dist/orchestration/workflow-notify-journal.d.ts +5 -3
  154. package/dist/orchestration/workflow-notify-journal.js +2 -1
  155. package/dist/permission-mode-admission.d.ts +68 -0
  156. package/dist/permission-mode-admission.js +23 -0
  157. package/dist/permission-rule-list.d.ts +55 -6
  158. package/dist/permission-rule-list.js +52 -5
  159. package/dist/plugins/adoption-log-sql.js +4 -4
  160. package/dist/plugins/approval-ask-store-sql.js +14 -14
  161. package/dist/plugins/approval-nonce-store.js +4 -4
  162. package/dist/plugins/blob-backend.d.ts +4 -4
  163. package/dist/plugins/blob-backend.js +7 -7
  164. package/dist/plugins/breaker-state-sql.js +2 -2
  165. package/dist/plugins/caching-session-store.d.ts +6 -0
  166. package/dist/plugins/caching-session-store.js +3 -0
  167. package/dist/plugins/checkpoint-index-columns.js +2 -2
  168. package/dist/plugins/checkpoint-store-sql.d.ts +65 -17
  169. package/dist/plugins/checkpoint-store-sql.js +90 -19
  170. package/dist/plugins/device-store-sql.d.ts +5 -2
  171. package/dist/plugins/device-store-sql.js +23 -20
  172. package/dist/plugins/ensure-index.d.ts +1 -2
  173. package/dist/plugins/execution-placement-sql.d.ts +19 -9
  174. package/dist/plugins/execution-placement-sql.js +20 -9
  175. package/dist/plugins/file-history-store-sql.d.ts +2 -2
  176. package/dist/plugins/file-history-store-sql.js +4 -4
  177. package/dist/plugins/file-run-store.d.ts +2 -2
  178. package/dist/plugins/file-run-store.js +1 -1
  179. package/dist/plugins/fork-routing-session-store.d.ts +10 -32
  180. package/dist/plugins/fork-routing-session-store.js +2 -61
  181. package/dist/plugins/image-bake-store-sql.js +9 -9
  182. package/dist/plugins/image-index-sql.js +11 -11
  183. package/dist/plugins/k8s-exec-protocol.d.ts +4 -1
  184. package/dist/plugins/k8s-exec-protocol.js +6 -1
  185. package/dist/plugins/leader-run-store-sql.js +4 -4
  186. package/dist/plugins/local-checkpoint-store.d.ts +10 -1
  187. package/dist/plugins/local-checkpoint-store.js +22 -0
  188. package/dist/plugins/local-session-store.d.ts +227 -35
  189. package/dist/plugins/local-session-store.js +518 -37
  190. package/dist/plugins/local-session-window.d.ts +17 -3
  191. package/dist/plugins/local-session-window.js +7 -13
  192. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  193. package/dist/plugins/mailbox-store-sql.js +4 -4
  194. package/dist/plugins/memory-engine-tidb.js +5 -5
  195. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  196. package/dist/plugins/memory-run-store.d.ts +2 -2
  197. package/dist/plugins/memory-run-store.js +1 -1
  198. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  199. package/dist/plugins/one-time-migrations.d.ts +52 -11
  200. package/dist/plugins/one-time-migrations.js +101 -39
  201. package/dist/plugins/org-delivery-quota-store.js +2 -2
  202. package/dist/plugins/outcome-ledger-sql.js +2 -2
  203. package/dist/plugins/permission-rule-store-sql.d.ts +4 -4
  204. package/dist/plugins/permission-rule-store-sql.js +16 -16
  205. package/dist/plugins/pg-pool.js +3 -2
  206. package/dist/plugins/pg-session-storage.d.ts +2 -0
  207. package/dist/plugins/pg-session-storage.js +3 -2
  208. package/dist/plugins/remote-env-adb.d.ts +7 -24
  209. package/dist/plugins/remote-env-adb.js +22 -5
  210. package/dist/plugins/remote-env-device.d.ts +2 -28
  211. package/dist/plugins/remote-env-device.js +2 -1
  212. package/dist/plugins/remote-env-e2b.d.ts +40 -27
  213. package/dist/plugins/remote-env-e2b.js +180 -20
  214. package/dist/plugins/remote-env-file-error.d.ts +1 -1
  215. package/dist/plugins/remote-env-file-error.js +8 -2
  216. package/dist/plugins/remote-env-host.d.ts +19 -35
  217. package/dist/plugins/remote-env-host.js +26 -3
  218. package/dist/plugins/remote-env-k8s.d.ts +22 -14
  219. package/dist/plugins/remote-env-k8s.js +150 -26
  220. package/dist/plugins/remote-env-local-docker.d.ts +27 -24
  221. package/dist/plugins/remote-env-local-docker.js +91 -24
  222. package/dist/plugins/remote-env-ssh.d.ts +4 -24
  223. package/dist/plugins/remote-env-ssh.js +4 -3
  224. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  225. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  226. package/dist/plugins/retention-lane-store-sql.js +2 -2
  227. package/dist/plugins/retention-store-sql.d.ts +1 -1
  228. package/dist/plugins/retention-store-sql.js +6 -6
  229. package/dist/plugins/run-store-sql.d.ts +3 -3
  230. package/dist/plugins/run-store-sql.js +19 -18
  231. package/dist/plugins/sandbox-read-floor.d.ts +273 -0
  232. package/dist/plugins/sandbox-read-floor.js +608 -0
  233. package/dist/plugins/schema-consistency.d.ts +6 -6
  234. package/dist/plugins/schema-consistency.js +1 -1
  235. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  236. package/dist/plugins/schema-ensure-legs.js +2 -2
  237. package/dist/plugins/schema-live-columns.d.ts +1 -1
  238. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  239. package/dist/plugins/session-ownership.d.ts +2 -1
  240. package/dist/plugins/session-placement.d.ts +17 -4
  241. package/dist/plugins/session-placement.js +2 -1
  242. package/dist/plugins/session-rules-column.d.ts +1 -1
  243. package/dist/plugins/session-rules-column.js +1 -1
  244. package/dist/plugins/session-store.d.ts +18 -20
  245. package/dist/plugins/session-store.js +4 -12
  246. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  247. package/dist/plugins/session-workspace-cell.js +1 -1
  248. package/dist/plugins/shared-memory-store-sql.js +2 -2
  249. package/dist/plugins/sql-driver.d.ts +10 -5
  250. package/dist/plugins/sql-driver.js +6 -6
  251. package/dist/plugins/sql-errors.js +1 -1
  252. package/dist/plugins/store-backend.d.ts +35 -6
  253. package/dist/plugins/store-backend.js +27 -10
  254. package/dist/plugins/store-contracts.d.ts +9 -2
  255. package/dist/plugins/task-list-store-sql.js +11 -11
  256. package/dist/plugins/tidb-pool.js +7 -6
  257. package/dist/plugins/tidb-session-storage.js +4 -4
  258. package/dist/plugins/tidb-session-store.d.ts +4 -1
  259. package/dist/plugins/tidb-session-store.js +16 -15
  260. package/dist/plugins/tool-result-store-sql.d.ts +2 -2
  261. package/dist/plugins/tool-result-store-sql.js +3 -3
  262. package/dist/plugins/user-input-ledger-sql.js +2 -2
  263. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  264. package/dist/plugins/workflow-run-store-sql.js +19 -19
  265. package/dist/plugins/write-behind-counter.d.ts +2 -3
  266. package/dist/plugins/write-behind-counter.js +5 -5
  267. package/dist/resource-suspend.d.ts +14 -12
  268. package/dist/resource-suspend.js +1 -1
  269. package/dist/resource-window.d.ts +4 -3
  270. package/dist/run-cancel-context.d.ts +34 -0
  271. package/dist/run-cancel-context.js +7 -0
  272. package/dist/run-local.js +14 -19
  273. package/dist/runs.d.ts +47 -16
  274. package/dist/runs.js +112 -113
  275. package/dist/security.d.ts +2 -2
  276. package/dist/server-secret-env.d.ts +2 -1
  277. package/dist/server-secret-env.js +2 -4
  278. package/dist/session-error-wire.d.ts +22 -4
  279. package/dist/session-error-wire.js +16 -0
  280. package/dist/session-incarnation.d.ts +10 -4
  281. package/dist/session-incarnation.js +1 -1
  282. package/dist/session-policy-wire.d.ts +1 -1
  283. package/dist/session-sync-kernel.d.ts +42 -1
  284. package/dist/session-sync-kernel.js +17 -0
  285. package/dist/session-sync.js +16 -4
  286. package/dist/sql-engine-coordinates.d.ts +49 -0
  287. package/dist/sql-engine-coordinates.js +27 -0
  288. package/dist/sql-engine-posture.d.ts +1 -1
  289. package/dist/sql-engine-posture.js +1 -1
  290. package/dist/standing-answer-grant.d.ts +31 -2
  291. package/dist/standing-answer-grant.js +14 -2
  292. package/dist/store-posture.d.ts +1 -1
  293. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  294. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  295. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  296. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  297. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  298. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  299. package/dist/task-a2a.d.ts +1 -1
  300. package/dist/task-a2a.js +5 -6
  301. package/dist/task-mcp.d.ts +21 -8
  302. package/dist/task-mcp.js +17 -9
  303. package/dist/task-settings.d.ts +52 -25
  304. package/dist/task-settings.js +91 -28
  305. package/dist/terminal.d.ts +103 -19
  306. package/dist/terminal.js +72 -20
  307. package/dist/trace/core-keyset-guard.d.ts +79 -2
  308. package/dist/trace/exit-seat.d.ts +7 -0
  309. package/dist/trace/exit-seat.js +1 -0
  310. package/dist/trace/model-segment.d.ts +2 -2
  311. package/dist/trace/redact.d.ts +3 -1
  312. package/dist/trace/redact.js +395 -110
  313. package/dist/trace/self-minted-wire.d.ts +226 -2
  314. package/dist/trace/self-minted-wire.js +79 -0
  315. package/dist/verifier-pause-card.d.ts +8 -0
  316. package/dist/verifier-pause-card.js +19 -0
  317. package/dist/web-admin-client/apply-effective.d.ts +5 -7
  318. package/dist/web-admin-client/apply-effective.js +2 -2
  319. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  320. package/dist/web-admin-client/candidate-gate.js +1 -1
  321. package/dist/web-admin-client/http-client.d.ts +5 -4
  322. package/dist/web-admin-client/http-client.js +2 -2
  323. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  324. package/dist/web-admin-client/read-warnings.js +5 -9
  325. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +3 -2
  326. package/package.json +2 -2
  327. package/wire-changes/server@7.140.0.json +184 -0
  328. package/wire-changes/server@7.141.0.json +367 -0
@@ -55,6 +55,11 @@ export function buildMigrationReceipts() {
55
55
  executionPlacementsBackfilled: 0,
56
56
  askExecutionLanesBackfilled: 0,
57
57
  executionPlacementSessionsUnreadable: [],
58
+ localArchiveRowFactsBackfilled: 0,
59
+ localArchivesAnonymized: 0,
60
+ localArchivesKeptStamped: [],
61
+ localArchivesUnreadable: [],
62
+ localArchiveSnapshotsRecovered: 0,
58
63
  };
59
64
  }
60
65
  function completedReport(r) {
@@ -100,7 +105,12 @@ function changedAnything(r) {
100
105
  r.deviceBindingTablesDropped > 0 ||
101
106
  r.executionPlacementsBackfilled > 0 ||
102
107
  r.askExecutionLanesBackfilled > 0 ||
103
- r.executionPlacementSessionsUnreadable.length > 0);
108
+ r.executionPlacementSessionsUnreadable.length > 0 ||
109
+ r.localArchiveRowFactsBackfilled > 0 ||
110
+ r.localArchivesAnonymized > 0 ||
111
+ r.localArchivesKeptStamped.length > 0 ||
112
+ r.localArchivesUnreadable.length > 0 ||
113
+ r.localArchiveSnapshotsRecovered > 0);
104
114
  }
105
115
  function readingsSnapshot(r) {
106
116
  return {
@@ -113,6 +123,8 @@ function readingsSnapshot(r) {
113
123
  parkedApprovalCardRowsUnreadable: [...r.parkedApprovalCardRowsUnreadable],
114
124
  parkedApprovalCardRowsTooLong: [...r.parkedApprovalCardRowsTooLong],
115
125
  executionPlacementSessionsUnreadable: [...r.executionPlacementSessionsUnreadable],
126
+ localArchivesKeptStamped: [...r.localArchivesKeptStamped],
127
+ localArchivesUnreadable: [...r.localArchivesUnreadable],
116
128
  };
117
129
  }
118
130
  function receiptSentence(r) {
@@ -381,7 +393,7 @@ export const ONE_TIME_MIGRATIONS = [
381
393
  removal: "删 migrateOn 的 M23 步(`inheritPlacedSessionOwners`:placed ∧ 属主 NULL 的行按 session_id 键集分页单趟扫描 + 同一条语句连读根 / 父两行 + 按认领句回写属主)与 `PLACED_OWNER_SCAN_PAGE` / `placedOwnerScanSql` / `scannedPlacement` / `scannedFaces` / `scannedPlacedSources`(`isOrphanSessionOwner` 若再无别的读者一并删);" +
382
394
  "**同窗同删的另一半**(认领:按律给升级前写下的无主子代行写属主 —— 停驻挂点对宿主无主行做的就是本条对那一行的单行步):`delegated-session-owner.ts` 的 `CLAIM_OWNERLESS_PLACED_SQL` / `claimOwnerlessParams` / `OwnerlessClaim` / " +
383
395
  "`settleDelegatedOwner` 的认领一步(删后它只剩树比对与属主比对)/ `DelegationTreeStore.claimOwnerless`," +
384
- "两只 SQL 店(`tidb-session-store.ts` / `pg-session-storage.ts`)的 `claimOwnerless`、`CachingSessionStore` 的转发、`OwnerAwareSessionStore.claimOwnerless`、`delegatedOwnerHostOf` 对它的「与 `placementOf` 同在或同缺」;" +
396
+ "两只 SQL 店(`tidb-session-store.ts` / `pg-session-storage.ts`)与 local 店(`local-session-store.ts` 的 `claimOwnerless` 字段与 `claimOwnerlessPlaced`)的 `claimOwnerless`、`CachingSessionStore` 的转发、`OwnerAwareSessionStore.claimOwnerless`、`delegatedOwnerHostOf` 对它的「与 `placementOf` 同在或同缺」;" +
385
397
  "报告、日志行与 `NO_SQL_MIGRATIONS` 的 `placedSessionOwnersInherited` / `placedSessionOwnersOrphaned` / `placedSessionOwnersRemaining` 与 `placed_session_owners_orphaned` 那一行 warn;" +
386
398
  "`test/one-time-migrations.test.ts` 的 M23 组与 `test/one-time-migrations-db-integration.test.ts` 的 M23 格。" +
387
399
  "**不随本条删**:属主为 NULL 的 placed 行只对 fleet 调用方放行(`security.ts` 的 `ownerlessRowOpen` / `sessionOwnerCellOf` 与各门读 `placementOf` 的那一次)—— 那是常设的读面律",
@@ -531,6 +543,24 @@ export const ONE_TIME_MIGRATIONS = [
531
543
  "跳过了带本条的版本直接升的库响亮拒启,出路 = 先在带本条的版本起服一次让切换走完,再升;缺执行放置列的库由列集对账缺列拒启(`config.schema_mismatch`,拒句给 ALTER),同一出路",
532
544
  reportKeys: ["executionPlacementColumnsAdded", "deviceBindingsMoved", "deviceBindingsOrphaned", "deviceBindingTablesDropped", "executionPlacementsBackfilled", "askExecutionLanesBackfilled", "executionPlacementSessionsUnreadable"],
533
545
  },
546
+ {
547
+ id: "M33",
548
+ introducedIn: "7.141.0",
549
+ releasedOn: "2026-10-30",
550
+ removableAfter: "2026-11-29",
551
+ removal: "删 `LocalSessionStore.migrateLocalArchives` 与它的私有助手 `anonymizeArchiveHeld` / `fillRowFacts`、`settleArchiveSnapshots` 里除最后一句常设臂 `assertNoArchiveSnapshots(dir)` 以外的一切(逐份消化的循环与 `settleArchiveSnapshot`)、" +
552
+ "`archiveSnapshotsSettled` 字段、`syncArchiveDirectory`、`rowFactsFilled` / `ArchivedRowFacts` / `archivedRowFactsOf` / `ARCHIVE_SNAPSHOT_RECORD` / `ArchiveSnapshotRecord` / `readArchiveSnapshotRecord` / `sameLog` / " +
553
+ "`ArchiveRewriteStep` / `LocalArchiveMigrationReading` / `errnoOf`、" +
554
+ "`archiveRewriteReadScope` / `noteReplayInArchiveRewriteRead` 与 `createLocalSessionRepo` 座里调它的那一腿、`SidecarMap.syncToDisk`(若再无别的调用方)与 `fsyncPaths`(同);" +
555
+ "`store-backend.ts` 的 `LocalBackend.runOneTimeMigrations` 里对 `migrateLocalArchives` 的调用与报告五键的那五行;本文件的 `LocalArchiveKeptReason` / `LocalArchiveKeptStamped` / `LocalArchiveUnreadable`、" +
556
+ "报告 / 收据 / `changedAnything` / `readingsSnapshot` / `readingFields` 里的 `localArchiveRowFactsBackfilled` / `localArchivesAnonymized` / `localArchivesKeptStamped` / `localArchivesUnreadable` / `localArchiveSnapshotsRecovered`、" +
557
+ "`local_archive_kept_stamped` / `local_archive_unreadable` 两行 warn、读数行 note 里的 M33 句与头注 M33 段;`test/local-archive-owner.test.ts` 里标题含 `P3` / `P4` / `P5` / `P6` / `F1` / `F2` / `F3` 的格与 `legacyRoot` / `writeArchive` / `boot` / `M33_ZERO` / `m33Of` 助手。" +
558
+ "**不随本条删**:行格的 `createdAt` / `forkedFrom` 两键(常设的数据形)、`LocalArchiveSnapshotsPresentError` / `assertNoArchiveSnapshots` / `LOCAL_ARCHIVE_SNAPSHOT_DIR` 与 `LocalBackend.ensureSchema` 里调它的那一句、" +
559
+ "`session-error-wire.ts` 的 `StampedSessionArchiveError` / `STAMPED_SESSION_ARCHIVE_SENTENCE` / `sessionErrorWireOf` 与本店两处带戳判(fork 的源、改日志推送的落点)、行上缺建档时刻的 F 类读法(`server.local-session-row.created-at-absent`)",
560
+ afterRemoval: "三只常设臂接住:① 数据根下的快照目录非空 ⇒ 拒启(`config.local_archive_snapshot_present`,点名文件与两条出路);② 跳过了带本条的版本直接升的数据根(或本条留着的存档),带属主戳的存档被 fork 与改日志的推送同一只具名拒(409 `conflict` + 固定句,零写 / 行格净零写)," +
561
+ "读 / 续跑 / 拉取照常 —— 出路 = 删掉会话后重推,或升级时先过一次带本条的版本并在窗内起服;③ 行上没有建档时刻的格(同一类数据根)⇒ 列表 / 会话视图 / 交出的元数据读纪元(F 类 tag 计数)",
562
+ reportKeys: ["localArchiveRowFactsBackfilled", "localArchivesAnonymized", "localArchivesKeptStamped", "localArchivesUnreadable", "localArchiveSnapshotsRecovered"],
563
+ },
534
564
  ];
535
565
  export function migrationWindowOpen(window, today) {
536
566
  return window.removableAfter === null || today <= window.removableAfter;
@@ -602,7 +632,7 @@ function pgDb(client) {
602
632
  };
603
633
  }
604
634
  async function tableExists(db, dialect, table) {
605
- const sql = dialect === "tidb"
635
+ const sql = dialect === "mysql"
606
636
  ? `SELECT 1 AS present FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = '${table}'`
607
637
  : `SELECT 1 AS present FROM information_schema.tables WHERE table_schema = current_schema() AND table_name = '${table}'`;
608
638
  return (await db.rows(sql)).length > 0;
@@ -617,12 +647,12 @@ async function execUntil(db, sql, postcondition) {
617
647
  }
618
648
  }
619
649
  async function columnExists(db, dialect, table, column) {
620
- const scope = dialect === "tidb" ? "DATABASE()" : "current_schema()";
650
+ const scope = dialect === "mysql" ? "DATABASE()" : "current_schema()";
621
651
  const sql = `SELECT 1 AS present FROM information_schema.columns WHERE table_schema = ${scope} AND table_name = '${table}' AND column_name = '${column}'`;
622
652
  return (await db.rows(sql)).length > 0;
623
653
  }
624
654
  export async function runSqlOneTimeMigrations(dialect, pool, input) {
625
- return dialect === "tidb" ? withMysqlLock(pool, (db) => migrateOn(db, "tidb", input)) : withPgLock(pool, (db) => migrateOn(db, "pg", input));
655
+ return dialect === "mysql" ? withMysqlLock(pool, (db) => migrateOn(db, "mysql", input)) : withPgLock(pool, (db) => migrateOn(db, "pg", input));
626
656
  }
627
657
  async function withMysqlLock(pool, run) {
628
658
  const conn = await pool.getConnection();
@@ -663,10 +693,10 @@ async function migrateOn(db, dialect, input) {
663
693
  return completedReport(receipts);
664
694
  }
665
695
  async function migrateStepsOn(db, dialect, receipts, input) {
666
- receipts.holderDeathCodeRowsBackfilled += await db.exec(dialect === "tidb"
696
+ receipts.holderDeathCodeRowsBackfilled += await db.exec(dialect === "mysql"
667
697
  ? "UPDATE task_run SET error_code = ? WHERE status = 'failed' AND error_code IS NULL AND error = ?"
668
698
  : "UPDATE task_run SET error_code = $1 WHERE status = 'failed' AND error_code IS NULL AND error = $2", [RUN_HOLDER_DEATH_CODES.stalled, RUN_STALLED_ERROR]);
669
- receipts.holderDeathCodeRowsBackfilled += await db.exec(dialect === "tidb"
699
+ receipts.holderDeathCodeRowsBackfilled += await db.exec(dialect === "mysql"
670
700
  ? "UPDATE task_run SET error_code = ? WHERE status = 'failed' AND error_code IS NULL AND (error = ? OR error LIKE ?)"
671
701
  : "UPDATE task_run SET error_code = $1 WHERE status = 'failed' AND error_code IS NULL AND (error = $2 OR error LIKE $3)", [RUN_HOLDER_DEATH_CODES.orphaned, RUN_ORPHANED_ERROR, `${RUN_ORPHANED_ERROR} (%`]);
672
702
  await migrateShadowedRuleKey(db, dialect, receipts);
@@ -727,7 +757,7 @@ function switchComplete(s) {
727
757
  return s.placementColumns.size === SESSION_META_PLACEMENT_COLUMNS.length && (!s.askTable || s.askLaneColumn) && !s.bindingTable && !s.parkedTable;
728
758
  }
729
759
  function askLaneBackfillSql(dialect) {
730
- return dialect === "tidb"
760
+ return dialect === "mysql"
731
761
  ? `UPDATE ${APPROVAL_ASK_TABLE} a LEFT JOIN session_meta sm ON sm.session_id = a.session_id AND sm.placement_kind IS NULL SET a.execution_lane = COALESCE(sm.execution_lane, ?) WHERE a.execution_lane = ''`
732
762
  : `UPDATE ${APPROVAL_ASK_TABLE} a SET execution_lane = COALESCE((SELECT sm.execution_lane FROM session_meta sm WHERE sm.session_id = a.session_id AND sm.placement_kind IS NULL), $1) WHERE a.execution_lane = ''`;
733
763
  }
@@ -735,7 +765,7 @@ async function parkedRelationNamesWithRetiredPrefix(db) {
735
765
  const rows = await db.rows(`SELECT i.relname AS name FROM pg_index x JOIN pg_class i ON i.oid = x.indexrelid JOIN pg_class t ON t.oid = x.indrelid WHERE t.relname = '${PARKED_DEVICE_BINDING_TABLE}' AND t.relnamespace = current_schema()::regnamespace AND i.relname LIKE '${RETIRED_DEVICE_BINDING_TABLE}\\_%'`);
736
766
  return rows.map((r) => String(r["name"])).filter((name) => name.startsWith(`${RETIRED_DEVICE_BINDING_TABLE}_`) && !name.startsWith(PARKED_DEVICE_BINDING_TABLE));
737
767
  }
738
- const bindingRootKey = (dialect, alias) => (dialect === "tidb" ? `CONVERT(${alias}.root_session_id USING utf8mb4) COLLATE utf8mb4_bin` : `${alias}.root_session_id`);
768
+ const bindingRootKey = (dialect, alias) => (dialect === "mysql" ? `CONVERT(${alias}.root_session_id USING utf8mb4) COLLATE utf8mb4_bin` : `${alias}.root_session_id`);
739
769
  async function refuseOnSwitchFence(read, dialect, shape) {
740
770
  if (!switchStarted(shape) || !shape.bindingTable)
741
771
  return;
@@ -744,7 +774,7 @@ async function refuseOnSwitchFence(read, dialect, shape) {
744
774
  return;
745
775
  const roots = rows.slice(0, FENCE_ROOTS_SHOWN).map((r) => (Buffer.isBuffer(r["root"]) ? r["root"].toString("utf8") : String(r["root"])));
746
776
  const more = rows.length > FENCE_ROOTS_SHOWN ? ` (and more — the table holds over ${FENCE_ROOTS_SHOWN} rows)` : "";
747
- const rename = dialect === "tidb" ? `RENAME TABLE ${RETIRED_DEVICE_BINDING_TABLE} TO ${PARKED_DEVICE_BINDING_TABLE};` : `ALTER TABLE ${RETIRED_DEVICE_BINDING_TABLE} RENAME TO ${PARKED_DEVICE_BINDING_TABLE};`;
777
+ const rename = dialect === "mysql" ? `RENAME TABLE ${RETIRED_DEVICE_BINDING_TABLE} TO ${PARKED_DEVICE_BINDING_TABLE};` : `ALTER TABLE ${RETIRED_DEVICE_BINDING_TABLE} RENAME TO ${PARKED_DEVICE_BINDING_TABLE};`;
748
778
  const adopt = shape.parkedTable
749
779
  ? `DELETE FROM ${PARKED_DEVICE_BINDING_TABLE} WHERE root_session_id IN (SELECT root_session_id FROM ${RETIRED_DEVICE_BINDING_TABLE}); INSERT INTO ${PARKED_DEVICE_BINDING_TABLE} (${BINDING_COLUMNS}) SELECT ${BINDING_COLUMNS} FROM ${RETIRED_DEVICE_BINDING_TABLE}; DELETE FROM ${RETIRED_DEVICE_BINDING_TABLE};`
750
780
  : rename;
@@ -795,10 +825,10 @@ export async function assertExecutionPlacementSwitchAdmissible(read, dialect, in
795
825
  await refuseOnAmbiguousBackfill(read, dialect, shape, input.lanes);
796
826
  }
797
827
  export async function assertExecutionPlacementSwitchAdmissibleOnPool(dialect, pool, input) {
798
- if (dialect === "tidb") {
828
+ if (dialect === "mysql") {
799
829
  const conn = await pool.getConnection();
800
830
  try {
801
- await assertExecutionPlacementSwitchAdmissible(async (sql) => (await conn.query(sql))[0], "tidb", input);
831
+ await assertExecutionPlacementSwitchAdmissible(async (sql) => (await conn.query(sql))[0], "mysql", input);
802
832
  }
803
833
  finally {
804
834
  conn.release();
@@ -830,7 +860,7 @@ const EXECUTION_PLACEMENT_SWITCH_STEPS = [
830
860
  const s = await shape();
831
861
  if (!s.bindingTable || s.placementColumns.has("execution_role"))
832
862
  return;
833
- const sql = dialect === "tidb" ? `RENAME TABLE ${RETIRED_DEVICE_BINDING_TABLE} TO ${PARKED_DEVICE_BINDING_TABLE}` : `ALTER TABLE ${RETIRED_DEVICE_BINDING_TABLE} RENAME TO ${PARKED_DEVICE_BINDING_TABLE}`;
863
+ const sql = dialect === "mysql" ? `RENAME TABLE ${RETIRED_DEVICE_BINDING_TABLE} TO ${PARKED_DEVICE_BINDING_TABLE}` : `ALTER TABLE ${RETIRED_DEVICE_BINDING_TABLE} RENAME TO ${PARKED_DEVICE_BINDING_TABLE}`;
834
864
  await execUntil(db, sql, async () => {
835
865
  const after = await shape();
836
866
  return after.parkedTable && !after.bindingTable;
@@ -862,7 +892,7 @@ const EXECUTION_PLACEMENT_SWITCH_STEPS = [
862
892
  }
863
893
  const ask = await shape();
864
894
  if (ask.askTable && !ask.askLaneColumn) {
865
- await execUntil(db, `ALTER TABLE ${APPROVAL_ASK_TABLE} ADD COLUMN ${APPROVAL_ASK_EXECUTION_LANE_COLUMN[dialect === "tidb" ? "mysql" : "pg"]}`, async () => (await shape()).askLaneColumn);
895
+ await execUntil(db, `ALTER TABLE ${APPROVAL_ASK_TABLE} ADD COLUMN ${APPROVAL_ASK_EXECUTION_LANE_COLUMN[dialect]}`, async () => (await shape()).askLaneColumn);
866
896
  receipts.executionPlacementColumnsAdded += 1;
867
897
  }
868
898
  },
@@ -973,7 +1003,7 @@ async function dropRejectedMailboxKeys(db, dialect, receipts) {
973
1003
  for (const table of MAILBOX_KEYED_TABLES)
974
1004
  if (await tableExists(db, dialect, table))
975
1005
  present.push(table);
976
- const p = (i) => (dialect === "tidb" ? "?" : `$${i}`);
1006
+ const p = (i) => (dialect === "mysql" ? "?" : `$${i}`);
977
1007
  for (const table of present) {
978
1008
  let after;
979
1009
  for (;;) {
@@ -1007,14 +1037,14 @@ const M24_OWNER_INCARNATION = "m24-backfill";
1007
1037
  const M24_COPY_BATCH_BYTES = 32 * 1024 * 1024;
1008
1038
  const M24_COPY_SCAN_PAGE = 1000;
1009
1039
  function journalKeyBound(dialect, op, first) {
1010
- const p = (i) => (dialect === "tidb" ? "?" : `$${first + i}`);
1040
+ const p = (i) => (dialect === "mysql" ? "?" : `$${first + i}`);
1011
1041
  return op === "after"
1012
1042
  ? `(${WORKFLOW_JOURNAL_TABLE}.run_id > ${p(0)} OR (${WORKFLOW_JOURNAL_TABLE}.run_id = ${p(1)} AND ordinal > ${p(2)}))`
1013
1043
  : `(${WORKFLOW_JOURNAL_TABLE}.run_id < ${p(0)} OR (${WORKFLOW_JOURNAL_TABLE}.run_id = ${p(1)} AND ordinal <= ${p(2)}))`;
1014
1044
  }
1015
1045
  async function copyJournalInBatches(db, dialect) {
1016
1046
  const T = WORKFLOW_JOURNAL_TABLE;
1017
- const bytes = dialect === "tidb" ? "LENGTH(result)" : "OCTET_LENGTH(result)";
1047
+ const bytes = dialect === "mysql" ? "LENGTH(result)" : "OCTET_LENGTH(result)";
1018
1048
  const copyHead = `INSERT INTO ${WORKFLOW_JOURNAL_STAGING} (run_id, incarnation, ordinal, call_key, result, created_at_ms) ` +
1019
1049
  `SELECT ${T}.run_id, o.incarnation, ordinal, call_key, ${jsonColumnText(dialect, "result")}, created_at_ms FROM ${T} ` +
1020
1050
  `JOIN ${WORKFLOW_JOURNAL_OWNER_TABLE} o ON o.run_id = ${T}.run_id AND o.scope = ${T}.${RETIRED_JOURNAL_SCOPE_COLUMN} WHERE `;
@@ -1053,7 +1083,7 @@ export async function moveWorkflowJournalScope(db, dialect, receipts) {
1053
1083
  receipts.workflowJournalScopeColumn = "current";
1054
1084
  return;
1055
1085
  }
1056
- const ddl = (dialect === "tidb" ? SCHEMA_STATEMENTS : PG_SCHEMA_STATEMENTS).find((stmt) => stmt.includes(`CREATE TABLE IF NOT EXISTS ${T} (`));
1086
+ const ddl = (dialect === "mysql" ? SCHEMA_STATEMENTS : PG_SCHEMA_STATEMENTS).find((stmt) => stmt.includes(`CREATE TABLE IF NOT EXISTS ${T} (`));
1057
1087
  if (ddl === undefined)
1058
1088
  throw new Error(`one-time migration M24: the central schema carries no definition of ${T} — refusing to rebuild a table it cannot define`);
1059
1089
  if (await exists(S))
@@ -1067,7 +1097,7 @@ export async function moveWorkflowJournalScope(db, dialect, receipts) {
1067
1097
  await db.exec(ddl.replace(`CREATE TABLE IF NOT EXISTS ${T} (`, `CREATE TABLE IF NOT EXISTS ${S} (`));
1068
1098
  await copyJournalInBatches(db, dialect);
1069
1099
  const swapped = async () => !(await exists(S)) && (await exists(T)) && !(await legacy());
1070
- if (dialect === "tidb") {
1100
+ if (dialect === "mysql") {
1071
1101
  await execUntil(db, `RENAME TABLE ${T} TO ${O}, ${S} TO ${T}`, swapped);
1072
1102
  receipts.workflowJournalForeignScopeRowsRemoved += foreign;
1073
1103
  await execUntil(db, `DROP TABLE IF EXISTS ${O}`, async () => !(await exists(O)));
@@ -1110,9 +1140,9 @@ const M17_PROVEN_GRAMMAR = 1;
1110
1140
  const RETIRED_BUCKET_PATTERNS = ['%"commandDeny"%', '%"commandAllow"%'];
1111
1141
  async function scanSessionRuleRows(db, dialect, column, patterns, visit) {
1112
1142
  const text = jsonColumnTextExpr(dialect, column);
1113
- const prefilter = patterns.length === 0 ? "" : ` AND (${patterns.map((_, i) => `${text} LIKE ${dialect === "tidb" ? "?" : `$${i + 2}`}`).join(" OR ")})`;
1114
- const scan = dialect === "tidb"
1115
- ? `SELECT policy_key, session_id, rev, ${jsonColumnText("tidb", column)} FROM ${SESSION_POLICY_TABLE} WHERE policy_key > ?${prefilter} ORDER BY policy_key LIMIT ${SESSION_RULES_SCAN_PAGE}`
1143
+ const prefilter = patterns.length === 0 ? "" : ` AND (${patterns.map((_, i) => `${text} LIKE ${dialect === "mysql" ? "?" : `$${i + 2}`}`).join(" OR ")})`;
1144
+ const scan = dialect === "mysql"
1145
+ ? `SELECT policy_key, session_id, rev, ${jsonColumnText("mysql", column)} FROM ${SESSION_POLICY_TABLE} WHERE policy_key > ?${prefilter} ORDER BY policy_key LIMIT ${SESSION_RULES_SCAN_PAGE}`
1116
1146
  : `SELECT policy_key, session_id, rev, ${jsonColumnText("pg", column)} FROM ${SESSION_POLICY_TABLE} WHERE policy_key > $1${prefilter} ORDER BY policy_key LIMIT ${SESSION_RULES_SCAN_PAGE}`;
1117
1147
  let cursor = "";
1118
1148
  for (;;) {
@@ -1137,7 +1167,7 @@ async function scanSessionRuleRows(db, dialect, column, patterns, visit) {
1137
1167
  async function judgeOldGrammarRows(db, dialect, receipts) {
1138
1168
  const column = OLD_GRAMMAR_SESSION_RULES_COLUMN;
1139
1169
  const text = jsonColumnTextExpr(dialect, column);
1140
- const cas = dialect === "tidb"
1170
+ const cas = dialect === "mysql"
1141
1171
  ? `UPDATE ${SESSION_POLICY_TABLE} SET ${column} = ? WHERE policy_key = ? AND CAST(${text} AS BINARY) = CAST(? AS BINARY)`
1142
1172
  : `UPDATE ${SESSION_POLICY_TABLE} SET ${column} = $1::jsonb WHERE policy_key = $2 AND ${text} = $3`;
1143
1173
  await scanSessionRuleRows(db, dialect, column, [], async (row) => {
@@ -1188,12 +1218,12 @@ export async function migrateSessionRuleGrammar(db, dialect, receipts) {
1188
1218
  });
1189
1219
  }
1190
1220
  function renameSessionRulesColumnSql(dialect, from, to) {
1191
- const definition = SESSION_RULES_COLUMN.tidb.slice(SESSION_RULES_COLUMN.name.length);
1192
- return dialect === "tidb" ? `ALTER TABLE ${SESSION_POLICY_TABLE} CHANGE COLUMN ${from} ${to}${definition}` : `ALTER TABLE ${SESSION_POLICY_TABLE} RENAME COLUMN ${from} TO ${to}`;
1221
+ const definition = SESSION_RULES_COLUMN.mysql.slice(SESSION_RULES_COLUMN.name.length);
1222
+ return dialect === "mysql" ? `ALTER TABLE ${SESSION_POLICY_TABLE} CHANGE COLUMN ${from} ${to}${definition}` : `ALTER TABLE ${SESSION_POLICY_TABLE} RENAME COLUMN ${from} TO ${to}`;
1193
1223
  }
1194
1224
  const WORKFLOW_PARK_INDEX_TABLE = "workflow_agent_session";
1195
1225
  async function primaryKeyWidth(db, dialect, table) {
1196
- const sql = dialect === "tidb"
1226
+ const sql = dialect === "mysql"
1197
1227
  ? `SELECT COUNT(*) AS n FROM information_schema.key_column_usage WHERE table_schema = DATABASE() AND table_name = '${table}' AND constraint_name = 'PRIMARY'`
1198
1228
  : `SELECT COUNT(*) AS n FROM information_schema.table_constraints tc JOIN information_schema.key_column_usage kcu ` +
1199
1229
  `ON kcu.constraint_name = tc.constraint_name AND kcu.table_schema = tc.table_schema AND kcu.table_name = tc.table_name ` +
@@ -1207,10 +1237,10 @@ async function reshapeWorkflowParkIndex(db, dialect, receipts) {
1207
1237
  const S = WORKFLOW_PARK_INDEX_STAGING;
1208
1238
  const O = WORKFLOW_PARK_INDEX_RETIRED;
1209
1239
  const exists = (t) => tableExists(db, dialect, t);
1210
- const ddl = (dialect === "tidb" ? SCHEMA_STATEMENTS : PG_SCHEMA_STATEMENTS).find((stmt) => stmt.includes(`CREATE TABLE IF NOT EXISTS ${T} (`));
1240
+ const ddl = (dialect === "mysql" ? SCHEMA_STATEMENTS : PG_SCHEMA_STATEMENTS).find((stmt) => stmt.includes(`CREATE TABLE IF NOT EXISTS ${T} (`));
1211
1241
  if (ddl === undefined)
1212
1242
  throw new Error(`one-time migration M9: the central schema carries no definition of ${T} — refusing to reshape a table it cannot rebuild`);
1213
- const copyPointers = (to, from) => db.exec(dialect === "tidb"
1243
+ const copyPointers = (to, from) => db.exec(dialect === "mysql"
1214
1244
  ? `INSERT IGNORE INTO ${to} (session_id, run_id, agent_key, scope) SELECT session_id, run_id, agent_key, scope FROM ${from}`
1215
1245
  : `INSERT INTO ${to} (session_id, run_id, agent_key, scope) SELECT session_id, run_id, agent_key, scope FROM ${from} ON CONFLICT DO NOTHING`);
1216
1246
  const resumed = await exists(S);
@@ -1230,7 +1260,7 @@ async function reshapeWorkflowParkIndex(db, dialect, receipts) {
1230
1260
  await db.exec(ddl.replace(`CREATE TABLE IF NOT EXISTS ${T} (`, `CREATE TABLE IF NOT EXISTS ${S} (`));
1231
1261
  receipts.workflowParkIndexRowsCarried += await copyPointers(S, T);
1232
1262
  const swapped = async () => !(await exists(S)) && (await exists(T)) && (await primaryKeyWidth(db, dialect, T)) !== 1;
1233
- if (dialect === "tidb") {
1263
+ if (dialect === "mysql") {
1234
1264
  await execUntil(db, `RENAME TABLE ${T} TO ${O}, ${S} TO ${T}`, swapped);
1235
1265
  await execUntil(db, `DROP TABLE IF EXISTS ${O}`, async () => !(await exists(O)));
1236
1266
  }
@@ -1260,7 +1290,7 @@ async function execAtomically(db, statements, postcondition) {
1260
1290
  }
1261
1291
  }
1262
1292
  async function finishParkIndexShape(db, dialect) {
1263
- await ensureIndexes(async (sql, params) => (dialect === "tidb" ? [await db.rows(sql, params)] : { rows: await db.rows(sql, params) }), dialect === "tidb" ? "mysql" : "pg", CENTRAL_SCHEMA_INDEXES.filter((spec) => spec.table === WORKFLOW_PARK_INDEX_TABLE));
1293
+ await ensureIndexes(async (sql, params) => (dialect === "mysql" ? [await db.rows(sql, params)] : { rows: await db.rows(sql, params) }), dialect, CENTRAL_SCHEMA_INDEXES.filter((spec) => spec.table === WORKFLOW_PARK_INDEX_TABLE));
1264
1294
  if (dialect !== "pg")
1265
1295
  return;
1266
1296
  const conventional = `${WORKFLOW_PARK_INDEX_TABLE}_pkey`;
@@ -1293,7 +1323,7 @@ async function migrateShadowedRuleKey(db, dialect, receipts) {
1293
1323
  const like = `%"${RETIRED_SHADOWED_RULE_KEY}"%`;
1294
1324
  let after = "";
1295
1325
  for (;;) {
1296
- const rows = await db.rows(dialect === "tidb"
1326
+ const rows = await db.rows(dialect === "mysql"
1297
1327
  ? `SELECT token, ${jsonColumnText(dialect, "risk_descriptor")} FROM checkpoint WHERE ${jsonColumnTextExpr(dialect, "risk_descriptor")} LIKE ? AND token > ? ORDER BY token LIMIT ${SHADOWED_RULE_BATCH}`
1298
1328
  : `SELECT token, ${jsonColumnText(dialect, "risk_descriptor")} FROM checkpoint WHERE ${jsonColumnTextExpr(dialect, "risk_descriptor")} LIKE $1 AND token > $2 ORDER BY token LIMIT ${SHADOWED_RULE_BATCH}`, [like, after]);
1299
1329
  for (const r of rows) {
@@ -1309,7 +1339,7 @@ async function migrateShadowedRuleKey(db, dialect, receipts) {
1309
1339
  }
1310
1340
  if (next.kind === "untouched")
1311
1341
  continue;
1312
- receipts.allowRuleShadowRowsMigrated += await db.exec(dialect === "tidb"
1342
+ receipts.allowRuleShadowRowsMigrated += await db.exec(dialect === "mysql"
1313
1343
  ? `UPDATE checkpoint SET risk_descriptor = ? WHERE token = ? AND ${jsonColumnTextExpr(dialect, "risk_descriptor")} = ?`
1314
1344
  : `UPDATE checkpoint SET risk_descriptor = $1 WHERE token = $2 AND ${jsonColumnTextExpr(dialect, "risk_descriptor")} = $3`, [next.text, token, text]);
1315
1345
  }
@@ -1342,7 +1372,7 @@ async function migrateRetiredCardWords(db, dialect, receipts) {
1342
1372
  const cardText = jsonColumnTextExpr(dialect, "card_json");
1343
1373
  let after = "";
1344
1374
  for (;;) {
1345
- const rows = await db.rows(dialect === "tidb"
1375
+ const rows = await db.rows(dialect === "mysql"
1346
1376
  ? `SELECT ask_id, ${jsonColumnText(dialect, "card_json")} FROM approval_ask WHERE (${cardText} LIKE ? OR ${cardText} LIKE ?) AND ask_id > ? ORDER BY ask_id LIMIT ${RETIRED_CARD_WORD_BATCH}`
1347
1377
  : `SELECT ask_id, ${jsonColumnText(dialect, "card_json")} FROM approval_ask WHERE (${cardText} LIKE $1 OR ${cardText} LIKE $2) AND ask_id > $3 ORDER BY ask_id LIMIT ${RETIRED_CARD_WORD_BATCH}`, [...RETIRED_CARD_WORD_PATTERNS, after]);
1348
1378
  for (const r of rows) {
@@ -1358,7 +1388,7 @@ async function migrateRetiredCardWords(db, dialect, receipts) {
1358
1388
  }
1359
1389
  if (next.kind === "untouched")
1360
1390
  continue;
1361
- receipts.retiredCardWordAskRowsRewritten += await db.exec(dialect === "tidb"
1391
+ receipts.retiredCardWordAskRowsRewritten += await db.exec(dialect === "mysql"
1362
1392
  ? `UPDATE approval_ask SET card_json = ? WHERE ask_id = ? AND CAST(${cardText} AS BINARY) = CAST(? AS BINARY)`
1363
1393
  : `UPDATE approval_ask SET card_json = $1 WHERE ask_id = $2 AND ${cardText} = $3`, [next.text, askId, text]);
1364
1394
  }
@@ -1398,7 +1428,7 @@ function m21Subtree(cell, where) {
1398
1428
  }
1399
1429
  }
1400
1430
  export async function repairParkedRows(db, dialect, receipts) {
1401
- const ph = (n) => (dialect === "tidb" ? "?" : `$${n}`);
1431
+ const ph = (n) => (dialect === "mysql" ? "?" : `$${n}`);
1402
1432
  const kinds = APPROVAL_GATE_KINDS;
1403
1433
  const kindSlots = kinds.map((_, i) => ph(i + 1)).join(", ");
1404
1434
  const scan = `SELECT token, session_id, gate_kind, ${jsonColumnText(dialect, "checkpoint", M21_GATE)}, ${jsonColumnText(dialect, "checkpoint", M21_PENDING_ACTION)} FROM checkpoint ` +
@@ -1441,7 +1471,7 @@ export async function repairParkedRows(db, dialect, receipts) {
1441
1471
  continue;
1442
1472
  }
1443
1473
  const next = encode({ ...doc, gate: { ...gate, approvalCard: QUESTION_CARD } }, "checkpoint (M21 question-card repair)");
1444
- receipts.parkedQuestionCardsWritten += await db.exec(dialect === "tidb"
1474
+ receipts.parkedQuestionCardsWritten += await db.exec(dialect === "mysql"
1445
1475
  ? `UPDATE checkpoint SET checkpoint = ? WHERE token = ? AND status = 'pending' AND CAST(${docText} AS BINARY) = CAST(? AS BINARY)`
1446
1476
  : `UPDATE checkpoint SET checkpoint = $1::jsonb WHERE token = $2 AND status = 'pending' AND ${docText} = $3`, [next, token, String(full.checkpoint)]);
1447
1477
  }
@@ -1454,7 +1484,7 @@ const readM29Document = createObjectColumnReader();
1454
1484
  export async function backfillParkedApprovalCards(db, dialect, receipts) {
1455
1485
  if (!(await columnExists(db, dialect, "checkpoint", "approval_card")))
1456
1486
  return;
1457
- const ph = (n) => (dialect === "tidb" ? "?" : `$${n}`);
1487
+ const ph = (n) => (dialect === "mysql" ? "?" : `$${n}`);
1458
1488
  const kinds = APPROVAL_GATE_KINDS;
1459
1489
  const scan = `SELECT token FROM checkpoint WHERE status = 'pending' AND approval_card IS NULL AND gate_kind IN (${kinds.map((_, i) => ph(i + 1)).join(", ")}) ` +
1460
1490
  `AND token > ${ph(kinds.length + 1)} ORDER BY token LIMIT ${PARKED_CARD_BACKFILL_PAGE}`;
@@ -1510,7 +1540,7 @@ function placedOwnerScanSql(dialect) {
1510
1540
  "p.session_id AS parent_row, p.owner AS parent_owner, p.incarnation AS parent_incarnation, p.placement_kind AS parent_kind, " +
1511
1541
  "c.placement_root_session_id AS root_id, c.placement_parent_session_id AS parent_id " +
1512
1542
  "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, '') " +
1513
- `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}`);
1543
+ `WHERE c.placement_kind IS NOT NULL AND c.owner IS NULL AND c.session_id > ${dialect === "mysql" ? "?" : "$1"} ORDER BY c.session_id LIMIT ${PLACED_OWNER_SCAN_PAGE}`);
1514
1544
  }
1515
1545
  function scannedPlacement(r) {
1516
1546
  return {
@@ -1602,6 +1632,11 @@ function readingFields(r) {
1602
1632
  executionPlacementsBackfilled: r.executionPlacementsBackfilled,
1603
1633
  askExecutionLanesBackfilled: r.askExecutionLanesBackfilled,
1604
1634
  executionPlacementSessionsUnreadable: r.executionPlacementSessionsUnreadable.length,
1635
+ localArchiveRowFactsBackfilled: r.localArchiveRowFactsBackfilled,
1636
+ localArchivesAnonymized: r.localArchivesAnonymized,
1637
+ localArchivesKeptStamped: r.localArchivesKeptStamped.length,
1638
+ localArchivesUnreadable: r.localArchivesUnreadable.length,
1639
+ localArchiveSnapshotsRecovered: r.localArchiveSnapshotsRecovered,
1605
1640
  };
1606
1641
  }
1607
1642
  export function logOneTimeMigrations(log, backend, report) {
@@ -1650,7 +1685,12 @@ export function logOneTimeMigrations(log, backend, report) {
1650
1685
  "M32: session↔device bindings moved from the retired table device_session onto the root session rows (the table is first renamed device_session_retired, " +
1651
1686
  "the eight execution placement columns are added to session_meta, every binding overwrites the device placement of its root session row — a binding whose " +
1652
1687
  "root session row is gone is dropped and counted, named by a device_bindings_orphaned line — and the renamed table is dropped), and session rows that have " +
1653
- "runs but no execution placement placed on the only declared lane; once the switch is complete this step does nothing). Each step " +
1688
+ "runs but no execution placement placed on the only declared lane; once the switch is complete this step does nothing; " +
1689
+ "M33 (local backend only): each local session's creation time and fork lineage were copied from its stored archive into its session-metadata cell, and archives " +
1690
+ "that still carried an importer's owner stamp were rewritten as anonymous with every entry kept as it was (a snapshot of the log is written before each rewrite " +
1691
+ "and removed after it; an interrupted rewrite is finished from the snapshot on the next boot); an archive whose log the import check refuses, whose re-import would " +
1692
+ "change its content, or whose read skipped unreadable records is left byte for byte as it was and named by a local_archive_kept_stamped line, and a session whose " +
1693
+ "archive header cannot be read is named by a local_archive_unreadable line). Each step " +
1654
1694
  "runs on every boot until its window closes (the release that introduced it + 30 days; the per-step date is in " +
1655
1695
  "removableAfter, null while that release is not published yet) and is deleted in the first minor after that — start a " +
1656
1696
  "version that still carries a step at least once before upgrading past its deletion.",
@@ -1658,6 +1698,28 @@ export function logOneTimeMigrations(log, backend, report) {
1658
1698
  logReceiptRows(log, backend, report);
1659
1699
  }
1660
1700
  function logReceiptRows(log, backend, r) {
1701
+ for (const row of r.localArchivesKeptStamped) {
1702
+ log.warn("local_archive_kept_stamped", {
1703
+ backend,
1704
+ sessionId: row.sessionId,
1705
+ reason: row.reason,
1706
+ ...(row.detail !== undefined ? { detail: row.detail } : {}),
1707
+ note: "this local session's stored archive still carries the owner stamp an earlier version wrote, and the one-time migration that rewrites such archives as anonymous left it byte for byte as it was " +
1708
+ "(reason invalid = the import check refuses its log; normalizes = re-importing would change its content; records_skipped = reading it skipped unreadable records, which a rewrite would erase). " +
1709
+ "Reading, resuming and pulling the session work as before; forking it is refused and a push that changes its log answers 409 conflict. Way out: delete the session and push it again " +
1710
+ "(a push onto an absent session creates it anew), or repair the archive under the data root's sessions directory. Named on every boot until then",
1711
+ });
1712
+ }
1713
+ for (const row of r.localArchivesUnreadable) {
1714
+ log.warn("local_archive_unreadable", {
1715
+ backend,
1716
+ sessionId: row.sessionId,
1717
+ ...(row.errno !== undefined ? { errno: row.errno } : {}),
1718
+ note: "this local session's stored archive cannot be read (its header, or the whole archive), so the one-time migration could neither copy its creation time into its session-metadata cell nor rewrite it; " +
1719
+ "nothing was written. Every request that needs the session keeps answering the archive's own fault until the archive under the data root's sessions directory is restored from a kept copy " +
1720
+ "(the next boot then migrates it) or the session is deleted. Named on every boot until then",
1721
+ });
1722
+ }
1661
1723
  if (r.deviceBindingsOrphaned > 0) {
1662
1724
  log.warn("device_bindings_orphaned", {
1663
1725
  backend,
@@ -128,8 +128,8 @@ export class SqlOrgDeliveryQuotaStore {
128
128
  constructor(db) {
129
129
  this.db = db;
130
130
  }
131
- q(tidb, pg) {
132
- return this.db.dialect === "tidb" ? tidb : pg;
131
+ q(mysql, pg) {
132
+ return this.db.dialect === "mysql" ? mysql : pg;
133
133
  }
134
134
  async admitAttempt(fromScope, policy) {
135
135
  return this.inTx(ORG_DELIVERY_SENDER_QUOTA_TABLE, policy, (conn, at) => this.admitIn(conn, fromScope, at, policy));
@@ -87,8 +87,8 @@ export class SqlOutcomeLedger {
87
87
  constructor(db) {
88
88
  this.db = db;
89
89
  }
90
- q(tidb, pg) {
91
- return this.db.dialect === "tidb" ? tidb : pg;
90
+ q(mysql, pg) {
91
+ return this.db.dialect === "mysql" ? mysql : pg;
92
92
  }
93
93
  json(value) {
94
94
  return dialectJsonEncoder(this.db.dialect)(value);
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  import { type PermissionRuleWriter, type DurableRulePartition, type DurableRulePartitionProvider, type RuleScope, type RuleSyncFrontier, type RuleBehavior, type RuleCandidate, type RuleApprovalRecord, type StaleRuleApprovalRecord, type RuleApprovalRecordStore, type RuleOwner } from "@sema-agent/core";
3
3
  import type { Pool as MySqlPool } from "mysql2/promise";
4
4
  import type { PgQueryFn } from "./pg-query.js";
5
- import { type SqlDriver } from "./sql-driver.js";
5
+ import { type SqlDriver, type SqlDialect } from "./sql-driver.js";
6
6
  import { type IndexSpec } from "./ensure-index.js";
7
7
  /**
8
8
  * 一只 durable 分区的写面,或 `undefined`(该 backend 从引擎侧看是只读的)。
@@ -105,7 +105,7 @@ export declare function ensureTiDBPermissionRuleSchema(pool: MySqlPool): Promise
105
105
  */
106
106
  export declare function assertPermissionRuleApprovalSchema(query: (sql: string) => Promise<{
107
107
  rows: Record<string, unknown>[];
108
- }>, dialect: "tidb" | "pg"): Promise<void>;
108
+ }>, dialect: SqlDialect): Promise<void>;
109
109
  /**
110
110
  * [ref] A7 升级前置断言 —— **拒启**(形与理由逐字照 {@link assertPermissionRuleApprovalSchema} 的配方,
111
111
  * 那条头注写了这道门为什么必须是拒启而不是 warn:本仓不发 `ALTER TABLE`,`CREATE TABLE IF NOT EXISTS`
@@ -127,7 +127,7 @@ export declare function assertPermissionRuleApprovalSchema(query: (sql: string)
127
127
  */
128
128
  export declare function assertPermissionRuleTicketSchema(query: (sql: string) => Promise<{
129
129
  rows: Record<string, unknown>[];
130
- }>, dialect: "tidb" | "pg"): Promise<void>;
130
+ }>, dialect: SqlDialect): Promise<void>;
131
131
  /** [ref]:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgPermissionRuleSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
132
132
  export declare const PERMISSION_RULE_INDEXES: readonly IndexSpec[];
133
133
  export declare function ensurePgPermissionRuleSchema(q: PgQueryFn): Promise<void>;
@@ -502,7 +502,7 @@ export interface UndurableScopePurgeDb {
502
502
  * 读数写进调用方递进来的账(`into`,{@link UndurableScopePurgeReadings}):删掉的行在那只桶的回写提交之后追加 ⇒ 本函数半途抛出时,已删的行已经在账上;
503
503
  * 错误**原样**上抛(不加工、不自铸拒因句 —— 收据由迁移段那一只收据账随拒因带走,[ref])。
504
504
  */
505
- export declare function purgeUndurableScopeRows(db: UndurableScopePurgeDb, dialect: "tidb" | "pg", into: UndurableScopePurgeReadings, now?: () => number): Promise<void>;
505
+ export declare function purgeUndurableScopeRows(db: UndurableScopePurgeDb, dialect: SqlDialect, into: UndurableScopePurgeReadings, now?: () => number): Promise<void>;
506
506
  /** 三个 SQL 面的一次性装配束(one driver, three faces)。`durable` = [ref] 的 durable 分区后端;
507
507
  * 引擎接缝上的统一店由 `main.ts` 用 `createPermissionRuleStoreProvider({ durable })` 合成一次。 */
508
508
  export interface PermissionRuleStores {
@@ -410,7 +410,7 @@ function refuseRowsTheValidatorRefuses(ownerKey, rules, tombstones) {
410
410
  }
411
411
  }
412
412
  function fencedCasWhere(dialect, firstPlaceholder, ownerKey, cas) {
413
- const ph = (offset) => (dialect === "tidb" ? "?" : `$${firstPlaceholder + offset}`);
413
+ const ph = (offset) => (dialect === "mysql" ? "?" : `$${firstPlaceholder + offset}`);
414
414
  return {
415
415
  sql: `owner_key = ${ph(0)} AND ${cas.column} = ${ph(1)} AND (grammar IS NULL OR grammar <= ${ph(2)})`,
416
416
  params: [ownerKey, cas.expected, SESSION_RULES_GRAMMAR],
@@ -430,7 +430,7 @@ async function writeBucket(exec, dialect, ownerKey, cur, next, nowMs) {
430
430
  const body = { rules: next.rules, tombstones: next.tombstones, quarantined: cur.quarantined, ...(cur.observedVector !== undefined ? { observedVector: cur.observedVector } : {}) };
431
431
  const enc = dialectProtocolJsonEncoder(dialect);
432
432
  const where = fencedCasWhere(dialect, 7, ownerKey, { column: "rev", expected: cur.rev });
433
- const affected = await exec(dialect === "tidb"
433
+ const affected = await exec(dialect === "mysql"
434
434
  ? `UPDATE ${PERMISSION_RULE_TABLE} SET rules_json = ?, tombstones_json = ?, quarantined_json = ?, checksum = ?, grammar = ?, rev = rev + 1, updated_at_ms = ? WHERE ${where.sql}`
435
435
  : `UPDATE ${PERMISSION_RULE_TABLE} SET rules_json = $1, tombstones_json = $2, quarantined_json = $3, checksum = $4, grammar = $5, rev = rev + 1, updated_at_ms = $6 WHERE ${where.sql}`, [
436
436
  enc(next.rules, "permission_rule.rules_json"),
@@ -460,13 +460,13 @@ class SqlDurableRulePartition {
460
460
  return dialectProtocolJsonEncoder(this.db.dialect)(v, label);
461
461
  }
462
462
  q(tidbSql, pgSql) {
463
- return this.db.dialect === "tidb" ? tidbSql : pgSql;
463
+ return this.db.dialect === "mysql" ? tidbSql : pgSql;
464
464
  }
465
465
  async read() {
466
466
  return refusingUnreadableColumns(`bucket ${this.ownerKey}`, () => this.readBucketRow());
467
467
  }
468
468
  async readBucketRow() {
469
- const { rows } = await this.db.query(this.q(`SELECT ${bucketRowColumns("tidb")} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = ?`, `SELECT ${bucketRowColumns("pg")} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = $1`), [this.ownerKey]);
469
+ const { rows } = await this.db.query(this.q(`SELECT ${bucketRowColumns("mysql")} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = ?`, `SELECT ${bucketRowColumns("pg")} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = $1`), [this.ownerKey]);
470
470
  const row = rows[0];
471
471
  if (row === undefined)
472
472
  return { ...EMPTY_BUCKET, rules: [], tombstones: [], quarantined: [] };
@@ -604,7 +604,7 @@ export class SqlRuleApprovalRecordStore {
604
604
  this.now = now;
605
605
  }
606
606
  q(tidbSql, pgSql) {
607
- return this.db.dialect === "tidb" ? tidbSql : pgSql;
607
+ return this.db.dialect === "mysql" ? tidbSql : pgSql;
608
608
  }
609
609
  enc(v, label) {
610
610
  return dialectProtocolJsonEncoder(this.db.dialect)(v, label);
@@ -613,7 +613,7 @@ export class SqlRuleApprovalRecordStore {
613
613
  return refusingUnreadableColumns(`approval record ${id}`, () => this.readApprovalRow(id));
614
614
  }
615
615
  async readApprovalRow(id) {
616
- const { rows } = await this.db.query(this.q(`SELECT record_id, owner_kind, principal, kind, state, version, rev, ${jsonColumnText("tidb", "candidates_json")}, ${jsonColumnText("tidb", "offers_json")}, selected_offer, ${jsonColumnText("tidb", "redeemed_dots_json")}, tool_call_id, bound_input_hash, command, ${jsonColumnText("tidb", "edited_json")}, created_at_iso FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE record_id = ?`, `SELECT record_id, owner_kind, principal, kind, state, version, rev, ${jsonColumnText("pg", "candidates_json")}, ${jsonColumnText("pg", "offers_json")}, selected_offer, ${jsonColumnText("pg", "redeemed_dots_json")}, tool_call_id, bound_input_hash, command, ${jsonColumnText("pg", "edited_json")}, created_at_iso FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE record_id = $1`), [id]);
616
+ const { rows } = await this.db.query(this.q(`SELECT record_id, owner_kind, principal, kind, state, version, rev, ${jsonColumnText("mysql", "candidates_json")}, ${jsonColumnText("mysql", "offers_json")}, selected_offer, ${jsonColumnText("mysql", "redeemed_dots_json")}, tool_call_id, bound_input_hash, command, ${jsonColumnText("mysql", "edited_json")}, created_at_iso FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE record_id = ?`, `SELECT record_id, owner_kind, principal, kind, state, version, rev, ${jsonColumnText("pg", "candidates_json")}, ${jsonColumnText("pg", "offers_json")}, selected_offer, ${jsonColumnText("pg", "redeemed_dots_json")}, tool_call_id, bound_input_hash, command, ${jsonColumnText("pg", "edited_json")}, created_at_iso FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE record_id = $1`), [id]);
617
617
  const row = rows[0];
618
618
  if (row === undefined)
619
619
  return undefined;
@@ -717,7 +717,7 @@ export class SqlRuleImportTicketStore {
717
717
  this.db = db;
718
718
  }
719
719
  q(tidbSql, pgSql) {
720
- return this.db.dialect === "tidb" ? tidbSql : pgSql;
720
+ return this.db.dialect === "mysql" ? tidbSql : pgSql;
721
721
  }
722
722
  async dbNowMs() {
723
723
  return sqlDbNowMs(this.db, PERMISSION_RULE_TICKET_TABLE);
@@ -755,7 +755,7 @@ export class SqlRuleImportTicketStore {
755
755
  if (row.expiresAtMs <= row.dbNowMs)
756
756
  return { ok: false, reason: "expired" };
757
757
  const claimId = randomUUID();
758
- const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.tidb}, claim_id = ? WHERE ticket_id = ? AND owner_key = ? AND purpose = ? AND consumed_at_ms IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.tidb}`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.pg}, claim_id = $1 WHERE ticket_id = $2 AND owner_key = $3 AND purpose = $4 AND consumed_at_ms IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.pg}`), [claimId, ticketId, ownerKey, purpose]);
758
+ const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.mysql}, claim_id = ? WHERE ticket_id = ? AND owner_key = ? AND purpose = ? AND consumed_at_ms IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.mysql}`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.pg}, claim_id = $1 WHERE ticket_id = $2 AND owner_key = $3 AND purpose = $4 AND consumed_at_ms IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.pg}`), [claimId, ticketId, ownerKey, purpose]);
759
759
  if (affected === 1)
760
760
  return { ok: true, approvalId: row.approvalId, payloadHash: row.payloadHash, claimId };
761
761
  const after = await this.readRow(ticketId);
@@ -768,11 +768,11 @@ export class SqlRuleImportTicketStore {
768
768
  return { ok: false, reason: "consumed" };
769
769
  }
770
770
  async release(ticketId, principal, claimId, mustRemainValidMs = 0) {
771
- const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = NULL, claim_id = NULL WHERE ticket_id = ? AND owner_key = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.tidb} + ?`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = NULL, claim_id = NULL WHERE ticket_id = $1 AND owner_key = $2 AND claim_id = $3 AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.pg} + $4`), [ticketId, buildRuleOwnerKey({ kind: "principal", principal }), claimId, mustRemainValidMs]);
771
+ const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = NULL, claim_id = NULL WHERE ticket_id = ? AND owner_key = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.mysql} + ?`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = NULL, claim_id = NULL WHERE ticket_id = $1 AND owner_key = $2 AND claim_id = $3 AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND expires_at_ms > ${SQL_DB_NOW_MS.pg} + $4`), [ticketId, buildRuleOwnerKey({ kind: "principal", principal }), claimId, mustRemainValidMs]);
772
772
  return affected === 1;
773
773
  }
774
774
  async settle(ticketId, principal, claimId, outcome) {
775
- const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET redeemed_outcome = ?, settled_at_ms = ${SQL_DB_NOW_MS.tidb} WHERE ticket_id = ? AND owner_key = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET redeemed_outcome = $1, settled_at_ms = ${SQL_DB_NOW_MS.pg} WHERE ticket_id = $2 AND owner_key = $3 AND claim_id = $4 AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL`), [
775
+ const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET redeemed_outcome = ?, settled_at_ms = ${SQL_DB_NOW_MS.mysql} WHERE ticket_id = ? AND owner_key = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET redeemed_outcome = $1, settled_at_ms = ${SQL_DB_NOW_MS.pg} WHERE ticket_id = $2 AND owner_key = $3 AND claim_id = $4 AND consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL`), [
776
776
  dialectProtocolJsonEncoder(this.db.dialect)(outcome, "permission_rule_ticket.redeemed_outcome"),
777
777
  ticketId,
778
778
  buildRuleOwnerKey({ kind: "principal", principal }),
@@ -805,11 +805,11 @@ export class SqlRuleImportTicketStore {
805
805
  if (row.claimId === undefined)
806
806
  return { ok: false };
807
807
  const claimId = randomUUID();
808
- const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.tidb}, claim_id = ? WHERE ticket_id = ? AND owner_key = ? AND purpose = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND consumed_at_ms <= ${SQL_DB_NOW_MS.tidb} - ? AND redeemed_outcome IS NULL`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.pg}, claim_id = $1 WHERE ticket_id = $2 AND owner_key = $3 AND purpose = $4 AND claim_id = $5 AND consumed_at_ms IS NOT NULL AND consumed_at_ms <= ${SQL_DB_NOW_MS.pg} - $6 AND redeemed_outcome IS NULL`), [claimId, ticketId, buildRuleOwnerKey({ kind: "principal", principal }), purpose, row.claimId, minClaimAgeMs]);
808
+ const { affected } = await this.db.query(this.q(`UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.mysql}, claim_id = ? WHERE ticket_id = ? AND owner_key = ? AND purpose = ? AND claim_id = ? AND consumed_at_ms IS NOT NULL AND consumed_at_ms <= ${SQL_DB_NOW_MS.mysql} - ? AND redeemed_outcome IS NULL`, `UPDATE ${PERMISSION_RULE_TICKET_TABLE} SET consumed_at_ms = ${SQL_DB_NOW_MS.pg}, claim_id = $1 WHERE ticket_id = $2 AND owner_key = $3 AND purpose = $4 AND claim_id = $5 AND consumed_at_ms IS NOT NULL AND consumed_at_ms <= ${SQL_DB_NOW_MS.pg} - $6 AND redeemed_outcome IS NULL`), [claimId, ticketId, buildRuleOwnerKey({ kind: "principal", principal }), purpose, row.claimId, minClaimAgeMs]);
809
809
  return affected === 1 ? { ok: true, approvalId: row.approvalId, payloadHash: row.payloadHash, claimId } : { ok: false };
810
810
  }
811
811
  async readRow(ticketId) {
812
- const { rows } = await this.db.query(this.q(`SELECT owner_key, approval_id, payload_hash, purpose, expires_at_ms, consumed_at_ms, claim_id, ${jsonColumnText("tidb", "redeemed_outcome")}, ${SQL_DB_NOW_MS.tidb} AS db_now_ms FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ticket_id = ?`, `SELECT owner_key, approval_id, payload_hash, purpose, expires_at_ms, consumed_at_ms, claim_id, ${jsonColumnText("pg", "redeemed_outcome")}, ${SQL_DB_NOW_MS.pg} AS db_now_ms FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ticket_id = $1`), [ticketId]);
812
+ const { rows } = await this.db.query(this.q(`SELECT owner_key, approval_id, payload_hash, purpose, expires_at_ms, consumed_at_ms, claim_id, ${jsonColumnText("mysql", "redeemed_outcome")}, ${SQL_DB_NOW_MS.mysql} AS db_now_ms FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ticket_id = ?`, `SELECT owner_key, approval_id, payload_hash, purpose, expires_at_ms, consumed_at_ms, claim_id, ${jsonColumnText("pg", "redeemed_outcome")}, ${SQL_DB_NOW_MS.pg} AS db_now_ms FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ticket_id = $1`), [ticketId]);
813
813
  const row = rows[0];
814
814
  if (row === undefined)
815
815
  return undefined;
@@ -834,7 +834,7 @@ export async function purgeUndurableScopeRows(db, dialect, into, now = Date.now)
834
834
  const cols = `owner_key, ${bucketRowColumns(dialect)}`;
835
835
  let after = "";
836
836
  for (;;) {
837
- const page = await db.rows(dialect === "tidb"
837
+ const page = await db.rows(dialect === "mysql"
838
838
  ? `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > ? ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`
839
839
  : `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key > $1 ORDER BY owner_key LIMIT ${UNDURABLE_PURGE_PAGE}`, [after]);
840
840
  for (const first of page) {
@@ -857,7 +857,7 @@ export async function purgeUndurableScopeRows(db, dialect, into, now = Date.now)
857
857
  settled = true;
858
858
  break;
859
859
  case "conflict":
860
- row = (await db.rows(dialect === "tidb" ? `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = ?` : `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = $1`, [ownerKey]))[0];
860
+ row = (await db.rows(dialect === "mysql" ? `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = ?` : `SELECT ${cols} FROM ${PERMISSION_RULE_TABLE} WHERE owner_key = $1`, [ownerKey]))[0];
861
861
  if (row === undefined)
862
862
  settled = true;
863
863
  break;
@@ -907,13 +907,13 @@ function contentFreeRefusal(ownerKey, e) {
907
907
  return `permission_rule bucket ${ownerKey}: the bucket judge threw ${e instanceof Error ? e.name : typeof e} (its text is withheld here: it may quote the row)`;
908
908
  }
909
909
  export function createSqlPermissionRuleStores(db, now) {
910
- const q = (tidb, pg) => (db.dialect === "tidb" ? tidb : pg);
910
+ const q = (mysql, pg) => (db.dialect === "mysql" ? mysql : pg);
911
911
  return {
912
912
  durable: new SqlDurableRulePartitionProvider(db, now),
913
913
  approvals: new SqlRuleApprovalRecordStore(db, now),
914
914
  tickets: new SqlRuleImportTicketStore(db),
915
915
  reapExpired: async (nowMs) => {
916
- const deadTickets = await db.query(q(`DELETE FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE (consumed_at_ms IS NULL AND expires_at_ms <= ${SQL_DB_NOW_MS.tidb} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (redeemed_outcome IS NOT NULL AND settled_at_ms IS NOT NULL AND settled_at_ms <= ${SQL_DB_NOW_MS.tidb} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND consumed_at_ms < ${SQL_DB_NOW_MS.tidb} - ${RULE_PENDING_APPROVAL_RETENTION_MS}) LIMIT ${RULE_REAP_BATCH}`, `DELETE FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ctid IN (SELECT ctid FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE (consumed_at_ms IS NULL AND expires_at_ms <= ${SQL_DB_NOW_MS.pg} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (redeemed_outcome IS NOT NULL AND settled_at_ms IS NOT NULL AND settled_at_ms <= ${SQL_DB_NOW_MS.pg} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND consumed_at_ms < ${SQL_DB_NOW_MS.pg} - ${RULE_PENDING_APPROVAL_RETENTION_MS}) LIMIT ${RULE_REAP_BATCH})`), []);
916
+ const deadTickets = await db.query(q(`DELETE FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE (consumed_at_ms IS NULL AND expires_at_ms <= ${SQL_DB_NOW_MS.mysql} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (redeemed_outcome IS NOT NULL AND settled_at_ms IS NOT NULL AND settled_at_ms <= ${SQL_DB_NOW_MS.mysql} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND consumed_at_ms < ${SQL_DB_NOW_MS.mysql} - ${RULE_PENDING_APPROVAL_RETENTION_MS}) LIMIT ${RULE_REAP_BATCH}`, `DELETE FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE ctid IN (SELECT ctid FROM ${PERMISSION_RULE_TICKET_TABLE} WHERE (consumed_at_ms IS NULL AND expires_at_ms <= ${SQL_DB_NOW_MS.pg} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (redeemed_outcome IS NOT NULL AND settled_at_ms IS NOT NULL AND settled_at_ms <= ${SQL_DB_NOW_MS.pg} - ${RULE_IMPORT_TICKET_GC_GRACE_MS}) OR (consumed_at_ms IS NOT NULL AND redeemed_outcome IS NULL AND consumed_at_ms < ${SQL_DB_NOW_MS.pg} - ${RULE_PENDING_APPROVAL_RETENTION_MS}) LIMIT ${RULE_REAP_BATCH})`), []);
917
917
  const orphanPending = await db.query(q(`DELETE FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE state = 'pending' AND created_at_ms < ? AND NOT EXISTS (SELECT 1 FROM ${PERMISSION_RULE_TICKET_TABLE} t WHERE t.approval_id = ${PERMISSION_RULE_APPROVAL_TABLE}.record_id) LIMIT ${RULE_REAP_BATCH}`, `DELETE FROM ${PERMISSION_RULE_APPROVAL_TABLE} WHERE ctid IN (SELECT a.ctid FROM ${PERMISSION_RULE_APPROVAL_TABLE} a WHERE a.state = 'pending' AND a.created_at_ms < $1 AND NOT EXISTS (SELECT 1 FROM ${PERMISSION_RULE_TICKET_TABLE} t WHERE t.approval_id = a.record_id) LIMIT ${RULE_REAP_BATCH})`), [nowMs - RULE_PENDING_APPROVAL_RETENTION_MS]);
918
918
  return deadTickets.affected + orphanPending.affected;
919
919
  },