@sema-agent/server 7.137.0 → 7.139.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 +600 -142
  2. package/README.md +7 -3
  3. package/README.zh-CN.md +6 -3
  4. package/USAGE.md +132 -41
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/lib/lanes.json +3 -2
  7. package/dist/adoption/wire.d.ts +3 -3
  8. package/dist/approval-card.d.ts +23 -16
  9. package/dist/approval-card.js +1 -0
  10. package/dist/audit.js +1 -0
  11. package/dist/background-agent-name.d.ts +5 -0
  12. package/dist/background-agent-name.js +5 -0
  13. package/dist/bench/s1/arms.d.ts +1 -1
  14. package/dist/bench/s1/arms.js +1 -3
  15. package/dist/bench/s1/run-firm.js +2 -2
  16. package/dist/boot/budget-tracing.d.ts +1 -12
  17. package/dist/boot/deployment-mods.d.ts +46 -16
  18. package/dist/boot/deployment-mods.js +82 -6
  19. package/dist/boot/device-lane.d.ts +8 -3
  20. package/dist/boot/device-lane.js +7 -2
  21. package/dist/boot/execution-env.d.ts +4 -2
  22. package/dist/boot/execution-env.js +16 -24
  23. package/dist/boot/execution-lane.d.ts +1 -1
  24. package/dist/boot/governance-seams.d.ts +12 -18
  25. package/dist/boot/governance-seams.js +10 -24
  26. package/dist/boot/leader.d.ts +3 -0
  27. package/dist/boot/leader.js +1 -0
  28. package/dist/boot/memory-consolidation.js +2 -2
  29. package/dist/boot/org-delivery.js +15 -2
  30. package/dist/boot/resolve-spec.d.ts +4 -1
  31. package/dist/boot/resolve-spec.js +29 -19
  32. package/dist/boot/retention-lane.d.ts +14 -8
  33. package/dist/boot/retention-lane.js +2 -8
  34. package/dist/boot/runner-deps.d.ts +10 -4
  35. package/dist/boot/runner-deps.js +2 -0
  36. package/dist/boot/session-faces.d.ts +0 -3
  37. package/dist/boot/session-faces.js +15 -8
  38. package/dist/boot/stage-02-web-admin-pull.js +2 -2
  39. package/dist/boot/stage-04-stores.d.ts +1 -12
  40. package/dist/boot/stage-05-execution-env.d.ts +3 -12
  41. package/dist/boot/stage-05-execution-env.js +8 -5
  42. package/dist/boot/stage-06-runners.d.ts +3 -12
  43. package/dist/boot/stage-06-runners.js +5 -3
  44. package/dist/boot/stage-07-capability-layer.d.ts +3 -12
  45. package/dist/boot/stage-07-capability-layer.js +2 -2
  46. package/dist/boot/stage-08-reapers.d.ts +3 -12
  47. package/dist/boot/stage-09-leader.d.ts +3 -12
  48. package/dist/boot/stage-09-leader.js +2 -3
  49. package/dist/boot/stage-10-http-server.d.ts +3 -12
  50. package/dist/boot/stage-10-http-server.js +5 -3
  51. package/dist/boot/stores.js +4 -4
  52. package/dist/boot/web-admin.d.ts +5 -2
  53. package/dist/boot/web-admin.js +5 -5
  54. package/dist/brain.js +5 -5
  55. package/dist/budget.d.ts +17 -15
  56. package/dist/budget.js +2 -2
  57. package/dist/capabilities/mcp-probe.d.ts +36 -11
  58. package/dist/capabilities/mcp-probe.js +44 -5
  59. package/dist/capabilities/scenarios.d.ts +1 -1
  60. package/dist/capability-facts.d.ts +21 -7
  61. package/dist/capability-facts.js +13 -3
  62. package/dist/checkpoint-identity.d.ts +13 -6
  63. package/dist/config-catalog.js +14 -8
  64. package/dist/config-invariants.d.ts +23 -3
  65. package/dist/config-invariants.js +42 -0
  66. package/dist/config-provider.d.ts +19 -24
  67. package/dist/config-provider.js +27 -12
  68. package/dist/config-types.d.ts +25 -12
  69. package/dist/config.d.ts +33 -0
  70. package/dist/config.js +52 -20
  71. package/dist/delegated-session-owner.d.ts +2 -2
  72. package/dist/deployment-shape.d.ts +7 -7
  73. package/dist/device-enrollment.d.ts +41 -2
  74. package/dist/device-enrollment.js +27 -12
  75. package/dist/device-store.d.ts +58 -85
  76. package/dist/device-store.js +19 -49
  77. package/dist/device-ws-hub.js +2 -1
  78. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  79. package/dist/exec-cut.d.ts +60 -0
  80. package/dist/exec-cut.js +36 -0
  81. package/dist/execution-lane-caps.d.ts +19 -0
  82. package/dist/execution-lane-caps.js +10 -0
  83. package/dist/execution-placement.d.ts +259 -0
  84. package/dist/execution-placement.js +199 -0
  85. package/dist/field-conflict.d.ts +3 -2
  86. package/dist/field-conflict.js +1 -0
  87. package/dist/gate-settlement.d.ts +71 -0
  88. package/dist/gate-settlement.js +40 -0
  89. package/dist/hooks/classic-stations.d.ts +1 -0
  90. package/dist/hooks/classic-stations.js +1 -0
  91. package/dist/hooks/command-spawn.d.ts +2 -2
  92. package/dist/hooks/hook-runner.js +14 -14
  93. package/dist/hosted-posture.d.ts +2 -2
  94. package/dist/http/admission.js +10 -19
  95. package/dist/http/checkpoint-ctx.d.ts +21 -12
  96. package/dist/http/checkpoint-ctx.js +4 -2
  97. package/dist/http/claimed-run.d.ts +1 -1
  98. package/dist/http/dispatch.d.ts +4 -0
  99. package/dist/http/dispatch.js +3 -0
  100. package/dist/http/durable-run-leg.js +12 -6
  101. package/dist/http/execution-context.d.ts +218 -0
  102. package/dist/http/execution-context.js +330 -0
  103. package/dist/http/memory-compliance-failure-reply.js +3 -0
  104. package/dist/http/resume-legs.d.ts +5 -11
  105. package/dist/http/resume-legs.js +179 -104
  106. package/dist/http/route-ctx.d.ts +44 -59
  107. package/dist/http/route-ctx.js +2 -5
  108. package/dist/http/routes/a2a-serve.js +10 -4
  109. package/dist/http/routes/agents-roster.d.ts +1 -1
  110. package/dist/http/routes/agents-roster.js +5 -1
  111. package/dist/http/routes/approvals-assistant.js +6 -6
  112. package/dist/http/routes/background.d.ts +29 -1
  113. package/dist/http/routes/background.js +29 -2
  114. package/dist/http/routes/capabilities.js +12 -10
  115. package/dist/http/routes/devices.d.ts +13 -19
  116. package/dist/http/routes/devices.js +21 -26
  117. package/dist/http/routes/mcp-reconnect.d.ts +3 -2
  118. package/dist/http/routes/mcp-reconnect.js +7 -5
  119. package/dist/http/routes/memory-consolidation.d.ts +1 -1
  120. package/dist/http/routes/memory-origin.d.ts +1 -1
  121. package/dist/http/routes/rules.js +3 -3
  122. package/dist/http/routes/runs.js +11 -7
  123. package/dist/http/routes/session-sync.js +110 -15
  124. package/dist/http/routes/sessions.d.ts +6 -0
  125. package/dist/http/routes/sessions.js +119 -13
  126. package/dist/http/routes/side-query.d.ts +12 -4
  127. package/dist/http/routes/side-query.js +6 -8
  128. package/dist/http/routes/tasks.js +800 -782
  129. package/dist/http/server.d.ts +61 -39
  130. package/dist/http/server.js +11 -0
  131. package/dist/http/session-inherit-env.d.ts +3 -4
  132. package/dist/http/session-inherit-env.js +1 -1
  133. package/dist/http/wire-types.d.ts +22 -18
  134. package/dist/identity-source.d.ts +1 -1
  135. package/dist/index.d.ts +3 -0
  136. package/dist/index.js +1 -0
  137. package/dist/leader/wire.d.ts +7 -0
  138. package/dist/leader/wire.js +5 -4
  139. package/dist/mcp-assertion-header.d.ts +51 -40
  140. package/dist/mcp-assertion-header.js +10 -11
  141. package/dist/mcp-authorization-face.d.ts +27 -0
  142. package/dist/mcp-authorization-face.js +15 -0
  143. package/dist/memory-operator-faces.d.ts +13 -4
  144. package/dist/memory-operator-faces.js +1 -17
  145. package/dist/memory-sync-client.d.ts +53 -14
  146. package/dist/memory-sync-client.js +187 -92
  147. package/dist/memory-sync.d.ts +1 -1
  148. package/dist/memory-sync.js +3 -2
  149. package/dist/model-route-gap.d.ts +6 -22
  150. package/dist/model-route-gap.js +7 -5
  151. package/dist/mods-declaration.d.ts +110 -0
  152. package/dist/mods-declaration.js +109 -0
  153. package/dist/observability/cost-taxonomy.d.ts +2 -2
  154. package/dist/observability/err-text.d.ts +5 -0
  155. package/dist/observability/err-text.js +9 -0
  156. package/dist/observability/fail-open.d.ts +17 -1
  157. package/dist/observability/fail-open.js +17 -1
  158. package/dist/observability/run-terminal.js +0 -1
  159. package/dist/observability/secret-env-scrub.d.ts +1 -1
  160. package/dist/observability/secret-env-scrub.js +1 -1
  161. package/dist/orchestration/workflow-notify-journal.js +4 -4
  162. package/dist/org-memory-admission.d.ts +4 -3
  163. package/dist/org-memory-admission.js +6 -6
  164. package/dist/parked-decide.d.ts +49 -11
  165. package/dist/parked-decide.js +25 -26
  166. package/dist/parked-revive-surface.d.ts +12 -15
  167. package/dist/parked-revive-surface.js +3 -6
  168. package/dist/peer-seat.d.ts +156 -0
  169. package/dist/peer-seat.js +273 -0
  170. package/dist/permission-rule-vocab.d.ts +7 -7
  171. package/dist/permission-rule-vocab.js +3 -3
  172. package/dist/plain-string-record.d.ts +38 -0
  173. package/dist/plain-string-record.js +38 -0
  174. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  175. package/dist/plugins/approval-ask-store-file.js +11 -0
  176. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  177. package/dist/plugins/approval-ask-store-memory.js +12 -0
  178. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  179. package/dist/plugins/approval-ask-store-sql.js +30 -12
  180. package/dist/plugins/background-agent-store-sql.js +6 -3
  181. package/dist/plugins/caching-session-store.d.ts +11 -2
  182. package/dist/plugins/caching-session-store.js +15 -3
  183. package/dist/plugins/checkpoint-store-sql.d.ts +21 -25
  184. package/dist/plugins/checkpoint-store-sql.js +13 -15
  185. package/dist/plugins/device-store-sql.d.ts +25 -51
  186. package/dist/plugins/device-store-sql.js +49 -103
  187. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  188. package/dist/plugins/execution-placement-sql.js +223 -0
  189. package/dist/plugins/file-history-store-sql.d.ts +7 -6
  190. package/dist/plugins/file-history-store-sql.js +8 -7
  191. package/dist/plugins/file-run-store.d.ts +6 -1
  192. package/dist/plugins/file-run-store.js +4 -0
  193. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  194. package/dist/plugins/fork-routing-session-store.js +16 -0
  195. package/dist/plugins/k8s-pod-reaper.d.ts +2 -8
  196. package/dist/plugins/k8s-pod-reaper.js +3 -3
  197. package/dist/plugins/local-checkpoint-store.d.ts +1 -11
  198. package/dist/plugins/local-checkpoint-store.js +0 -1
  199. package/dist/plugins/local-session-store.d.ts +183 -45
  200. package/dist/plugins/local-session-store.js +412 -105
  201. package/dist/plugins/mailbox-store-sql.d.ts +11 -3
  202. package/dist/plugins/mailbox-store-sql.js +103 -15
  203. package/dist/plugins/memory-key-guards.d.ts +4 -0
  204. package/dist/plugins/memory-key-guards.js +1 -0
  205. package/dist/plugins/memory-run-store.d.ts +1 -1
  206. package/dist/plugins/memory-sync-store-pg.d.ts +16 -5
  207. package/dist/plugins/memory-sync-store-tidb.d.ts +3 -3
  208. package/dist/plugins/one-time-migrations.d.ts +117 -6
  209. package/dist/plugins/one-time-migrations.js +383 -14
  210. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  211. package/dist/plugins/permission-rule-store-sql.d.ts +13 -15
  212. package/dist/plugins/permission-rule-store-sql.js +34 -29
  213. package/dist/plugins/pg-pool.d.ts +7 -3
  214. package/dist/plugins/pg-session-storage.d.ts +41 -22
  215. package/dist/plugins/pg-session-storage.js +136 -55
  216. package/dist/plugins/remote-env-adb.d.ts +22 -4
  217. package/dist/plugins/remote-env-adb.js +70 -62
  218. package/dist/plugins/remote-env-device.d.ts +32 -12
  219. package/dist/plugins/remote-env-device.js +18 -17
  220. package/dist/plugins/remote-env-e2b.d.ts +19 -3
  221. package/dist/plugins/remote-env-e2b.js +41 -42
  222. package/dist/plugins/remote-env-file-error.d.ts +2 -0
  223. package/dist/plugins/remote-env-file-error.js +1 -1
  224. package/dist/plugins/remote-env-host.d.ts +23 -8
  225. package/dist/plugins/remote-env-host.js +66 -70
  226. package/dist/plugins/remote-env-k8s.d.ts +13 -16
  227. package/dist/plugins/remote-env-k8s.js +61 -54
  228. package/dist/plugins/remote-env-local-docker.d.ts +30 -5
  229. package/dist/plugins/remote-env-local-docker.js +46 -59
  230. package/dist/plugins/remote-env-ssh.d.ts +18 -4
  231. package/dist/plugins/remote-env-ssh.js +72 -56
  232. package/dist/plugins/remote-shell.d.ts +100 -3
  233. package/dist/plugins/remote-shell.js +166 -2
  234. package/dist/plugins/retention-store-sql.d.ts +23 -8
  235. package/dist/plugins/retention-store-sql.js +13 -1
  236. package/dist/plugins/run-store-sql.d.ts +13 -3
  237. package/dist/plugins/run-store-sql.js +5 -0
  238. package/dist/plugins/schema-consistency.d.ts +15 -5
  239. package/dist/plugins/schema-consistency.js +14 -8
  240. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  241. package/dist/plugins/schema-drift-sentence.js +38 -0
  242. package/dist/plugins/session-fork.d.ts +63 -12
  243. package/dist/plugins/session-fork.js +38 -21
  244. package/dist/plugins/session-ownership.d.ts +7 -0
  245. package/dist/plugins/session-ownership.js +5 -0
  246. package/dist/plugins/session-row-sql.d.ts +22 -0
  247. package/dist/plugins/session-row-sql.js +81 -0
  248. package/dist/plugins/session-store.d.ts +1 -1
  249. package/dist/plugins/session-workspace-cell.d.ts +17 -0
  250. package/dist/plugins/session-workspace-cell.js +38 -0
  251. package/dist/plugins/store-backend.d.ts +1 -1
  252. package/dist/plugins/store-backend.js +26 -14
  253. package/dist/plugins/store-contracts.d.ts +25 -5
  254. package/dist/plugins/tidb-pool.d.ts +12 -7
  255. package/dist/plugins/tidb-pool.js +15 -3
  256. package/dist/plugins/tidb-session-store.d.ts +74 -28
  257. package/dist/plugins/tidb-session-store.js +132 -53
  258. package/dist/plugins/tool-result-store-sql.d.ts +16 -8
  259. package/dist/plugins/tool-result-store-sql.js +26 -17
  260. package/dist/plugins/worktree-isolation.d.ts +3 -2
  261. package/dist/plugins/worktree-isolation.js +1 -7
  262. package/dist/project-identity.d.ts +1 -14
  263. package/dist/project-identity.js +0 -4
  264. package/dist/project-memory.d.ts +9 -15
  265. package/dist/project-memory.js +5 -4
  266. package/dist/request-key-closure.js +1 -1
  267. package/dist/request-value-closure.d.ts +6 -7
  268. package/dist/request-value-closure.js +1 -3
  269. package/dist/rules-consent.js +6 -4
  270. package/dist/run-local.d.ts +6 -0
  271. package/dist/run-local.js +36 -26
  272. package/dist/runs.d.ts +9 -2
  273. package/dist/runs.js +9 -2
  274. package/dist/sandbox-git-plane.d.ts +2 -2
  275. package/dist/security.d.ts +38 -12
  276. package/dist/security.js +8 -2
  277. package/dist/server-secret-env.d.ts +15 -8
  278. package/dist/server-secret-env.js +4 -2
  279. package/dist/session-children-retire.d.ts +27 -9
  280. package/dist/session-children-retire.js +9 -5
  281. package/dist/session-incarnation.d.ts +19 -10
  282. package/dist/session-incarnation.js +5 -3
  283. package/dist/session-row.d.ts +114 -5
  284. package/dist/session-row.js +55 -5
  285. package/dist/session-titler.d.ts +2 -2
  286. package/dist/session-workspace.d.ts +130 -0
  287. package/dist/session-workspace.js +83 -0
  288. package/dist/spec-fields.d.ts +1 -1
  289. package/dist/standing-answer-grant.d.ts +2 -13
  290. package/dist/standing-answer-grant.js +0 -7
  291. package/dist/task-a2a.d.ts +44 -14
  292. package/dist/task-a2a.js +46 -20
  293. package/dist/task-cwd.d.ts +6 -1
  294. package/dist/task-mcp.d.ts +33 -20
  295. package/dist/task-mcp.js +46 -22
  296. package/dist/tool-approval.d.ts +38 -8
  297. package/dist/tool-approval.js +57 -13
  298. package/dist/trace/core-keyset-guard.d.ts +33 -18
  299. package/dist/trace/engine-notice-wire.d.ts +1 -1
  300. package/dist/trace/engine-notice-wire.js +2 -0
  301. package/dist/trace/exit-seat.d.ts +20 -1
  302. package/dist/trace/exit-seat.js +3 -0
  303. package/dist/trace/extension-call-face.d.ts +33 -0
  304. package/dist/trace/extension-call-face.js +39 -0
  305. package/dist/trace/project.d.ts +26 -7
  306. package/dist/trace/project.js +30 -3
  307. package/dist/trace/redact.js +42 -13
  308. package/dist/trace/self-minted-wire.d.ts +240 -12
  309. package/dist/trace/self-minted-wire.js +100 -4
  310. package/dist/trace/wire-projection-faces.d.ts +6 -6
  311. package/dist/trace/wire-projection-faces.js +6 -5
  312. package/dist/url-refusal.d.ts +1 -1
  313. package/dist/user-authored-text.d.ts +2 -2
  314. package/dist/user-authored-text.js +16 -1
  315. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  316. package/dist/web-admin-client/apply-effective.js +20 -10
  317. package/dist/web-admin-client/center-request.d.ts +3 -2
  318. package/dist/web-admin-client/center-request.js +2 -3
  319. package/dist/web-admin-client/skills-mcp.d.ts +8 -4
  320. package/dist/web-admin-client/skills-mcp.js +32 -18
  321. package/dist/web-admin-client/types.d.ts +11 -0
  322. package/package.json +3 -3
  323. package/wire-changes/server@7.138.0.json +446 -0
  324. package/wire-changes/server@7.139.0.json +348 -0
  325. package/dist/http/device-placement.d.ts +0 -175
  326. package/dist/http/device-placement.js +0 -145
  327. package/dist/http/mcp-remote-text.d.ts +0 -5
  328. package/dist/http/mcp-remote-text.js +0 -7
@@ -1,13 +1,13 @@
1
1
  import type { Pool as MySqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
- import type { MailboxAckResult, MailboxAppendMessage, MailboxLease, MailboxStore } from "@sema-agent/core";
3
+ import type { MailboxAckResult, MailboxAppendMessage, MailboxDecision, MailboxDecisionOutcome, MailboxLease, MailboxStore } from "@sema-agent/core";
4
4
  import { type IndexSpec } from "./ensure-index.js";
5
5
  import type { MailboxRecipientLifecycle, MailboxRecipientTombstone } from "./mailbox-recipient-lifecycle.js";
6
6
  export declare const MAILBOX_TABLE = "mailbox";
7
7
  export declare const MAILBOX_MSG_TABLE = "mailbox_message";
8
8
  /** [ref] 收件人墓碑表(预删除态的**唯一**真源;形与理由见 `mailbox-recipient-lifecycle.ts` 模块头)。
9
- * 🔴 为什么是**新表**而不是给 `mailbox` 加两列:加列走的是本仓「删库重建、不开 ALTER seam」那条政策
10
- * (存量库首跑必红);而 `CREATE TABLE IF NOT EXISTS` 一张新表在存量库上零破坏、零运维动作。语义上也更干净
9
+ * 🔴 为什么是**新表**而不是给 `mailbox` 加两列:加列走的是本仓「启动 DDL 不开 ALTER seam」那条政策
10
+ * (存量库缺列起服列册对账拒启、要运维先手工 `ALTER`);而 `CREATE TABLE IF NOT EXISTS` 一张新表在存量库上零破坏、零运维动作。语义上也更干净
11
11
  * —— `mailbox` 行是**盒**事实(next_seq / lease),墓碑是**收件人**事实,两者的生命周期本来就不同
12
12
  * (`drop` 同时终结两者 —— 墓碑随箱亡,理由全文见 `mailbox-recipient-lifecycle.ts` 模块头)。 */
13
13
  export declare const MAILBOX_TOMBSTONE_TABLE = "mailbox_tombstone";
@@ -69,6 +69,12 @@ export declare class TiDBMailboxStore implements MailboxStore, MailboxRecipientL
69
69
  append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
70
70
  claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
71
71
  ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
72
+ /**
73
+ * core 7.57.0 #972b —— 一条记录的留置决定从 `expect`(缺席 = 从未 held)移到 `next`:比较并设置,对每一次别的移动与 `ack` 原子
74
+ * (锁序与 ack / drop / reap 同为「盒行 → 消息行」,当前读);**不受租约围栏**(人从哪个终端答都行)。坏调用(词不在闭集 / seq 不是正整数 /
75
+ * 决定律不许的边)先于键判官与任何副作用以 `mailbox.invalid_decision_move` 拒(core 的唯一验证器 `assertMailboxDecisionMove`);判官拒的键没有箱 ⇒ gone。
76
+ */
77
+ moveDecision(scope: string, handle: string, seq: number, expect: MailboxDecision | undefined, next: MailboxDecision): Promise<MailboxDecisionOutcome>;
72
78
  releaseLease(scope: string, handle: string, owner: string): Promise<void>;
73
79
  peekCount(scope: string, handle: string): Promise<number>;
74
80
  drop(scope: string, handle: string): Promise<void>;
@@ -93,6 +99,8 @@ export declare class PgMailboxStore implements MailboxStore, MailboxRecipientLif
93
99
  append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
94
100
  claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
95
101
  ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<MailboxAckResult>;
102
+ /** core 7.57.0 #972b 留置决定的比较并设置(TiDB 孪生同注:验证先于一切、锁序盒行 → 消息行、不受租约围栏、判官拒的键 ⇒ gone)。 */
103
+ moveDecision(scope: string, handle: string, seq: number, expect: MailboxDecision | undefined, next: MailboxDecision): Promise<MailboxDecisionOutcome>;
96
104
  releaseLease(scope: string, handle: string, owner: string): Promise<void>;
97
105
  peekCount(scope: string, handle: string): Promise<number>;
98
106
  drop(scope: string, handle: string): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import { isUtf8 } from "node:buffer";
2
- import { MAILBOX_INVALID_PEER_META_CODE, MAILBOX_TOMBSTONED_RECIPIENT_CODE, MailboxStoreError, readMailboxPeerMeta } from "@sema-agent/core";
2
+ import { MAILBOX_INVALID_PEER_META_CODE, MAILBOX_RECORD_ID_MAX_CHARS, MAILBOX_TOMBSTONED_RECIPIENT_CODE, MailboxStoreError, assertMailboxDecisionMove, judgeMailboxAppendById, readMailboxAppend, readMailboxPeerMeta, readStoredMailboxDecision } from "@sema-agent/core";
3
3
  import { pgHasUnstorable, pgSanitizeText, PgUnstorableError } from "./pg-safe-json.js";
4
4
  import { isWellFormedUnicode } from "../principal-jwt.js";
5
5
  import { ensureIndexes } from "./ensure-index.js";
@@ -25,15 +25,36 @@ export const MAILBOX_KEY_MAX_UTF8_BYTES = 190;
25
25
  export function mailboxKeyProblem(what, v) {
26
26
  if (what === "scope" && (v === undefined || v === ""))
27
27
  return "MailboxStore: refusing a message without a scope (an empty scope addresses no box)";
28
+ const unaddressable = byteExactIdentityProblem(what, v);
29
+ if (unaddressable !== undefined)
30
+ return unaddressable;
31
+ if (Buffer.byteLength(v, "utf8") > MAILBOX_KEY_MAX_UTF8_BYTES) {
32
+ return `MailboxStore: ${what} exceeds the ${MAILBOX_KEY_MAX_UTF8_BYTES}-byte key column (refusing — a truncated key would desync from its byte-exact lookups)`;
33
+ }
34
+ return undefined;
35
+ }
36
+ function byteExactIdentityProblem(what, v) {
28
37
  if (!isWellFormedUnicode(v))
29
38
  return `MailboxStore: ${what} is not well-formed Unicode (lone surrogate) (refusing — the driver would store it as U+FFFD and alias a different key)`;
30
39
  if ([...v].some((ch) => ch.charCodeAt(0) < 0x20 || ch.charCodeAt(0) === 0x7f))
31
40
  return `MailboxStore: ${what} contains control characters (refusing — identity keys must be byte-exact addressable)`;
32
- if (Buffer.byteLength(v, "utf8") > MAILBOX_KEY_MAX_UTF8_BYTES) {
33
- return `MailboxStore: ${what} exceeds the ${MAILBOX_KEY_MAX_UTF8_BYTES}-byte key column (refusing — a truncated key would desync from its byte-exact lookups)`;
34
- }
35
41
  return undefined;
36
42
  }
43
+ function appendSnapshot(msg) {
44
+ const rec = readMailboxAppend(msg);
45
+ if (rec.id !== undefined) {
46
+ const problem = byteExactIdentityProblem("record id", rec.id);
47
+ if (problem !== undefined)
48
+ throw new Error(problem);
49
+ }
50
+ return rec;
51
+ }
52
+ function replayedSeq(held, scope, handle, logger, asStored) {
53
+ return judgeMailboxAppendById(rowsToMessages(held, scope, handle, logger), asStored);
54
+ }
55
+ function asUtf8Stored(v) {
56
+ return Buffer.from(v, "utf8").toString("utf8");
57
+ }
37
58
  function assertMailboxKey(what, v) {
38
59
  const problem = mailboxKeyProblem(what, v);
39
60
  if (problem !== undefined)
@@ -130,6 +151,7 @@ function rowsToMessages(rows, scope, handle, logger) {
130
151
  };
131
152
  const hopChain = read("hop_chain", () => decodeHopChain(r.hop_chain, r.seq));
132
153
  const peerMeta = read("peer_meta", () => decodePeerMeta(r.peer_meta, r.seq));
154
+ const decision = decisionOfColumn(r.decision);
133
155
  out.push({
134
156
  seq: Number(r.seq),
135
157
  ...(r.from_id !== null && r.from_id !== undefined ? { from: bufToStr(r.from_id) } : {}),
@@ -137,10 +159,21 @@ function rowsToMessages(rows, scope, handle, logger) {
137
159
  sentAt: Number(r.sent_at_ms),
138
160
  ...(hopChain !== undefined ? { hopChain } : {}),
139
161
  ...(peerMeta !== undefined ? { peerMeta } : {}),
162
+ ...(decision !== undefined ? { decision } : {}),
163
+ ...(r.record_id !== null && r.record_id !== undefined ? { id: String(r.record_id) } : {}),
140
164
  });
141
165
  }
142
166
  return out;
143
167
  }
168
+ function decisionOfColumn(raw) {
169
+ return raw === null || raw === undefined ? undefined : readStoredMailboxDecision(bufToStr(raw));
170
+ }
171
+ function decisionMoveVerdict(rows, expect) {
172
+ if (rows.length === 0)
173
+ return { ok: false, current: "gone" };
174
+ const current = decisionOfColumn(rows[0].decision);
175
+ return current === expect ? "write" : { ok: false, current: current ?? "unset" };
176
+ }
144
177
  export async function ensureTiDBMailboxSchema(pool) {
145
178
  await pool.query(`CREATE TABLE IF NOT EXISTS ${MAILBOX_TABLE} (
146
179
  scope_key VARBINARY(190) NOT NULL,
@@ -166,8 +199,15 @@ export async function ensureTiDBMailboxSchema(pool) {
166
199
  -- peer_meta(S-90;core fc52b4d4 design/385 片2b):drain 点权威判断读的 typed peer 记录,JSON 文本;
167
200
  -- NULL = 缺席(**有语义**:pre-385/foreign 记录,drain 永不回溯准入;与空对象绝不互折)。写入面先过 core
168
201
  -- readMailboxPeerMeta(坏值带码拒于任何副作用前)+ PEER_META_MAX_BYTES 守卫关掉静默截断类。
169
- -- 新列按 2026-07-26 schema policy 直接折进 CREATE(不开 ALTER seam,存量库删库重建)。
202
+ -- 新列直接折进 CREATE(启动 DDL 不开 ALTER seam);存量库缺列 ⇒ 起服列册对账拒启并给出可粘贴的 ALTER 语句,由运维在升级前执行。
170
203
  peer_meta TEXT NULL,
204
+ -- decision(core 7.57.0 #972b):收件人设置留置的这条记录的**决定**(七词闭集 MAILBOX_DECISIONS);NULL = 从未 held。
205
+ -- 只经 moveDecision 的比较并设置移动,从不随 append 写;随记录生随记录亡(ack / reap / drop 删行即删它)—— 决定是记录自己的字段。
206
+ decision VARCHAR(16) NULL,
207
+ -- record_id(core 7.59.0):生产者铸的记录身份;NULL = 没说身份(每次 append 都是一条新记录)。盒持有一条这身份的记录时,append 经引擎的
208
+ -- judgeMailboxAppendById 判(同记录 ⇒ 答它的 seq、零写;不同 ⇒ mailbox.duplicate_record_id),判在 append 的盒行锁之内 —— 盒行 FOR UPDATE 串行了
209
+ -- 同一只盒的全部 append,「(scope, handle, id) 在持有行里至多一行」由它成立;ack / reap / drop 删行即还身份。宽 = 引擎的 MAILBOX_RECORD_ID_MAX_CHARS(列宽读那只常量,不抄数)。
210
+ record_id VARCHAR(${MAILBOX_RECORD_ID_MAX_CHARS}) NULL,
171
211
  PRIMARY KEY (scope_key, handle, seq),
172
212
  KEY idx_mbm_sent (scope_key, handle, sent_at_ms)
173
213
  ) COLLATE utf8mb4_bin`);
@@ -204,6 +244,8 @@ export async function ensurePgMailboxSchema(q) {
204
244
  sent_at_ms BIGINT NOT NULL,
205
245
  hop_chain TEXT COLLATE "C", -- core 5.17.0 design/176(TiDB twin 同注:NULL = 缺席且有语义)
206
246
  peer_meta TEXT COLLATE "C", -- S-90 core fc52b4d4 design/385 片2b(TiDB twin 同注:typed peer 记录 JSON;NULL = 缺席且有语义)
247
+ decision VARCHAR(16) COLLATE "C", -- core 7.57.0 #972b(TiDB twin 同注:留置决定,NULL = 从未 held;只经 moveDecision 移动,随记录亡)
248
+ record_id VARCHAR(${MAILBOX_RECORD_ID_MAX_CHARS}) COLLATE "C", -- core 7.59.0(TiDB twin 同注:生产者铸的记录身份,NULL = 没说身份;盒行锁之内经引擎判,随记录亡)
207
249
  PRIMARY KEY (scope_key, handle, seq)
208
250
  )`);
209
251
  await q(`CREATE TABLE IF NOT EXISTS ${MAILBOX_TOMBSTONE_TABLE} (
@@ -242,8 +284,9 @@ export class TiDBMailboxStore {
242
284
  async append(scope, handle, msg) {
243
285
  assertMailboxKey("scope", scope);
244
286
  assertMailboxKey("handle", handle);
245
- const hopChain = encodeHopChain(msg.hopChain);
246
- const peerMeta = encodePeerMeta(msg.peerMeta);
287
+ const rec = appendSnapshot(msg);
288
+ const hopChain = encodeHopChain(rec.hopChain);
289
+ const peerMeta = encodePeerMeta(rec.peerMeta);
247
290
  return this.tx(async (c) => {
248
291
  await c.query(`INSERT INTO ${MAILBOX_TABLE} (scope_key, handle, scope, next_seq) VALUES (?, ?, ?, 1) ON DUPLICATE KEY UPDATE scope_key = scope_key`, [scope, handle, scope]);
249
292
  let [rows] = (await c.query(`SELECT next_seq FROM ${MAILBOX_TABLE} WHERE scope_key = ? AND handle = ? FOR UPDATE`, [scope, handle]));
@@ -254,9 +297,15 @@ export class TiDBMailboxStore {
254
297
  const [tomb] = await c.query(`SELECT 1 FROM ${MAILBOX_TOMBSTONE_TABLE} WHERE scope_key = ? AND handle = ? FOR UPDATE`, [scope, handle]);
255
298
  if (tomb.length > 0)
256
299
  throw tombstonedRecipientError(scope, handle);
300
+ if (rec.id !== undefined) {
301
+ const [held] = await c.query(`SELECT seq, from_id, content, sent_at_ms, ${jsonColumnText("tidb", "hop_chain")}, ${jsonColumnText("tidb", "peer_meta")}, decision, record_id FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? AND handle = ? AND record_id = ? FOR UPDATE`, [scope, handle, rec.id]);
302
+ const replay = replayedSeq(held, scope, handle, this.logger, { ...rec, content: asUtf8Stored(rec.content), ...(rec.from !== undefined ? { from: asUtf8Stored(rec.from) } : {}) });
303
+ if (replay !== undefined)
304
+ return replay;
305
+ }
257
306
  const seq = Number(rows[0].next_seq);
258
307
  await c.query(`UPDATE ${MAILBOX_TABLE} SET next_seq = ? WHERE scope_key = ? AND handle = ?`, [seq + 1, scope, handle]);
259
- await c.query(`INSERT INTO ${MAILBOX_MSG_TABLE} (scope_key, handle, seq, from_id, content, sent_at_ms, hop_chain, peer_meta) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [scope, handle, seq, msg.from ?? null, msg.content, msg.sentAt, hopChain, peerMeta]);
308
+ await c.query(`INSERT INTO ${MAILBOX_MSG_TABLE} (scope_key, handle, seq, from_id, content, sent_at_ms, hop_chain, peer_meta, record_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, [scope, handle, seq, rec.from ?? null, rec.content, rec.sentAt, hopChain, peerMeta, rec.id ?? null]);
260
309
  return seq;
261
310
  });
262
311
  }
@@ -274,7 +323,7 @@ export class TiDBMailboxStore {
274
323
  bufToStr(box.lease_owner) !== owner) {
275
324
  return null;
276
325
  }
277
- const [msgs] = (await c.query(`SELECT seq, from_id, content, sent_at_ms, ${jsonColumnText("tidb", "hop_chain")}, ${jsonColumnText("tidb", "peer_meta")} FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? AND handle = ? ORDER BY seq FOR UPDATE`, [scope, handle]));
326
+ const [msgs] = (await c.query(`SELECT seq, from_id, content, sent_at_ms, ${jsonColumnText("tidb", "hop_chain")}, ${jsonColumnText("tidb", "peer_meta")}, decision, record_id FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? AND handle = ? ORDER BY seq FOR UPDATE`, [scope, handle]));
278
327
  if (msgs.length === 0)
279
328
  return null;
280
329
  const maxSeq = Number(msgs[msgs.length - 1].seq);
@@ -295,6 +344,22 @@ export class TiDBMailboxStore {
295
344
  return { acked: true };
296
345
  });
297
346
  }
347
+ async moveDecision(scope, handle, seq, expect, next) {
348
+ assertMailboxDecisionMove(seq, expect, next);
349
+ if (keyless(scope, handle))
350
+ return { ok: false, current: "gone" };
351
+ return this.tx(async (c) => {
352
+ const [boxes] = await c.query(`SELECT 1 FROM ${MAILBOX_TABLE} WHERE scope_key = ? AND handle = ? FOR UPDATE`, [scope, handle]);
353
+ if (boxes.length === 0)
354
+ return { ok: false, current: "gone" };
355
+ const [rows] = await c.query(`SELECT decision FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? AND handle = ? AND seq = ? FOR UPDATE`, [scope, handle, seq]);
356
+ const verdict = decisionMoveVerdict(rows, expect);
357
+ if (verdict !== "write")
358
+ return verdict;
359
+ await c.query(`UPDATE ${MAILBOX_MSG_TABLE} SET decision = ? WHERE scope_key = ? AND handle = ? AND seq = ?`, [next, scope, handle, seq]);
360
+ return { ok: true };
361
+ });
362
+ }
298
363
  async releaseLease(scope, handle, owner) {
299
364
  if (keyless(scope, handle, owner))
300
365
  return;
@@ -384,11 +449,12 @@ export class PgMailboxStore {
384
449
  async append(scope, handle, msg) {
385
450
  assertMailboxKey("scope", scope);
386
451
  assertMailboxKey("handle", handle);
387
- if (msg.from !== undefined && pgHasUnstorable(msg.from))
452
+ const rec = appendSnapshot(msg);
453
+ if (rec.from !== undefined && pgHasUnstorable(rec.from))
388
454
  throw new PgUnstorableError("mailbox from");
389
- const content = pgSanitizeText(msg.content);
390
- const hopChain = encodeHopChain(msg.hopChain);
391
- const peerMeta = encodePeerMeta(msg.peerMeta);
455
+ const content = pgSanitizeText(rec.content);
456
+ const hopChain = encodeHopChain(rec.hopChain);
457
+ const peerMeta = encodePeerMeta(rec.peerMeta);
392
458
  return this.tx(async (c) => {
393
459
  await c.query(`INSERT INTO ${MAILBOX_TABLE} (scope_key, handle, scope, next_seq) VALUES ($1, $2, $3, 1) ON CONFLICT (scope_key, handle) DO NOTHING`, [
394
460
  scope,
@@ -407,9 +473,15 @@ export class PgMailboxStore {
407
473
  const { rows: tomb } = await c.query(`SELECT 1 FROM ${MAILBOX_TOMBSTONE_TABLE} WHERE scope_key = $1 AND handle = $2 FOR UPDATE`, [scope, handle]);
408
474
  if (tomb.length > 0)
409
475
  throw tombstonedRecipientError(scope, handle);
476
+ if (rec.id !== undefined) {
477
+ const { rows: held } = await c.query(`SELECT seq, from_id, content, sent_at_ms, ${jsonColumnText("pg", "hop_chain")}, ${jsonColumnText("pg", "peer_meta")}, decision, record_id FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 AND handle = $2 AND record_id = $3 FOR UPDATE`, [scope, handle, rec.id]);
478
+ const replay = replayedSeq(held, scope, handle, this.logger, { ...rec, content });
479
+ if (replay !== undefined)
480
+ return replay;
481
+ }
410
482
  const seq = Number(rows[0].next_seq);
411
483
  await c.query(`UPDATE ${MAILBOX_TABLE} SET next_seq = $1 WHERE scope_key = $2 AND handle = $3`, [seq + 1, scope, handle]);
412
- await c.query(`INSERT INTO ${MAILBOX_MSG_TABLE} (scope_key, handle, seq, from_id, content, sent_at_ms, hop_chain, peer_meta) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`, [scope, handle, seq, msg.from ?? null, content, msg.sentAt, hopChain, peerMeta]);
484
+ await c.query(`INSERT INTO ${MAILBOX_MSG_TABLE} (scope_key, handle, seq, from_id, content, sent_at_ms, hop_chain, peer_meta, record_id) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`, [scope, handle, seq, rec.from ?? null, content, rec.sentAt, hopChain, peerMeta, rec.id ?? null]);
413
485
  return seq;
414
486
  });
415
487
  }
@@ -424,7 +496,7 @@ export class PgMailboxStore {
424
496
  const box = boxes[0];
425
497
  if (box.lease_owner !== null && Number(box.lease_expires_at_ms) > now && String(box.lease_owner) !== owner)
426
498
  return null;
427
- const { rows: msgs } = await c.query(`SELECT seq, from_id, content, sent_at_ms, ${jsonColumnText("pg", "hop_chain")}, ${jsonColumnText("pg", "peer_meta")} FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 AND handle = $2 ORDER BY seq FOR UPDATE`, [scope, handle]);
499
+ const { rows: msgs } = await c.query(`SELECT seq, from_id, content, sent_at_ms, ${jsonColumnText("pg", "hop_chain")}, ${jsonColumnText("pg", "peer_meta")}, decision, record_id FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 AND handle = $2 ORDER BY seq FOR UPDATE`, [scope, handle]);
428
500
  if (msgs.length === 0)
429
501
  return null;
430
502
  const maxSeq = Number(msgs[msgs.length - 1].seq);
@@ -445,6 +517,22 @@ export class PgMailboxStore {
445
517
  return { acked: true };
446
518
  });
447
519
  }
520
+ async moveDecision(scope, handle, seq, expect, next) {
521
+ assertMailboxDecisionMove(seq, expect, next);
522
+ if (keyless(scope, handle))
523
+ return { ok: false, current: "gone" };
524
+ return this.tx(async (c) => {
525
+ const { rows: boxes } = await c.query(`SELECT 1 FROM ${MAILBOX_TABLE} WHERE scope_key = $1 AND handle = $2 FOR UPDATE`, [scope, handle]);
526
+ if (boxes.length === 0)
527
+ return { ok: false, current: "gone" };
528
+ const { rows } = await c.query(`SELECT decision FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 AND handle = $2 AND seq = $3 FOR UPDATE`, [scope, handle, seq]);
529
+ const verdict = decisionMoveVerdict(rows, expect);
530
+ if (verdict !== "write")
531
+ return verdict;
532
+ await c.query(`UPDATE ${MAILBOX_MSG_TABLE} SET decision = $1 WHERE scope_key = $2 AND handle = $3 AND seq = $4`, [next, scope, handle, seq]);
533
+ return { ok: true };
534
+ });
535
+ }
448
536
  async releaseLease(scope, handle, owner) {
449
537
  if (keyless(scope, handle, owner))
450
538
  return;
@@ -15,6 +15,10 @@ export declare const MEMORY_SLUG_COLUMN_CHARS = 512;
15
15
  * sync_cursor/push_queue/history)。`formatUserScope`/`formatProjScope` 的段编码(百分号转义)会
16
16
  * **膨胀**——非 ASCII principal 编码后可超列宽,故产出处与写口两道都要判。 */
17
17
  export declare const MEMORY_SCOPE_COLUMN_CHARS = 190;
18
+ /** memory-sync 店 `sync_cursor.peer` 列宽(两方言 VARCHAR(190) 同宽)—— 中心侧请求的 `peer` 落这一列。两个读者:中心侧验型
19
+ * (`memory-sync.ts` `parseMemorySyncRequest`,超宽 400)与客户端的身份铸点(`memory-sync-client.ts` `memorySyncPeerOf`,超宽拒启:
20
+ * 身份就是请求的 `peer`,装不进这一列的身份每一轮都会被中心拒)。 */
21
+ export declare const MEMORY_SYNC_PEER_COLUMN_CHARS = 190;
18
22
  export declare function assertSlugWidth(slug: string): void;
19
23
  export declare function assertMemoryScopeWidth(scope: string): void;
20
24
  /**
@@ -1,5 +1,6 @@
1
1
  export const MEMORY_SLUG_COLUMN_CHARS = 512;
2
2
  export const MEMORY_SCOPE_COLUMN_CHARS = 190;
3
+ export const MEMORY_SYNC_PEER_COLUMN_CHARS = 190;
3
4
  function widthChars(v) {
4
5
  return [...v].length;
5
6
  }
@@ -47,7 +47,7 @@ export declare class MemoryRunStore implements InProcessClaimFence {
47
47
  * OBSERVED, see {@link CreateRunOutcome}) and the session has no active run (the task_active unique-key claim). */
48
48
  createRun(taskId: string, sessionId: string, owner: string | null, instanceId: string, meta?: NewRunMeta): Promise<CreateRunOutcome>;
49
49
  /** Undo THIS submission's binding (key + body fingerprint → null) — called only where the submit surface refuses the submission
50
- * before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, a device-placement reject):
50
+ * before any work and terminalizes the row it just claimed (the sync leg's zero-work refusal, an execution-placement reject):
51
51
  * the error answer invites a retry, and a retry with the same key must run, not replay the refusal. The row stays (it
52
52
  * is the refusal's record); only the binding is freed. */
53
53
  releaseIdemKey(taskId: string): Promise<void>;
@@ -1,4 +1,3 @@
1
- import type { MemorySyncCursor } from "@sema-agent/core";
2
1
  import type { PgQueryFn } from "./pg-query.js";
3
2
  import { type IndexSpec } from "./ensure-index.js";
4
3
  /** Table names (single source). SAME names as the TiDB twin (the two dialects never share one DB). */
@@ -31,12 +30,24 @@ export interface ClaimedPushQueueItem {
31
30
  /** The lease deadline this claim holds the row until (nowMs + claimLeaseMs). */
32
31
  leasedUntilMs: number;
33
32
  }
33
+ /**
34
+ * 中心侧的同步游标 = 本服务作同步**中心**时,每个 (scope, peer) 的基线(`sync_cursors` 一行)—— 本服务自己的事实,本服务自己的型。
35
+ * 它**不是**引擎的 `MemorySyncCursor`:那一只是同步**客户端**的游标,引擎 7.59.0 起必带 `store`(客户端本地 File 店的物理根,`memorySyncStoreOf` 铸),
36
+ * 绑的是「这份基线是对着哪一只本地店取的」;中心这一侧的条目平面是部署的 DB 后端,没有本地店根可绑,游标也从不交给引擎判。
37
+ * 此前本文件借用了引擎那只型名(两者当时同形),引擎给它加了只属于客户端的成员之后,借用就成了两个事实共用一个名字 —— 本型收回。
38
+ */
39
+ export interface CentralSyncCursor {
40
+ readonly scope: string;
41
+ readonly peer: string;
42
+ readonly baseRevs: Readonly<Record<string, string>>;
43
+ readonly updatedAtMs: number;
44
+ }
34
45
  /** The dialect-neutral contract both twins implement (anti-drift: one shared integration suite,
35
46
  * test/memory-sync-store-db-integration.test.ts, runs the SAME scenarios against BOTH engines). */
36
47
  export interface MemorySyncStore {
37
- getCursor(scope: string, peer: string): Promise<MemorySyncCursor | undefined>;
48
+ getCursor(scope: string, peer: string): Promise<CentralSyncCursor | undefined>;
38
49
  /** Whole-row replace (round semantics — baseRevs is swapped as one map, never merged). */
39
- putCursor(cursor: MemorySyncCursor): Promise<void>;
50
+ putCursor(cursor: CentralSyncCursor): Promise<void>;
40
51
  enqueue(item: PushQueueItem): Promise<void>;
41
52
  /** Lease-claim up to `limit` due rows (next_attempt_at_ms <= nowMs), oldest-due first. Claimed rows
42
53
  * are invisible to other claimers until `complete`/`fail` or lease expiry (crash self-heal). */
@@ -66,8 +77,8 @@ export declare class PgMemorySyncStore implements MemorySyncStore {
66
77
  private readonly query;
67
78
  private readonly claimLeaseMs;
68
79
  constructor(query: PgQueryFn, opts?: PgMemorySyncStoreOptions);
69
- getCursor(scope: string, peer: string): Promise<MemorySyncCursor | undefined>;
70
- putCursor(cursor: MemorySyncCursor): Promise<void>;
80
+ getCursor(scope: string, peer: string): Promise<CentralSyncCursor | undefined>;
81
+ putCursor(cursor: CentralSyncCursor): Promise<void>;
71
82
  enqueue(item: PushQueueItem): Promise<void>;
72
83
  claimDue(nowMs: number, limit: number): Promise<ClaimedPushQueueItem[]>;
73
84
  complete(id: string): Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import type { ClaimedPushQueueItem, MemoryHistoryAppendRow, MemoryHistoryRow, MemoryHistoryStore, MemorySyncStore, PushQueueItem } from "./memory-sync-store-pg.js";
2
- import type { MemorySyncCursor } from "@sema-agent/core";
2
+ import { type CentralSyncCursor } from "./memory-sync-store-pg.js";
3
3
  import type { Pool } from "mysql2/promise";
4
4
  /** Table names (single source) — SAME names as PG_MEMORY_SYNC_TABLES (the two dialects never share
5
5
  * one database). */
@@ -21,8 +21,8 @@ export declare class TiDBMemorySyncStore implements MemorySyncStore {
21
21
  private readonly pool;
22
22
  private readonly claimLeaseMs;
23
23
  constructor(pool: Pool, opts?: TiDBMemorySyncStoreOptions);
24
- getCursor(scope: string, peer: string): Promise<MemorySyncCursor | undefined>;
25
- putCursor(cursor: MemorySyncCursor): Promise<void>;
24
+ getCursor(scope: string, peer: string): Promise<CentralSyncCursor | undefined>;
25
+ putCursor(cursor: CentralSyncCursor): Promise<void>;
26
26
  enqueue(item: PushQueueItem): Promise<void>;
27
27
  claimDue(nowMs: number, limit: number): Promise<ClaimedPushQueueItem[]>;
28
28
  complete(id: string): Promise<void>;
@@ -227,9 +227,12 @@
227
227
  * · **M25 单用户记忆项目层换键形**(7.133.0)—— 不在店里、不在起服里(与 M10 同为**窗内才存在的行为臂**,但它是改写):项目层键从 `repo:<sha256(项目根)[:24]>`
228
228
  * (7.98.0–7.132.x)换成带主体、跨设备的 `userrepo:<属主段>/<档>.<摘要>`(文法 `project-layer-key.ts`)。退役键是路径哈希、反推不出路径 ⇒ 起服没法枚举改写;
229
229
  * 改为**每个项目第一次跑任务时**由项目层准备一步(`memory-project-layer.ts` `createMemoryProjectLayers`)把该 checkout 的 `repo:` 层经 core `migrateScope`
230
- * 折进新键(File 与两只 SQL 孪生同一条;id 保留、CAS,冲突留原地下次再试)。同一窗同删的另一半:同步客户端腿的旧游标文件 `sync-cursor-<peer>.json`(只按 peer 分区)
231
- * 第一轮收编为同步层的新游标(`memory-sync-client.ts` `adoptLegacyCursor`)。本条不写起服读数(`reportKeys` 空,与 M7 / M10 同形);折了什么由运行期 info
232
- * `memory_project_layer_folded` / `memory_sync_cursor_adopted` 说。常设的另一条律(`r` 档时本 checkout 的 `p` 档折进当前档)不是迁移,不随本条删。
230
+ * 折进新键(File 与两只 SQL 孪生同一条;id 保留、CAS,冲突留原地下次再试)。本条不写起服读数(`reportKeys` 空,与 M7 / M10 同形);折了什么由运行期 info
231
+ * `memory_project_layer_folded` 说。常设的另一条律(`r` 档时本 checkout 的 `p` 档折进当前档)不是迁移,不随本条删。
232
+ * **曾属本条的另一半已退役(如实)**:同步客户端腿的旧游标文件 `sync-cursor-<peer>.json`(只按 peer 分区)的收编臂。引擎 7.59.0 起游标必带店绑定 `store`,
233
+ * 升级前落盘的旧文件都没有这一员,收编的形判从不认它 ⇒ 臂对任何存量文件都不可达,已整条删码(收编函数、守卫旗标、日志行一并删,零引用有钉:
234
+ * `test/memory-sync-client.test.ts`)。旧文件不再被当游标;它只回答「同步层有没有一份店绑定之前的基线」—— 新路径上没有文件而它是同步层的无绑定基线 ⇒
235
+ * 同步层不同步、运维通告点名它(与新路径上的无绑定游标同一条律,`memory-sync-client.ts` 头注 ⑤;不是本条的迁移臂,不随本条删)。
233
236
  * · **M26 删掉退役的共享断路器表 `circuit_breaker`**(7.133.0)—— 断路器键的路由段从 baseUrl **原串**换成路由身份的摘要(铸点与形 `breaker-key.ts`),
234
237
  * 新一代键写**新表** `breaker_state`(中央 DDL 建)。旧表 `circuit_breaker` 的行是旧键(`<provider>:<id>@<baseUrl>`,网关根原串 query 可带凭据),
235
238
  * 新代码不读不写它、它也没有 TTL 腿 ⇒ 不删就永远在共享库里。**整只删表**(`DROP TABLE IF EXISTS circuit_breaker`,不看任何一行的内容):
@@ -284,6 +287,35 @@
284
287
  * ≤7.136.x 的读者只认 `promptProfile`,读到本条改写过的体(旧键已删、换成 `promptLean`)回到缺省形 —— 本版副本起服之后仍在续跑的 7.136.x 副本、回滚到 7.136.x 之后,
285
288
  * 被改写过的 `"classic"` 行静默回短形;也不能给旧读者留旧键(那是同一事实的两份表示,本版读者对仍带旧键的体响亮拒)⇒ 升级序是**先停光 ≤7.136.x 副本、再起第一个 7.137.0 副本**,
286
289
  * 回滚之前先决完或取消停驻的任务(随包 MIGRATION 7.137.0 第 4 条;与 M28 的混跑登记同形)。
290
+ * · **M31 记忆同步游标搬到每层一只的路径**(7.139.0,引擎 7.60.0)—— 一轮的对端改由 transport 的身份(规范化的中心基址)说,游标改成每层一只
291
+ * `sync-cursors/<scope 摘要>.json`(对端是游标里的一格事实,只由引擎判)。升级前的游标落在上一版的分区目录 `sync-cursors/central/<同名>` 与更早的
292
+ * `sync-cursor-central.json` —— 新路径上找不到它们 ⇒ 那几层以空基线首轮跑(没人选过的重收敛:上一轮之后在一侧删掉的条目从另一侧回来)。
293
+ * ⇒ **同步执行器的第一轮先把它们搬到各自那一层的路径**(只 `rename`、字节不变;编排在 `memory-sync-client.ts` `relocateSyncCursorsM31`,不在本文件的
294
+ * 迁移段里 —— 那一段跑在 SQL 店层,游标是 File 记忆根下的文件),搬来的游标照常交引擎判:它们绑的对端是 `"central"`,不等于任何铸出的身份 ⇒
295
+ * 这一层拒同步并点名两条出路(运维确认同一只中心后把游标的 `peer` 改成新身份,或删游标接受空基线重收敛)。目标路径上已有游标 ⇒ 不搬、一行 warn;
296
+ * 更早的那只文件读不成对象 / 没有 scope ⇒ 原地不动、一行 warn。读数不进 {@link OneTimeMigrationReport}(`reportKeys` 空),逐文件一行 info / warn。窗外不搬。
297
+ * · **M32 执行放置切换**(7.139.0)—— 执行放置是根会话行上这一位住客的一格(`execution-placement.ts` 头注),根会话 ↔ 设备的绑定是这一格的 writer(device, D);
298
+ * ≤7.137.x 的绑定住在单独一张表 `device_session` 里(起服无条件建它)。运行期只有会话行一种读法 ⇒ **一次性**把绑定整行搬进会话行、表退役,存量有 run 的会话
299
+ * 回填放置。判据只有库自己的形(读者 = 本文件的两只在场探针 `tableExists` / `columnExists`):**切换开始** ⇔ `session_meta.execution_role` 列在 ∨ 中途表
300
+ * `device_session_retired` 在;**切换收尾** ⇔ 八列都在 ∧ 两张绑定表都不在(这就是「跑过了」的标记,没有账表)。
301
+ * · **DDL 前只读探针**({@link assertExecutionPlacementSwitchAdmissible},与退役 schema 探针同层、同一只调用点 —— `store-backend.ts` 的 `withRetiredSchemaProbe`,先于第一条 CREATE ⇒ 拒时库零写;
302
+ * 全新库不判):**围栏(唯一一条)** 切换开始 ∧ `device_session` 在 ∧ 有行 ⇒ 拒 `fence`(旧版副本在切换开始后还在写它,或手写行 —— 谁新谁旧本服务判不出、不替人挑;
303
+ * 拒句按方言给「采旧 = 改名进中途表、起服时整行覆盖」/「弃旧 = DROP」两条语句与根会话列表);**回填前提** 切换未收尾 ∧ 车道表不是一元 ∧ 回填前提表里任一员答「还有」⇒ 拒 `backfill_ambiguous`
304
+ * (今天车道表恒一元 ⇒ 这一臂在声明多车道之前不可达;回填前提表是有序可扩的一张,问行车道的那一员由审批店那一侧补)。码 `config.execution_placement_switch_refused`。
305
+ * · **迁移段**(本段最后一步,锁内;切换已收尾 ⇒ 零动作;否则先在锁内复判围栏,再按有序步骤表逐步收敛 —— 每步先读形再判、DDL 按后置条件 `execUntil`):
306
+ * ⓪ 切换开始 ∧ `device_session` 在(围栏判过 ⇒ 空的:旧版副本在已切换库上起服建回的空表)⇒ DROP · ① `device_session` 在 ∧ 执行列不在 ⇒ 改名成中途表(先于加列)·
307
+ * ② 八列里缺的逐列 ADD(列定义 = 中央 DDL 内插的同一份)· ③ 中途表整行覆盖根会话行的 device 位(`rev` 原值、`bound_at → execution_placed_at`;根会话行不在 / 是委派分区行的孤儿丢弃并计数)·
308
+ * ④ 有 run、没有执行格、不是委派分区行的会话行 ⇒ writer(表唯一项)(device 车道 ⇒ writer(device, ∅) = 有 run 而设备未知;不是一元表而还有要回填的 ⇒ 与探针同一只拒)·
309
+ * (问行车道回填由审批店那一侧补在 ④ 之后)· ⑤ 中途表 DROP。中断 ⇒ 下一次起服从首个未满足的后置条件续(钉:真库逐步注入中断后续跑收敛、不触围栏)。
310
+ * · **local 车道**:没有绑定表、没有 `session_meta`(device 车道只在 SQL 后端)⇒ 只有 ④ 与回填前提:读本地 run 店有 run 的会话,执行席读出 absent 的 ⇒ 经同一只席 `place` 成 writer(表唯一项)
311
+ * (`LocalBackend.runOneTimeMigrations`);local 没有「切换收尾」的形可读 ⇒ 窗内每次起服都判,首跑之后它只碰认领与放置之间被打断的会话(与 `place` 会落下的格同值)。
312
+ * 有 run、而**存档读不出**的会话 —— 会话元数据格缺(升级链上从没被再读过的老会话)而探针读不出,或 fork 创建的格而探针说不出存档在不在 ——:回填跳过它并点名
313
+ * (读数 `executionPlacementSessionsUnreadable` + 一行一条 warn `execution_placement_session_unreadable`,带会话 id 与故障词〔闭集 `corrupt_session` / `invalid_session` / `storage`;
314
+ * `storage` = 读存档文件时抛了别的故障:权限 / 磁盘 I/O / 超大,再带 `errno`〕),**不拒启**、一个字节没写 —— 回填只补一格放置、不动存档,读不出的存档不归它判
315
+ * (这一支只收**探针对这只会话的读故障**;会话元数据边档本身读不出、问账日志压实失败等整库级故障照旧拒启);该会话的请求照旧答存档的故障,存档修好(从留存副本恢复,
316
+ * 或 `storage` 时权限 / 磁盘修好)后的下一次起服照常补放置;存档移走则它不再是本服务的会话(不补放置、不再点名;fork 创建的会话例外 —— 它的格还在,读作 fork 未落定,出路是同键重试那次 fork 或删除该会话)。
317
+ * · **旧版在已切换库上**:≤7.137.x 起服建回空 `device_session`,随后列册对账见执行列为幽灵列拒启、零行写;再起本版 ⇒ ⓪ 删掉那张空表。回滚的唯一支持形 = 恢复切换前的备份。
318
+ * 读数:`executionPlacementColumnsAdded` / `deviceBindingsMoved` / `deviceBindingsOrphaned`(非零 ⇒ 另一行 warn)/ `deviceBindingTablesDropped` / `executionPlacementsBackfilled` / `executionPlacementSessionsUnreadable`(点名数组 `{ sessionId, fault, errno? }`,只 local;一行一条 warn),收据只记起服日志行。
287
319
  *
288
320
  * ## 收据账(一次起服一只,每一条迁移同一只)
289
321
  * 每一步把它**已提交**的改写 / 删除与点名读数当场记进同一只收据账({@link MigrationReceipts}):计数随每次提交累加、行随每次提交追加、形读数在那一步落定时写。走完 ⇒
@@ -301,7 +333,11 @@
301
333
  import type { Pool as MysqlPool } from "mysql2/promise";
302
334
  import type { Pool as PgPool } from "pg";
303
335
  import { type AllowEntryInertReason, type GateKind } from "@sema-agent/core";
336
+ import { StoreBootRefusalError } from "./store-contracts.js";
304
337
  import { type UndurableScopeRuleRow, type UnreadableRuleBucket } from "./permission-rule-store-sql.js";
338
+ import type { SchemaProbeRead } from "./schema-live-columns.js";
339
+ import type { ExecutionLane } from "../execution-lane-caps.js";
340
+ import { type LocalPlacementBackfillSeat, type UnusableArchiveWord } from "../execution-placement.js";
305
341
  /** 迁移读数(启动日志一行写全;测试按它断言,不靠日志文本)。 */
306
342
  export interface OneTimeMigrationReport {
307
343
  /** M6:本次给「只有句子」的死亡判决行补上机读码的 run 行数(SQL `task_run` / local `run.json`)。非零 = 这次迁移真写了存量行,必须让运维看见。 */
@@ -372,6 +408,21 @@ export interface OneTimeMigrationReport {
372
408
  /** M29:该回填、卡词却放不进列的停驻行 —— 门上的卡词超过 `approval_card` 列的容量(MySQL 协议方言 TEXT = 65,535 UTF-8 字节;PG 无上限,恒空)。引擎铸不出这样的词,
373
409
  * 只会是手改 / 外来写者写下的行;一个字节没写(列留 NULL,读面按缺列读 —— 与「无卡」同读),起服不挡。一行一条 warn 点名(会话 id + 字节数)。SQL 独有;local 恒空。 */
374
410
  readonly parkedApprovalCardRowsTooLong: readonly ParkedApprovalCardRowTooLong[];
411
+ /** M32:本次给 `session_meta` 补上的执行放置列数(八列里缺的那几列;新库 / 切换已收尾恒 0)。非零 = 这次真改了表形。SQL 独有;local 恒 0。 */
412
+ readonly executionPlacementColumnsAdded: number;
413
+ /** M32:本次从退役的绑定表(经中途名)整行搬进根会话行执行格的绑定数(驱动回报的受影响行数)。SQL 独有;local 恒 0。 */
414
+ readonly deviceBindingsMoved: number;
415
+ /** M32:搬的时候指向的根会话行不在(或是委派分区行)⇒ 丢弃的绑定数(非零 ⇒ 另一行 warn 点名条数)。SQL 独有;local 恒 0。 */
416
+ readonly deviceBindingsOrphaned: number;
417
+ /** M32:本次删掉的绑定表数(⓪ 空的退役表 / ⑤ 搬完的中途表;0–2)。SQL 独有;local 恒 0。 */
418
+ readonly deviceBindingTablesDropped: number;
419
+ /** M32:本次回填的执行放置数(有 run 行、没有执行格的会话 ⇒ writer(表唯一项))。SQL 与 local 都有。非零 = 真写了存量行。 */
420
+ readonly executionPlacementsBackfilled: number;
421
+ /** M32 ④b:本次回填了出处车道的问行数(车道未回填的问行 ⇒ 根会话放置的车道,无放置 / 无会话行 ⇒ 表唯一项)。SQL 与 local(问账日志一次压实)都有。非零 = 真写了存量行。 */
422
+ readonly askExecutionLanesBackfilled: number;
423
+ /** M32 local 腿:该回填放置、却读不出的会话 —— 有 run、存档读不出(会话头读不出 / 结构走不通 / 读存档文件时抛了别的故障:权限 / 磁盘 I/O / 超大)。一个字节没写,起服不挡:
424
+ * 该会话的请求照旧答存档的故障;存档修好(从留存副本恢复,或权限 / 磁盘修好)后的下一次起服照常补放置。点名行数组(会话 id + 故障词,`storage` 再带 errno),一行一条 warn,每次起服都报直到处置。SQL 独有的反面:SQL 没有这一形(行就是会话);SQL 恒空。 */
425
+ readonly executionPlacementSessionsUnreadable: readonly ExecutionPlacementSessionUnreadable[];
375
426
  }
376
427
  /** 退役的两只命令桶(引擎 7.51.0 起不是桶;只给 M17 点名认)。 */
377
428
  export type RetiredSessionRuleBucket = "commandAllow" | "commandDeny";
@@ -412,6 +463,16 @@ export interface ParkedApprovalCardRowUnreadable {
412
463
  export interface ParkedApprovalCardRowTooLong extends ParkedApprovalCardRowUnreadable {
413
464
  readonly bytes: number;
414
465
  }
466
+ /**
467
+ * M32 local 腿被跳过的一只会话:会话 id 是点名坐标;故障词是闭集 {@link UnusableArchiveWord}(`corrupt_session` = 会话头读不出;`invalid_session` = 读得出、结构走不通;
468
+ * `storage` = 读的时候抛了别的故障,I/O 级 —— 权限 / 磁盘 / 超大,此时 `errno` 带那只故障的 `code`)。盘上路径与故障句不出店。
469
+ */
470
+ export interface ExecutionPlacementSessionUnreadable {
471
+ readonly sessionId: string;
472
+ readonly fault: UnusableArchiveWord;
473
+ /** 仅 `fault === "storage"` 且那只故障带字符串 `code` 时在场(`EACCES` / `EIO` / `ERR_STRING_TOO_LONG` ……)。 */
474
+ readonly errno?: string;
475
+ }
415
476
  /** 三个形读数(M9 主键 / M17 列名 / M24 scope 列)—— 那一步**落定**之前收据账里缺席(读数行要它们齐;被打断的那一行只带已落定的)。 */
416
477
  type ShapeReadingKey = "workflowParkIndexKey" | "sessionRuleColumn" | "workflowJournalScopeColumn";
417
478
  /**
@@ -482,8 +543,8 @@ export declare function migrationWindowOpen(window: Pick<OneTimeMigrationWindow,
482
543
  * 还没有日期可写)⇒ 响亮抛,不铸一句带空日期的句子。
483
544
  */
484
545
  export declare function migrationWindowEnd(id: string): string;
485
- /** local 车道上 SQL 独有的几条(M8 / M9 / M11 / M17 / M18 / M21 / M23 / M24 / M26 / M27 / M29)的读数(M6 两车道都有,local 腿的读数由 `LocalBackend.runOneTimeMigrations` 现算):那几张表 / 列在文件底座上从来不存在 ⇒ 如实答 absent / 0。 */
486
- export declare const NO_SQL_MIGRATIONS: Omit<OneTimeMigrationReport, "holderDeathCodeRowsBackfilled">;
546
+ /** local 车道上 SQL 独有的几条(M8 / M9 / M11 / M17 / M18 / M21 / M23 / M24 / M26 / M27 / M29)的读数(M6 与 M32 的回填两车道都有,local 腿的读数由 `LocalBackend.runOneTimeMigrations` 现算):那几张表 / 列在文件底座上从来不存在 ⇒ 如实答 absent / 0。 */
547
+ export declare const NO_SQL_MIGRATIONS: Omit<OneTimeMigrationReport, "holderDeathCodeRowsBackfilled" | "executionPlacementsBackfilled" | "askExecutionLanesBackfilled" | "executionPlacementSessionsUnreadable">;
487
548
  /** 启动期的日志口(logger 的子集)。两级各有其用,见 {@link logOneTimeMigrations}。 */
488
549
  export interface MigrationLogSink {
489
550
  warn(msg: string, meta?: Record<string, unknown>): void;
@@ -516,7 +577,57 @@ export interface MigrationDb {
516
577
  * 本函数**不问**退役 schema:带着它的库在更早处就被 `ensureSchema()` 自身拒掉了(起服对账一族 `schema-consistency.ts` 的探针口,先于建表);走公开序列
517
578
  * `createStoreBackend()` → `ensureSchema()` → `runOneTimeMigrations()` 的嵌入方与本服务的起服序列一样,有这一问、零动作。
518
579
  */
519
- export declare function runSqlOneTimeMigrations(dialect: "tidb" | "pg", pool: MysqlPool | PgPool): Promise<OneTimeMigrationReport>;
580
+ export declare function runSqlOneTimeMigrations(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, input: ExecutionPlacementSwitchInput): Promise<OneTimeMigrationReport>;
581
+ /** M32 的起服拒码(确定的拒启表态:再等也等不来另一个答案;不是线上 wire 码,与 `config.schema_mismatch` 同一族)。 */
582
+ export declare const EXECUTION_PLACEMENT_SWITCH_REFUSED_CODE = "config.execution_placement_switch_refused";
583
+ /** 拒因闭集:`fence` = 切换开始之后退役绑定表里又出现了行(旧版副本 / 手工写下的);`backfill_ambiguous` = 车道表不止一项而还有要回填的存量。 */
584
+ export type ExecutionPlacementSwitchRefusalReason = "fence" | "backfill_ambiguous";
585
+ /**
586
+ * M32 的起服拒(DDL 前探针与迁移段同一只错误)。具名类型 + 机读码 + 拒因:日志消费端与测试按 `code` / `reason` 分派,不锚文案。
587
+ * 继承 {@link StoreBootRefusalError}:SQL 开库的等待窗不重试它(确定的拒启表态)。
588
+ */
589
+ export declare class ExecutionPlacementSwitchRefusedError extends StoreBootRefusalError {
590
+ readonly reason: ExecutionPlacementSwitchRefusalReason;
591
+ readonly code = "config.execution_placement_switch_refused";
592
+ constructor(reason: ExecutionPlacementSwitchRefusalReason, detail: string);
593
+ }
594
+ /** M32 的输入:本部署声明的有序执行车道表(本版 `REMOTE_EXEC` 单值 ⇒ 一元表,`deployedExecutionLanes`)。回填只在一元表下有唯一答案。 */
595
+ export interface ExecutionPlacementSwitchInput {
596
+ readonly lanes: readonly ExecutionLane[];
597
+ }
598
+ /**
599
+ * M32 的 **DDL 前只读探针**(与退役 schema 探针同层、同一只调用点:`store-backend.ts` 的 `withRetiredSchemaProbe`,先于第一条 CREATE):围栏与回填前提。
600
+ * 拒在任何 DDL 之前 ⇒ 库零写。全新库(没有 `session_meta`)不判。纯读,不取 schema 锁(与退役 schema 探针同理)。
601
+ */
602
+ export declare function assertExecutionPlacementSwitchAdmissible(read: SchemaProbeRead, dialect: "tidb" | "pg", input: ExecutionPlacementSwitchInput): Promise<void>;
603
+ /** 探针口(入参形与本文件的迁移段入口 {@link runSqlOneTimeMigrations} 同:方言 + 它的池;借一条连接,用完即还)。 */
604
+ export declare function assertExecutionPlacementSwitchAdmissibleOnPool(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, input: ExecutionPlacementSwitchInput): Promise<void>;
605
+ /**
606
+ * M32(规则与理由见头注 M32 段):切换已收尾(八列在 ∧ 两张绑定表都不在)⇒ 零动作;否则先复判围栏(探针之后、锁内这一刻又有旧版副本写进行 ⇒ 同一只拒),
607
+ * 再按步骤表逐步收敛。跑在一次性迁移的锁里(与别的迁移步同一条连接)。导出给真库钉驱动「每步中断后续跑」。
608
+ */
609
+ export declare function switchExecutionPlacement(db: MigrationDb, dialect: "tidb" | "pg", receipts: MigrationReceipts, input: ExecutionPlacementSwitchInput): Promise<void>;
610
+ /** {@link backfillLocalExecutionPlacements} 的结果:回填了几只会话 · 读不出而被跳过的会话(点名)。 */
611
+ export interface LocalPlacementBackfill {
612
+ readonly backfilled: number;
613
+ readonly unreadable: readonly ExecutionPlacementSessionUnreadable[];
614
+ }
615
+ /**
616
+ * M32 的 **local 腿**(local 车道没有绑定表、没有 `session_meta`,只有 ④ 与回填前提):本地 run 店里有 run 的会话(`FileRunStore.sessionIdsWithRuns`),
617
+ * 执行席读出 absent 的 ⇒ 经**同一只席**的 `place` 写成 writer(表唯一项)(写只按住客谓词;forking / mirror / writer 与行不在的一律不碰)。
618
+ * 席读出「存档读不出」({@link LocalPlacementBackfillSeat}:会话头读不出 / 结构走不通 / 读的时候抛了别的故障 —— I/O 级的权限、磁盘、超大)⇒ **跳过并点名**
619
+ * (`unreadable`:会话 id + 故障词〔闭集 {@link UnusableArchiveWord}〕,`storage` 再带 errno;一个字节没写,不拒启):
620
+ * 回填不动存档、只补一格放置,读不出的存档不归它判;该会话的请求照旧答存档的故障,存档修好后的下一次起服照常补。读不出的会话不计入「还有要回填的」。
621
+ * 车道表不是一元而还有 absent 的 ⇒ 与 SQL 同一只拒(`backfill_ambiguous`),一个字节没写。起服时调(`LocalBackend.runOneTimeMigrations`,任何读者之前;数据根独占)。
622
+ */
623
+ export declare function backfillLocalExecutionPlacements(sessionIds: Iterable<string>, seat: LocalPlacementBackfillSeat, input: ExecutionPlacementSwitchInput): Promise<LocalPlacementBackfill>;
624
+ /**
625
+ * M32 ④b 的 **local 腿**:问账日志里升级前写下的问(没有出处车道)记成车道表的唯一项,一次压实重写整本账(`FileApprovalAskStore.backfillExecutionLanes`)。
626
+ * 车道表不是一元而还有缺车道的问 ⇒ 与 SQL 同一只拒(`backfill_ambiguous`),一个字节没写。返回回填数。起服时调(与 ④ 的 local 腿同位,任何读者之前)。
627
+ */
628
+ export declare function backfillLocalAskExecutionLanes(store: {
629
+ backfillExecutionLanes(lanes: readonly ExecutionLane[], refuse: (pending: number) => never): number;
630
+ }, input: ExecutionPlacementSwitchInput): number;
520
631
  /**
521
632
  * M24(7.133.0)—— workflow journal 的属主 scope 从逐行列搬进 `workflow_journal_owner` 一行,journal 行改按 `(run_id, incarnation, ordinal)` 记。规则与理由在
522
633
  * 头注 M24 段;这里只写各步为什么这样写。