@sema-agent/server 7.138.0 → 7.140.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 (332) hide show
  1. package/MIGRATION.md +457 -7
  2. package/README.md +7 -4
  3. package/README.zh-CN.md +6 -4
  4. package/USAGE.md +91 -41
  5. package/deploy/sema-up/README.md +1 -1
  6. package/deploy/sema-up/lib/lanes.json +3 -2
  7. package/dist/adoption/runner.js +1 -1
  8. package/dist/adoption/sql.js +1 -1
  9. package/dist/adoption/wire.d.ts +3 -3
  10. package/dist/approval-card.d.ts +26 -26
  11. package/dist/bench/s1/arms.d.ts +1 -1
  12. package/dist/bench/s1/arms.js +1 -3
  13. package/dist/bench/s1/live-deps.js +3 -2
  14. package/dist/bench/s1/row.d.ts +5 -2
  15. package/dist/bench/s1/row.js +2 -22
  16. package/dist/bench/s1/run-firm.js +2 -2
  17. package/dist/boot/adoption.js +1 -1
  18. package/dist/boot/device-lane.d.ts +8 -3
  19. package/dist/boot/device-lane.js +7 -2
  20. package/dist/boot/execution-env.d.ts +4 -2
  21. package/dist/boot/execution-env.js +28 -34
  22. package/dist/boot/execution-lane.d.ts +9 -1
  23. package/dist/boot/execution-lane.js +6 -1
  24. package/dist/boot/governance-seams.d.ts +3 -2
  25. package/dist/boot/governance-seams.js +9 -11
  26. package/dist/boot/memory-boundary.d.ts +14 -51
  27. package/dist/boot/memory-boundary.js +10 -20
  28. package/dist/boot/memory-consolidation.d.ts +15 -11
  29. package/dist/boot/memory-consolidation.js +25 -14
  30. package/dist/boot/resolve-spec.d.ts +4 -6
  31. package/dist/boot/resolve-spec.js +10 -13
  32. package/dist/boot/retention-lane.d.ts +5 -5
  33. package/dist/boot/retention-lane.js +0 -6
  34. package/dist/boot/runner-deps.d.ts +6 -4
  35. package/dist/boot/runner-deps.js +3 -1
  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-03-store-backend.d.ts +2 -6
  40. package/dist/boot/stage-03-store-backend.js +2 -3
  41. package/dist/boot/stage-04-stores.d.ts +2 -6
  42. package/dist/boot/stage-04-stores.js +5 -5
  43. package/dist/boot/stage-05-execution-env.d.ts +2 -6
  44. package/dist/boot/stage-05-execution-env.js +2 -2
  45. package/dist/boot/stage-06-runners.d.ts +2 -6
  46. package/dist/boot/stage-06-runners.js +2 -3
  47. package/dist/boot/stage-07-capability-layer.d.ts +2 -6
  48. package/dist/boot/stage-08-reapers.d.ts +2 -6
  49. package/dist/boot/stage-09-leader.d.ts +2 -6
  50. package/dist/boot/stage-09-leader.js +1 -2
  51. package/dist/boot/stage-10-http-server.d.ts +2 -6
  52. package/dist/boot/stage-10-http-server.js +7 -6
  53. package/dist/boot/stores.d.ts +3 -6
  54. package/dist/boot/stores.js +27 -40
  55. package/dist/boot/task-list-lane.d.ts +1 -1
  56. package/dist/boot/task-list-lane.js +1 -1
  57. package/dist/boot/web-admin.js +37 -11
  58. package/dist/capabilities/execution-lane.d.ts +2 -2
  59. package/dist/capabilities/mcp-probe.js +1 -1
  60. package/dist/capabilities/scenarios.d.ts +1 -1
  61. package/dist/capability-facts.d.ts +15 -7
  62. package/dist/capability-facts.js +8 -3
  63. package/dist/config-catalog.js +9 -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 +33 -37
  67. package/dist/config-provider.js +51 -30
  68. package/dist/config-types.d.ts +21 -7
  69. package/dist/config.d.ts +33 -0
  70. package/dist/config.js +46 -16
  71. package/dist/delegated-session-owner.d.ts +4 -3
  72. package/dist/delegated-session-owner.js +1 -1
  73. package/dist/deployment-shape.d.ts +10 -9
  74. package/dist/deployment-shape.js +4 -4
  75. package/dist/device-enrollment.d.ts +41 -2
  76. package/dist/device-enrollment.js +27 -12
  77. package/dist/device-store.d.ts +58 -85
  78. package/dist/device-store.js +19 -49
  79. package/dist/device-ws-hub.js +2 -1
  80. package/dist/env-name-allowlist-knobs.d.ts +5 -4
  81. package/dist/exec-cut.d.ts +60 -0
  82. package/dist/exec-cut.js +36 -0
  83. package/dist/execution-lane-caps.d.ts +115 -105
  84. package/dist/execution-lane-caps.js +76 -82
  85. package/dist/execution-placement.d.ts +259 -0
  86. package/dist/execution-placement.js +199 -0
  87. package/dist/field-conflict.d.ts +3 -2
  88. package/dist/field-conflict.js +1 -0
  89. package/dist/hosted-posture.d.ts +2 -2
  90. package/dist/http/admission.js +8 -19
  91. package/dist/http/checkpoint-ctx.d.ts +21 -12
  92. package/dist/http/checkpoint-ctx.js +4 -2
  93. package/dist/http/claimed-run.d.ts +1 -1
  94. package/dist/http/durable-run-leg.js +9 -5
  95. package/dist/http/execution-context.d.ts +218 -0
  96. package/dist/http/execution-context.js +330 -0
  97. package/dist/http/memory-compliance-failure-reply.d.ts +3 -1
  98. package/dist/http/memory-compliance-failure-reply.js +5 -0
  99. package/dist/http/memory-layer-reply.d.ts +30 -0
  100. package/dist/http/memory-layer-reply.js +24 -0
  101. package/dist/http/resume-legs.js +63 -36
  102. package/dist/http/retired-service-deps.d.ts +1 -1
  103. package/dist/http/retired-service-deps.js +1 -1
  104. package/dist/http/route-ctx.d.ts +14 -8
  105. package/dist/http/routes/adoption.js +1 -1
  106. package/dist/http/routes/background.d.ts +29 -1
  107. package/dist/http/routes/background.js +29 -2
  108. package/dist/http/routes/devices.d.ts +13 -19
  109. package/dist/http/routes/devices.js +21 -26
  110. package/dist/http/routes/memory-bundle.js +3 -0
  111. package/dist/http/routes/memory-compliance.js +8 -1
  112. package/dist/http/routes/memory-consolidation.js +12 -1
  113. package/dist/http/routes/memory-origin.js +14 -2
  114. package/dist/http/routes/memory-policy.js +5 -0
  115. package/dist/http/routes/session-sync.js +110 -15
  116. package/dist/http/routes/sessions.d.ts +6 -0
  117. package/dist/http/routes/sessions.js +143 -22
  118. package/dist/http/routes/side-query.d.ts +12 -4
  119. package/dist/http/routes/side-query.js +4 -5
  120. package/dist/http/routes/tasks.js +41 -33
  121. package/dist/http/server.d.ts +40 -44
  122. package/dist/http/server.js +13 -2
  123. package/dist/http/session-inherit-env.d.ts +3 -4
  124. package/dist/http/session-inherit-env.js +1 -1
  125. package/dist/http/wire-types.d.ts +8 -12
  126. package/dist/identity-source.d.ts +1 -1
  127. package/dist/identity-source.js +1 -1
  128. package/dist/index.d.ts +1 -0
  129. package/dist/lane-home-probe.d.ts +68 -42
  130. package/dist/lane-home-probe.js +53 -30
  131. package/dist/leader/wire.js +5 -4
  132. package/dist/memory-bundle-engine.d.ts +16 -9
  133. package/dist/memory-bundle-engine.js +29 -6
  134. package/dist/memory-operator-faces.d.ts +85 -30
  135. package/dist/memory-operator-faces.js +136 -30
  136. package/dist/memory-project-layer.d.ts +3 -21
  137. package/dist/memory-project-layer.js +11 -8
  138. package/dist/memory-scope-stores.d.ts +73 -0
  139. package/dist/memory-scope-stores.js +86 -0
  140. package/dist/memory-scope.d.ts +29 -5
  141. package/dist/memory-scope.js +16 -4
  142. package/dist/memory-sync-client.d.ts +70 -18
  143. package/dist/memory-sync-client.js +215 -117
  144. package/dist/memory-sync.d.ts +1 -1
  145. package/dist/memory-sync.js +3 -2
  146. package/dist/observability/cost-taxonomy.d.ts +2 -2
  147. package/dist/observability/fail-open.d.ts +12 -0
  148. package/dist/observability/fail-open.js +12 -0
  149. package/dist/observability/run-terminal.js +0 -1
  150. package/dist/orchestration/workflow-notify-journal.js +4 -4
  151. package/dist/org-memory-admission.d.ts +4 -3
  152. package/dist/org-memory-admission.js +6 -6
  153. package/dist/parked-decide.d.ts +34 -9
  154. package/dist/parked-decide.js +12 -19
  155. package/dist/parked-revive-surface.d.ts +12 -15
  156. package/dist/parked-revive-surface.js +3 -6
  157. package/dist/peer-seat.d.ts +13 -7
  158. package/dist/peer-seat.js +37 -3
  159. package/dist/plugins/adoption-log-sql.js +4 -4
  160. package/dist/plugins/approval-ask-store-file.d.ts +7 -0
  161. package/dist/plugins/approval-ask-store-file.js +11 -0
  162. package/dist/plugins/approval-ask-store-memory.d.ts +6 -0
  163. package/dist/plugins/approval-ask-store-memory.js +12 -0
  164. package/dist/plugins/approval-ask-store-sql.d.ts +22 -5
  165. package/dist/plugins/approval-ask-store-sql.js +44 -26
  166. package/dist/plugins/approval-nonce-store.js +4 -4
  167. package/dist/plugins/blob-backend.d.ts +4 -4
  168. package/dist/plugins/blob-backend.js +7 -7
  169. package/dist/plugins/breaker-state-sql.js +2 -2
  170. package/dist/plugins/caching-session-store.d.ts +6 -2
  171. package/dist/plugins/caching-session-store.js +9 -3
  172. package/dist/plugins/checkpoint-index-columns.js +2 -2
  173. package/dist/plugins/checkpoint-store-sql.d.ts +6 -5
  174. package/dist/plugins/checkpoint-store-sql.js +21 -23
  175. package/dist/plugins/device-store-sql.d.ts +25 -51
  176. package/dist/plugins/device-store-sql.js +68 -122
  177. package/dist/plugins/ensure-index.d.ts +1 -2
  178. package/dist/plugins/execution-placement-sql.d.ts +151 -0
  179. package/dist/plugins/execution-placement-sql.js +223 -0
  180. package/dist/plugins/file-history-store-sql.d.ts +9 -8
  181. package/dist/plugins/file-history-store-sql.js +9 -9
  182. package/dist/plugins/file-run-store.d.ts +6 -1
  183. package/dist/plugins/file-run-store.js +4 -0
  184. package/dist/plugins/fork-routing-session-store.d.ts +6 -0
  185. package/dist/plugins/fork-routing-session-store.js +16 -0
  186. package/dist/plugins/image-bake-store-sql.js +9 -9
  187. package/dist/plugins/image-index-sql.js +11 -11
  188. package/dist/plugins/leader-run-store-sql.js +4 -4
  189. package/dist/plugins/local-session-store.d.ts +149 -50
  190. package/dist/plugins/local-session-store.js +382 -107
  191. package/dist/plugins/mailbox-store-sql.d.ts +2 -1
  192. package/dist/plugins/mailbox-store-sql.js +4 -4
  193. package/dist/plugins/memory-engine-tidb.js +5 -5
  194. package/dist/plugins/memory-key-guards.d.ts +4 -0
  195. package/dist/plugins/memory-key-guards.js +1 -0
  196. package/dist/plugins/memory-optout-grant-store-sql.js +1 -1
  197. package/dist/plugins/memory-run-store.d.ts +1 -1
  198. package/dist/plugins/memory-sync-store-tidb.js +3 -3
  199. package/dist/plugins/one-time-migrations.d.ts +117 -8
  200. package/dist/plugins/one-time-migrations.js +408 -38
  201. package/dist/plugins/org-delivery-quota-store.js +2 -2
  202. package/dist/plugins/outcome-ledger-sql.d.ts +2 -2
  203. package/dist/plugins/outcome-ledger-sql.js +2 -2
  204. package/dist/plugins/permission-rule-store-sql.d.ts +17 -19
  205. package/dist/plugins/permission-rule-store-sql.js +29 -32
  206. package/dist/plugins/pg-pool.d.ts +7 -3
  207. package/dist/plugins/pg-session-storage.d.ts +31 -20
  208. package/dist/plugins/pg-session-storage.js +120 -52
  209. package/dist/plugins/remote-env-adb.d.ts +18 -19
  210. package/dist/plugins/remote-env-adb.js +91 -66
  211. package/dist/plugins/remote-env-device.d.ts +23 -31
  212. package/dist/plugins/remote-env-device.js +19 -17
  213. package/dist/plugins/remote-env-e2b.d.ts +27 -21
  214. package/dist/plugins/remote-env-e2b.js +91 -45
  215. package/dist/plugins/remote-env-file-error.d.ts +3 -1
  216. package/dist/plugins/remote-env-file-error.js +9 -3
  217. package/dist/plugins/remote-env-host.d.ts +31 -34
  218. package/dist/plugins/remote-env-host.js +91 -72
  219. package/dist/plugins/remote-env-k8s.d.ts +13 -29
  220. package/dist/plugins/remote-env-k8s.js +63 -55
  221. package/dist/plugins/remote-env-local-docker.d.ts +24 -12
  222. package/dist/plugins/remote-env-local-docker.js +49 -61
  223. package/dist/plugins/remote-env-ssh.d.ts +11 -19
  224. package/dist/plugins/remote-env-ssh.js +74 -57
  225. package/dist/plugins/remote-shell.d.ts +100 -3
  226. package/dist/plugins/remote-shell.js +166 -2
  227. package/dist/plugins/resume-anchor-store-sql.js +2 -2
  228. package/dist/plugins/retention-lane-store-sql.d.ts +1 -1
  229. package/dist/plugins/retention-lane-store-sql.js +2 -2
  230. package/dist/plugins/retention-store-sql.d.ts +10 -7
  231. package/dist/plugins/retention-store-sql.js +13 -7
  232. package/dist/plugins/run-store-sql.d.ts +13 -3
  233. package/dist/plugins/run-store-sql.js +20 -15
  234. package/dist/plugins/schema-consistency.d.ts +21 -11
  235. package/dist/plugins/schema-consistency.js +15 -9
  236. package/dist/plugins/schema-drift-sentence.d.ts +27 -0
  237. package/dist/plugins/schema-drift-sentence.js +38 -0
  238. package/dist/plugins/schema-ensure-legs.d.ts +2 -3
  239. package/dist/plugins/schema-ensure-legs.js +2 -2
  240. package/dist/plugins/schema-live-columns.d.ts +1 -1
  241. package/dist/plugins/session-capture-record-store-sql.js +1 -1
  242. package/dist/plugins/session-fork.d.ts +54 -0
  243. package/dist/plugins/session-fork.js +38 -1
  244. package/dist/plugins/session-row-sql.d.ts +22 -0
  245. package/dist/plugins/session-row-sql.js +81 -0
  246. package/dist/plugins/session-rules-column.d.ts +1 -1
  247. package/dist/plugins/session-rules-column.js +1 -1
  248. package/dist/plugins/session-store.d.ts +1 -1
  249. package/dist/plugins/session-workspace-cell.d.ts +1 -1
  250. package/dist/plugins/session-workspace-cell.js +1 -1
  251. package/dist/plugins/shared-memory-store-sql.js +2 -2
  252. package/dist/plugins/sql-driver.d.ts +10 -5
  253. package/dist/plugins/sql-driver.js +6 -6
  254. package/dist/plugins/sql-errors.js +1 -1
  255. package/dist/plugins/store-backend.d.ts +5 -7
  256. package/dist/plugins/store-backend.js +23 -11
  257. package/dist/plugins/store-contracts.d.ts +25 -5
  258. package/dist/plugins/task-list-store-sql.js +11 -11
  259. package/dist/plugins/tidb-pool.d.ts +12 -7
  260. package/dist/plugins/tidb-pool.js +13 -7
  261. package/dist/plugins/tidb-session-storage.js +4 -4
  262. package/dist/plugins/tidb-session-store.d.ts +57 -26
  263. package/dist/plugins/tidb-session-store.js +127 -59
  264. package/dist/plugins/tool-result-store-sql.d.ts +5 -4
  265. package/dist/plugins/tool-result-store-sql.js +21 -13
  266. package/dist/plugins/user-input-ledger-sql.js +2 -2
  267. package/dist/plugins/workflow-journal-store-sql.js +5 -5
  268. package/dist/plugins/workflow-run-store-sql.js +19 -19
  269. package/dist/plugins/write-behind-counter.d.ts +2 -3
  270. package/dist/plugins/write-behind-counter.js +5 -5
  271. package/dist/project-memory.d.ts +9 -15
  272. package/dist/project-memory.js +5 -4
  273. package/dist/request-key-closure.js +1 -1
  274. package/dist/request-value-closure.d.ts +6 -7
  275. package/dist/request-value-closure.js +1 -3
  276. package/dist/resource-suspend.d.ts +14 -12
  277. package/dist/resource-suspend.js +1 -1
  278. package/dist/resource-window.d.ts +4 -3
  279. package/dist/run-local.d.ts +6 -0
  280. package/dist/run-local.js +34 -35
  281. package/dist/runs.d.ts +10 -5
  282. package/dist/runs.js +52 -66
  283. package/dist/security.d.ts +38 -12
  284. package/dist/security.js +8 -2
  285. package/dist/server-secret-env.d.ts +9 -5
  286. package/dist/session-children-retire.d.ts +27 -9
  287. package/dist/session-children-retire.js +9 -5
  288. package/dist/session-incarnation.d.ts +20 -10
  289. package/dist/session-incarnation.js +5 -3
  290. package/dist/session-policy-wire.d.ts +1 -1
  291. package/dist/session-row.d.ts +114 -5
  292. package/dist/session-row.js +55 -5
  293. package/dist/session-titler.d.ts +2 -2
  294. package/dist/sql-engine-posture.d.ts +1 -1
  295. package/dist/sql-engine-posture.js +1 -1
  296. package/dist/{plugins → stores-sql}/background-agent-store-sql.d.ts +2 -2
  297. package/dist/{plugins → stores-sql}/background-agent-store-sql.js +8 -8
  298. package/dist/{plugins → stores-sql}/session-policy-store-sql.d.ts +7 -5
  299. package/dist/{plugins → stores-sql}/session-policy-store-sql.js +12 -12
  300. package/dist/{plugins → stores-sql}/usage-window-store-sql.d.ts +1 -1
  301. package/dist/{plugins → stores-sql}/usage-window-store-sql.js +3 -3
  302. package/dist/task-a2a.d.ts +1 -1
  303. package/dist/task-a2a.js +5 -6
  304. package/dist/task-cwd.d.ts +6 -1
  305. package/dist/task-mcp.d.ts +22 -9
  306. package/dist/task-mcp.js +17 -9
  307. package/dist/terminal.d.ts +27 -0
  308. package/dist/terminal.js +6 -0
  309. package/dist/tool-approval.d.ts +6 -0
  310. package/dist/tool-approval.js +8 -1
  311. package/dist/trace/project.d.ts +1 -0
  312. package/dist/trace/project.js +1 -0
  313. package/dist/trace/redact.d.ts +3 -1
  314. package/dist/trace/redact.js +80 -28
  315. package/dist/trace/self-minted-wire.d.ts +169 -2
  316. package/dist/trace/self-minted-wire.js +54 -1
  317. package/dist/url-refusal.d.ts +1 -1
  318. package/dist/web-admin-client/apply-effective.d.ts +17 -3
  319. package/dist/web-admin-client/apply-effective.js +20 -10
  320. package/dist/web-admin-client/candidate-gate.d.ts +11 -7
  321. package/dist/web-admin-client/candidate-gate.js +1 -1
  322. package/dist/web-admin-client/http-client.d.ts +5 -4
  323. package/dist/web-admin-client/http-client.js +2 -2
  324. package/dist/web-admin-client/read-warnings.d.ts +7 -17
  325. package/dist/web-admin-client/read-warnings.js +5 -9
  326. package/dist/web-admin-client/skills-mcp.js +3 -2
  327. package/dist/web-admin-client/types.d.ts +6 -0
  328. package/package.json +3 -3
  329. package/wire-changes/server@7.139.0.json +348 -0
  330. package/wire-changes/server@7.140.0.json +184 -0
  331. package/dist/http/device-placement.d.ts +0 -175
  332. package/dist/http/device-placement.js +0 -145
@@ -44,6 +44,7 @@ import { type AskState, type BatchState } from "../approval-ask-machine.js";
44
44
  import type { HostSettledBy } from "../host-decision.js";
45
45
  import { type AskRowCause, type SettleCause } from "../approval-deny-reasons.js";
46
46
  import { type IndexSpec } from "./ensure-index.js";
47
+ import { type ExecutionLane } from "../execution-lane-caps.js";
47
48
  export type AskDecision = "approve" | "deny";
48
49
  export interface AskRow {
49
50
  askId: string;
@@ -52,6 +53,11 @@ export interface AskRow {
52
53
  sessionId: string;
53
54
  /** 租户 scope——哨兵归一在消费层(车2+),店里存原值(设计定稿 §3)。 */
54
55
  owner: string | null;
56
+ /**
57
+ * 造这只问的那条执行的车道(审批出处带车道:问行唯一铸点 `ensureAsk` 与 `taskId` / `sessionId` 同处写;既有行命中一个字节不改)。
58
+ * 读根授权只读问自己记下的车道。存量行随执行放置切换的一次性迁移回填(SQL `approval_ask.execution_lane`、local 问账日志压实)。
59
+ */
60
+ executionLane: ExecutionLane;
55
61
  batchId: string;
56
62
  toolCallId: string;
57
63
  /** 车3 §3.2′ 换轴:数值 `leg` → `legKey`(= `sha256(resume token)` 的 hex,首腿空串)。语义与派生
@@ -174,6 +180,8 @@ export interface NewAskRow {
174
180
  sourceTaskId: string;
175
181
  sessionId: string;
176
182
  owner?: string | null;
183
+ /** 出处的执行车道(必填;语义见 {@link AskRow.executionLane})。 */
184
+ executionLane: ExecutionLane;
177
185
  batchId: string;
178
186
  toolCallId: string;
179
187
  legKey: string;
@@ -562,6 +570,14 @@ export interface ApprovalAskStore {
562
570
  getBatch(batchId: string): Promise<BatchRow | null>;
563
571
  }
564
572
  export declare const APPROVAL_ASK_TABLE = "approval_ask";
573
+ /**
574
+ * `approval_ask.execution_lane` 的列定义(两方言;唯一属主)—— 建表 DDL 内联它,存量库的加列只由执行放置切换的一次性迁移做(`one-time-migrations.ts` M32 步 ②),
575
+ * 那一步也读这里的定义。`DEFAULT ''` 只为存量库一句加列;回填(同迁移步 ④b)之后没有 '' 行,读口对 '' 与词表外的值一律响亮拒({@link executionLaneColumnOf})。
576
+ */
577
+ export declare const APPROVAL_ASK_EXECUTION_LANE_COLUMN: {
578
+ readonly mysql: "execution_lane VARCHAR(32) NOT NULL DEFAULT ''";
579
+ readonly pg: "execution_lane VARCHAR(32) COLLATE \"C\" NOT NULL DEFAULT ''";
580
+ };
565
581
  export declare const APPROVAL_BATCH_TABLE = "approval_batch";
566
582
  /**
567
583
  * TiDB/MySQL-protocol 侧的两条建表语句,**抽成导出数组**(车3 刀 3a):`tidb-pool.ts` 的
@@ -571,7 +587,8 @@ export declare const APPROVAL_BATCH_TABLE = "approval_batch";
571
587
  * 下面的 `ensureTiDBApprovalAskSchema` 保留为遍历这个数组的薄壳(集成测试直接调它建表,
572
588
  * 不必为了建两张表去拉起整套中央 schema)。
573
589
  *
574
- * SCHEMA POLICY 同 `tidb-pool.ts` 头注:纯 CREATE,禁 ALTER 增量 seam,改列直接改这里 + 重建库
590
+ * SCHEMA POLICY 同 `tidb-pool.ts` 头注:纯 CREATE,禁 ALTER 增量 seam,加列直接改这里(新建库随它到货;带数据的存量库由列集对账拒启并打印 `ALTER`,部署者自己执行;
591
+ * 改列类型 / 可空性列集对账看不见,另走一次性迁移;只有空库 / 开发机可以整库重建〔销毁全部数据〕)
575
592
  * (`docs/schema/baseline-mysql.sql` 是由这些文本机器录制出的**产物**,永不手改)。
576
593
  *
577
594
  * ── 列宽的**测量依据**([ref] A10;U2 逐列取证在档)──────────────────────────────────────────────────
@@ -619,18 +636,18 @@ export declare function ensurePgApprovalAskSchema(q: PgQueryFn): Promise<void>;
619
636
  * [ref] 升级前置断言 —— **拒启**,不是 warn(形照 [ref] 的 `assertPermissionRuleApprovalSchema` /
620
637
  * [ref] 的 `assertToolResultProvenanceSchema` 两条先例)。
621
638
  *
622
- * 病:本仓不发 `ALTER TABLE`(SCHEMA POLICY:改列就改 CREATE + 删库重建)。于是一台**没删表**就升上来
639
+ * 病:本仓不发 `ALTER TABLE`(SCHEMA POLICY:代码是唯一真源,存量库缺列由部署者按拒启句打印的 ALTER 自己执行)。于是一台**没跟上**就升上来
623
640
  * 的部署,`CREATE TABLE IF NOT EXISTS` 对存量 `approval_ask` 是空操作 —— 本车新加的
624
641
  * `rule_command` / `rule_scope_root` 两列不在,而 `ensureAsk` 的 INSERT 与 `askCols` 的每一条读
625
642
  * **无条件**引用它们。后果不是「少一个新功能」,是**整条流内审批协议**在那台机器上是坏的:
626
643
  * · 每一只 ask 的 `ensureAsk` 撞 unknown column ⇒ 被 `noteStoreError` 吞掉 ⇒ 整只 ask 退回 park 路由
627
644
  * (卡不落库、`GET /v1/approvals` 的重放面恒空、迟到决议腿恒 404);
628
645
  * · 而 `/v1/capabilities` 的 `streamApproval` 仍然报 active ⇒「says yes ⟺ route works」整句为假。
629
- * 没有这道断言,运维得不到**任何**「这次升级必须重建表」的信号 —— 正是 [ref]「禁静默降级」要挡的形。
646
+ * 没有这道断言,运维得不到**任何**「这次升级必须先给表加列」的信号 —— 正是 [ref]「禁静默降级」要挡的形。
630
647
  *
631
648
  * 判据 = **能力探测**(恒空的 `WHERE 1=0` 读)+ **方言的缺列错误码**(判据属主 `sql-errors.ts` 的
632
- * {@link isMissingColumnError});其余错误**原样 rethrow**,一个字都不加工 —— 一句破坏性的
633
- * 「去 DROP TABLE」指路比它要挡的缺陷更贵([ref] 那两轮收窄的结论逐字继承)。
649
+ * {@link isMissingColumnError});其余错误**原样 rethrow**,一个字都不加工 —— 把一次瞬时故障诊断成「缺列」的拒因
650
+ * 比它要挡的缺陷更贵([ref] 那两轮收窄的结论逐字继承)。拒因的出路是**闭集三条**(与整库对账同一份,{@link describeProbedTableDrift}),没有销毁性的一条。
634
651
  *
635
652
  * 🔴 位置与两条先例同款:**不在** `ensureSchema` 里(那条通道的契约是「只发 CREATE」,有运行时门看着),
636
653
  * 放在 DDL 之后、任何路由装配之前;且**只在流内审批协议真会上场时**跑(判据 = 与协调器同一个符号
@@ -2,8 +2,10 @@ import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
2
2
  import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
3
3
  import { APPROVAL_ENDING_CAUSES, causeOfAskRow } from "../approval-deny-reasons.js";
4
4
  import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
5
+ import { describeProbedTableDrift } from "./schema-drift-sentence.js";
5
6
  import { decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, readTriStateJsonValueColumn } from "./sql-json-column.js";
6
7
  import { ensureIndexes } from "./ensure-index.js";
8
+ import { isExecutionLane } from "../execution-lane-caps.js";
7
9
  export function assertIntegralEpochMs(field, value) {
8
10
  if (!Number.isSafeInteger(value)) {
9
11
  throw new TypeError(`approval-ask store: ${field} must be an integer epoch-ms (BIGINT column), got ${String(value)}`);
@@ -99,6 +101,17 @@ export function projectDecideReading(row, owner) {
99
101
  return askRowCarriesOwner(row, owner) ? row : null;
100
102
  }
101
103
  export const APPROVAL_ASK_TABLE = "approval_ask";
104
+ export const APPROVAL_ASK_EXECUTION_LANE_COLUMN = {
105
+ mysql: "execution_lane VARCHAR(32) NOT NULL DEFAULT ''",
106
+ pg: `execution_lane VARCHAR(32) COLLATE "C" NOT NULL DEFAULT ''`,
107
+ };
108
+ function executionLaneColumnOf(raw, askId) {
109
+ const lane = typeof raw === "string" ? raw : "";
110
+ if (!isExecutionLane(lane)) {
111
+ throw new Error(`approval-ask-store: ask ${askId} carries execution_lane ${JSON.stringify(lane)}, which is not an execution lane — the execution placement switch migration backfills every row; refusing rather than guess the lane this ask was raised on`);
112
+ }
113
+ return lane;
114
+ }
102
115
  export const APPROVAL_BATCH_TABLE = "approval_batch";
103
116
  export const TIDB_APPROVAL_ASK_STATEMENTS = [
104
117
  `CREATE TABLE IF NOT EXISTS ${APPROVAL_BATCH_TABLE} (
@@ -118,6 +131,9 @@ export const TIDB_APPROVAL_ASK_STATEMENTS = [
118
131
  source_task_id VARCHAR(255) NOT NULL,
119
132
  session_id VARCHAR(64) NOT NULL,
120
133
  owner VARCHAR(190) NULL,
134
+ -- execution_lane:造这只问的那条执行的车道(审批出处带车道;ensureAsk 铸行时写,既有行命中不改)。DEFAULT '' 只为存量库一句加列
135
+ -- (一次性迁移加列并按根会话放置回填,迁移后没有 '' 行)。
136
+ ${APPROVAL_ASK_EXECUTION_LANE_COLUMN.mysql},
121
137
  batch_id VARCHAR(64) NOT NULL,
122
138
  tool_call_id VARCHAR(255) NOT NULL,
123
139
  -- leg_key: 车3 §3.2′ 身份轴——sha256(resume checkpoint token) 的 hex(64 字符),首腿存空串。
@@ -172,7 +188,7 @@ export const TIDB_APPROVAL_ASK_STATEMENTS = [
172
188
  UNIQUE KEY uq_approval_ask_idem (task_id, idempotency_key),
173
189
  KEY idx_ask_task_state (task_id, state),
174
190
  -- idx_ask_session_state: 车3 §5.1 的 sync 腿开流重放读口(listPendingBySession)的支撑索引。
175
- -- 表尚未进生产 + SCHEMA POLICY 是删库重建 ⇒ 此刻加索引零迁移成本,晚一车加就要动生产表。
191
+ -- 表尚未进生产 ⇒ 此刻加索引零迁移成本,晚一车加就要动生产表。
176
192
  KEY idx_ask_session_state (session_id, state),
177
193
  KEY idx_ask_batch (batch_id),
178
194
  KEY idx_ask_state (state)
@@ -206,6 +222,8 @@ export async function ensurePgApprovalAskSchema(q) {
206
222
  source_task_id VARCHAR(255) COLLATE "C" NOT NULL,
207
223
  session_id VARCHAR(64) COLLATE "C" NOT NULL,
208
224
  owner VARCHAR(190) COLLATE "C",
225
+ -- execution_lane —— MySQL twin 逐字同形(语义见那侧注)。
226
+ ${APPROVAL_ASK_EXECUTION_LANE_COLUMN.pg},
209
227
  batch_id VARCHAR(64) COLLATE "C" NOT NULL,
210
228
  tool_call_id VARCHAR(255) COLLATE "C" NOT NULL,
211
229
  -- leg_key: 车3 §3.2′ 身份轴——sha256(resume checkpoint token) 的 hex(64 字符),首腿存空串
@@ -258,18 +276,16 @@ export async function assertApprovalAskRuleMaterialSchema(query, dialect) {
258
276
  `persisted on the row too, so a replica that does not hold the waiting leg replays the approved bytes; every ask-row ` +
259
277
  `write and read names these columns unconditionally, so on this table the whole in-stream approval protocol degrades silently (no card ` +
260
278
  `is persisted, the replay list stays empty, late decisions 404) while /v1/capabilities still reports it active. ` +
261
- `This repository ships no ALTER TABLE migrations, so the table must be recreated: run ` +
262
- `\`DROP TABLE ${APPROVAL_ASK_TABLE}; DROP TABLE ${APPROVAL_BATCH_TABLE};\` on the ` +
263
- `${dialect === "pg" ? "PostgreSQL" : "MySQL-protocol"} backend and restart — the schema is recreated at boot. ` +
264
- `The cost is bounded: these two tables hold IN-FLIGHT approval asks (cards awaiting an answer) plus settled ` +
265
- `bookkeeping; the durable park face (checkpoint rows) and the persisted rules themselves are untouched, so an ` +
266
- `in-flight card falls back to the durable gate (\`POST /v1/approvals/:sessionId/decide\`). ` +
267
- `Underlying probe error: ${err instanceof Error ? err.message : String(err)}`);
279
+ `The table holds IN-FLIGHT approval asks (cards awaiting an answer) plus settled bookkeeping; the durable park face ` +
280
+ `(checkpoint rows) and the persisted rules themselves are untouched, so an in-flight card falls back to the durable gate ` +
281
+ `(\`POST /v1/approvals/:sessionId/decide\`) until the columns exist.\n` +
282
+ `Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
283
+ (await describeProbedTableDrift(query, dialect, [APPROVAL_ASK_TABLE])));
268
284
  }
269
285
  }
270
286
  function askCols(dialect) {
271
287
  return [
272
- "ask_id, task_id, source_task_id, session_id, owner, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state",
288
+ "ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state",
273
289
  "provisional, rev, decision",
274
290
  jsonColumnText(dialect, "decision_actor"),
275
291
  "decision_note, settled_by",
@@ -307,6 +323,7 @@ function mapAskRowBody(r, askId) {
307
323
  sourceTaskId: String(r.source_task_id),
308
324
  sessionId: String(r.session_id),
309
325
  owner: strOrNull(r.owner),
326
+ executionLane: executionLaneColumnOf(r.execution_lane, askId),
310
327
  batchId: String(r.batch_id),
311
328
  toolCallId: String(r.tool_call_id),
312
329
  legKey: String(r.leg_key),
@@ -369,7 +386,7 @@ function patchColumns(patch, json) {
369
386
  return out;
370
387
  }
371
388
  function ph(dialect, n) {
372
- return dialect === "tidb" ? "?" : `$${n}`;
389
+ return dialect === "mysql" ? "?" : `$${n}`;
373
390
  }
374
391
  function pendingClaimWhere(dialect, firstParam) {
375
392
  return `ask_id = ${ph(dialect, firstParam)} AND batch_id = ${ph(dialect, firstParam + 1)} AND state = 'STREAM_PENDING'`;
@@ -377,15 +394,15 @@ function pendingClaimWhere(dialect, firstParam) {
377
394
  function decideOwnerWhere(dialect, owner, param) {
378
395
  if (owner === null)
379
396
  return { sql: "owner IS NULL", params: [] };
380
- return dialect === "tidb" ? { sql: "CAST(owner AS BINARY) = CAST(? AS BINARY)", params: [owner] } : { sql: `owner = ${ph(dialect, param)}`, params: [owner] };
397
+ return dialect === "mysql" ? { sql: "CAST(owner AS BINARY) = CAST(? AS BINARY)", params: [owner] } : { sql: `owner = ${ph(dialect, param)}`, params: [owner] };
381
398
  }
382
399
  export class SqlApprovalAskStore {
383
400
  db;
384
401
  constructor(db) {
385
402
  this.db = db;
386
403
  }
387
- q(tidb, pg) {
388
- return this.db.dialect === "tidb" ? tidb : pg;
404
+ q(mysql, pg) {
405
+ return this.db.dialect === "mysql" ? mysql : pg;
389
406
  }
390
407
  json(value) {
391
408
  return dialectJsonEncoder(this.db.dialect)(value);
@@ -394,7 +411,7 @@ export class SqlApprovalAskStore {
394
411
  return isDupKeyError(this.db.dialect, e);
395
412
  }
396
413
  async getAskOn(conn, askId) {
397
- const { rows } = await conn.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = $1`), [askId]);
414
+ const { rows } = await conn.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = $1`), [askId]);
398
415
  return rows[0] ? mapAskRow(rows[0]) : null;
399
416
  }
400
417
  async getBatchOn(conn, batchId) {
@@ -410,14 +427,15 @@ export class SqlApprovalAskStore {
410
427
  try {
411
428
  await conn.begin();
412
429
  await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_BATCH_TABLE} (batch_id, task_id, state, rev, created_at_ms, updated_at_ms) VALUES (?, ?, 'OPEN', 0, ?, ?)`, `INSERT INTO ${APPROVAL_BATCH_TABLE} (batch_id, task_id, state, rev, created_at_ms, updated_at_ms) VALUES ($1, $2, 'OPEN', 0, $3, $4) ON CONFLICT (batch_id) DO NOTHING`), [row.batchId, row.taskId, now, now]);
413
- const askInsert = await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, denied_reason, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
414
- "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ?, ?, ?, ?, ?, ?, ?)", `INSERT INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, denied_reason, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
415
- "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, $11, $12, $13, $14, $15, $16, $17) ON CONFLICT (ask_id) DO NOTHING"), [
430
+ const askInsert = await conn.query(this.q(`INSERT IGNORE INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, denied_reason, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
431
+ "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, ?, ?, ?, ?, ?, ?, ?)", `INSERT INTO ${APPROVAL_ASK_TABLE} (ask_id, task_id, source_task_id, session_id, owner, execution_lane, batch_id, tool_call_id, leg_key, parent_tool_call_id, bound_input_hash, state, provisional, rev, decision, decision_actor, decision_note, settled_by, decided_at_ms, denied_reason, gate_token, gate_bound_call_id, gate_bound_input_hash, idempotency_key, card_json, rule_command, rule_scope_root, schema_version, expires_at_ms, created_at_ms, updated_at_ms) ` +
432
+ "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 'STREAM_PENDING', 0, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, $12, $13, $14, $15, $16, $17, $18) ON CONFLICT (ask_id) DO NOTHING"), [
416
433
  row.askId,
417
434
  row.taskId,
418
435
  row.sourceTaskId,
419
436
  row.sessionId,
420
437
  row.owner ?? null,
438
+ row.executionLane,
421
439
  row.batchId,
422
440
  row.toolCallId,
423
441
  row.legKey,
@@ -686,40 +704,40 @@ export class SqlApprovalAskStore {
686
704
  }
687
705
  }
688
706
  async listByState(state, limit) {
689
- const { rows } = await this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE state = ? ORDER BY updated_at_ms ASC, created_at_ms ASC LIMIT ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE state = $1 ORDER BY updated_at_ms ASC, created_at_ms ASC LIMIT $2`), [state, limit]);
707
+ const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE state = ? ORDER BY updated_at_ms ASC, created_at_ms ASC LIMIT ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE state = $1 ORDER BY updated_at_ms ASC, created_at_ms ASC LIMIT $2`), [state, limit]);
690
708
  return rows.map(mapAskRow);
691
709
  }
692
710
  async listPendingByTask(taskId, signal) {
693
711
  if (signal?.aborted === true)
694
712
  throw abortedReadError("listPendingByTask");
695
- const { rows } = await withReadSignal(this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND state = 'STREAM_PENDING' ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND state = 'STREAM_PENDING' ORDER BY created_at_ms ASC`), [taskId]), signal, "listPendingByTask");
713
+ const { rows } = await withReadSignal(this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND state = 'STREAM_PENDING' ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND state = 'STREAM_PENDING' ORDER BY created_at_ms ASC`), [taskId]), signal, "listPendingByTask");
696
714
  return rows.map(mapAskRow);
697
715
  }
698
716
  async listPendingBySession(sessionId, owner, signal) {
699
717
  if (signal?.aborted === true)
700
718
  throw abortedReadError("listPendingBySession");
701
719
  const { rows } = await withReadSignal(this.db.query(owner === null
702
- ? this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'STREAM_PENDING' AND owner IS NULL ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'STREAM_PENDING' AND owner IS NULL ORDER BY created_at_ms ASC`)
703
- : this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'STREAM_PENDING' AND owner = ? ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'STREAM_PENDING' AND owner = $2 ORDER BY created_at_ms ASC`), owner === null ? [sessionId] : [sessionId, owner]), signal, "listPendingBySession");
720
+ ? this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'STREAM_PENDING' AND owner IS NULL ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'STREAM_PENDING' AND owner IS NULL ORDER BY created_at_ms ASC`)
721
+ : this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'STREAM_PENDING' AND owner = ? ORDER BY created_at_ms ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'STREAM_PENDING' AND owner = $2 ORDER BY created_at_ms ASC`), owner === null ? [sessionId] : [sessionId, owner]), signal, "listPendingBySession");
704
722
  return rows.map(mapAskRow);
705
723
  }
706
724
  async listByCall(call) {
707
- const { rows } = await this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND leg_key = ? AND tool_call_id = ? AND source_task_id = ? ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND leg_key = $2 AND tool_call_id = $3 AND source_task_id = $4 ORDER BY created_at_ms ASC, ask_id ASC`), [call.taskId, call.legKey, call.toolCallId, call.sourceTaskId]);
725
+ const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND leg_key = ? AND tool_call_id = ? AND source_task_id = ? ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND leg_key = $2 AND tool_call_id = $3 AND source_task_id = $4 ORDER BY created_at_ms ASC, ask_id ASC`), [call.taskId, call.legKey, call.toolCallId, call.sourceTaskId]);
708
726
  return rows.map(mapAskRow).filter((r) => askRowOnCall(r, call));
709
727
  }
710
728
  async listParkingBySession(sessionId) {
711
- const { rows } = await this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'PARKING' ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'PARKING' ORDER BY created_at_ms ASC, ask_id ASC`), [sessionId]);
729
+ const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = ? AND state = 'PARKING' ORDER BY created_at_ms ASC, ask_id ASC`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE session_id = $1 AND state = 'PARKING' ORDER BY created_at_ms ASC, ask_id ASC`), [sessionId]);
712
730
  return rows.map(mapAskRow).filter((r) => r.sessionId === sessionId);
713
731
  }
714
732
  async getAsk(askId) {
715
- const { rows } = await this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = $1`), [
733
+ const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE ask_id = $1`), [
716
734
  askId,
717
735
  ]);
718
736
  return rows[0] ? mapAskRow(rows[0]) : null;
719
737
  }
720
738
  async getByIdempotencyKey(taskId, key) {
721
739
  assertIdempotencyKeyShape(key);
722
- const { rows } = await this.db.query(this.q(`SELECT ${askCols("tidb")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND idempotency_key = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND idempotency_key = $2`), [taskId, key]);
740
+ const { rows } = await this.db.query(this.q(`SELECT ${askCols("mysql")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ? AND idempotency_key = ?`, `SELECT ${askCols("pg")} FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1 AND idempotency_key = $2`), [taskId, key]);
723
741
  return rows[0] ? mapAskRow(rows[0]) : null;
724
742
  }
725
743
  async deferReconcile(askId, expectedState, expectedRev, nowMs) {
@@ -754,7 +772,7 @@ export class SqlApprovalAskStore {
754
772
  const ANCHOR_COLUMNS = ["task_id", "session_id", "source_task_id"];
755
773
  let keepSql = "";
756
774
  let keepParams = [];
757
- if (keep.length > 0 && dialect === "tidb") {
775
+ if (keep.length > 0 && dialect === "mysql") {
758
776
  const list = keep.map(() => "?").join(", ");
759
777
  keepSql = ANCHOR_COLUMNS.map((c) => ` AND ${c} NOT IN (${list})`).join("");
760
778
  keepParams = ANCHOR_COLUMNS.flatMap(() => keep);
@@ -10,15 +10,15 @@ export class SqlApprovalNonceStore {
10
10
  constructor(db) {
11
11
  this.db = db;
12
12
  }
13
- q(tidb, pg) {
14
- return this.db.dialect === "tidb" ? tidb : pg;
13
+ q(mysql, pg) {
14
+ return this.db.dialect === "mysql" ? mysql : pg;
15
15
  }
16
16
  async nowMs() {
17
17
  return sqlDbNowMs(this.db, APPROVAL_NONCE_TABLE);
18
18
  }
19
19
  async consume(sessionId, nonce, expiresAtMs) {
20
20
  try {
21
- await this.db.query(this.q(`INSERT INTO ${APPROVAL_NONCE_TABLE} (session_id, nonce, consumed_at_ms, expires_at_ms) VALUES (?,?,${SQL_DB_NOW_MS.tidb},?)`, `INSERT INTO ${APPROVAL_NONCE_TABLE} (session_id, nonce, consumed_at_ms, expires_at_ms) VALUES ($1,$2,${SQL_DB_NOW_MS.pg},$3)`), [sessionId, nonce, expiresAtMs]);
21
+ await this.db.query(this.q(`INSERT INTO ${APPROVAL_NONCE_TABLE} (session_id, nonce, consumed_at_ms, expires_at_ms) VALUES (?,?,${SQL_DB_NOW_MS.mysql},?)`, `INSERT INTO ${APPROVAL_NONCE_TABLE} (session_id, nonce, consumed_at_ms, expires_at_ms) VALUES ($1,$2,${SQL_DB_NOW_MS.pg},$3)`), [sessionId, nonce, expiresAtMs]);
22
22
  return true;
23
23
  }
24
24
  catch (err) {
@@ -28,7 +28,7 @@ export class SqlApprovalNonceStore {
28
28
  }
29
29
  }
30
30
  async reapExpired() {
31
- const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_NONCE_TABLE} WHERE expires_at_ms <= ${SQL_DB_NOW_MS.tidb}`, `DELETE FROM ${APPROVAL_NONCE_TABLE} WHERE expires_at_ms <= ${SQL_DB_NOW_MS.pg}`), []);
31
+ const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_NONCE_TABLE} WHERE expires_at_ms <= ${SQL_DB_NOW_MS.mysql}`, `DELETE FROM ${APPROVAL_NONCE_TABLE} WHERE expires_at_ms <= ${SQL_DB_NOW_MS.pg}`), []);
32
32
  return res.affected;
33
33
  }
34
34
  }
@@ -1,6 +1,6 @@
1
1
  import type { Pool as MysqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
- import { type SqlEngineFacts, type SqlEngineReading, type SqlWallSource, type SqlWallsReading } from "./sql-driver.js";
3
+ import { type SqlEngineFacts, type SqlEngineReading, type SqlWallSource, type SqlWallsReading, type SqlDialect } from "./sql-driver.js";
4
4
  import { type Logger } from "../observability/logger.js";
5
5
  /**
6
6
  * 文本协议的 blob 膨胀律 —— mysql2 `query()` 把 Buffer 参数写成 `X'<hex>'`,一字节两个字符,所以承载一个 blob 的语句至少是它的 2 倍。
@@ -115,7 +115,7 @@ export declare class SqlBlobBackend implements BlobBackend {
115
115
  private readonly db;
116
116
  /** Where a stale connection's replacement is logged ({@link connectJudged}); the boot assembly passes the service logger. */
117
117
  private readonly log;
118
- constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number, table?: string, log?: Pick<Logger, "warn">);
118
+ constructor(dialect: SqlDialect, pool: MysqlPool | PgPool, maxBytes?: number, table?: string, log?: Pick<Logger, "warn">);
119
119
  /** The created_at-refreshing content-addressed upsert — VERBATIM the tidb/pg stores' `putBlobRow`. Refreshes
120
120
  * created_at even on a dedup hit so a re-used/just-written blob is "touched=now" and GC-grace-protected.
121
121
  * (a): an over-cap blob is REJECTED (typed {@link BlobTooLargeError}) BEFORE the driver ever sees the bytes —
@@ -140,7 +140,7 @@ export declare class SqlBlobBackend implements BlobBackend {
140
140
  /** SHARED index op(workspace 浏览面 #3,[ref]②)—— per-blob 字节数,读 SQL INDEX 的 `byte_len`
141
141
  * 列(sqlHasBlobs 同款单 IN 查询;MinIO 后端的 bytes-NULL 索引行同样带 byte_len,两后端同源)。
142
142
  * tree/file 面的 size 投影与「超限先拒后拉」都靠它——绝不为拿大小去 getBlob 整块字节。 */
143
- export declare function sqlBlobSizes(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, hashes: string[]): Promise<Map<string, number>>;
143
+ export declare function sqlBlobSizes(dialect: SqlDialect, pool: MysqlPool | PgPool, hashes: string[]): Promise<Map<string, number>>;
144
144
  export interface MinioBlobBackendConfig {
145
145
  endpoint: string;
146
146
  bucket: string;
@@ -198,7 +198,7 @@ export declare class MinioBlobBackend implements BlobBackend {
198
198
  /** Blob-domain index table ([ref] 域隔离,SqlBlobBackend.table 同款;默认 = E19 纪元的 snapshot_blob)。 */
199
199
  private readonly indexTable;
200
200
  constructor(cfg: MinioBlobBackendConfig, index?: {
201
- dialect: "tidb" | "pg";
201
+ dialect: SqlDialect;
202
202
  pool: MysqlPool | PgPool;
203
203
  table?: string;
204
204
  });
@@ -76,7 +76,7 @@ export class SqlBlobBackend {
76
76
  this.pool = pool;
77
77
  this.explicitCapBytes = maxBytes;
78
78
  this.table = table;
79
- this.db = dialect === "tidb" ? mysqlDriver(pool) : pgDriver(pool);
79
+ this.db = dialect === "mysql" ? mysqlDriver(pool) : pgDriver(pool);
80
80
  this.log = log ?? createLogger();
81
81
  }
82
82
  async putBlob(hash, bytes) {
@@ -90,7 +90,7 @@ export class SqlBlobBackend {
90
90
  if (judged.refusal !== undefined)
91
91
  throw new BlobTooLargeError(bytes.byteLength, judged.refusal.cap);
92
92
  try {
93
- await judged.conn.query(this.dialect === "tidb"
93
+ await judged.conn.query(this.dialect === "mysql"
94
94
  ? `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`
95
95
  : `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
96
96
  }
@@ -99,7 +99,7 @@ export class SqlBlobBackend {
99
99
  }
100
100
  }
101
101
  async getBlob(hash) {
102
- if (this.dialect === "tidb") {
102
+ if (this.dialect === "mysql") {
103
103
  const [rows] = await this.pool.query(`SELECT bytes FROM ${this.table} WHERE blob_hash = ?`, [hash]);
104
104
  return rows[0]?.bytes != null ? new Uint8Array(rows[0].bytes) : undefined;
105
105
  }
@@ -118,7 +118,7 @@ async function sqlHasBlobs(dialect, pool, hashes, table = "snapshot_blob") {
118
118
  if (hashes.length === 0)
119
119
  return new Set();
120
120
  const distinct = [...new Set(hashes)];
121
- if (dialect === "tidb") {
121
+ if (dialect === "mysql") {
122
122
  const ph = distinct.map(() => "?").join(",");
123
123
  const [rows] = await pool.query(`SELECT blob_hash FROM ${table} WHERE blob_hash IN (${ph})`, distinct);
124
124
  return new Set(rows.map((r) => String(r.blob_hash)));
@@ -135,7 +135,7 @@ export async function sqlBlobSizes(dialect, pool, hashes) {
135
135
  const out = new Map();
136
136
  for (let i = 0; i < distinct.length; i += CHUNK) {
137
137
  const part = distinct.slice(i, i + CHUNK);
138
- if (dialect === "tidb") {
138
+ if (dialect === "mysql") {
139
139
  const ph = part.map(() => "?").join(",");
140
140
  const [rows] = await pool.query(`SELECT blob_hash, byte_len FROM snapshot_blob WHERE blob_hash IN (${ph})`, part);
141
141
  for (const r of rows)
@@ -155,7 +155,7 @@ async function sqlDeleteIndexRowsPastGrace(dialect, pool, hashes, table = "snaps
155
155
  return [];
156
156
  const distinct = [...new Set(hashes)];
157
157
  const cutoff = new Date(Date.now() - BLOB_GC_GRACE_MS);
158
- if (dialect === "tidb") {
158
+ if (dialect === "mysql") {
159
159
  const ph = distinct.map(() => "?").join(",");
160
160
  const grace = await pool
161
161
  .query(`SELECT blob_hash FROM ${table} WHERE created_at < ? AND blob_hash IN (${ph})`, [cutoff, ...distinct])
@@ -216,7 +216,7 @@ export class MinioBlobBackend {
216
216
  throw new Error(`MinioBlobBackend.putBlob ${hash}: HTTP ${res.status} ${detail.slice(0, 200)}`);
217
217
  }
218
218
  if (this.indexPool && this.indexDialect) {
219
- if (this.indexDialect === "tidb") {
219
+ if (this.indexDialect === "mysql") {
220
220
  await this.indexPool.query(`INSERT INTO ${this.indexTable} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,NULL,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`, [hash, bytes.byteLength, new Date()]);
221
221
  }
222
222
  else {
@@ -24,8 +24,8 @@ export class SqlBreakerState {
24
24
  this.db = db;
25
25
  this.onWriteFail = onWriteFail;
26
26
  }
27
- q(tidb, pg) {
28
- return this.db.dialect === "tidb" ? tidb : pg;
27
+ q(mysql, pg) {
28
+ return this.db.dialect === "mysql" ? mysql : pg;
29
29
  }
30
30
  get(key) {
31
31
  return this.cache.get(key);
@@ -54,7 +54,7 @@ export declare class CachingSessionStore implements SessionStore {
54
54
  private readonly onStaleHit?;
55
55
  /** core `SessionStore.headOf` —— 按 id 读一行(属主 / 住客 / 叶子)的唯一读口,**纯转发、不经暖缓存**(答的是行,绝不从暖缓存里那只会话的元数据答 ——
56
56
  * 缓存的会话活得比「另一只店删掉又重建了这一行」长)。内层店必有(本类的暖命中按它核新鲜)。🔴 必须转发:生产默认把真店包进本类,授权口 / 属主门 /
57
- * parked 两读 / k8s 工作区空间 / SSE 探针 / resume-at 读的都是本类。 */
57
+ * k8s 工作区空间 / SSE 探针 / resume-at 读的都是本类。 */
58
58
  readonly headOf: SessionHeadReader["headOf"];
59
59
  readonly register?: (sessionId: string, owner: string | null) => Promise<void>;
60
60
  /** [ref] 委派继承律的认领口,纯转发(写的是行上的属主那一格 —— 只 CAS 子行自己,不碰暖缓存里的会话;停驻挂点在默认部署上读的就是本类 —— 与 `placementOf` 同转发同缺:内店记录委派树 ⇒ 两只都在)。 */
@@ -72,12 +72,16 @@ export declare class CachingSessionStore implements SessionStore {
72
72
  q?: string;
73
73
  }) => Promise<SessionListItem[]>;
74
74
  readonly fork?: (sourceId: string, owner: string | null) => Promise<string | null>;
75
+ /** fork 续写的店契约(原样转发;与 fork 同理绕过暖缓存 —— 读写的是持久行,新 id 本来就不在缓存里)。 */
76
+ readonly forkTo?: OwnerAwareSessionStore["forkTo"];
77
+ /** 执行席(原样转发:放置是会话**行**上的一格,缓存只缓存会话日志的暖句柄,不缓存行格 —— 读与写都直达内层店)。 */
78
+ readonly executionPlacement?: OwnerAwareSessionStore["executionPlacement"];
75
79
  readonly deleteSession?: (sessionId: string, owner: string | null) => Promise<boolean>;
76
80
  /** The session row's list projection for one id + its writable bits (`title` / `pinned`) — DURABLE, pure pass-through
77
81
  * (they read / write `session_meta` / the local sidecars, never the warm cache). 🔴 Must forward (the retention / placements
78
82
  * lesson): the production default wraps the real store in this class, and the route's fact `sessionPatch` reads THIS object. */
79
83
  readonly sessionSummaryOf?: (sessionId: string) => Promise<SessionListItem | undefined>;
80
- readonly patchSessionMeta?: OwnerAwareSessionStore["patchSessionMeta"];
84
+ readonly writeSessionRowCells?: OwnerAwareSessionStore["writeSessionRowCells"];
81
85
  /** The auto-titler's NULL-only title write (SQL twins + local sidecar carry it; pure pass-through). */
82
86
  readonly setTitleIfNull?: (sessionId: string, title: string) => Promise<boolean>;
83
87
  readonly probeTitle?: (sessionId: string) => Promise<"none" | "untitled" | "titled">;
@@ -12,9 +12,11 @@ export class CachingSessionStore {
12
12
  claimOwnerless;
13
13
  listSessions;
14
14
  fork;
15
+ forkTo;
16
+ executionPlacement;
15
17
  deleteSession;
16
18
  sessionSummaryOf;
17
- patchSessionMeta;
19
+ writeSessionRowCells;
18
20
  setTitleIfNull;
19
21
  probeTitle;
20
22
  readInstructionSource;
@@ -57,8 +59,8 @@ export class CachingSessionStore {
57
59
  this.listSessions = ownerAware.listSessions.bind(inner);
58
60
  if (ownerAware.sessionSummaryOf)
59
61
  this.sessionSummaryOf = ownerAware.sessionSummaryOf.bind(inner);
60
- if (ownerAware.patchSessionMeta)
61
- this.patchSessionMeta = ownerAware.patchSessionMeta.bind(inner);
62
+ if (ownerAware.writeSessionRowCells)
63
+ this.writeSessionRowCells = ownerAware.writeSessionRowCells.bind(inner);
62
64
  if (ownerAware.setTitleIfNull)
63
65
  this.setTitleIfNull = ownerAware.setTitleIfNull.bind(inner);
64
66
  if (ownerAware.probeTitle)
@@ -73,6 +75,10 @@ export class CachingSessionStore {
73
75
  this.recordSessionWorkspace = ownerAware.recordSessionWorkspace.bind(inner);
74
76
  if (ownerAware.fork)
75
77
  this.fork = ownerAware.fork.bind(inner);
78
+ if (ownerAware.forkTo)
79
+ this.forkTo = ownerAware.forkTo.bind(inner);
80
+ if (ownerAware.executionPlacement)
81
+ this.executionPlacement = ownerAware.executionPlacement;
76
82
  if (ownerAware.exportEntries)
77
83
  this.exportEntries = ownerAware.exportEntries.bind(inner);
78
84
  if (ownerAware.listEntryIds)
@@ -33,7 +33,7 @@ export function buildCheckpointIndexCells(cp) {
33
33
  };
34
34
  }
35
35
  export const APPROVAL_CARD_COLUMN_MAX_BYTES = {
36
- tidb: 65_535,
36
+ mysql: 65_535,
37
37
  pg: Number.POSITIVE_INFINITY,
38
38
  };
39
39
  export function judgeCheckpointIndexCells(cp, dialect) {
@@ -83,7 +83,7 @@ function renderFacetCase(compared) {
83
83
  return `CASE ${CHECKPOINT_FACET_CASES.map((c) => `WHEN ${compared(c.column)} IN (${c.words.map(literal).join(", ")}) THEN ${literal(c.facet)}`).join(" ")} END`;
84
84
  }
85
85
  export const CHECKPOINT_FACET_SQL = Object.freeze({
86
- tidb: renderFacetCase((column) => `CAST(${column} AS BINARY)`),
86
+ mysql: renderFacetCase((column) => `CAST(${column} AS BINARY)`),
87
87
  pg: renderFacetCase((column) => column),
88
88
  });
89
89
  export const CHECKPOINT_SWEEP_ORDER_SQL = "CASE WHEN approval_card IS NULL THEN 1 ELSE 0 END ASC, deadline ASC, token ASC";
@@ -240,8 +240,8 @@ export declare function pendingRowPrecedes(a: {
240
240
  *
241
241
  * 🔴 为什么是一张表而不是每列一个 `assert*ColumnSchema` 函数([ref] 归一):[ref]① 那次是一列一函数,
242
242
  * 本批 [ref] 又要加两列 —— 照抄就是第二、第三份三十行的同形代码,而它们**唯一的差别只有列名与那句话**。
243
- * 探测法(恒空 `WHERE 1=0` 读 + 方言缺列码)、失败方向(只在**可证**缺列时才给删表指路)、恢复动作
244
- * (DROP TABLE checkpoint 的代价交代)三者对整张表是同一条,所以规则只留一条,列变成数据:下一次加列
243
+ * 探测法(恒空 `WHERE 1=0` 读 + 方言缺列码)、失败方向(只在**可证**缺列时才拒)、恢复动作
244
+ * (部署者按拒因打印的 ALTER 在原表上加列;表里是在飞的停驻,不动它)三者对整张表是同一条,所以规则只留一条,列变成数据:下一次加列
245
245
  * 只加一行,不再多一条规则。
246
246
  */
247
247
  export declare const CHECKPOINT_ADDED_COLUMNS: ReadonlyArray<{
@@ -254,14 +254,15 @@ export declare const CHECKPOINT_ADDED_COLUMNS: ReadonlyArray<{
254
254
  * 依据是本仓 assert*Schema 探针族的真先例与失败方向本身)。[ref] 起判据对 {@link CHECKPOINT_ADDED_COLUMNS}
255
255
  * **整张列册**跑(见那张表的头注:归一的理由)。
256
256
  *
257
- * 病:本仓不发 `ALTER TABLE`(SCHEMA POLICY:改列就改 CREATE + 删库重建)。一台**没删表**就升上来的
257
+ * 病:本仓不发 `ALTER TABLE`(SCHEMA POLICY:代码是唯一真源,存量库缺列由部署者按拒启句打印的 ALTER 自己执行)。一台**没跟上**就升上来的
258
258
  * 部署,`CREATE TABLE IF NOT EXISTS` 对存量 `checkpoint` 是空操作 —— 后加的列不在,而写口与读口
259
259
  * **无条件**引用它们。没有这道断言,服务照常过启动与健康检查,直到运行期才响,且首撞点在用户的
260
260
  * 审批 / resume 路径上。
261
261
  *
262
262
  * 判据 = **能力探测**(恒空的 `WHERE 1=0` 读)+ **方言的缺列错误码**({@link isMissingColumnError},
263
- * 判据属主 `sql-errors.ts`);其余错误**原样 rethrow**,一个字都不加工 —— 一句破坏性的「去 DROP TABLE」
264
- * 指路比它要挡的缺陷更贵([ref] 两轮收窄的结论逐字继承)。
263
+ * 判据属主 `sql-errors.ts`);其余错误**原样 rethrow**,一个字都不加工 —— 把一次瞬时故障诊断成「缺列」的拒因
264
+ * 比它要挡的缺陷更贵([ref] 两轮收窄的结论逐字继承)。拒因的出路是**闭集三条**(与整库对账同一份,{@link describeProbedTableDrift}),
265
+ * 没有销毁性的一条:这张表里是在飞的停驻,出路是原表上加列。
265
266
  *
266
267
  * 位置:与三条先例同款,不在 `ensureSchema` 里(那条通道的契约是「只发 CREATE」),放在 boot 的
267
268
  * store 装配段、路由装配之前;**无条件**跑(与 [ref] tool_result 探针同档)——checkpoint 表在两方言的