@sema-agent/server 7.148.0 → 7.150.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/MIGRATION.md +551 -9
  2. package/README.md +17 -2
  3. package/README.zh-CN.md +5 -1
  4. package/USAGE.md +126 -68
  5. package/dist/adoption/wire.d.ts +3 -3
  6. package/dist/approval-card.d.ts +33 -33
  7. package/dist/approval-gate-currency.d.ts +1 -1
  8. package/dist/approval-leg-origin.d.ts +33 -0
  9. package/dist/approval-leg-origin.js +4 -0
  10. package/dist/audit.d.ts +1 -1
  11. package/dist/audit.js +2 -2
  12. package/dist/auth-bridge.d.ts +9 -1
  13. package/dist/auth-bridge.js +4 -4
  14. package/dist/auth-keys.d.ts +10 -1
  15. package/dist/auth-keys.js +8 -0
  16. package/dist/background-session-reap.d.ts +1 -1
  17. package/dist/boot/deferred-sandbox-path-env.d.ts +5 -3
  18. package/dist/boot/deferred-sandbox-path-env.js +1 -2
  19. package/dist/boot/execution-env.js +10 -11
  20. package/dist/boot/execution-lane.d.ts +6 -4
  21. package/dist/boot/execution-lane.js +3 -1
  22. package/dist/boot/org-delivery.d.ts +3 -2
  23. package/dist/boot/parked-revive-gate.d.ts +5 -5
  24. package/dist/boot/parked-revive-gate.js +2 -3
  25. package/dist/boot/resolve-spec.d.ts +4 -5
  26. package/dist/boot/resolve-spec.js +66 -28
  27. package/dist/boot/retention-lane.d.ts +1 -1
  28. package/dist/boot/runner-deps.js +3 -1
  29. package/dist/boot/session-faces.js +21 -21
  30. package/dist/boot/stage-01-config.d.ts +1 -0
  31. package/dist/boot/stage-01-config.js +4 -3
  32. package/dist/boot/stage-02-web-admin-pull.d.ts +1 -0
  33. package/dist/boot/stage-02-web-admin-pull.js +10 -2
  34. package/dist/boot/stage-03-store-backend.d.ts +1 -0
  35. package/dist/boot/stage-03-store-backend.js +4 -4
  36. package/dist/boot/stage-04-stores.d.ts +1 -0
  37. package/dist/boot/stage-05-execution-env.d.ts +1 -0
  38. package/dist/boot/stage-06-runners.d.ts +2 -1
  39. package/dist/boot/stage-06-runners.js +1 -2
  40. package/dist/boot/stage-07-capability-layer.d.ts +2 -1
  41. package/dist/boot/stage-07-capability-layer.js +1 -1
  42. package/dist/boot/stage-08-reapers.d.ts +2 -1
  43. package/dist/boot/stage-09-leader.d.ts +2 -1
  44. package/dist/boot/stage-10-http-server.d.ts +35 -2
  45. package/dist/boot/stage-10-http-server.js +9 -4
  46. package/dist/boot/stores.d.ts +3 -0
  47. package/dist/boot/stores.js +22 -19
  48. package/dist/boot/web-admin.d.ts +1 -1
  49. package/dist/capabilities/tool-defer.d.ts +1 -1
  50. package/dist/capability-facts.d.ts +70 -60
  51. package/dist/capability-facts.js +45 -49
  52. package/dist/card-settle-stored-scope.d.ts +56 -0
  53. package/dist/card-settle-stored-scope.js +27 -0
  54. package/dist/card-void.d.ts +7 -12
  55. package/dist/card-void.js +2 -7
  56. package/dist/config-catalog.d.ts +12 -0
  57. package/dist/config-catalog.js +37 -33
  58. package/dist/config-invariants.d.ts +5 -4
  59. package/dist/config-invariants.js +4 -4
  60. package/dist/config-types.d.ts +49 -16
  61. package/dist/config.d.ts +6 -5
  62. package/dist/config.js +35 -24
  63. package/dist/context-window-notice.d.ts +31 -0
  64. package/dist/context-window-notice.js +28 -0
  65. package/dist/deployment-governance.d.ts +7 -28
  66. package/dist/deployment-governance.js +1 -14
  67. package/dist/deployment-shape.d.ts +20 -11
  68. package/dist/deployment-shape.js +1 -0
  69. package/dist/device-ws-hub.d.ts +1 -0
  70. package/dist/device-ws-hub.js +64 -28
  71. package/dist/device-ws-protocol.d.ts +20 -6
  72. package/dist/device-ws-protocol.js +9 -2
  73. package/dist/engine-identity.d.ts +7 -1
  74. package/dist/engine-identity.js +5 -0
  75. package/dist/env-name-allowlist-knobs.d.ts +6 -5
  76. package/dist/env-name-allowlist-knobs.js +5 -0
  77. package/dist/execution-lane-caps.d.ts +20 -5
  78. package/dist/execution-lane-caps.js +18 -4
  79. package/dist/field-conflict.d.ts +2 -2
  80. package/dist/fleet/engine-signal.d.ts +14 -1
  81. package/dist/fleet/engine-signal.js +5 -1
  82. package/dist/fleet/fleet-reconciler.d.ts +1 -1
  83. package/dist/fleet/serving-lease.d.ts +18 -3
  84. package/dist/fleet/serving-lease.js +81 -35
  85. package/dist/fleet-client.d.ts +13 -11
  86. package/dist/fleet-client.js +53 -33
  87. package/dist/history-anchor.js +1 -1
  88. package/dist/hitl-delivery.d.ts +1 -3
  89. package/dist/hitl-delivery.js +1 -1
  90. package/dist/hooks/command-spawn.d.ts +7 -2
  91. package/dist/hooks/command-spawn.js +13 -6
  92. package/dist/hooks/hook-runner.d.ts +5 -0
  93. package/dist/hooks/hook-runner.js +11 -7
  94. package/dist/http/active-run-conflict.d.ts +3 -7
  95. package/dist/http/active-run-conflict.js +2 -2
  96. package/dist/http/admission.js +5 -5
  97. package/dist/http/approval-sweeps.js +5 -3
  98. package/dist/http/caller-trust.d.ts +2 -2
  99. package/dist/http/caller-trust.js +3 -3
  100. package/dist/http/checkpoint-ctx.d.ts +18 -63
  101. package/dist/http/checkpoint-ctx.js +1 -24
  102. package/dist/http/claim-heartbeat.d.ts +6 -6
  103. package/dist/http/claim-heartbeat.js +6 -6
  104. package/dist/http/claimed-run.d.ts +16 -9
  105. package/dist/http/claimed-run.js +8 -0
  106. package/dist/http/dispatch.js +7 -5
  107. package/dist/http/durable-run-leg.d.ts +8 -1
  108. package/dist/http/durable-run-leg.js +34 -15
  109. package/dist/http/execution-context.d.ts +115 -14
  110. package/dist/http/execution-context.js +130 -40
  111. package/dist/http/owner-access.d.ts +246 -21
  112. package/dist/http/owner-access.js +186 -8
  113. package/dist/http/principal-gate.d.ts +18 -11
  114. package/dist/http/principal-gate.js +3 -1
  115. package/dist/http/readiness.d.ts +4 -2
  116. package/dist/http/resume-ctx-read.d.ts +14 -3
  117. package/dist/http/resume-ctx-read.js +24 -3
  118. package/dist/http/resume-legs.d.ts +35 -3
  119. package/dist/http/resume-legs.js +262 -106
  120. package/dist/http/route-ctx.d.ts +29 -11
  121. package/dist/http/routes/a2a-serve.js +17 -14
  122. package/dist/http/routes/approvals-assistant.d.ts +3 -2
  123. package/dist/http/routes/approvals-assistant.js +11 -11
  124. package/dist/http/routes/capabilities.js +5 -4
  125. package/dist/http/routes/config-catalog.d.ts +1 -1
  126. package/dist/http/routes/diagnostics.d.ts +3 -1
  127. package/dist/http/routes/diagnostics.js +30 -5
  128. package/dist/http/routes/mcp-reconnect.js +2 -2
  129. package/dist/http/routes/memory-origin.d.ts +1 -1
  130. package/dist/http/routes/notify-wake.js +2 -3
  131. package/dist/http/routes/principal-inbox.d.ts +3 -1
  132. package/dist/http/routes/principal-inbox.js +3 -2
  133. package/dist/http/routes/projects.d.ts +1 -0
  134. package/dist/http/routes/projects.js +5 -3
  135. package/dist/http/routes/retention-ops.d.ts +1 -1
  136. package/dist/http/routes/run-permission-mode.d.ts +1 -1
  137. package/dist/http/routes/run-permission-mode.js +2 -2
  138. package/dist/http/routes/runs.d.ts +1 -1
  139. package/dist/http/routes/runs.js +33 -37
  140. package/dist/http/routes/session-memory-sync.js +4 -5
  141. package/dist/http/routes/session-sync.d.ts +24 -5
  142. package/dist/http/routes/session-sync.js +45 -37
  143. package/dist/http/routes/sessions-list.d.ts +1 -1
  144. package/dist/http/routes/sessions.d.ts +1 -1
  145. package/dist/http/routes/sessions.js +18 -44
  146. package/dist/http/routes/tasks.js +136 -101
  147. package/dist/http/routes/trace-usage.js +14 -14
  148. package/dist/http/routes/workflows.d.ts +1 -0
  149. package/dist/http/routes/workflows.js +20 -38
  150. package/dist/http/server.d.ts +12 -9
  151. package/dist/http/server.js +35 -6
  152. package/dist/http/stored-body-replay.d.ts +3 -24
  153. package/dist/http/stored-body-replay.js +2 -12
  154. package/dist/http/submit-ctx.d.ts +2 -2
  155. package/dist/http/submit-ctx.js +5 -5
  156. package/dist/http/submit-replay.d.ts +15 -2
  157. package/dist/http/submit-replay.js +10 -4
  158. package/dist/http/wire-types.d.ts +6 -3
  159. package/dist/index.d.ts +3 -1
  160. package/dist/index.js +1 -0
  161. package/dist/leader/endpoint.js +9 -8
  162. package/dist/leg-facts.d.ts +13 -2
  163. package/dist/leg-facts.js +2 -0
  164. package/dist/local-store-declaration.d.ts +54 -0
  165. package/dist/local-store-declaration.js +59 -0
  166. package/dist/mcp-assertion-header.d.ts +1 -1
  167. package/dist/mcp-assertion-header.js +1 -1
  168. package/dist/mcp-authorization-face.d.ts +18 -14
  169. package/dist/mcp-authorization-face.js +3 -2
  170. package/dist/memory-project-layer.js +1 -1
  171. package/dist/memory-scope.d.ts +4 -4
  172. package/dist/memory-scope.js +4 -4
  173. package/dist/memory-store-refusal.d.ts +1 -1
  174. package/dist/memory-sync-client.js +2 -2
  175. package/dist/model-context-window.d.ts +50 -11
  176. package/dist/model-context-window.js +20 -1
  177. package/dist/model-entry-refusal.d.ts +5 -4
  178. package/dist/model-entry-refusal.js +17 -3
  179. package/dist/model-reach-scope.d.ts +2 -1
  180. package/dist/observability/corrupt-read-seat.d.ts +1 -1
  181. package/dist/observability/fail-open.d.ts +19 -7
  182. package/dist/observability/fail-open.js +19 -7
  183. package/dist/observability/logger.d.ts +7 -0
  184. package/dist/observability/logger.js +3 -1
  185. package/dist/observability/metrics.js +1 -1
  186. package/dist/observability/run-terminal.d.ts +1 -1
  187. package/dist/observability/run-terminal.js +1 -0
  188. package/dist/observability/secret-env-scrub.d.ts +1 -1
  189. package/dist/observability/secret-env-scrub.js +1 -1
  190. package/dist/org-delivery-gate.d.ts +31 -6
  191. package/dist/org-delivery-gate.js +7 -4
  192. package/dist/owner-faces.d.ts +30 -5
  193. package/dist/owner-faces.js +2 -0
  194. package/dist/park-row-commit.d.ts +67 -0
  195. package/dist/park-row-commit.js +76 -0
  196. package/dist/parked-decide.d.ts +9 -7
  197. package/dist/parked-decide.js +25 -6
  198. package/dist/peer-directory.d.ts +1 -1
  199. package/dist/peer-seat.d.ts +1 -1
  200. package/dist/peer-seat.js +52 -59
  201. package/dist/plugins/adoption-log-sql.d.ts +3 -3
  202. package/dist/plugins/adoption-log-sql.js +19 -19
  203. package/dist/plugins/approval-ask-store-sql.d.ts +1 -1
  204. package/dist/plugins/approval-ask-store-sql.js +3 -5
  205. package/dist/plugins/approval-exemption-skill-cutoff.d.ts +64 -0
  206. package/dist/plugins/approval-exemption-skill-cutoff.js +122 -0
  207. package/dist/plugins/approval-exemption-skill-m46.d.ts +0 -6
  208. package/dist/plugins/approval-exemption-skill-m46.js +6 -10
  209. package/dist/plugins/approval-exemption-store.d.ts +26 -1
  210. package/dist/plugins/approval-exemption-store.js +28 -19
  211. package/dist/plugins/ensure-schema-lock-policy.d.ts +36 -3
  212. package/dist/plugins/ensure-schema-lock-policy.js +75 -9
  213. package/dist/plugins/exec-path-law.d.ts +34 -0
  214. package/dist/plugins/exec-path-law.js +13 -0
  215. package/dist/plugins/file-run-store.d.ts +22 -9
  216. package/dist/plugins/file-run-store.js +51 -23
  217. package/dist/plugins/local-checkpoint-store.d.ts +8 -1
  218. package/dist/plugins/local-checkpoint-store.js +5 -1
  219. package/dist/plugins/local-data-root-admission.d.ts +52 -0
  220. package/dist/plugins/local-data-root-admission.js +100 -0
  221. package/dist/plugins/local-engine-high-water.d.ts +28 -7
  222. package/dist/plugins/local-engine-high-water.js +27 -22
  223. package/dist/plugins/local-session-store.d.ts +134 -49
  224. package/dist/plugins/local-session-store.js +149 -108
  225. package/dist/plugins/memory-run-store.d.ts +14 -9
  226. package/dist/plugins/memory-run-store.js +48 -22
  227. package/dist/plugins/one-time-migration-marker.d.ts +19 -3
  228. package/dist/plugins/one-time-migration-marker.js +7 -1
  229. package/dist/plugins/one-time-migrations.d.ts +110 -59
  230. package/dist/plugins/one-time-migrations.js +224 -136
  231. package/dist/plugins/org-delivery-quota-store.d.ts +34 -11
  232. package/dist/plugins/org-delivery-quota-store.js +29 -14
  233. package/dist/plugins/permission-rule-store-sql.d.ts +1 -1
  234. package/dist/plugins/permission-rule-store-sql.js +5 -9
  235. package/dist/plugins/pg-pool.d.ts +1 -1
  236. package/dist/plugins/pg-pool.js +14 -12
  237. package/dist/plugins/posix-shell-fs.js +4 -2
  238. package/dist/plugins/remote-env-adb.js +3 -2
  239. package/dist/plugins/remote-env-device.d.ts +3 -2
  240. package/dist/plugins/remote-env-device.js +8 -6
  241. package/dist/plugins/remote-env-e2b.d.ts +4 -8
  242. package/dist/plugins/remote-env-e2b.js +21 -15
  243. package/dist/plugins/remote-env-host.d.ts +51 -10
  244. package/dist/plugins/remote-env-host.js +97 -31
  245. package/dist/plugins/remote-env-k8s.js +7 -4
  246. package/dist/plugins/remote-env-local-docker.d.ts +6 -3
  247. package/dist/plugins/remote-env-local-docker.js +11 -25
  248. package/dist/plugins/remote-env-ssh.js +4 -3
  249. package/dist/plugins/remote-shell.d.ts +3 -1
  250. package/dist/plugins/remote-shell.js +3 -3
  251. package/dist/plugins/replica-registration.d.ts +35 -0
  252. package/dist/plugins/replica-registration.js +48 -0
  253. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  254. package/dist/plugins/run-store-sql.d.ts +27 -8
  255. package/dist/plugins/run-store-sql.js +46 -18
  256. package/dist/plugins/scenario-prefix-switch-m42.d.ts +8 -12
  257. package/dist/plugins/scenario-prefix-switch-m42.js +9 -6
  258. package/dist/plugins/schema-consistency.d.ts +91 -5
  259. package/dist/plugins/schema-consistency.js +189 -7
  260. package/dist/plugins/schema-drift-sentence.d.ts +6 -5
  261. package/dist/plugins/schema-drift-sentence.js +13 -5
  262. package/dist/plugins/serving-requirement.d.ts +49 -0
  263. package/dist/plugins/serving-requirement.js +53 -0
  264. package/dist/plugins/session-placement.d.ts +5 -4
  265. package/dist/plugins/shell-sentinel-owner-m48.d.ts +22 -0
  266. package/dist/plugins/shell-sentinel-owner-m48.js +6 -0
  267. package/dist/plugins/stop-then-start-sentence.d.ts +55 -0
  268. package/dist/plugins/stop-then-start-sentence.js +51 -0
  269. package/dist/plugins/store-backend.d.ts +23 -9
  270. package/dist/plugins/store-backend.js +58 -24
  271. package/dist/plugins/store-contracts.d.ts +45 -5
  272. package/dist/plugins/store-contracts.js +19 -0
  273. package/dist/plugins/tidb-pool.d.ts +11 -6
  274. package/dist/plugins/tidb-pool.js +22 -12
  275. package/dist/plugins/workspace-custody.d.ts +44 -0
  276. package/dist/plugins/workspace-custody.js +47 -0
  277. package/dist/process-instance.d.ts +3 -0
  278. package/dist/process-instance.js +7 -0
  279. package/dist/project-context.d.ts +66 -0
  280. package/dist/project-context.js +70 -0
  281. package/dist/project-identity.d.ts +8 -7
  282. package/dist/project-identity.js +40 -17
  283. package/dist/project-layer-key.d.ts +13 -2
  284. package/dist/project-layer-key.js +9 -1
  285. package/dist/question.d.ts +9 -21
  286. package/dist/question.js +28 -19
  287. package/dist/read-deny-floor-notice.d.ts +36 -0
  288. package/dist/read-deny-floor-notice.js +40 -0
  289. package/dist/{plain-string-record.d.ts → request-declaration-disposition.d.ts} +9 -23
  290. package/dist/request-declaration-disposition.js +39 -0
  291. package/dist/run-cancel-card.d.ts +28 -0
  292. package/dist/run-cancel-card.js +20 -0
  293. package/dist/run-cancel-context.d.ts +10 -0
  294. package/dist/run-cancel-context.js +1 -0
  295. package/dist/run-local.js +28 -13
  296. package/dist/runs.d.ts +31 -7
  297. package/dist/runs.js +34 -16
  298. package/dist/security.d.ts +3 -3
  299. package/dist/server-secret-env.d.ts +34 -20
  300. package/dist/server-secret-env.js +61 -18
  301. package/dist/session-agent.d.ts +4 -12
  302. package/dist/session-agent.js +0 -6
  303. package/dist/session-children-retire.d.ts +6 -3
  304. package/dist/session-children-retire.js +2 -5
  305. package/dist/session-incarnation.d.ts +18 -4
  306. package/dist/session-incarnation.js +4 -1
  307. package/dist/session-notify-door.d.ts +4 -4
  308. package/dist/session-notify-door.js +5 -6
  309. package/dist/session-rules-seat.d.ts +4 -3
  310. package/dist/session-rules-seat.js +3 -11
  311. package/dist/session-sync.js +3 -3
  312. package/dist/session-workspace.d.ts +3 -3
  313. package/dist/store-admission.d.ts +33 -0
  314. package/dist/store-admission.js +49 -0
  315. package/dist/store-posture.d.ts +5 -0
  316. package/dist/store-posture.js +18 -1
  317. package/dist/stores-sql/checkpoint-store-sql.d.ts +22 -3
  318. package/dist/stores-sql/checkpoint-store-sql.js +109 -23
  319. package/dist/stores-sql/file-history-store-sql.d.ts +1 -1
  320. package/dist/stores-sql/file-history-store-sql.js +3 -5
  321. package/dist/stores-sql/retention-store-sql.d.ts +1 -1
  322. package/dist/stores-sql/tool-result-store-sql.d.ts +1 -1
  323. package/dist/stores-sql/tool-result-store-sql.js +4 -6
  324. package/dist/task-a2a.d.ts +3 -8
  325. package/dist/task-a2a.js +25 -27
  326. package/dist/task-cwd.d.ts +27 -10
  327. package/dist/task-cwd.js +3 -0
  328. package/dist/task-mcp.d.ts +3 -3
  329. package/dist/task-mcp.js +1 -1
  330. package/dist/task-settings.d.ts +13 -0
  331. package/dist/task-settings.js +14 -7
  332. package/dist/terminal.d.ts +2 -0
  333. package/dist/terminal.js +21 -6
  334. package/dist/tool-approval.d.ts +50 -41
  335. package/dist/tool-approval.js +96 -73
  336. package/dist/trace/engine-notice-wire.d.ts +38 -21
  337. package/dist/trace/engine-notice-wire.js +25 -4
  338. package/dist/trace/ledger-sink.d.ts +3 -1
  339. package/dist/trace/project.d.ts +8 -5
  340. package/dist/trace/project.js +11 -9
  341. package/dist/trace/projection-table.d.ts +33 -2
  342. package/dist/trace/redact.d.ts +12 -0
  343. package/dist/trace/redact.js +5 -1
  344. package/dist/trace/self-minted-wire.d.ts +268 -56
  345. package/dist/trace/self-minted-wire.js +110 -40
  346. package/dist/trace/tool-card.d.ts +3 -0
  347. package/dist/trace/tool-card.js +24 -0
  348. package/dist/trace/tool-output-media.d.ts +2 -1
  349. package/dist/unattended-permission-mode.d.ts +1 -1
  350. package/dist/web-admin-client/apply-effective.d.ts +1 -1
  351. package/dist/web-admin-client/apply-effective.js +22 -9
  352. package/dist/web-admin-client/effective-keys.d.ts +1 -1
  353. package/dist/web-admin-client/skills-mcp.d.ts +1 -1
  354. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -29
  355. package/docs/error-text-lexicon.json +317 -0
  356. package/package.json +4 -2
  357. package/scripts/embed-minimal.mjs +183 -0
  358. package/wire-changes/server@7.149.0.json +372 -0
  359. package/wire-changes/server@7.150.0.json +335 -0
  360. package/dist/plain-string-record.js +0 -70
@@ -0,0 +1,35 @@
1
+ import type { SqlDialect, SqlExec } from "./sql-driver.js";
2
+ /** 表名。 */
3
+ export declare const REPLICA_REGISTRATION_TABLE = "replica_registration";
4
+ /**
5
+ * 本构建的能力名单(常设、只增):每一条读停-迁-起证据的一次性迁移一个名。删码之后名字留着 —— 名单只增不减(钉:`test/replica-registration.test.ts`)。
6
+ * 守卫读证据时传的名在型上就要在这里(`ReplicaCapability`):不在名单里的迁移读不了证据,编译期红。
7
+ */
8
+ export declare const REPLICA_CAPABILITIES: readonly ["M40", "M41", "M42", "M43", "M51"];
9
+ export type ReplicaCapability = (typeof REPLICA_CAPABILITIES)[number];
10
+ /** 留存视界(写者钟):登记早于此的别人的行在下一次登记时删。量级取远大于任一副本的寿命与副本间钟差。 */
11
+ export declare const REPLICA_REGISTRATION_HORIZON_MS: number;
12
+ /** 登记行(纯数据)。 */
13
+ export interface ReplicaRegistration {
14
+ readonly instanceId: string;
15
+ /** 发布线(`package.json` 的版本串;只作归因,不进任何判据)。 */
16
+ readonly releaseLine: string;
17
+ readonly capabilities: readonly string[];
18
+ readonly hostName: string;
19
+ readonly pid: number;
20
+ }
21
+ /** 本进程的登记行:身份 · 本构建的发布线 · 能力名单 · 主机 · pid。 */
22
+ export declare function buildThisReplicaRegistration(): ReplicaRegistration;
23
+ /** 能力格的存形:逗号连接。 */
24
+ export declare function capabilitiesCell(capabilities: readonly string[]): string;
25
+ /** `knows(r, M)`:能力名单里有 M(整词相等;读不出的格 ⇒ 不知道,保守)。观察与准入共用这一只判据。 */
26
+ export declare function knowsCapability(capabilities: readonly string[], migrationId: string): boolean;
27
+ /** 能力格的读形(存形的逆;空串 ⇒ 空名单)。 */
28
+ export declare function capabilitiesOfCell(cell: unknown): readonly string[];
29
+ /** 建表句(两方言;幂等)。中央建表语句读它(`tidb-pool.ts` / `pg-pool.ts`),与 `one-time-migration-marker.ts` 同形:键列钉二进制排序。 */
30
+ export declare function replicaRegistrationDdl(dialect: SqlDialect): string;
31
+ /** 登记:先删视界外的别人的行,再缺席才落本行(两句,各自自动提交)。 */
32
+ export declare function registerReplica(db: SqlExec, dialect: SqlDialect, me: ReplicaRegistration, nowMs: number): Promise<void>;
33
+ /** 读这几只身份的登记行(不在的身份不出现在结果里;空入参 ⇒ 不读库)。 */
34
+ export declare function readReplicaRegistrations(db: SqlExec, dialect: SqlDialect, instanceIds: readonly string[]): Promise<Map<string, ReplicaRegistration>>;
35
+ //# sourceMappingURL=replica-registration.d.ts.map
@@ -0,0 +1,48 @@
1
+ import { hostname } from "node:os";
2
+ import { processInstanceId } from "../process-instance.js";
3
+ import { serviceVersion } from "../service-version.js";
4
+ export const REPLICA_REGISTRATION_TABLE = "replica_registration";
5
+ export const REPLICA_CAPABILITIES = ["M40", "M41", "M42", "M43", "M51"];
6
+ export const REPLICA_REGISTRATION_HORIZON_MS = 90 * 24 * 60 * 60 * 1000;
7
+ export function buildThisReplicaRegistration() {
8
+ return { instanceId: processInstanceId(), releaseLine: serviceVersion(), capabilities: REPLICA_CAPABILITIES, hostName: hostname(), pid: process.pid };
9
+ }
10
+ export function capabilitiesCell(capabilities) {
11
+ return capabilities.join(",");
12
+ }
13
+ export function knowsCapability(capabilities, migrationId) {
14
+ return capabilities.includes(migrationId);
15
+ }
16
+ export function capabilitiesOfCell(cell) {
17
+ return typeof cell === "string" && cell !== "" ? cell.split(",") : [];
18
+ }
19
+ export function replicaRegistrationDdl(dialect) {
20
+ return dialect === "mysql"
21
+ ? `CREATE TABLE IF NOT EXISTS ${REPLICA_REGISTRATION_TABLE} (instance_id VARCHAR(64) NOT NULL PRIMARY KEY, release_line VARCHAR(64) NOT NULL, capabilities VARCHAR(1024) NOT NULL, host_name VARCHAR(255) NOT NULL, pid BIGINT NOT NULL, registered_at_ms BIGINT NOT NULL) COLLATE utf8mb4_bin`
22
+ : `CREATE TABLE IF NOT EXISTS ${REPLICA_REGISTRATION_TABLE} (instance_id VARCHAR(64) COLLATE "C" NOT NULL PRIMARY KEY, release_line VARCHAR(64) COLLATE "C" NOT NULL, capabilities VARCHAR(1024) COLLATE "C" NOT NULL, host_name VARCHAR(255) COLLATE "C" NOT NULL, pid BIGINT NOT NULL, registered_at_ms BIGINT NOT NULL)`;
23
+ }
24
+ const ph = (dialect, i) => (dialect === "mysql" ? "?" : `$${i}`);
25
+ export async function registerReplica(db, dialect, me, nowMs) {
26
+ await db.query(`DELETE FROM ${REPLICA_REGISTRATION_TABLE} WHERE registered_at_ms < ${ph(dialect, 1)} AND instance_id <> ${ph(dialect, 2)}`, [nowMs - REPLICA_REGISTRATION_HORIZON_MS, me.instanceId]);
27
+ const insert = `INSERT INTO ${REPLICA_REGISTRATION_TABLE} (instance_id, release_line, capabilities, host_name, pid, registered_at_ms) VALUES (${[1, 2, 3, 4, 5, 6].map((i) => ph(dialect, i)).join(", ")})`;
28
+ await db.query(dialect === "mysql" ? `${insert} ON DUPLICATE KEY UPDATE instance_id = instance_id` : `${insert} ON CONFLICT (instance_id) DO NOTHING`, [
29
+ me.instanceId,
30
+ me.releaseLine,
31
+ capabilitiesCell(me.capabilities),
32
+ me.hostName,
33
+ me.pid,
34
+ nowMs,
35
+ ]);
36
+ }
37
+ export async function readReplicaRegistrations(db, dialect, instanceIds) {
38
+ const out = new Map();
39
+ if (instanceIds.length === 0)
40
+ return out;
41
+ const { rows } = await db.query(`SELECT instance_id, release_line, capabilities, host_name, pid FROM ${REPLICA_REGISTRATION_TABLE} WHERE instance_id IN (${instanceIds.map((_, i) => ph(dialect, i + 1)).join(", ")})`, [...instanceIds]);
42
+ for (const r of rows) {
43
+ const instanceId = String(r["instance_id"]);
44
+ out.set(instanceId, { instanceId, releaseLine: String(r["release_line"]), capabilities: capabilitiesOfCell(r["capabilities"]), hostName: String(r["host_name"]), pid: Number(r["pid"]) });
45
+ }
46
+ return out;
47
+ }
48
+ //# sourceMappingURL=replica-registration.js.map
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * 托管留存执行面的**非破坏性 SQL 半场** —— SINGLE-FILE DUAL-DIALECT([ref] A12;先例 =
3
- * `retention-store-sql.ts` 本身)。[ref] 车2,设计稿 `docs/DESIGN-270-retention-lane.md` v1.3。
3
+ * `retention-store-sql.ts` 本身)。[ref] 车2,设计稿 `docs/[ref]-retention-lane.md` v1.3。
4
4
  *
5
5
  * ── 为什么与车1 的 `retention-store-sql.ts` 分家(而不是往那只店上再挂三组方法)────────────────────
6
6
  * 车1 那只店是 core `ManagedRetentionCapability` 的**实现体**:它的每一个公开方法都是一次**破坏性**
@@ -170,8 +170,16 @@ export declare class SqlRunStore {
170
170
  * 同一只入口、同列同序列化),不是引擎交出的原件:`paused.token` / `failed.nestedPause.token` 两座在落列前摘掉,
171
171
  * 所以 poll 这一行的调用方(`GET /v1/runs/:id → result`)拿到的是「停在哪扇门上」而拿不到 resume 能力。
172
172
  * `result = null` = 这条腿没观察到 park 结果(断连竞态 / 重开 re-park),列写空 —— 与修前行形同。
173
+ *
174
+ * 🔴 **停驻写的 CAS = 「行仍归 `legId` 这条腿 ∧ 仍在跑(或已停在本词上)∧ 没挂取消请求」**(内存 / 文件孪生同一句):
175
+ * · 持有腿(`leg_id`,认领铸、交回认领者):一笔丢了回执的停驻写之后卡被决、下一条腿认领了这条行 ⇒ 前一条腿的再提交落不到接手腿的行上;
176
+ * · `running`:失联收割已把行落成 `failed` 并删掉 `task_active` 认领时,这一写若把它翻回停驻,会话就没了锁、卡还待决 ⇒ 收割的判决立住;
177
+ * · 取消旗:取消动词对在跑行只置旗(`requestCancel`,CAS `status = 'running'`);停驻写认这面旗 ⇒ 同一行上两只写各 CAS 对方的事实,恰一个赢
178
+ * (旗先 ⇒ 停驻不落,提交点以取消收场;停驻先 ⇒ 置旗落空,取消路由按停驻结算)。
179
+ * 交回 CAS 的答:命中行数 > 0 ⇔ 这一写之后行停在这个词上(MySQL 协议缺省带 FOUND_ROWS ⇒ 计的是匹配行,同值重写照算;PG 的 rowCount 同义);
180
+ * 0 ⇔ 上面三件有一件不成立(或行不在)—— 是哪一件由提交点读行答(`park-row-commit.ts`)。
173
181
  */
174
- setSuspended(taskId: string, result: PersistedTaskResult | null): Promise<void>;
182
+ setSuspended(taskId: string, result: PersistedTaskResult | null, legId: string): Promise<boolean>;
175
183
  /**
176
184
  * [ref] D-B: PARK a run that paused on a REVIEW gate — a `needs_review` result (a dry-run diff review OR a
177
185
  * pre-action `plan_review`; core assemble-result slot 8.6). Like {@link setSuspended} this is a NON-terminal park
@@ -179,8 +187,8 @@ export declare class SqlRunStore {
179
187
  * resume path (`getActiveTaskId` → `markResuming`) can find + claim the row. core treats a review pause "like a
180
188
  * suspend, an INTENTIONAL durable pause" ([ref] §2.5) — the service MUST park it the same way, else the run is
181
189
  * terminalized + its lock released and the plan_review/dry_run_review resume can never run (it gets
182
- * taskId=undefined and drives the model unprotected on an unlocked session). CAS on `running`/`needs_review`
183
- * (reaper-revert guard, mirrors setSuspended). The park→resume→claim invariant is pinned on BOTH REAL engines by
190
+ * taskId=undefined and drives the model unprotected on an unlocked session). CAS = {@link setSuspended}'s three conditions
191
+ * (holding leg · `running`/`needs_review` · no cancel request). The park→resume→claim invariant is pinned on BOTH REAL engines by
184
192
  * `test/run-store-reaper-integration.test.ts` (`inv#1` row ownership across park→resume→handover, `inv#2` two
185
193
  * replicas racing one session ⇒ exactly one claim wins, `inv#3`/`inv#3'` stale-claim takeover vs fresh/parked
186
194
  * claims), closing the former required-before-GA gap. Keep it that way: the P2 lease-SQL bug and this
@@ -189,14 +197,15 @@ export declare class SqlRunStore {
189
197
  * mocked run-store unit tests stayed green. A mock cannot observe a unique-key claim race, a rolled-back loser,
190
198
  * or a multi-table reaper DELETE.
191
199
  */
192
- setNeedsReview(taskId: string, result: PersistedTaskResult | null): Promise<void>;
200
+ setNeedsReview(taskId: string, result: PersistedTaskResult | null, legId: string): Promise<boolean>;
193
201
  /**
194
202
  * Durable F4 resume: a suspended run is being actively resumed. Flip it back to `running` (CAS — only if
195
203
  * still suspended) and refresh liveness, so the in-flight resume is protected by the running-run heartbeat
196
204
  * and is NOT killed by `reapSuspended` (which targets `status='suspended'` past the approval TTL). Without
197
205
  * this the row sits at `suspended`/updated_at=suspend-time for the WHOLE resume execution, so a resume that
198
- * crosses the TTL is marked `failed` mid-run and its session lock (`task_active`) is released. Returns true
199
- * if it claimed the flip (false ⇒ the reaper already expired it, or it isn't suspended).
206
+ * crosses the TTL is marked `failed` mid-run and its session lock (`task_active`) is released. Returns the
207
+ * HOLDING LEG this claim minted ({@link RunRecord.legId}; the resumed leg's park writes carry it) when it claimed
208
+ * the flip, `undefined` otherwise (the reaper already expired it, or it isn't suspended).
200
209
  *
201
210
  * [ref](codex R1 [medium],红先双库证):认领 = **接管 `instance_id`**。此前该列只在 createRun 铸、此处不动,
202
211
  * 于是 A 创建、C 认领 resume 的行永远记 A —— 而 steer/interrupt 的「他副本」臂与 sendHandleMiss 都拿这列当
@@ -204,10 +213,10 @@ export declare class SqlRunStore {
204
213
  * 语义自此成文:`instance_id` = **当前持有副本**(createRun 铸、markResuming 认领接管);CAS 输不改列
205
214
  * (一个输掉的认领不许覆盖赢家的持有记录——同一条条件 UPDATE 天然保证)。
206
215
  */
207
- markResuming(taskId: string, instanceId: string): Promise<boolean>;
216
+ markResuming(taskId: string, instanceId: string): Promise<string | undefined>;
208
217
  /**
209
218
  * 认领当写栅栏用(进程内两孪生的契约 = `store-contracts.ts` 的 `InProcessClaimFence`;本孪生是它的事务形):一个事务 ——
210
- * ① 锁读这条 run 行且它在跑(`task_run … FOR UPDATE`);② 锁读会话的认领行且它是这条 run 的(`task_active … FOR UPDATE`);
219
+ * ① 锁读这条 run 行且它在跑(`task_run … FOR UPDATE`;副本与持有腿两列随行交给 `write`,见 `FencedRun`);② 锁读会话的认领行且它是这条 run 的(`task_active … FOR UPDATE`);
211
220
  * ③ 刷新这条 run 的活性戳;④ `write` 在**同一个事务连接**上跑(另收锁住那一行的 {@link FencedRun});提交即放锁。任一没读到 ⇒ 不持有,不刷新、`write` 不调。`write` 抛或提交失败 ⇒ 整只抛。
212
221
  * 🔴 **取锁序 = task_run → task_active**,与 `setTerminal` / `reapStale` / `reapSuspended` 同序(它们先改 run 行、再删认领行):两行用两条语句按序锁,
213
222
  * 不用连表锁读(那一形的先后由计划器定,与终局 / 收割相反时两只事务各攥一行成环)。`createRun` 先锁根会话行再插认领行(本序从不碰 `session_meta`)、
@@ -238,6 +247,16 @@ export declare class SqlRunStore {
238
247
  * reject): the error answer invites a retry, and a retry with the same key must run, not replay the refusal. The row stays (it
239
248
  * is the refusal's record); only the binding is freed. */
240
249
  releaseIdemKey(taskId: string): Promise<void>;
250
+ /** 放一条行上提交幂等键绑定的那一句(`releaseIdemKey` 与 `withdrawMintedRun` 的非在跑臂同读)。 */
251
+ private releaseIdemKeySql;
252
+ /**
253
+ * 撤回本请求铸的这一条 run(认领没放行,行从没交给引擎)—— 回到认领之前「没有这一行」的样子。唯一调用口 = 撤回表 `withdrawClaim` 的 `run` 臂
254
+ * (经 `http/claimed-run.ts` 的 `withdrawClaimedRun`)。本请求的行 = `task_id` 与建行交回的持有腿 `leg_id` 这一对(与停驻写的 CAS 同一只身份读法):
255
+ * 行上的持有腿不是 `legId`(没有这一行 / 别的腿持着 / 列之前的行)⇒ 一个字节不动。一个事务、取锁序 `task_run` → `task_active`(与终局 / 收割 / 栅栏同序):行仍在跑(`status = 'running'`:
256
+ * 没被收割、没终局)⇒ 删它的事件、它的认领、行本身(幂等键与指纹在行上,随行去);已终局 ⇒ 行零删 —— 死亡判决或别的终局写先到,那一份就是这条行的结局 ——
257
+ * 只放本请求在这一行上绑的提交幂等键;停驻 ⇒ 一个字节不动(撤回只对本请求铸的行,而它从没停驻过;口径与另两孪生同)(撤回之后本请求答的是「原样再发」,同键再发要真跑,不重放那一份结局)。交回删没删。
258
+ */
259
+ withdrawMintedRun(taskId: string, legId: string): Promise<boolean>;
241
260
  /** The body fingerprint this row's binding of `idem.key` holds — binding `idem.bodyFingerprint` first when it holds none (a key
242
261
  * bound before the column existed): first arrival binds, everyone after reads. The fingerprint belongs to the KEY that bound
243
262
  * the row, so a row this key is not bound to (hit by its `task_id` under another key, or made without a key) is never written
@@ -5,7 +5,7 @@ import { pgSanitizeText } from "./pg-safe-json.js";
5
5
  import { toIso as iso } from "./sql-row-helpers.js";
6
6
  import { recordFailOpen } from "../observability/fail-open.js";
7
7
  import { createObjectColumnReader, decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText, readJsonObjectColumn, readNonNullJsonValueColumn } from "./sql-json-column.js";
8
- import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, RUN_STALL_DEFAULT_WIRING, RUN_STALL_VERDICT_UNLANDED_TAG, isParkedRunStatus, runLivenessOf, settledLegObservationOf } from "./store-contracts.js";
8
+ import { RUN_HOLDER_DEATH_CODES, RUN_STALLED_ERROR, RUN_STALL_DEFAULT_WIRING, RUN_STALL_VERDICT_UNLANDED_TAG, isParkedRunStatus, isTerminalRunStatus, mintRunLegId, runLivenessOf, settledLegObservationOf } from "./store-contracts.js";
9
9
  import { notifyRunTerminal, runTerminalLogLevel } from "../observability/run-terminal.js";
10
10
  import { mysqlDriver, pgDriver, dialectJsonEncoder, SQL_DB_NOW_MS, sqlDbNowMs } from "./sql-driver.js";
11
11
  import { isDupKeyError } from "./sql-errors.js";
@@ -14,7 +14,7 @@ import { readRunIdentityCells, runIdentityCells } from "../identity-source.js";
14
14
  import { decideSpaceCommitAbandon, decideSpaceCommitClaim, fenceSpaceCommitAtTerminal, spaceCommitStateOf } from "../workspace-space-commit.js";
15
15
  function runRowCols(dialect) {
16
16
  return (`task_id, session_id, owner, status, ${jsonColumnText(dialect, "result")}, error, error_code, job_id, source, ` +
17
- "identity_source, identity_scope, identity_principal_inc, instance_id, cancel_requested, objective_preview, created_at, updated_at");
17
+ "identity_source, identity_scope, identity_principal_inc, instance_id, leg_id, cancel_requested, objective_preview, created_at, updated_at");
18
18
  }
19
19
  const readPersistedTaskResultBody = createObjectColumnReader();
20
20
  function decodeResultColumn(v, taskId) {
@@ -101,18 +101,19 @@ export class SqlRunStore {
101
101
  }
102
102
  }
103
103
  async createRun(taskId, sessionId, owner, instanceId, meta = {}) {
104
+ const legId = mintRunLegId();
104
105
  const conn = await this.db.connect();
105
106
  try {
106
- const first = await this.createRunOn(conn, taskId, sessionId, owner, instanceId, meta);
107
+ const first = await this.createRunOn(conn, taskId, sessionId, owner, instanceId, legId, meta);
107
108
  if (first.activeTaskId === undefined || !(await this.settleDueHolderOn(conn, first.activeTaskId)))
108
109
  return first;
109
- return await this.createRunOn(conn, taskId, sessionId, owner, instanceId, meta);
110
+ return await this.createRunOn(conn, taskId, sessionId, owner, instanceId, legId, meta);
110
111
  }
111
112
  finally {
112
113
  conn.release();
113
114
  }
114
115
  }
115
- async createRunOn(conn, taskId, sessionId, owner, instanceId, meta) {
116
+ async createRunOn(conn, taskId, sessionId, owner, instanceId, legId, meta) {
116
117
  const idemKey = meta.idem?.key ?? null;
117
118
  let stage = "claim";
118
119
  try {
@@ -127,13 +128,14 @@ export class SqlRunStore {
127
128
  await conn.query(this.q("INSERT INTO task_active (session_id, task_id, since) VALUES (?,?,?)", "INSERT INTO task_active (session_id, task_id, since) VALUES ($1,$2,$3)"), [sessionId, taskId, now]);
128
129
  stage = "row";
129
130
  const cells = runIdentityCells(meta.identity ?? null);
130
- await conn.query(this.q("INSERT INTO task_run (task_id, session_id, owner, status, instance_id, job_id, source, identity_source, identity_scope, identity_principal_inc, objective_preview, idem_key, idem_body_fingerprint, created_at, updated_at) " +
131
- "VALUES (?,?,?,'running',?,?,?,?,?,?,?,?,?,?,?)", "INSERT INTO task_run (task_id, session_id, owner, status, instance_id, job_id, source, identity_source, identity_scope, identity_principal_inc, objective_preview, idem_key, idem_body_fingerprint, created_at, updated_at) " +
132
- "VALUES ($1,$2,$3,'running',$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)"), [
131
+ await conn.query(this.q("INSERT INTO task_run (task_id, session_id, owner, status, instance_id, leg_id, job_id, source, identity_source, identity_scope, identity_principal_inc, objective_preview, idem_key, idem_body_fingerprint, created_at, updated_at) " +
132
+ "VALUES (?,?,?,'running',?,?,?,?,?,?,?,?,?,?,?,?)", "INSERT INTO task_run (task_id, session_id, owner, status, instance_id, leg_id, job_id, source, identity_source, identity_scope, identity_principal_inc, objective_preview, idem_key, idem_body_fingerprint, created_at, updated_at) " +
133
+ "VALUES ($1,$2,$3,'running',$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)"), [
133
134
  taskId,
134
135
  sessionId,
135
136
  owner,
136
137
  instanceId,
138
+ legId,
137
139
  meta.jobId ?? null,
138
140
  meta.source ?? null,
139
141
  cells.identitySource,
@@ -150,7 +152,7 @@ export class SqlRunStore {
150
152
  now,
151
153
  ]);
152
154
  await conn.commit();
153
- return { ok: true };
155
+ return { ok: true, legId };
154
156
  }
155
157
  catch (err) {
156
158
  if (!this.isDupKey(err))
@@ -363,22 +365,25 @@ export class SqlRunStore {
363
365
  }
364
366
  }
365
367
  }
366
- async setSuspended(taskId, result) {
368
+ async setSuspended(taskId, result, legId) {
367
369
  const sealed = sealRunRowResult(result);
368
- await this.db.query(this.q("UPDATE task_run SET status = 'suspended', result = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND status IN ('running','suspended')", "UPDATE task_run SET status = 'suspended', result = $1::jsonb, error_code = $2, updated_at = $3 WHERE task_id = $4 AND status IN ('running','suspended')"), [sealed == null ? null : this.json(sealed), runRowErrorCodeOf(sealed), new Date(), taskId]);
370
+ const { affected } = await this.db.query(this.q("UPDATE task_run SET status = 'suspended', result = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND leg_id = ? AND status IN ('running','suspended') AND cancel_requested = 0", "UPDATE task_run SET status = 'suspended', result = $1::jsonb, error_code = $2, updated_at = $3 WHERE task_id = $4 AND leg_id = $5 AND status IN ('running','suspended') AND cancel_requested = 0"), [sealed == null ? null : this.json(sealed), runRowErrorCodeOf(sealed), new Date(), taskId, legId]);
371
+ return affected > 0;
369
372
  }
370
- async setNeedsReview(taskId, result) {
373
+ async setNeedsReview(taskId, result, legId) {
371
374
  const sealed = sealRunRowResult(result);
372
- await this.db.query(this.q("UPDATE task_run SET status = 'needs_review', result = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND status IN ('running','needs_review')", "UPDATE task_run SET status = 'needs_review', result = $1::jsonb, error_code = $2, updated_at = $3 WHERE task_id = $4 AND status IN ('running','needs_review')"), [sealed == null ? null : this.json(sealed), runRowErrorCodeOf(sealed), new Date(), taskId]);
375
+ const { affected } = await this.db.query(this.q("UPDATE task_run SET status = 'needs_review', result = ?, error_code = ?, updated_at = ? WHERE task_id = ? AND leg_id = ? AND status IN ('running','needs_review') AND cancel_requested = 0", "UPDATE task_run SET status = 'needs_review', result = $1::jsonb, error_code = $2, updated_at = $3 WHERE task_id = $4 AND leg_id = $5 AND status IN ('running','needs_review') AND cancel_requested = 0"), [sealed == null ? null : this.json(sealed), runRowErrorCodeOf(sealed), new Date(), taskId, legId]);
376
+ return affected > 0;
373
377
  }
374
378
  async markResuming(taskId, instanceId) {
375
- const { affected } = await this.db.query(this.q("UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, result = NULL, error_code = NULL, instance_id = ?, updated_at = ? WHERE task_id = ? AND status IN ('suspended','needs_review')", "UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, result = NULL, error_code = NULL, instance_id = $1, updated_at = $2 WHERE task_id = $3 AND status IN ('suspended','needs_review')"), [instanceId, new Date(), taskId]);
376
- return affected === 1;
379
+ const legId = mintRunLegId();
380
+ const { affected } = await this.db.query(this.q("UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, result = NULL, error_code = NULL, instance_id = ?, leg_id = ?, updated_at = ? WHERE task_id = ? AND status IN ('suspended','needs_review')", "UPDATE task_run SET status = 'running', cancel_requested = 0, preempt_requested = 0, result = NULL, error_code = NULL, instance_id = $1, leg_id = $2, updated_at = $3 WHERE task_id = $4 AND status IN ('suspended','needs_review')"), [instanceId, legId, new Date(), taskId]);
381
+ return affected === 1 ? legId : undefined;
377
382
  }
378
383
  async whileHoldingClaim(sessionId, taskId, write) {
379
384
  let fenced = { held: false };
380
385
  await this.tx(async (conn) => {
381
- const run = await conn.query(this.q("SELECT task_id, instance_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id, instance_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
386
+ const run = await conn.query(this.q("SELECT task_id, instance_id, leg_id FROM task_run WHERE task_id = ? AND status = 'running' FOR UPDATE", "SELECT task_id, instance_id, leg_id FROM task_run WHERE task_id = $1 AND status = 'running' FOR UPDATE"), [taskId]);
382
387
  const row = run.rows[0];
383
388
  if (row === undefined)
384
389
  return;
@@ -387,7 +392,7 @@ export class SqlRunStore {
387
392
  if (holder === undefined || String(holder.task_id) !== taskId)
388
393
  return;
389
394
  await conn.query(this.q("UPDATE task_run SET updated_at = ? WHERE task_id = ?", "UPDATE task_run SET updated_at = $1 WHERE task_id = $2"), [new Date(), taskId]);
390
- fenced = { held: true, value: await write(conn, { instanceId: row.instance_id ?? null }) };
395
+ fenced = { held: true, value: await write(conn, { instanceId: row.instance_id ?? null, legId: row.leg_id ?? null }) };
391
396
  });
392
397
  return fenced;
393
398
  }
@@ -409,6 +414,7 @@ export class SqlRunStore {
409
414
  source: r.source ?? null,
410
415
  identity: readRunIdentityCells(r.identity_source, r.identity_scope, r.identity_principal_inc),
411
416
  instanceId: r.instance_id ?? null,
417
+ legId: r.leg_id ?? null,
412
418
  cancelRequested: Boolean(r.cancel_requested),
413
419
  objectivePreview: r.objective_preview ?? null,
414
420
  createdAt: iso(r.created_at),
@@ -433,7 +439,29 @@ export class SqlRunStore {
433
439
  return rows[0] ? this.rowToRun(rows[0]) : undefined;
434
440
  }
435
441
  async releaseIdemKey(taskId) {
436
- await this.db.query(this.q("UPDATE task_run SET idem_key = NULL, idem_body_fingerprint = NULL WHERE task_id = ?", "UPDATE task_run SET idem_key = NULL, idem_body_fingerprint = NULL WHERE task_id = $1"), [taskId]);
442
+ await this.db.query(this.releaseIdemKeySql(), [taskId]);
443
+ }
444
+ releaseIdemKeySql() {
445
+ return this.q("UPDATE task_run SET idem_key = NULL, idem_body_fingerprint = NULL WHERE task_id = ?", "UPDATE task_run SET idem_key = NULL, idem_body_fingerprint = NULL WHERE task_id = $1");
446
+ }
447
+ async withdrawMintedRun(taskId, legId) {
448
+ let withdrawn = false;
449
+ await this.tx(async (conn) => {
450
+ const { rows } = await conn.query(this.q("SELECT status FROM task_run WHERE task_id = ? AND leg_id = ? FOR UPDATE", "SELECT status FROM task_run WHERE task_id = $1 AND leg_id = $2 FOR UPDATE"), [taskId, legId]);
451
+ if (rows.length === 0)
452
+ return;
453
+ const status = rows[0].status;
454
+ if (status !== "running") {
455
+ if (isTerminalRunStatus(status))
456
+ await conn.query(this.releaseIdemKeySql(), [taskId]);
457
+ return;
458
+ }
459
+ await conn.query(this.q("DELETE FROM task_event WHERE task_id = ?", "DELETE FROM task_event WHERE task_id = $1"), [taskId]);
460
+ await conn.query(this.q("DELETE FROM task_active WHERE task_id = ?", "DELETE FROM task_active WHERE task_id = $1"), [taskId]);
461
+ await conn.query(this.q("DELETE FROM task_run WHERE task_id = ?", "DELETE FROM task_run WHERE task_id = $1"), [taskId]);
462
+ withdrawn = true;
463
+ });
464
+ return withdrawn;
437
465
  }
438
466
  async bindIdemBodyFingerprint(taskId, idem) {
439
467
  await this.db.query(this.q("UPDATE task_run SET idem_body_fingerprint = ? WHERE task_id = ? AND idem_key = ? AND idem_body_fingerprint IS NULL", "UPDATE task_run SET idem_body_fingerprint = $1 WHERE task_id = $2 AND idem_key = $3 AND idem_body_fingerprint IS NULL"), [idem.bodyFingerprint, taskId, idem.key]);
@@ -1,4 +1,5 @@
1
1
  import { StoreBootRefusalError } from "./store-contracts.js";
2
+ import { type OlderReplicaEvidence } from "./stop-then-start-sentence.js";
2
3
  /** 本条的拒启码(机读)。 */
3
4
  export declare const SCENARIO_PREFIX_SWITCH_REFUSED_CODE = "config.scenario_prefix_switch_refused";
4
5
  /** 本条在标记表(`one-time-migration-marker.ts`)里的那一行的键。 */
@@ -18,26 +19,21 @@ export interface ScenarioPrefixHit {
18
19
  export interface ScenarioPrefixReceipt {
19
20
  scenarioPrefixSourcesScanned: number;
20
21
  }
21
- /** 拒因闭集:存量命中 / 零命中而更早版本的副本可能仍在服务(SQL 的停-迁-起)。 */
22
- export type ScenarioPrefixRefusalReason = "stored_prefix" | "live_runs";
23
- /** 判活读数(`live_runs` 那一拒带):判活窗内心跳过的 run 数与可以再起的最早时刻。 */
24
- export interface ScenarioPrefixLiveRuns {
25
- readonly liveRuns: number;
26
- readonly windowSec: number;
27
- readonly restartAfter: string;
28
- readonly after: string;
29
- }
22
+ /** 拒因闭集:存量命中 / 零命中而有停-迁-起证据(更早版本的副本可能仍在服务;SQL 的停-迁-起)。 */
23
+ export type ScenarioPrefixRefusalReason = "stored_prefix" | "older_replica";
30
24
  /**
31
25
  * M42 的起服拒。具名类型 + 机读码 + 拒因;继承 {@link StoreBootRefusalError}(SQL 开库的等待窗不重试它;隐式 local 的降级臂不吞)。一个字节没改(扫描只读,标记没写)。
32
26
  * · `stored_prefix` —— 存量里有以保留前缀开头的身份(`hits` 逐来源点名);
33
- * · `live_runs` —— 零命中,但判活窗内仍有 run 在心跳:更早版本的副本可能还在服务,扫描之后它还会收下这类身份 ⇒ 全停之后再起(`live` 带时刻)。
27
+ * · `older_replica` —— 零命中,但有停-迁-起证据:更早版本的副本可能还在服务,扫描之后它还会收下这类身份 ⇒ 全停之后再起(`live` = 证据,带时刻)。
34
28
  */
35
29
  export declare class ScenarioPrefixSwitchRefusedError extends StoreBootRefusalError {
36
30
  readonly reason: ScenarioPrefixRefusalReason;
37
31
  readonly hits: readonly ScenarioPrefixHit[];
38
- readonly live?: ScenarioPrefixLiveRuns | undefined;
32
+ readonly live?: OlderReplicaEvidence | undefined;
39
33
  readonly code = "config.scenario_prefix_switch_refused";
40
- constructor(reason: ScenarioPrefixRefusalReason, hits: readonly ScenarioPrefixHit[], live?: ScenarioPrefixLiveRuns | undefined);
34
+ constructor(reason: ScenarioPrefixRefusalReason, hits: readonly ScenarioPrefixHit[], live?: OlderReplicaEvidence | undefined,
35
+ /** 库此刻还差的列(SQL 腿经 `describeUpgradeDriftTail` 读到的;列齐 / local 腿 ⇒ 空串):一次起服读全。 */
36
+ tail?: string);
41
37
  private static sentence;
42
38
  }
43
39
  /** local 腿的一处来源:店名 + 它的计数口(以保留前缀开头的属主行数)。 */
@@ -2,6 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { SCENARIO_SUBJECT_PREFIX } from "../owner-brands.js";
3
3
  import { atomicWriteFileSync } from "./owner-only-file-mode.js";
4
4
  import { StoreBootRefusalError } from "./store-contracts.js";
5
+ import { olderReplicaEvidenceSentence, stopThenStartAction } from "./stop-then-start-sentence.js";
5
6
  export const SCENARIO_PREFIX_SWITCH_REFUSED_CODE = "config.scenario_prefix_switch_refused";
6
7
  export const SCENARIO_PREFIX_MARKER_ID = "M42";
7
8
  export const LOCAL_MARKER_FILE = "one-time-migration-M42.json";
@@ -12,8 +13,8 @@ export class ScenarioPrefixSwitchRefusedError extends StoreBootRefusalError {
12
13
  hits;
13
14
  live;
14
15
  code = SCENARIO_PREFIX_SWITCH_REFUSED_CODE;
15
- constructor(reason, hits, live) {
16
- super(ScenarioPrefixSwitchRefusedError.sentence(reason, hits, live));
16
+ constructor(reason, hits, live, tail = "") {
17
+ super(ScenarioPrefixSwitchRefusedError.sentence(reason, hits, live) + tail);
17
18
  this.reason = reason;
18
19
  this.hits = hits;
19
20
  this.live = live;
@@ -27,10 +28,12 @@ export class ScenarioPrefixSwitchRefusedError extends StoreBootRefusalError {
27
28
  `). This version reads that prefix as a scenario of a person, so these rows would be attributed to another person; it refuses to start rather than misread them. ` +
28
29
  `Nothing was changed. Rename or delete those identities (the identity boundary now rejects principals with this prefix), then restart — ` +
29
30
  `the check runs until it finds none, then records a marker in the store and does not run again.`);
30
- case "live_runs":
31
- return (`${SCENARIO_PREFIX_SWITCH_REFUSED_CODE}: no stored identity starts with the reserved prefix "${SCENARIO_SUBJECT_PREFIX}", but ${live?.liveRuns ?? 0} run(s) heartbeated on this database ` +
32
- `within the last ${live?.windowSec ?? 0}s — a replica of an earlier version may still be serving, and it accepts principals with that prefix, so a clean check now would not stay clean. ` +
33
- `Nothing was changed and no marker was written. Stop every replica of the earlier version, then start this version after ${live?.after ?? "the heartbeat window"} (this check is stop-then-start, not rolling).`);
31
+ case "older_replica":
32
+ if (live === undefined)
33
+ throw new Error("scenario-prefix-switch: an older_replica refusal carries its evidence");
34
+ return (`${SCENARIO_PREFIX_SWITCH_REFUSED_CODE}: no stored identity starts with the reserved prefix "${SCENARIO_SUBJECT_PREFIX}", but ${olderReplicaEvidenceSentence(live)} ` +
35
+ `— a replica of an earlier version may still be serving, and it accepts principals with that prefix, so a clean check now would not stay clean. ` +
36
+ `Nothing was changed and no marker was written. ${stopThenStartAction(live.after)}`);
34
37
  default: {
35
38
  const unhandled = reason;
36
39
  throw new Error(`scenario-prefix-switch: unknown refusal reason ${String(unhandled)}`);
@@ -36,6 +36,12 @@
36
36
  * · `key` —— 主键成员:给存量行加主键列等于改它们的身份,**不出语句**,只一句话(出路走「部署匹配的构建」)。
37
37
  * 反方向(构建可空、库里非空)不判:现网库里可能有,判了会让数量不明的库起不来。
38
38
  *
39
+ * ── 位置:建表之后、一次性迁移之前;比的是「待跑迁移跑完之后」的形 ─────────────────────────────────────
40
+ * 起服的每一只拒启判都先于第一笔一次性迁移写(拒启的那一次起服对存量一笔没改写;迁移读数只出现在起得来的那一次)。本探针因此在迁移段**之前**跑,
41
+ * 而库上可能还有本构建带着的迁移待跑(它们删 / 改名 / 补 / 重建的列):登记表每一行用 `shapeChanges` 声明它对表形做的事,{@link projectPendingShape}
42
+ * 是唯一一只投影 —— 先把库形投影成迁移之后的样子再对账。迁移要动的列不算不符;别的缺列 / 多列 / 可空列照拒。发车门 1.10 第三档读同一只投影。
43
+ * 迁移段之后起服再调一次、待跑集为空(收口):声明多报了迁移没做的改动 ⇒ 这一次拒(构建缺陷;不收口则投影在窗内每次起服都把那一处抹平)。
44
+ *
39
45
  * ── 失败方向:读不到就拒,不跳过 ─────────────────────────────────────────────────────────────
40
46
  * information_schema 查询抛错、或整库一行都读不出来 ⇒ **拒启**。探针的语义是「我证明了库等于代码」,
41
47
  * 证不出来时放行等于把一条保护性断言静默降级成祝福([ref]:保护型缺席必须 fail-closed)。
@@ -54,6 +60,8 @@
54
60
  * (探针先于它的第一条 CREATE)—— 一次会拒的起服不许先在库里建出几十张本版新表(`task_run` 还带着新列),否则拒句里「没动过库」就是假话,
55
61
  * 按拒句指路去 7.125.x 起服的人会撞上「库比 7.125.x 新」的第二道拒。嵌入方走公开序列 `createStoreBackend()` → `ensureSchema()` → `runOneTimeMigrations()`
56
62
  * 即得到这一问,零动作。
63
+ * 同一处(建表之前、只读)还有本族的另一只跳版臂:`Skill` 豁免截点(`approval-exemption-skill-cutoff.ts`)—— 证据不是 schema 而是行(截点之前的 `Skill` 豁免行),
64
+ * 截点、判与拒(`LegacySkillExemptionsPresentError`,同码)住在那一只模块;调用点同在 `withRetiredSchemaProbe`。
57
65
  * 残余(如实):运维在第一次起带迁移的版本之前就把两件退役 schema 都手工删掉的库,残留的旧拼法由门记录的读腿接住 —— 7.129.0 起(core 7.51.0 [ref])
58
66
  * 那只读腿按 core 的档案读法读,`unresolvable` 是 core 退役词注册表里的词 ⇒ 记录原词原样读回、不改写(`trace/project.ts` `screenedGate`);local 文件车道
59
67
  * 没有表与列,没有这一问 —— 引擎 File 店里已决 checkpoint 的记录按契约物理不可变,本仓没有合法缝改写它们,同由那条读腿接住。
@@ -113,6 +121,54 @@ export interface TableMismatch {
113
121
  }
114
122
  /** 对账(纯函数:期望 × 实况 → 不符表清单)。实况 = {@link readLiveColumns} 的读数(列在不在 + 可不可空,一次读)。 */
115
123
  export declare function diffSchema(expected: Map<string, ParsedTable>, live: LiveColumns): TableMismatch[];
124
+ /**
125
+ * 一条一次性迁移起服时对**存量表形**做的事(登记表 `plugins/one-time-migrations.ts` 一行的 `shapeChanges` 里的一项;纯数据,列名取自迁移语句读的那一份常量)。闭集四种,
126
+ * 条件都只读**迁移之前**的库形(与迁移步自己的判据同一个事实:列在不在):
127
+ * · `drop` —— 列在 ⇒ 删;
128
+ * · `rename` —— `to` 不在 ∧ `from` 里**恰有一列**在 ⇒ 那一列改名成 `to`(改名之后的可空性按方言:MySQL 协议的改名句是 `CHANGE COLUMN <旧名> <本构建的列定义>`,
129
+ * 列按本构建的声明重定义;PG 的 `RENAME COLUMN` 不动列定义,可空性随旧列走);别的形(两列都在 / 新名已在)迁移自己会撞库,不投影 ⇒ 对账照拒;
130
+ * · `add` —— 列不在 ⇒ 按本构建的声明加上;`whilePresent` 在场 ⇒ 只在那张表上还有其中任一列时才加(迁移步的判据就是那几列在不在);
131
+ * · `rebuild` —— `whilePresent` 列在 ∧ 本构建声明的列里除 `supplies`(迁移自己给值的那几列)以外每一列都在(迁移的搬运句从旧表读的就是这几列)⇒ 整张表按本构建的建表语句重建
132
+ * (列集与可空性 = 本构建的声明;旧表上别的列随重建丢掉 —— 迁移真落下的形就是这样);缺搬运要读的列 ⇒ 不投影(迁移会在搬运那一步失败)⇒ 对账照拒,拒在迁移写之前。
133
+ */
134
+ export type PendingShapeChange = {
135
+ readonly kind: "drop";
136
+ readonly table: string;
137
+ readonly columns: readonly string[];
138
+ } | {
139
+ readonly kind: "rename";
140
+ readonly table: string;
141
+ readonly from: readonly string[];
142
+ readonly to: string;
143
+ } | {
144
+ readonly kind: "add";
145
+ readonly table: string;
146
+ readonly column: string;
147
+ readonly whilePresent?: {
148
+ readonly table: string;
149
+ readonly anyOf: readonly string[];
150
+ };
151
+ } | {
152
+ readonly kind: "rebuild";
153
+ readonly table: string;
154
+ readonly whilePresent: string;
155
+ readonly supplies: readonly string[];
156
+ };
157
+ /** 一条迁移的表形声明(登记表的行结构上满足它:`id` + 可选的 `shapeChanges`)。 */
158
+ export interface PendingShapeSource {
159
+ readonly id: string;
160
+ readonly shapeChanges?: readonly PendingShapeChange[];
161
+ }
162
+ /** 投影的结果:迁移跑完之后的库形 + 真改了表形的那几条迁移(按登记序)。 */
163
+ export interface PendingShapeProjection {
164
+ readonly projected: LiveColumns;
165
+ readonly appliedBy: readonly string[];
166
+ }
167
+ /**
168
+ * 待跑的一次性迁移把库形改成什么(纯函数;**唯一**一只投影 —— 起服列册对账与发车门 1.10 第三档都读它)。条件读 `live`(迁移之前的形),改动落在一份拷贝上 ⇒ 与声明的先后无关。
169
+ * 一个(表 × 列)只许一条声明认领(`rebuild` 认领整张表):两条声明碰同一处 ⇒ 响亮抛(一件事一个属主,不挑一个)。
170
+ */
171
+ export declare function projectPendingShape(expected: Map<string, ParsedTable>, live: LiveColumns, sources: readonly PendingShapeSource[], dialect: SqlDialect): PendingShapeProjection;
116
172
  /** 缺一列时的配方(按 {@link columnFill} 三类);拒启句里这一列的那几行就是它的返回值,真库钉据此取期望而不手抄句子。 */
117
173
  export declare function missingColumnLines(dialect: SqlDialect, table: string, c: ParsedColumn): string[];
118
174
  /**
@@ -126,14 +182,44 @@ export declare function missingColumnLines(dialect: SqlDialect, table: string, c
126
182
  * 🔴 **没有销毁性的一条**:对带数据的库,任何「删库 / 重建」的指路都是教人销毁数据 —— 这里不写,拒启句的钉也守着(`test/schema-consistency.test.ts` ⑤)。
127
183
  */
128
184
  export declare function describeMismatchedTables(dialect: SqlDialect, mismatches: readonly TableMismatch[]): string[];
129
- /** 拒启文案(表头 + 逐表段与闭集三条出路 {@link describeMismatchedTables})。 */
130
- export declare function describeSchemaMismatch(dialect: SqlDialect, mismatches: readonly TableMismatch[]): string;
131
185
  /**
132
- * 启动期对账:**建表之后**跑,不符即抛(= 拒启)。两方言 + InnoDB 同腿(MySQL 协议共用 `mysql` 臂)。
186
+ * **升级清单**(多行;`[]` = 没有语句要部署者执行,清单不出):一次起服的拒句里「这只库此刻对本构建还差什么、先后顺序」一段。
187
+ * 缺列拒、判活拒、一次性迁移的拒本来各报各的一段 —— 运维补一句再起、才撞上下一族(列册对账在迁移段之后时,第四次起服才拒出其余全部 ALTER)。
188
+ * 清单把本构建对这一只库的全部动作写成一张:① 逐表的 ALTER(其下 {@link describeMismatchedTables} 的逐表段,全库一次列全)② 这只库**还欠**的停-迁-起迁移
189
+ * ③ 起服。第 ② 步逐行问登记行的 `stopThenStart.owedOn`(一次性迁移文件静态依赖本文件,所以惰性载入)—— 那条迁移的守卫自己问的同一只判据,清单不另判:
190
+ * 已经走过它们的库(标记落了 / 部署格已搬 / 零行旧词)一条不欠 ⇒ 第 ② 步与它的「不可滚动」动作段都不出,起服是第 ② 步。某一条读不出欠不欠(探针读抛)⇒ 照列并如实说
191
+ * 没读到(多停一次,不漏停)。`read` = 列册对账读列册的同一只无参读口。
192
+ */
193
+ export declare function describeUpgradeChecklist(read: SchemaProbeRead, dialect: SqlDialect, mismatches: readonly TableMismatch[]): Promise<string[]>;
194
+ /** 拒启文案(表头 + {@link describeUpgradeChecklist}〔有语句要执行时〕+ 逐表段与闭集三条出路 {@link describeMismatchedTables})。`checklist` 由调用方 `await describeUpgradeChecklist(…)` 交来(本函数保持同步纯函数,不读库)。 */
195
+ export declare function describeSchemaMismatch(dialect: SqlDialect, mismatches: readonly TableMismatch[], checklist?: readonly string[]): string;
196
+ /**
197
+ * 迁移段**之前 / 之中**读列册的拒句(单表探针的拒因、判活守卫的句尾)要的那一份:库经本构建**全部待跑迁移**之后({@link projectPendingShape},登记表各行的 `shapeChanges`,
198
+ * 与起服对账同一只投影)对本构建的不符里,只留**要部署者执行语句**的方向(缺列、声明非空而库里允许 NULL):
199
+ * · 待跑迁移要删 / 改名走的旧列、自己要建的列,投影已经按迁移自己的条件换成迁移之后的形 —— 不读成「库比构建新」,也不列成部署者的 `ALTER`
200
+ * (部署者照句子先加了,迁移的改名 / 加列反而撞上已存在的目标列、起服卡死);条件不成立的(如改名的源列不在)照列,迁移不会替它建;
201
+ * · 投影之后还剩的幽灵列〔库里有、代码没有〕去掉 —— 没有语句可给,名字又来自库里、不受信,不进「给运维的指令」。
202
+ * 迁移段之中读:已跑过的迁移的条件读到的是迁移之后的形、不再投影,所以读数与迁移段之前同一份。登记表惰性载入(一次性迁移文件静态依赖本文件)。
203
+ */
204
+ export declare function statementBearing(expected: Map<string, ParsedTable>, live: LiveColumns, dialect: SqlDialect): Promise<TableMismatch[]>;
205
+ /** 清单 + 逐表段 + 闭集三条出路(多行):单表探针的拒因与判活守卫的句尾共用的正文(列册对账的拒句是 {@link describeSchemaMismatch},同一份清单与逐表段)。 */
206
+ export declare function describeUpgradeBody(read: SchemaProbeRead, dialect: SqlDialect, mismatches: readonly TableMismatch[]): Promise<string[]>;
207
+ /** 句尾读列册的期限(ms):这一段是拒句上的附加,不许把已经成立的拒拖住 —— 读不回来就如实说没读到,拒照出。 */
208
+ export declare const DRIFT_TAIL_DEADLINE_MS = 10000;
209
+ /**
210
+ * 别的拒启(一次性迁移的判活守卫)句尾上的「这只库此刻还差的列」一段:读整库列册对账同一份读数({@link statementBearing} 之后),没有语句要执行 ⇒ `""`;否则一个空行 + 一行引子 +
211
+ * {@link describeUpgradeBody}。读不出列册 / 超过 {@link DRIFT_TAIL_DEADLINE_MS} ⇒ 响亮地说没读到及原因(不静默吞:吞了运维以为列是齐的;也不无限等:拒已经成立,
212
+ * 这一段只是附加 —— 读卡住的那条语句留在连接上,和迁移段里任何一条卡住的语句同一个去处)。`deadlineMs` 是测试缝。
213
+ */
214
+ export declare function describeUpgradeDriftTail(read: SchemaProbeRead, dialect: SqlDialect, deadlineMs?: number): Promise<string>;
215
+ /**
216
+ * 启动期对账:**建表之后、一次性迁移之前**跑,不符即抛(= 拒启;拒的时候迁移一笔没写)。两方言 + InnoDB 同腿(MySQL 协议共用 `mysql` 臂)。
217
+ * 比的是库**经待跑的一次性迁移之后**的形({@link projectPendingShape};`pending` = 本构建带着的迁移的表形声明,起服读登记表交来):迁移要删 / 改名 / 补的列不算不符,
218
+ * 别的缺列 / 多列 / 可空列照拒 —— 漏了升级前 DDL 的库在迁移改写任何一行之前就被拒。
133
219
  *
134
- * 失败方向见文件头注:读抛 / 整库零行 ⇒ 拒。
220
+ * 失败方向见文件头注:读抛 / 整库零行 ⇒ 拒。三条拒(读不出 / 整库零行 / 列集不符)与单表探针的拒启同一只出口 {@link SchemaMismatchRefusedError}(`code` = `config.schema_mismatch`)。
135
221
  */
136
- export declare function assertSchemaMatchesCode(read: SchemaProbeRead, dialect: SqlDialect): Promise<void>;
222
+ export declare function assertSchemaMatchesCode(read: SchemaProbeRead, dialect: SqlDialect, pending: readonly PendingShapeSource[]): Promise<void>;
137
223
  /**
138
224
  * 跳版升级的库的拒启:退役 schema(快照索引表 / 单座列)任一仍在。**具名类型**:锚类型不锚文案(日志消费端与测试按 `code` / 类型分派);
139
225
  * 码取列册对账同一个 `config.schema_mismatch`,运维面一个拒启码一个读法。拒句点名在场的坐标、说明第三条(旧拼法改写)同样没跑过、给出路。