@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
@@ -9,6 +9,7 @@ import { recordFailOpen } from "../observability/fail-open.js";
9
9
  import { durableRowIdentity, withoutCheckpointToken } from "../checkpoint-identity.js";
10
10
  import { mysqlDriver, pgDriver, dialectProtocolJsonEncoder } from "./sql-driver.js";
11
11
  import { isDupKeyError, isMissingColumnError, rowTooLargeOrSelf, StoreRowTooLargeError } from "./sql-errors.js";
12
+ import { describeProbedTableDrift } from "./schema-drift-sentence.js";
12
13
  import { MANAGED_RETENTION, UNMANAGED_RETENTION } from "./retention-store-sql.js";
13
14
  import { approvalPayloadFingerprint } from "./approval-payload-fingerprint.js";
14
15
  const CTX_REWRITE_PAGE = 500;
@@ -61,13 +62,10 @@ export async function assertCheckpointColumnSchema(query, dialect) {
61
62
  throw err;
62
63
  throw new Error(`checkpoint is missing the column ${column} — refusing to start. ` +
63
64
  `On this table ${breaksAt} — at runtime, after a healthy-looking boot. ` +
64
- "This repository ships no ALTER TABLE migrations, so the table must be recreated: run " +
65
- "`DROP TABLE checkpoint;` on the " +
66
- (dialect === "pg" ? "PostgreSQL" : "MySQL-protocol") +
67
- " backend and restart — the schema is recreated at boot. The cost is bounded but real: the table holds IN-FLIGHT " +
68
- "durable parks (suspended approvals/reviews awaiting a decision) — those cards are lost and their runs must be " +
69
- "re-triggered; `checkpoint_ctx`, run rows and every other persistent face are untouched. " +
70
- `Underlying probe error: ${err instanceof Error ? err.message : String(err)}`);
65
+ "The table holds IN-FLIGHT durable parks (suspended approvals/reviews awaiting a decision), so the way forward is an in-place " +
66
+ "ALTER that keeps them; `checkpoint_ctx`, run rows and every other persistent face are untouched.\n" +
67
+ `Underlying probe error: ${err instanceof Error ? err.message : String(err)}\n\n` +
68
+ (await describeProbedTableDrift(query, dialect, ["checkpoint"])));
71
69
  }
72
70
  }
73
71
  }
@@ -345,8 +343,8 @@ export class SqlCheckpointStore {
345
343
  this.logger = logger;
346
344
  this.retention = opts.retention ?? UNMANAGED_RETENTION;
347
345
  }
348
- q(tidb, pg) {
349
- return this.db.dialect === "tidb" ? tidb : pg;
346
+ q(mysql, pg) {
347
+ return this.db.dialect === "mysql" ? mysql : pg;
350
348
  }
351
349
  json(value, label) {
352
350
  return dialectProtocolJsonEncoder(this.db.dialect)(value, label);
@@ -355,7 +353,7 @@ export class SqlCheckpointStore {
355
353
  return isDupKeyError(this.db.dialect, e);
356
354
  }
357
355
  ph(n) {
358
- return this.db.dialect === "tidb" ? "?" : `$${n}`;
356
+ return this.db.dialect === "mysql" ? "?" : `$${n}`;
359
357
  }
360
358
  pendingRowQuery(select, sessionId, scope, binding) {
361
359
  const params = [sessionId];
@@ -426,8 +424,8 @@ export class SqlCheckpointStore {
426
424
  }
427
425
  }
428
426
  async get(token) {
429
- const { rows } = await this.db.query(this.q(`SELECT version, status, ${jsonColumnText("tidb", "checkpoint")}, ${jsonColumnText("tidb", "outcome")}, reopen_reason, rev, ` +
430
- `${jsonColumnText("tidb", "pending_steer_queue")}, ${jsonColumnText("tidb", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = ?`, `SELECT version, status, ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnText("pg", "outcome")}, reopen_reason, rev, ` +
427
+ const { rows } = await this.db.query(this.q(`SELECT version, status, ${jsonColumnText("mysql", "checkpoint")}, ${jsonColumnText("mysql", "outcome")}, reopen_reason, rev, ` +
428
+ `${jsonColumnText("mysql", "pending_steer_queue")}, ${jsonColumnText("mysql", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = ?`, `SELECT version, status, ${jsonColumnText("pg", "checkpoint")}, ${jsonColumnText("pg", "outcome")}, reopen_reason, rev, ` +
431
429
  `${jsonColumnText("pg", "pending_steer_queue")}, ${jsonColumnText("pg", "execution_outcome")}, execution_at_ms FROM checkpoint WHERE token = $1`), [token]);
432
430
  const r = rows[0];
433
431
  if (!r)
@@ -482,7 +480,7 @@ export class SqlCheckpointStore {
482
480
  }
483
481
  async recordExecutionOutcome(token, scope, gate) {
484
482
  for (let attempt = 0; attempt < EXECUTION_RECORD_CAS_ATTEMPTS; attempt++) {
485
- const { rows } = await this.db.query(this.q(`SELECT scope, status, ${jsonColumnText("tidb", "execution_outcome")} FROM checkpoint WHERE token = ?`, `SELECT scope, status, ${jsonColumnText("pg", "execution_outcome")} FROM checkpoint WHERE token = $1`), [token]);
483
+ const { rows } = await this.db.query(this.q(`SELECT scope, status, ${jsonColumnText("mysql", "execution_outcome")} FROM checkpoint WHERE token = ?`, `SELECT scope, status, ${jsonColumnText("pg", "execution_outcome")} FROM checkpoint WHERE token = $1`), [token]);
486
484
  const row = rows[0];
487
485
  const facts = row === undefined
488
486
  ? undefined
@@ -506,7 +504,7 @@ export class SqlCheckpointStore {
506
504
  async setPendingSteer(token, scope, steer) {
507
505
  const clean = validatePendingSteer(steer);
508
506
  for (let attempt = 0; attempt < STEER_CAS_ATTEMPTS; attempt++) {
509
- const { rows } = await this.db.query(this.q(`SELECT status, ${jsonColumnText("tidb", "pending_steer_queue")}, pending_steer_rev FROM checkpoint WHERE token = ? AND scope = ?`, `SELECT status, ${jsonColumnText("pg", "pending_steer_queue")}, pending_steer_rev FROM checkpoint WHERE token = $1 AND scope = $2`), [token, scope]);
507
+ const { rows } = await this.db.query(this.q(`SELECT status, ${jsonColumnText("mysql", "pending_steer_queue")}, pending_steer_rev FROM checkpoint WHERE token = ? AND scope = ?`, `SELECT status, ${jsonColumnText("pg", "pending_steer_queue")}, pending_steer_rev FROM checkpoint WHERE token = $1 AND scope = $2`), [token, scope]);
510
508
  const row = rows[0];
511
509
  if (!row || row.status !== "pending")
512
510
  return false;
@@ -539,12 +537,12 @@ export class SqlCheckpointStore {
539
537
  }
540
538
  async reapExpired(cutoff) {
541
539
  const res = await this.db.query(this.q("UPDATE checkpoint SET status = 'expired', decided_at_ms = ? " +
542
- `WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= ? AND COALESCE(${CHECKPOINT_FACET_SQL.tidb}, '') <> 'A') OR (terminal_at_ms IS NOT NULL AND terminal_at_ms <= ?))`, "UPDATE checkpoint SET status = 'expired', decided_at_ms = $1 " +
540
+ `WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= ? AND COALESCE(${CHECKPOINT_FACET_SQL.mysql}, '') <> 'A') OR (terminal_at_ms IS NOT NULL AND terminal_at_ms <= ?))`, "UPDATE checkpoint SET status = 'expired', decided_at_ms = $1 " +
543
541
  `WHERE status = 'pending' AND ((deadline IS NOT NULL AND deadline <= $2 AND COALESCE(${CHECKPOINT_FACET_SQL.pg}, '') <> 'A') OR (terminal_at_ms IS NOT NULL AND terminal_at_ms <= $3))`), [Date.now(), cutoff, cutoff]);
544
542
  return res.affected;
545
543
  }
546
544
  async listExpiredApprovalGates(cutoff, limit = 100) {
547
- const { rows } = await this.db.query(this.q(`SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= ? AND ${CHECKPOINT_FACET_SQL.tidb} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT ?`, `SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= $1 AND ${CHECKPOINT_FACET_SQL.pg} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT $2`), [cutoff, limit]);
545
+ const { rows } = await this.db.query(this.q(`SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= ? AND ${CHECKPOINT_FACET_SQL.mysql} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT ?`, `SELECT session_id, scope FROM checkpoint WHERE status = 'pending' AND deadline IS NOT NULL AND deadline <= $1 AND ${CHECKPOINT_FACET_SQL.pg} = 'A' ORDER BY ${CHECKPOINT_SWEEP_ORDER_SQL} LIMIT $2`), [cutoff, limit]);
548
546
  return rows.map((r) => ({ sessionId: String(r.session_id), scope: String(r.scope) }));
549
547
  }
550
548
  async listPending(scope) {
@@ -593,7 +591,7 @@ export class SqlCheckpointStore {
593
591
  return out.sort((a, b) => (b.riskDescriptor?.severity ?? 0) - (a.riskDescriptor?.severity ?? 0) || a.createdAt - b.createdAt);
594
592
  }
595
593
  async readParkedSkillCardFacts(token) {
596
- const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "checkpoint")} FROM checkpoint WHERE token = ? AND status = 'pending'`, `SELECT ${jsonColumnText("pg", "checkpoint")} FROM checkpoint WHERE token = $1 AND status = 'pending'`), [token]);
594
+ const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "checkpoint")} FROM checkpoint WHERE token = ? AND status = 'pending'`, `SELECT ${jsonColumnText("pg", "checkpoint")} FROM checkpoint WHERE token = $1 AND status = 'pending'`), [token]);
597
595
  const r = rows[0];
598
596
  if (r === undefined)
599
597
  return {};
@@ -616,7 +614,7 @@ export class SqlCheckpointStore {
616
614
  }
617
615
  }
618
616
  async listByScope(scope) {
619
- const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = ? ORDER BY created_at_ms ASC LIMIT 500`, `SELECT ${jsonColumnText("pg", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = $1 ORDER BY created_at_ms ASC LIMIT 500`), [scope]);
617
+ const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = ? ORDER BY created_at_ms ASC LIMIT 500`, `SELECT ${jsonColumnText("pg", "checkpoint")}, status FROM checkpoint WHERE status = 'pending' AND scope = $1 ORDER BY created_at_ms ASC LIMIT 500`), [scope]);
620
618
  const out = [];
621
619
  for (const r of rows) {
622
620
  const cp = refusing("a checkpoint nobody can resume", () => decodeCheckpointColumn(r.checkpoint, `in the ${scope} scheduler view`));
@@ -635,7 +633,7 @@ export class SqlCheckpointStore {
635
633
  return rows[0] ? String(rows[0].token) : null;
636
634
  }
637
635
  async findCheckpointCandidatesForAsk(scope, sessionId, toolCallId, sinceMs) {
638
- const { rows } = await this.db.query(this.q(`SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, version, ${jsonColumnText("tidb", "checkpoint")} FROM checkpoint ` +
636
+ const { rows } = await this.db.query(this.q(`SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, version, ${jsonColumnText("mysql", "checkpoint")} FROM checkpoint ` +
639
637
  "WHERE scope=? AND session_id=? AND created_at_ms>=? AND (tool_call_id=? OR tool_call_id IS NULL) ORDER BY created_at_ms ASC", `SELECT token, status, created_at_ms, tool_call_id, bound_input_hash, version, ${jsonColumnText("pg", "checkpoint")} FROM checkpoint ` +
640
638
  "WHERE scope=$1 AND session_id=$2 AND created_at_ms>=$3 AND (tool_call_id=$4 OR tool_call_id IS NULL) ORDER BY created_at_ms ASC"), [scope, sessionId, sinceMs, toolCallId]);
641
639
  const out = [];
@@ -673,7 +671,7 @@ export class SqlCheckpointStore {
673
671
  return out;
674
672
  }
675
673
  async findDecidedApprovalsForBinding(sessionId, boundCallId) {
676
- const { rows } = await this.db.query(this.q(`SELECT scope, bound_input_hash, ${jsonColumnText("tidb", "outcome")}, ${jsonColumnText("tidb", "execution_outcome")} FROM checkpoint ` +
674
+ const { rows } = await this.db.query(this.q(`SELECT scope, bound_input_hash, ${jsonColumnText("mysql", "outcome")}, ${jsonColumnText("mysql", "execution_outcome")} FROM checkpoint ` +
677
675
  `WHERE session_id=? AND tool_call_id=? AND status='resolved' AND decided_at_ms IS NOT NULL ORDER BY decided_at_ms DESC LIMIT ${DECIDED_REPLAY_SCAN_CAP}`, `SELECT scope, bound_input_hash, ${jsonColumnText("pg", "outcome")}, ${jsonColumnText("pg", "execution_outcome")} FROM checkpoint ` +
678
676
  `WHERE session_id=$1 AND tool_call_id=$2 AND status='resolved' AND decided_at_ms IS NOT NULL ORDER BY decided_at_ms DESC LIMIT ${DECIDED_REPLAY_SCAN_CAP}`), [sessionId, boundCallId]);
679
677
  return rows.map((r) => {
@@ -735,14 +733,14 @@ export class SqlCheckpointStore {
735
733
  }
736
734
  }
737
735
  async getCtx(sessionId) {
738
- const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("tidb", "ctx")} FROM checkpoint_ctx WHERE session_id=?`, `SELECT ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id=$1`), [sessionId]);
736
+ const { rows } = await this.db.query(this.q(`SELECT ${jsonColumnText("mysql", "ctx")} FROM checkpoint_ctx WHERE session_id=?`, `SELECT ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id=$1`), [sessionId]);
739
737
  return rows[0] ? refusing("a resume context nobody can read", () => decodeCtxColumn(rows[0].ctx, `for session ${JSON.stringify(sessionId)}`)) : null;
740
738
  }
741
739
  async rewriteCtxRows(rewrite) {
742
740
  const out = { scanned: 0, rewritten: 0, valuesRewritten: 0, unreadable: 0, tooLarge: 0 };
743
741
  let after = "";
744
742
  for (;;) {
745
- const { rows } = await this.db.query(this.q(`SELECT session_id, ${jsonColumnText("tidb", "ctx")} FROM checkpoint_ctx WHERE session_id > ? ORDER BY session_id LIMIT ${CTX_REWRITE_PAGE}`, `SELECT session_id, ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id > $1 ORDER BY session_id LIMIT ${CTX_REWRITE_PAGE}`), [after]);
743
+ const { rows } = await this.db.query(this.q(`SELECT session_id, ${jsonColumnText("mysql", "ctx")} FROM checkpoint_ctx WHERE session_id > ? ORDER BY session_id LIMIT ${CTX_REWRITE_PAGE}`, `SELECT session_id, ${jsonColumnText("pg", "ctx")} FROM checkpoint_ctx WHERE session_id > $1 ORDER BY session_id LIMIT ${CTX_REWRITE_PAGE}`), [after]);
746
744
  for (const row of rows) {
747
745
  const sessionId = String(row.session_id);
748
746
  after = sessionId;
@@ -791,7 +789,7 @@ export class SqlCheckpointStore {
791
789
  }
792
790
  async deleteBySession(sessionId, owner) {
793
791
  const ownerGuard = this.q("EXISTS (SELECT 1 FROM session_meta sm WHERE sm.session_id = ? AND sm.owner <=> ?)", "EXISTS (SELECT 1 FROM session_meta sm WHERE sm.session_id = $1 AND sm.owner IS NOT DISTINCT FROM $2)");
794
- const params = this.db.dialect === "tidb" ? [sessionId, sessionId, owner] : [sessionId, owner];
792
+ const params = this.db.dialect === "mysql" ? [sessionId, sessionId, owner] : [sessionId, owner];
795
793
  const res = await this.db.query(this.q(`DELETE FROM checkpoint WHERE session_id = ? AND ${ownerGuard}`, `DELETE FROM checkpoint WHERE session_id = $1 AND ${ownerGuard}`), params);
796
794
  await this.db.query(this.q(`DELETE FROM checkpoint_ctx WHERE session_id = ? AND ${ownerGuard}`, `DELETE FROM checkpoint_ctx WHERE session_id = $1 AND ${ownerGuard}`), params);
797
795
  return res.affected;
@@ -1,14 +1,16 @@
1
1
  /**
2
- * device lane 四表持久层(design/device-executor-lane-v2 §6)—— `DeviceStore` 双方言实现
2
+ * device lane 三表持久层(design/device-executor-lane-v2 §6)—— `DeviceStore` 双方言实现
3
3
  * (`TiDBDeviceStore` / `PgDeviceStore`,SINGLE-FILE DUAL-DIALECT,[ref] A12 形:一个
4
4
  * `SqlDeviceStore` 接 `SqlDriver`,两个薄 ctor 子类落方言绑定)。语义真源 = `../device-store.js`
5
5
  * (类型 + 闭集 + InMemory 参照实现),准入链 = `../device-enrollment.js`。
6
6
  *
7
- * 四张表:
7
+ * 三张表:
8
8
  * · `devices` —— 设备注册簿 + presence 投影(单设备单活连 ⇒ 1:1,省一次 join,§6 注①)
9
9
  * · `device_enroll_tokens` —— 一次性 enrollment 凭据(明文一现,落库只有 sha256)
10
- * · `device_sessions` —— **根会话**↔设备绑定(resume 路由真源,§4.5)
11
10
  * · `device_audit` —— 安全轴事件审计(§4.8 标「审计行」的 8 个 event)
11
+ * **根会话 ↔ 设备绑定不在本店的表里**:它是根会话行(`session_meta`)上这一位住客的执行格 writer(device, D)(列与语句的唯一属主 =
12
+ * `execution-placement-sql.ts`;规则在 `../execution-placement.ts` 头注)。读只经会话店的执行席 `seat.read(root)`;本店只有两只写 ——
13
+ * 首绑 {@link SqlDeviceStore.placeDeviceExecution} 与换绑 {@link SqlDeviceStore.rebindDeviceExecution},各是一只事务,锁序全局一条:设备行 → 根 `session_meta` → `task_*`。
12
14
  * 刻意**没有** `device_instructions` 表:指令 pending 表是内存态(§6 注③——指令比 run 短命,落库只会
13
15
  * 造「表里 pending、run 已死」的僵尸行;at-most-once 靠 §5.3 投递边界,不靠指令落库)。
14
16
  *
@@ -26,7 +28,6 @@
26
28
  *
27
29
  * ── 方言差异(A12 doctrine:每处显式写在调用点)────────────────────────────────────────────────────
28
30
  * - `?`(位置序) vs `$n`(显式编号);动态 WHERE(`listAudit`)靠 `ph(dialect, n)` 生成。
29
- * - `INSERT IGNORE` vs `ON CONFLICT (pk) DO NOTHING`(`bindSession` 的首绑 CAS)。
30
31
  * - `AUTO_INCREMENT` vs `BIGSERIAL`;`JSON` vs `JSONB`(+ `$n::jsonb` 显式 cast)。
31
32
  * - `KEY …` 内联 vs 独立 `CREATE INDEX IF NOT EXISTS`。
32
33
  * - 回读名单:两方言**同走**同事务 `UPDATE` 后 `SELECT`(PG 本可用 RETURNING,为保单一读路径不用——
@@ -46,19 +47,18 @@
46
47
  import type { Pool as MySqlPool } from "mysql2/promise";
47
48
  import type { Pool as PgPool } from "pg";
48
49
  import { type SqlDriver } from "./sql-driver.js";
49
- import { type AcquireConnectionInput, type AcquireConnectionResult, type AppendAuditInput, type BindSessionInput, type BindSessionResult, type ConsumeEnrollTokenInput, type ConsumeEnrollTokenResult, type DeviceAdmissionView, type DeviceAuditRow, type DeviceEnrollTokenRow, type DeviceOwner, type DeviceRow, type DeviceSessionRow, type DeviceStore, type HeartbeatConnectionInput, type IssueEnrollTokenInput, type ListAuditFilter, type RebindSessionInput, type RebindSessionResult, type RevokeDeviceInput, type RevokeDeviceResult } from "../device-store.js";
50
+ import { type AcquireConnectionInput, type AcquireConnectionResult, type AppendAuditInput, type ConsumeEnrollTokenInput, type ConsumeEnrollTokenResult, type DeviceAdmissionView, type DeviceAuditRow, type DeviceEnrollTokenRow, type DeviceOwner, type DeviceRow, type DeviceStore, type HeartbeatConnectionInput, type IssueEnrollTokenInput, type ListAuditFilter, type PlaceDeviceExecutionInput, type PlaceDeviceExecutionResult, type RebindDeviceExecutionInput, type RebindDeviceExecutionResult, type RevokeDeviceInput, type RevokeDeviceResult } from "../device-store.js";
50
51
  import { type IndexSpec } from "./ensure-index.js";
51
52
  /**
52
53
  * ── 命名(**落库一律单数**,与设计稿的复数形不同)──────────────────────────────────────────────────
53
- * design v2 §6 的 DDL 写的是 `devices` / `device_sessions` / `device_enrollment_tokens`(复数),但本仓有一条
54
+ * design v2 §6 的 DDL 写的是 `devices` / `device_enrollment_tokens` / `device_audit`(复数),但本仓有一条
54
55
  * 常驻机器门:`test/schema-naming-invariants.test.ts` 判据① —— 建表的表名必须在 `TABLE_VOCABULARY` 闭集里
55
56
  * **且词表全是单数形**。[ref] 车1 的 `retention_hold` / `retention_tombstone` 是同一处先例(那份设计稿同样
56
- * 写的复数,落库单数)。所以四张表落库名为 `device` / `device_session` / `device_enroll_token` /
57
+ * 写的复数,落库单数)。所以三张表落库名为 `device` / `device_enroll_token` /
57
58
  * `device_audit`,**表结构与设计稿逐列一致**,只有名字随仓规。新表 = 先进词表再建。
58
59
  */
59
60
  export declare const DEVICES_TABLE = "device";
60
61
  export declare const DEVICE_ENROLL_TOKENS_TABLE = "device_enroll_token";
61
- export declare const DEVICE_SESSIONS_TABLE = "device_session";
62
62
  export declare const DEVICE_AUDIT_TABLE = "device_audit";
63
63
  export declare const TIDB_DEVICE_STATEMENTS: readonly string[];
64
64
  /** {@link TIDB_DEVICE_STATEMENTS} 的遍历壳(集成测试用;生产装配由中央 ensureSchema 展开同一份数组)。 */
@@ -134,55 +134,29 @@ export declare class SqlDeviceStore implements DeviceStore {
134
134
  heartbeatConnection(input: HeartbeatConnectionInput): Promise<boolean>;
135
135
  releaseConnection(deviceId: string, generation: number): Promise<boolean>;
136
136
  /**
137
- * §4.3.2 首绑写协议 —— **单事务**:设备校验(active ∧ owner)与首绑 CAS 插入在一个事务里。
138
- *
139
- * 🔴 `FOR UPDATE`(设备行):设备行必须是**当前读**。普通读下,一次与吊销并发的绑定会读到「还 active」
140
- * 的旧版本,于是把 session 绑到一台**刚被吊销**的设备上(double-admit 形)。锁读在两引擎都是当前读 ——
141
- * 前提写在 {@link SqlTxConn.begin} 的 @contract txn.read-semantics。这把锁在 `devices` 表上,与下面
142
- * 被插入的 `device_session` 行不是同一行。
143
- *
144
- * 🔴 形 B · cas-insert([ref] / [ref])—— 本站点是全仓 `INSERT IGNORE` 的**正当用法**:
145
- * `INSERT IGNORE` / `ON CONFLICT DO NOTHING` 的 affected(1/0)是引擎对「谁赢」的**权威**回答,而
146
- * `ON DUPLICATE KEY UPDATE pk = pk` 的 affected 新行/既有行都是 1、分不出赢家 ⇒ 这里换不得动词。
147
- * 代价必须付清:**同一事务此后不得对被插入的那一行做加锁读**。InnoDB 下撞重复键的 IGNORE 在既有行
148
- * 取 **S 锁**,随后的 `SELECT … FOR UPDATE` 要升 X;两个并发首绑各持一把 S 互等 ⇒ ER_LOCK_DEADLOCK
149
- * (TiDB 悲观事务无共享锁,这一形在它上面恒绿 —— 双库门看不见)。于是两条路径分家:
150
- * · **赢家**(affected === 1):行是自己刚插的,回执用**入参 + 插入时刻**直接构造,零回读。插入时刻
151
- * 仍由 **DB 时钟**给出(插入前一句 `SELECT NOW(3)` / `SELECT now()`,再把它当参数写进 INSERT)——
152
- * 时间权威留在 DB,回执里的 `boundAtMs` 与落库值**逐字节同一个**,不是应用侧的近似。
153
- * · **输家**(affected === 0):**结束当前事务**,在**新事务**里 `SELECT … FOR UPDATE` 观察。那一刻
154
- * 赢家已提交,读到的是一把干净的 X 锁(无升级);判 同 deviceId = 幂等成功,异 deviceId =
155
- * `conflict`(无未定义态)。设备状态门在输家路径上已经过完,不需要重来。
137
+ * 设备行的**当前读**(`FOR UPDATE`):首绑 / 换绑两只事务的第一句(全局锁序的第一把:设备行 → 根 `session_meta` → `task_*`)。
138
+ * 普通读下,一次与吊销并发的绑定会读到「还 active」的旧版本,于是把会话绑到一台**刚被吊销**的设备上(double-admit 形);锁读在两引擎都是当前读 ——
139
+ * 前提写在 {@link SqlTxConn.begin} 的 @contract txn.read-semantics。行不在 / 不是该属主的 ⇒ `undefined`(反枚举同形)。
156
140
  */
157
- bindSession(input: BindSessionInput): Promise<BindSessionResult>;
141
+ private lockDeviceStatus;
158
142
  /**
159
- * 首绑 CAS 的**输家观察**(形 B 的后半,[ref])—— 独立事务,与那次 `INSERT IGNORE` 无锁关系。
143
+ * §4.3.2 首绑(§3.3 转换表 place / bind 两行)—— **一只事务**:锁设备行(active ∧ 属主)→ 锁定读根会话行的执行格 → 判官
144
+ * {@link settledDeviceBindOutcome}(与内存参照实现同一只)→ absent 用 place 语句、writer(device, ∅) 用 bind 语句,写只按住客谓词。
160
145
  *
161
- * 🔴 `FOR UPDATE`(**真库红修**,2026-08-28 双库跑当场抓到,mock 抓不到):TiDB 的悲观事务里**普通
162
- * SELECT 仍是快照读**(读的是本事务 start_ts 的视图),只有加锁读才是当前读。放在新事务里读同样要
163
- * 这把锁 —— 新事务的 start_ts 虽在赢家提交之后,但「读的是不是当前」这条判据不该靠时序巧合来满足。
164
- * 与旧形的**唯一**差别:此刻我们手上没有那一行的 S 锁,于是这把 X 是**直接取**的,不是升级 ⇒ 两个
165
- * 并发输家不会互等([ref] 的死锁形正是那次升级)。
166
- *
167
- * 行不在 = 赢家的绑定在这一瞬被 `unbindSession` 删了(真实可能,不是不变量破)。这仍然是**响亮**的:
168
- * 首绑既没成、也说不出冲突对象,折成任何一种业务回执都是撒谎([ref]:未知失败不伪装成正常拒绝)。
146
+ * 不读认领:这是认领持有者自己的初始放置写(读写序律唯一的例外,`execution-placement-sql.ts` 头注)。锁序:设备行 → 根 `session_meta`。
147
+ * 持着根行的锁判过「可写」之后那句 UPDATE 不可能 0 行 —— 0 行即驱动 / 引擎异常,响亮抛,不折成业务分支([ref])。
148
+ * 并发两只首绑在根行锁上串行:输家的锁定读看到赢家的格 ⇒ 同设备 `same`(幂等成功)/ 异设备 `device_conflict`(单赢家,无未定义态);
149
+ * 修前的 `INSERT IGNORE` 撞键 S 锁升级死锁形([ref])在这里不存在 —— 两边取的都是同一行的 X 锁,没有升级。
169
150
  */
170
- private observeLostBind;
151
+ placeDeviceExecution(input: PlaceDeviceExecutionInput): Promise<PlaceDeviceExecutionResult>;
171
152
  /**
172
- * O4 显式换绑(2026-08-30 v2 稿 §2)—— **单事务**:目标设备门(当前读)+ 幂等短路 + 前态 CAS +
173
- * `session_rebound` 审计行一起提交。三形语义 oracle = `InMemoryDeviceStore.rebindSession`。
174
- *
175
- * 🔴 两次 `FOR UPDATE`(bindSession 的同一条真库红的射程;当前读的前提见
176
- * {@link SqlTxConn.begin} 的 @contract txn.read-semantics):
177
- * · 设备行必须当前读 —— 快照读会把「与吊销并发的换绑」绑到一台刚被吊销的设备上(double-admit 形);
178
- * · 绑定行必须当前读且**持锁到提交** —— 两个并发换绑(同 expectedRev)在锁上串行:赢家 rev+1 提交,
179
- * 输家的锁读看到新 rev ⇒ 走 `rev_conflict` 携当前行(单赢家,无未定义态)。锁读判等之后,CAS
180
- * UPDATE(`rev = ?` 谓词 + `rev+1` 恒变列)在持锁下不可能 0 行 —— 0 行即驱动/引擎异常,响亮抛,
181
- * 不折成业务分支([ref]:未知失败不伪装成正常拒绝)。
153
+ * O4 显式换绑(2026-08-30 v2 稿 §2;§3.3 转换表 rebind 行)—— **一只事务**:锁目标设备行(在 / 属 owner / 未吊销)→ 读写序律那一读
154
+ * {@link lockRootAndReadClaim}(用本事务自己的连接先 `FOR UPDATE` 锁根 `session_meta` 行,再新语句锁定读 `task_active`;有认领 ⇒ `active_run`,
155
+ * 什么都不写 —— 这一读是「换绑不与在飞 run 交错」的**保证**,路由的池级 `getActiveTaskId` 只作早拒)→ 锁定读执行格 → 判官
156
+ * {@link settledDeviceRebindOutcome}(住客 ∧ 两属主列 ∧ rev;幂等短路先于写)→ CAS(`execution_rev + 1` 恒变列)→ `session_rebound` 审计行,同一事务提交。
157
+ * 审计写不进去 = 整次换绑失败(fail-closed,[ref])。持锁判过之后 CAS 不可能 0 行 —— 0 行即引擎异常,响亮抛。三形语义 oracle = `InMemoryDeviceStore.rebindDeviceExecution`。
182
158
  */
183
- rebindSession(input: RebindSessionInput): Promise<RebindSessionResult>;
184
- getSessionBinding(rootSessionId: string): Promise<DeviceSessionRow | null>;
185
- deleteByRootSession(rootSessionId: string): Promise<number>;
159
+ rebindDeviceExecution(input: RebindDeviceExecutionInput): Promise<RebindDeviceExecutionResult>;
186
160
  appendAudit(input: AppendAuditInput): Promise<void>;
187
161
  /** 审计写的**唯一**语句(池级与事务内共用;`SqlExec` 是两者的公共面)。 */
188
162
  private insertAuditOn;