@sema-agent/server 7.150.0 → 7.151.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 (341) hide show
  1. package/MIGRATION.md +185 -0
  2. package/USAGE.md +53 -25
  3. package/deploy/sema-up/README.md +7 -1
  4. package/deploy/sema-up/chart/Chart.yaml +1 -1
  5. package/deploy/sema-up/chart/templates/NOTES.txt +5 -0
  6. package/deploy/sema-up/chart/templates/_helpers.tpl +15 -16
  7. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -3
  8. package/deploy/sema-up/chart/templates/server.yaml +5 -3
  9. package/deploy/sema-up/chart/values-full-single.yaml +3 -1
  10. package/deploy/sema-up/chart/values.yaml +8 -1
  11. package/deploy/sema-up/cluster-up.sh +1 -0
  12. package/deploy/sema-up/kube-up.sh +1 -1
  13. package/deploy/sema-up/lib/flagshape.sh +1 -1
  14. package/deploy/sema-up/lib/helm-values-cluster.py +2 -0
  15. package/deploy/sema-up/lib/helm-values-kube.py +2 -0
  16. package/dist/adoption/import.js +3 -3
  17. package/dist/approval-ask-machine.d.ts +1 -1
  18. package/dist/approval-card.d.ts +2 -2
  19. package/dist/approval-gate-currency.d.ts +1 -1
  20. package/dist/auth-bridge.d.ts +20 -0
  21. package/dist/auth-bridge.js +9 -0
  22. package/dist/background-session-reap.d.ts +1 -1
  23. package/dist/bake-runner/main.d.ts +4 -2
  24. package/dist/bind-host.d.ts +2 -3
  25. package/dist/bind-host.js +2 -4
  26. package/dist/boot/budget-tracing.js +1 -1
  27. package/dist/boot/engine-lease.d.ts +5 -0
  28. package/dist/boot/engine-lease.js +1 -0
  29. package/dist/boot/execution-env.d.ts +8 -14
  30. package/dist/boot/execution-env.js +54 -17
  31. package/dist/boot/execution-lane.js +32 -5
  32. package/dist/boot/late-binds.d.ts +8 -4
  33. package/dist/boot/late-binds.js +7 -1
  34. package/dist/boot/memory-consolidation.d.ts +3 -2
  35. package/dist/boot/memory-consolidation.js +6 -6
  36. package/dist/boot/reapers.d.ts +5 -0
  37. package/dist/boot/reapers.js +12 -1
  38. package/dist/boot/resolve-spec.d.ts +3 -3
  39. package/dist/boot/resolve-spec.js +31 -22
  40. package/dist/boot/runner-deps.d.ts +9 -2
  41. package/dist/boot/runner-deps.js +3 -3
  42. package/dist/boot/session-age-out.d.ts +36 -0
  43. package/dist/boot/session-age-out.js +101 -0
  44. package/dist/boot/session-faces.d.ts +5 -14
  45. package/dist/boot/session-faces.js +9 -5
  46. package/dist/boot/stage-01-config.js +2 -0
  47. package/dist/boot/stage-05-execution-env.d.ts +5 -3
  48. package/dist/boot/stage-05-execution-env.js +2 -2
  49. package/dist/boot/stage-06-runners.d.ts +7 -4
  50. package/dist/boot/stage-06-runners.js +15 -6
  51. package/dist/boot/stage-07-capability-layer.d.ts +7 -4
  52. package/dist/boot/stage-07-capability-layer.js +2 -1
  53. package/dist/boot/stage-08-reapers.d.ts +7 -4
  54. package/dist/boot/stage-08-reapers.js +4 -2
  55. package/dist/boot/stage-09-leader.d.ts +11 -5
  56. package/dist/boot/stage-09-leader.js +12 -3
  57. package/dist/boot/stage-10-http-server.d.ts +11 -5
  58. package/dist/boot/stage-10-http-server.js +3 -6
  59. package/dist/boot/web-admin.js +3 -1
  60. package/dist/boot/workflow-orchestration.d.ts +7 -2
  61. package/dist/boot/workflow-orchestration.js +24 -7
  62. package/dist/bounded-rotation.d.ts +29 -0
  63. package/dist/bounded-rotation.js +24 -0
  64. package/dist/capability-facts.d.ts +22 -13
  65. package/dist/capability-facts.js +28 -18
  66. package/dist/center-credential.d.ts +25 -6
  67. package/dist/center-credential.js +25 -5
  68. package/dist/config-catalog.d.ts +26 -7
  69. package/dist/config-catalog.js +14 -11
  70. package/dist/config-invariants.d.ts +2 -1
  71. package/dist/config-invariants.js +4 -4
  72. package/dist/config-types.d.ts +10 -4
  73. package/dist/config.d.ts +51 -2
  74. package/dist/config.js +35 -24
  75. package/dist/deployment-shape.d.ts +9 -1
  76. package/dist/deployment-shape.js +2 -1
  77. package/dist/device-protocol.d.ts +5 -4
  78. package/dist/device-protocol.js +1 -1
  79. package/dist/device-ws-hub.js +3 -2
  80. package/dist/device-ws-protocol.d.ts +63 -10
  81. package/dist/device-ws-protocol.js +56 -25
  82. package/dist/engine-identity.d.ts +19 -7
  83. package/dist/engine-identity.js +10 -2
  84. package/dist/entry-mode-clamp-notice.d.ts +2 -1
  85. package/dist/execution-env-refusal.d.ts +23 -0
  86. package/dist/execution-env-refusal.js +5 -0
  87. package/dist/execution-lane-caps.d.ts +3 -1
  88. package/dist/field-conflict.d.ts +1 -1
  89. package/dist/file-history-capture.d.ts +11 -4
  90. package/dist/file-history-capture.js +9 -6
  91. package/dist/file-history-disclosure.d.ts +7 -5
  92. package/dist/file-history-disclosure.js +7 -5
  93. package/dist/fleet/drain-entry.d.ts +4 -0
  94. package/dist/hooks/hook-runner.js +2 -2
  95. package/dist/host-lsp-manager.d.ts +2 -2
  96. package/dist/http/admission.js +17 -2
  97. package/dist/http/approval-sweeps.js +5 -5
  98. package/dist/http/checkpoint-ctx.d.ts +2 -0
  99. package/dist/http/checkpoint-ctx.js +1 -0
  100. package/dist/http/dispatch.d.ts +2 -1
  101. package/dist/http/dispatch.js +34 -27
  102. package/dist/http/durable-run-leg.js +16 -16
  103. package/dist/http/health-self-report.d.ts +9 -1
  104. package/dist/http/health-self-report.js +2 -0
  105. package/dist/http/owner-access.d.ts +31 -11
  106. package/dist/http/owner-access.js +37 -6
  107. package/dist/http/resume-ctx-read.js +25 -3
  108. package/dist/http/resume-legs.d.ts +2 -1
  109. package/dist/http/resume-legs.js +259 -79
  110. package/dist/http/route-ctx.d.ts +18 -8
  111. package/dist/http/route-table.d.ts +1 -1
  112. package/dist/http/routes/admin-memory-optout.d.ts +1 -1
  113. package/dist/http/routes/agents-roster.js +2 -3
  114. package/dist/http/routes/approvals-assistant.js +4 -3
  115. package/dist/http/routes/background.d.ts +3 -3
  116. package/dist/http/routes/capabilities.js +8 -5
  117. package/dist/http/routes/fleet.js +10 -8
  118. package/dist/http/routes/projects.d.ts +3 -2
  119. package/dist/http/routes/projects.js +4 -5
  120. package/dist/http/routes/runs.js +102 -36
  121. package/dist/http/routes/session-sync.d.ts +4 -3
  122. package/dist/http/routes/session-sync.js +172 -77
  123. package/dist/http/routes/sessions-list.js +13 -7
  124. package/dist/http/routes/sessions.js +10 -6
  125. package/dist/http/routes/tasks.js +30 -16
  126. package/dist/http/routes/trace-usage.js +2 -2
  127. package/dist/http/server.d.ts +21 -27
  128. package/dist/http/server.js +2 -2
  129. package/dist/http/submit-ctx.js +5 -4
  130. package/dist/http/wire-types.d.ts +14 -4
  131. package/dist/identity-source.d.ts +20 -3
  132. package/dist/identity-source.js +14 -0
  133. package/dist/index.d.ts +4 -0
  134. package/dist/lane-home-probe.d.ts +3 -7
  135. package/dist/lane-home-probe.js +0 -1
  136. package/dist/leader/leader.d.ts +2 -1
  137. package/dist/leg-facts.d.ts +13 -5
  138. package/dist/leg-facts.js +2 -1
  139. package/dist/memory-scope.d.ts +5 -4
  140. package/dist/memory-scope.js +4 -4
  141. package/dist/memory-sync-client.d.ts +30 -2
  142. package/dist/memory-sync-client.js +16 -9
  143. package/dist/observability/err-text.d.ts +7 -0
  144. package/dist/observability/err-text.js +5 -0
  145. package/dist/observability/fail-open.d.ts +7 -3
  146. package/dist/observability/fail-open.js +7 -3
  147. package/dist/observability/metrics.js +1 -0
  148. package/dist/orchestration/workflow-notify-journal.d.ts +18 -1
  149. package/dist/orchestration/workflow-notify-journal.js +12 -1
  150. package/dist/orchestration/workflow-park-decision.d.ts +70 -18
  151. package/dist/orchestration/workflow-park-decision.js +49 -10
  152. package/dist/orchestration/workflow-park-holder.d.ts +65 -0
  153. package/dist/orchestration/workflow-park-holder.js +98 -0
  154. package/dist/orchestration/workflow-record-fence.d.ts +36 -0
  155. package/dist/orchestration/workflow-record-fence.js +42 -0
  156. package/dist/owner-faces.d.ts +1 -1
  157. package/dist/parked-decide.d.ts +104 -53
  158. package/dist/parked-decide.js +153 -117
  159. package/dist/peer-directory.d.ts +2 -2
  160. package/dist/peer-inbound-face.d.ts +2 -6
  161. package/dist/peer-inbound-face.js +0 -3
  162. package/dist/permission-mode-admission.d.ts +41 -5
  163. package/dist/permission-mode-admission.js +29 -3
  164. package/dist/plugins/adoption-log-sql.d.ts +3 -1
  165. package/dist/plugins/adoption-log-sql.js +7 -1
  166. package/dist/plugins/approval-ask-store-file.d.ts +10 -2
  167. package/dist/plugins/approval-ask-store-file.js +8 -0
  168. package/dist/plugins/approval-ask-store-memory.d.ts +6 -1
  169. package/dist/plugins/approval-ask-store-memory.js +28 -9
  170. package/dist/plugins/approval-ask-store-sql.d.ts +43 -2
  171. package/dist/plugins/approval-ask-store-sql.js +34 -5
  172. package/dist/plugins/approval-exemption-store.js +3 -0
  173. package/dist/plugins/approval-nonce-store.js +2 -0
  174. package/dist/plugins/breaker-state-sql.js +2 -0
  175. package/dist/plugins/caching-session-store.d.ts +9 -15
  176. package/dist/plugins/caching-session-store.js +6 -0
  177. package/dist/plugins/device-store-sql.js +6 -0
  178. package/dist/plugins/error-origin.d.ts +18 -0
  179. package/dist/plugins/error-origin.js +10 -0
  180. package/dist/plugins/execution-placement-sql.js +6 -6
  181. package/dist/plugins/image-bake-store-sql.js +6 -0
  182. package/dist/plugins/image-index-sql.js +4 -0
  183. package/dist/plugins/landed-attendants.d.ts +212 -0
  184. package/dist/plugins/landed-attendants.js +161 -0
  185. package/dist/plugins/leader-run-store-sql.js +4 -0
  186. package/dist/plugins/local-session-live-reference.d.ts +40 -0
  187. package/dist/plugins/local-session-live-reference.js +39 -0
  188. package/dist/plugins/local-session-store.d.ts +71 -33
  189. package/dist/plugins/local-session-store.js +129 -27
  190. package/dist/plugins/memory-engine-pg.js +4 -0
  191. package/dist/plugins/memory-engine-sql-apply.js +5 -2
  192. package/dist/plugins/memory-engine-tidb.js +4 -0
  193. package/dist/plugins/memory-optout-grant-store-sql.js +4 -0
  194. package/dist/plugins/memory-sync-store-pg.js +5 -0
  195. package/dist/plugins/memory-sync-store-tidb.js +5 -0
  196. package/dist/plugins/one-time-migrations.d.ts +57 -63
  197. package/dist/plugins/one-time-migrations.js +162 -90
  198. package/dist/plugins/org-delivery-quota-store.js +4 -1
  199. package/dist/plugins/outcome-ledger-sql.js +2 -0
  200. package/dist/plugins/permission-rule-store-sql.js +11 -4
  201. package/dist/plugins/pg-cost-quota.js +4 -0
  202. package/dist/plugins/pg-pool.js +24 -1
  203. package/dist/plugins/pg-rate-limiter.js +2 -0
  204. package/dist/plugins/posix-shell-fs.d.ts +6 -1
  205. package/dist/plugins/posix-shell-fs.js +4 -1
  206. package/dist/plugins/remote-env-adb.d.ts +19 -15
  207. package/dist/plugins/remote-env-adb.js +41 -15
  208. package/dist/plugins/remote-env-connect-seat.d.ts +105 -0
  209. package/dist/plugins/remote-env-connect-seat.js +72 -0
  210. package/dist/plugins/remote-env-device.d.ts +22 -5
  211. package/dist/plugins/remote-env-device.js +106 -8
  212. package/dist/plugins/remote-env-e2b.d.ts +15 -6
  213. package/dist/plugins/remote-env-e2b.js +42 -32
  214. package/dist/plugins/remote-env-file-error.js +6 -2
  215. package/dist/plugins/remote-env-host.d.ts +17 -0
  216. package/dist/plugins/remote-env-host.js +30 -38
  217. package/dist/plugins/remote-env-k8s.d.ts +41 -39
  218. package/dist/plugins/remote-env-k8s.js +53 -30
  219. package/dist/plugins/remote-env-local-docker.d.ts +13 -8
  220. package/dist/plugins/remote-env-local-docker.js +34 -41
  221. package/dist/plugins/remote-env-ssh.d.ts +18 -12
  222. package/dist/plugins/remote-env-ssh.js +60 -59
  223. package/dist/plugins/remote-shell.d.ts +98 -1
  224. package/dist/plugins/remote-shell.js +218 -0
  225. package/dist/plugins/resume-anchor-store-sql.d.ts +11 -2
  226. package/dist/plugins/resume-anchor-store-sql.js +12 -5
  227. package/dist/plugins/retention-lane-store-sql.js +4 -0
  228. package/dist/plugins/retention-sql.d.ts +20 -2
  229. package/dist/plugins/retention-sql.js +11 -0
  230. package/dist/plugins/run-store-sql.d.ts +21 -2
  231. package/dist/plugins/run-store-sql.js +31 -7
  232. package/dist/plugins/scenario-prefix-switch-m42.d.ts +1 -1
  233. package/dist/plugins/scenario-prefix-switch-m42.js +2 -2
  234. package/dist/plugins/schema-consistency.d.ts +4 -2
  235. package/dist/plugins/schema-consistency.js +19 -2
  236. package/dist/plugins/send-file-ledger.js +3 -0
  237. package/dist/plugins/serving-requirement.d.ts +26 -7
  238. package/dist/plugins/serving-requirement.js +11 -4
  239. package/dist/plugins/session-birth.d.ts +6 -2
  240. package/dist/plugins/session-capture-record-store-sql.js +2 -0
  241. package/dist/plugins/session-ext-table.d.ts +14 -1
  242. package/dist/plugins/session-ext-table.js +5 -1
  243. package/dist/plugins/session-row-sql.d.ts +49 -2
  244. package/dist/plugins/session-row-sql.js +56 -13
  245. package/dist/plugins/session-store-sql.d.ts +34 -18
  246. package/dist/plugins/session-store-sql.js +98 -17
  247. package/dist/plugins/shared-memory-store-sql.d.ts +1 -0
  248. package/dist/plugins/shared-memory-store-sql.js +6 -2
  249. package/dist/plugins/sql-driver.d.ts +2 -1
  250. package/dist/plugins/sql-driver.js +18 -3
  251. package/dist/plugins/sql-errors.d.ts +11 -6
  252. package/dist/plugins/sql-errors.js +5 -6
  253. package/dist/plugins/stop-then-start-sentence.d.ts +10 -4
  254. package/dist/plugins/stop-then-start-sentence.js +14 -10
  255. package/dist/plugins/stop-then-start.d.ts +109 -0
  256. package/dist/plugins/stop-then-start.js +104 -0
  257. package/dist/plugins/store-backend.d.ts +40 -13
  258. package/dist/plugins/store-backend.js +169 -27
  259. package/dist/plugins/store-boundary.d.ts +57 -0
  260. package/dist/plugins/store-boundary.js +139 -0
  261. package/dist/plugins/store-contracts.d.ts +17 -0
  262. package/dist/plugins/store-fault-sentence-m53.d.ts +7 -0
  263. package/dist/plugins/store-fault-sentence-m53.js +25 -0
  264. package/dist/plugins/store-fault.d.ts +26 -7
  265. package/dist/plugins/store-fault.js +18 -2
  266. package/dist/plugins/task-attachment-store.js +9 -0
  267. package/dist/plugins/task-list-store-sql.d.ts +2 -2
  268. package/dist/plugins/task-list-store-sql.js +10 -8
  269. package/dist/plugins/tidb-cost-quota.js +4 -0
  270. package/dist/plugins/tidb-pool.js +2 -1
  271. package/dist/plugins/tidb-rate-limiter.js +4 -0
  272. package/dist/plugins/user-input-ledger-sql.js +4 -0
  273. package/dist/plugins/workflow-run-store-sql.d.ts +28 -10
  274. package/dist/plugins/workflow-run-store-sql.js +64 -4
  275. package/dist/plugins/workspace-fill.d.ts +4 -7
  276. package/dist/plugins/workspace-fill.js +2 -1
  277. package/dist/plugins/write-behind-counter.js +5 -0
  278. package/dist/project-context.d.ts +0 -2
  279. package/dist/project-context.js +4 -8
  280. package/dist/project-registry.d.ts +46 -0
  281. package/dist/project-registry.js +44 -0
  282. package/dist/request-key-closure.js +1 -1
  283. package/dist/request-value-closure.d.ts +4 -0
  284. package/dist/request-value-closure.js +10 -0
  285. package/dist/run-acceptance.d.ts +3 -2
  286. package/dist/run-acceptance.js +4 -6
  287. package/dist/run-local.js +1 -0
  288. package/dist/runs.d.ts +37 -8
  289. package/dist/runs.js +53 -12
  290. package/dist/runtime-governance.d.ts +5 -4
  291. package/dist/security.d.ts +30 -32
  292. package/dist/security.js +41 -7
  293. package/dist/server-secret-env.d.ts +12 -10
  294. package/dist/server-secret-env.js +24 -15
  295. package/dist/session-bindings.d.ts +82 -0
  296. package/dist/session-bindings.js +18 -0
  297. package/dist/session-children-retire.d.ts +2 -2
  298. package/dist/session-children-retire.js +2 -6
  299. package/dist/session-deletion.d.ts +16 -1
  300. package/dist/session-error-wire.d.ts +3 -3
  301. package/dist/session-error-wire.js +3 -3
  302. package/dist/session-incarnation.d.ts +10 -0
  303. package/dist/session-incarnation.js +10 -1
  304. package/dist/session-kind.d.ts +57 -0
  305. package/dist/session-kind.js +22 -0
  306. package/dist/session-row.d.ts +21 -1
  307. package/dist/session-row.js +7 -1
  308. package/dist/session-sync-kernel.d.ts +24 -14
  309. package/dist/session-sync-kernel.js +3 -6
  310. package/dist/session-sync.d.ts +4 -98
  311. package/dist/session-sync.js +1 -71
  312. package/dist/session-titler.d.ts +10 -3
  313. package/dist/session-titler.js +19 -6
  314. package/dist/ssh-host-key.d.ts +5 -0
  315. package/dist/ssh-host-key.js +3 -0
  316. package/dist/stores-sql/file-history-store-sql.d.ts +52 -2
  317. package/dist/stores-sql/file-history-store-sql.js +108 -70
  318. package/dist/stores-sql/pg-session-storage.d.ts +34 -7
  319. package/dist/stores-sql/pg-session-storage.js +37 -13
  320. package/dist/stores-sql/retention-store-sql.d.ts +1 -9
  321. package/dist/stores-sql/retention-store-sql.js +2 -10
  322. package/dist/stores-sql/session-policy-store-sql.d.ts +30 -7
  323. package/dist/stores-sql/session-policy-store-sql.js +61 -44
  324. package/dist/stores-sql/session-write-hooks.d.ts +11 -0
  325. package/dist/stores-sql/tidb-session-store.d.ts +58 -7
  326. package/dist/stores-sql/tidb-session-store.js +37 -13
  327. package/dist/submission-exit.d.ts +3 -2
  328. package/dist/submission-exit.js +9 -1
  329. package/dist/task-cwd.d.ts +2 -2
  330. package/dist/task-settings.d.ts +6 -5
  331. package/dist/terminal.d.ts +33 -2
  332. package/dist/terminal.js +139 -9
  333. package/dist/tool-approval.d.ts +140 -6
  334. package/dist/tool-approval.js +165 -72
  335. package/dist/trace/self-minted-wire.d.ts +290 -27
  336. package/dist/trace/self-minted-wire.js +101 -13
  337. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +78 -27
  338. package/package.json +2 -2
  339. package/wire-changes/server@7.151.0.json +762 -0
  340. package/dist/http/session-inherit-env.d.ts +0 -36
  341. package/dist/http/session-inherit-env.js +0 -6
@@ -2,7 +2,10 @@ import { uuidv7 } from "@sema-agent/core";
2
2
  import { escapeLike } from "./sql-escape.js";
3
3
  import { jsonColumnText } from "./sql-json-column.js";
4
4
  import { pgSafeJsonStringify } from "./pg-safe-json.js";
5
- import { DELETING_TOKEN_COLUMN, SESSION_EXT_TABLE, ensureSessionExtRowStatement, readDeletingTokenOn } from "./session-ext-table.js";
5
+ import { DELETING_TOKEN_COLUMN, SESSION_EXT_TABLE, SESSION_KIND_COLUMN, ensureSessionExtRowStatement, readDeletingTokenOn } from "./session-ext-table.js";
6
+ import { sessionKindFromCell } from "../session-kind.js";
7
+ import { sessionLiveReferenceSql } from "./retention-sql.js";
8
+ import { lockHoldSentinel } from "./retention-lane-store-sql.js";
6
9
  import { decodeSessionWorkspaceCell, encodeSessionWorkspaceCell } from "./session-workspace-cell.js";
7
10
  import { createSqlExecutionPlacementSeat, markMirrorStatement, readCellOn, readClaimOn, writeCellOn } from "./execution-placement-sql.js";
8
11
  import { judgeSessionWrite, sessionWriteTouchesCell } from "../execution-placement.js";
@@ -10,9 +13,10 @@ import { BASELINE_IO_DEADLINE_MS, decodeInstructionSource, encodeInstructionSour
10
13
  import { forkLineageOfRow, headFromRow, incarnationFromCell } from "../session-incarnation.js";
11
14
  import { CROSS_PROCESS_TAKEOVER_AFTER_MS, deletionTokenMintedAt } from "../session-deletion.js";
12
15
  import { sessionRowTagKey } from "../session-row.js";
13
- import { writeSessionRowCellsSql } from "./session-row-sql.js";
16
+ import { lockSessionRootOn, withLandedRow, writeSessionRowCellsSql } from "./session-row-sql.js";
14
17
  import { bumpClientWritesOn, createSqlWithdrawJudge } from "./session-withdraw-sql.js";
15
18
  import { WITHDRAWABLE_ENTRY_TYPES } from "./session-birth.js";
19
+ import { sealStoreClass, sealStoreObject } from "./store-boundary.js";
16
20
  const RUN_PREVIEW_PRESENT = "CHAR_LENGTH(objective_preview) > 0";
17
21
  function binder(dialect) {
18
22
  const values = [];
@@ -57,17 +61,30 @@ export function createSqlSessionWriteHooks(db) {
57
61
  return { owner: row.owner === null || row.owner === undefined ? null : String(row.owner), forkKey: row.fork_key === null || row.fork_key === undefined ? null : String(row.fork_key) };
58
62
  },
59
63
  },
64
+ birth: {
65
+ async record(txn, born) {
66
+ const ensure = ensureSessionExtRowStatement(dialect, born.rootSessionId);
67
+ await txn.query(ensure.text, [...ensure.values]);
68
+ const b = binder(dialect);
69
+ const res = await txn.query(`UPDATE ${SESSION_EXT_TABLE} SET ${SESSION_KIND_COLUMN.name} = ${b.p(born.kind)} WHERE session_id = ${b.p(born.rootSessionId)}`, b.values);
70
+ if (res.affected !== 1)
71
+ throw new Error(`registration of session ${born.rootSessionId}: the session kind write matched ${res.affected} row(s) of a row this transaction just created — engine/driver anomaly, refusing to guess`);
72
+ },
73
+ },
60
74
  };
61
75
  }
76
+ export function sessionKindSql(view, p) {
77
+ return view === undefined ? `se.${SESSION_KIND_COLUMN.name} IS NULL` : `se.${SESSION_KIND_COLUMN.name} = ${p(view)}`;
78
+ }
79
+ const IDLE_SESSION_READ = { deadlineMs: 2_000 };
62
80
  const BASELINE_STATEMENT = { deadlineMs: BASELINE_IO_DEADLINE_MS };
63
81
  async function withLockedSessionExt(db, sessionId, lock, write) {
64
82
  const conn = await db.connect();
65
83
  const query = (sql, params) => conn.query(sql, params, lock.statement);
66
84
  try {
67
85
  await conn.begin();
68
- const b = binder(db.dialect);
69
- const locked = await query(`SELECT session_id FROM session_meta WHERE session_id = ${b.p(sessionId)}${lock.where === undefined ? "" : ` AND ${lock.where(b.p)}`} FOR UPDATE`, b.values);
70
- if (locked.rows.length === 0) {
86
+ const root = await lockSessionRootOn(async (text, values) => (await query(text, [...values])).rows, db.dialect, sessionId);
87
+ if (root === undefined || (lock.incarnation !== undefined && root.incarnation !== lock.incarnation)) {
71
88
  await conn.rollback();
72
89
  return false;
73
90
  }
@@ -90,6 +107,10 @@ const listedLineageOf = (sessionId, forkedFrom) => {
90
107
  const lineage = forkLineageOfRow(sessionId, forkedFrom, undefined);
91
108
  return lineage.forkedFrom !== undefined ? { forkedFrom: lineage.forkedFrom } : {};
92
109
  };
110
+ const listedKindOf = (sessionId, cell) => {
111
+ const kind = sessionKindFromCell(sessionId, cell);
112
+ return kind !== undefined ? { sessionKind: kind } : {};
113
+ };
93
114
  function holderMayBeAlive(holder, token) {
94
115
  switch (holder) {
95
116
  case "running":
@@ -123,8 +144,15 @@ export class SqlSessionStore {
123
144
  register(sessionId, owner) {
124
145
  return this.seat.register(sessionId, owner);
125
146
  }
126
- registerWithReceipt(sessionId, owner) {
127
- return this.seat.registerWithReceipt(sessionId, owner);
147
+ registerWithReceipt(sessionId, owner, kind) {
148
+ return this.seat.registerWithReceipt(sessionId, owner, kind);
149
+ }
150
+ async sessionKindOf(sessionId) {
151
+ const b = binder(this.db.dialect);
152
+ const row = (await this.db.query(`SELECT sm.incarnation, se.${SESSION_KIND_COLUMN.name} AS session_kind FROM session_meta sm LEFT JOIN ${SESSION_EXT_TABLE} se ON se.session_id = sm.session_id WHERE sm.session_id = ${b.p(sessionId)}`, b.values)).rows[0];
153
+ if (row === undefined)
154
+ return undefined;
155
+ return { incarnation: incarnationFromCell(sessionId, row.incarnation, "absent"), kind: sessionKindFromCell(sessionId, row.session_kind) };
128
156
  }
129
157
  withdrawBirth(birth, ownTaskId) {
130
158
  const cond = {
@@ -190,7 +218,7 @@ export class SqlSessionStore {
190
218
  sweepStagingSessions() {
191
219
  return this.seat.sweepStagingSessions();
192
220
  }
193
- sessionDeletion = {
221
+ sessionDeletion = sealStoreObject({
194
222
  fence: (sessionId, owner, opts) => this.fenceDeletion(sessionId, owner, opts),
195
223
  holds: async (sessionId, token) => {
196
224
  const b = binder(this.db.dialect);
@@ -201,21 +229,25 @@ export class SqlSessionStore {
201
229
  const rows = (await this.db.query(`SELECT sm.session_id, sm.owner, se.${DELETING_TOKEN_COLUMN.name} AS token FROM session_meta sm JOIN ${SESSION_EXT_TABLE} se ON se.session_id = sm.session_id WHERE se.${DELETING_TOKEN_COLUMN.name} IS NOT NULL ORDER BY sm.session_id`, [])).rows;
202
230
  return rows.map((r) => ({ sessionId: String(r.session_id), owner: r.owner ?? null, token: String(r.token) }));
203
231
  },
204
- };
232
+ }, "sql-session-deletion", { fence: "exit", holds: "exit", pending: "exit" });
205
233
  async fenceDeletion(sessionId, owner, opts) {
206
234
  const dialect = this.db.dialect;
207
235
  const conn = await this.db.connect();
208
236
  try {
209
237
  await conn.begin();
210
238
  const rows = async (sql, values) => (await conn.query(sql, [...values])).rows;
211
- const root = (await rows(`SELECT owner, incarnation FROM session_meta WHERE session_id = ${dialect === "pg" ? "$1" : "?"} FOR UPDATE`, [sessionId]))[0];
239
+ if (opts.ageOut !== undefined && (await lockHoldSentinel(conn, dialect, owner ?? ""))) {
240
+ await conn.rollback();
241
+ return undefined;
242
+ }
243
+ const root = await lockSessionRootOn(rows, dialect, sessionId);
212
244
  if (root === undefined || (root.owner ?? null) !== owner) {
213
245
  await conn.rollback();
214
246
  return undefined;
215
247
  }
216
248
  const held = await readDeletingTokenOn(rows, dialect, sessionId);
217
249
  if (held === null) {
218
- if (opts.expect !== undefined) {
250
+ if (opts.expect !== undefined || (opts.ageOut !== undefined && !(await this.ageOutHoldsOn(rows, sessionId, root, opts.ageOut)))) {
219
251
  await conn.rollback();
220
252
  return undefined;
221
253
  }
@@ -251,6 +283,45 @@ export class SqlSessionStore {
251
283
  conn.release();
252
284
  }
253
285
  }
286
+ async ageOutHoldsOn(rows, sessionId, root, ageOut) {
287
+ if (incarnationFromCell(sessionId, root.incarnation, "absent") !== ageOut.incarnation)
288
+ return false;
289
+ const lastActivity = root.updated_at instanceof Date ? root.updated_at : new Date(String(root.updated_at));
290
+ if (!(lastActivity.getTime() < ageOut.idleBefore.getTime()))
291
+ return false;
292
+ const b = binder(this.db.dialect);
293
+ const cell = (await rows(`SELECT ${SESSION_KIND_COLUMN.name} AS session_kind FROM ${SESSION_EXT_TABLE} WHERE session_id = ${b.p(sessionId)} FOR UPDATE`, b.values))[0];
294
+ if (sessionKindFromCell(sessionId, cell?.session_kind) !== ageOut.kind)
295
+ return false;
296
+ const live = binder(this.db.dialect);
297
+ return (await rows(`SELECT 1 AS live FROM session_meta sm WHERE sm.session_id = ${live.p(sessionId)} AND ${sessionLiveReferenceSql(this.db.dialect)} FOR UPDATE`, live.values)).length === 0;
298
+ }
299
+ async listIdleOfKind(kind, idleBefore, page) {
300
+ const b = binder(this.db.dialect);
301
+ const where = [sessionKindSql(kind, b.p), `sm.updated_at < ${b.p(idleBefore)}`, `NOT ${sessionLiveReferenceSql(this.db.dialect)}`];
302
+ if (page.after !== undefined) {
303
+ const at = new Date(page.after.lastActivityAt);
304
+ where.push(`(sm.updated_at > ${b.p(at)} OR (sm.updated_at = ${b.p(at)} AND sm.session_id > ${b.p(page.after.sessionId)}))`);
305
+ }
306
+ const sql = `SELECT sm.session_id, sm.owner, sm.incarnation, sm.updated_at FROM session_meta sm JOIN ${SESSION_EXT_TABLE} se ON se.session_id = sm.session_id ` +
307
+ `WHERE ${where.join(" AND ")} ORDER BY sm.updated_at ASC, sm.session_id ASC LIMIT ${b.p(page.limit)}`;
308
+ const conn = await this.db.connect();
309
+ try {
310
+ const rows = (await conn.query(sql, b.values, IDLE_SESSION_READ)).rows;
311
+ return rows.map((r) => {
312
+ const sessionId = String(r.session_id);
313
+ return {
314
+ sessionId,
315
+ owner: r.owner ?? null,
316
+ incarnation: incarnationFromCell(sessionId, r.incarnation, "absent"),
317
+ lastActivityAt: r.updated_at instanceof Date ? r.updated_at.toISOString() : new Date(String(r.updated_at)).toISOString(),
318
+ };
319
+ });
320
+ }
321
+ finally {
322
+ conn.release();
323
+ }
324
+ }
254
325
  get size() {
255
326
  return this.seat.size;
256
327
  }
@@ -265,7 +336,7 @@ export class SqlSessionStore {
265
336
  return this.selectListRows({
266
337
  limit: opts.limit,
267
338
  where: (p) => {
268
- const where = ["sm.placement_kind IS NULL"];
339
+ const where = ["sm.placement_kind IS NULL", sessionKindSql(opts.sessionKind, p)];
269
340
  if (opts.owner !== undefined)
270
341
  where.push(opts.includeUnowned ? `(sm.owner = ${p(opts.owner)} OR sm.owner IS NULL)` : `sm.owner = ${p(opts.owner)}`);
271
342
  if (opts.q) {
@@ -311,7 +382,7 @@ export class SqlSessionStore {
311
382
  runAnd() +
312
383
  " ) pr WHERE rn = 1) pv ON pv.session_id = sm.session_id ";
313
384
  const where = [...q.where(b.p), `se.${DELETING_TOKEN_COLUMN.name} IS NULL`];
314
- const sql = "SELECT sm.session_id, sm.owner, sm.incarnation, sm.updated_at AS last_activity, sm.created_at AS first_activity, sm.forked_from, se.title, se.pinned, " +
385
+ const sql = `SELECT sm.session_id, sm.owner, sm.incarnation, sm.updated_at AS last_activity, sm.created_at AS first_activity, sm.forked_from, se.title, se.pinned, se.${SESSION_KIND_COLUMN.name} AS session_kind, ` +
315
386
  " COALESCE(r.run_count, 0) AS run_count, pv.objective_preview, latest.status, latest.task_id AS last_run_id, latest.last_task_run_id " +
316
387
  "FROM session_meta sm " +
317
388
  `LEFT JOIN ${SESSION_EXT_TABLE} se ON se.session_id = sm.session_id ` +
@@ -342,6 +413,7 @@ export class SqlSessionStore {
342
413
  pinned,
343
414
  ...sessionRowTagKey({ sessionId, owner, incarnation, cells: { title, pinned } }),
344
415
  ...listedLineageOf(sessionId, r.forked_from),
416
+ ...listedKindOf(sessionId, r.session_kind),
345
417
  };
346
418
  return { row, occupant: { owner, incarnation } };
347
419
  });
@@ -356,11 +428,12 @@ export class SqlSessionStore {
356
428
  return "none";
357
429
  return row.title === null || row.title === undefined ? "untitled" : "titled";
358
430
  }
359
- async setTitleIfNull(sessionId, title) {
360
- return withLockedSessionExt(this.db, sessionId, {}, async (query) => {
431
+ async setTitleIfNull(sessionId, title, landed) {
432
+ const out = await withLandedRow(this.db, sessionId, landed, async (tx) => {
361
433
  const b = binder(this.db.dialect);
362
- return (await query(`UPDATE ${SESSION_EXT_TABLE} SET title = ${b.p(title)} WHERE session_id = ${b.p(sessionId)} AND title IS NULL`, b.values)).affected > 0;
434
+ return (await tx.query(`UPDATE ${SESSION_EXT_TABLE} SET title = ${b.p(title)} WHERE session_id = ${b.p(sessionId)} AND title IS NULL`, b.values)).affected > 0;
363
435
  });
436
+ return out.landed && out.value;
364
437
  }
365
438
  async readInstructionSource(sessionId) {
366
439
  const b = binder(this.db.dialect);
@@ -389,10 +462,18 @@ export class SqlSessionStore {
389
462
  async recordSessionWorkspace(sessionId, incarnation, workspace) {
390
463
  const dialect = this.db.dialect;
391
464
  const cell = encodeSessionWorkspaceCell(workspace, dialect === "pg" ? pgSafeJsonStringify : JSON.stringify, sessionId);
392
- await withLockedSessionExt(this.db, sessionId, { where: (p) => `incarnation = ${p(incarnation)}` }, async (query) => {
465
+ await withLockedSessionExt(this.db, sessionId, { incarnation }, async (query) => {
393
466
  const b = binder(dialect);
394
467
  return (await query(`UPDATE ${SESSION_EXT_TABLE} SET workspace = ${b.p(cell)}${dialect === "pg" ? "::jsonb" : ""} WHERE session_id = ${b.p(sessionId)}`, b.values)).affected > 0;
395
468
  });
396
469
  }
397
470
  }
471
+ sealStoreClass(SqlSessionStore, "sql-session-store", {
472
+ acquire: "exit", register: "exit", registerWithReceipt: "exit", withdrawBirth: "exit", placementOf: "exit", listPlaced: "exit", headOf: "exit",
473
+ deletionHeadOf: "exit", touch: "exit", release: "exit", forget: "exit", fork: "exit", forkTo: "exit", exportEntries: "exit", listEntryIds: "exit",
474
+ exportEntriesStream: "exit", importEntries: "exit", replaceEntries: "exit", beginImportStaging: "exit", readStagedEntries: "exit",
475
+ sweepStagingSessions: "exit", dispose: "exit", listSessions: "exit", listSessionRows: "exit", sessionSummaryOf: "exit", writeSessionRowCells: "exit",
476
+ probeTitle: "exit", setTitleIfNull: "exit", readInstructionSource: "exit", recordInstructionSource: "exit", readSessionWorkspace: "exit",
477
+ recordSessionWorkspace: "exit", sessionKindOf: "exit", listIdleOfKind: "exit",
478
+ });
398
479
  //# sourceMappingURL=session-store-sql.js.map
@@ -118,6 +118,7 @@ export declare class SqlSharedMemoryStore implements SharedMemoryStoreProvider {
118
118
  * 并发读被写者串起来,而这是会话附着的热路径)。规则少了一条:全店从此只有写方法开事务。
119
119
  */
120
120
  private readBinding;
121
+ /** 交给引擎的读器(快照兑现之后才拨库)—— 出店口,造对象处封(读器上的库错同样只带原码 + 稳定句)。 */
121
122
  private readerFor;
122
123
  /** One keyset window. Always the PAGED shape — a large library must never be materialized per call. */
123
124
  private listDocuments;
@@ -2,6 +2,7 @@ import { writeLogText } from "../observability/logger.js";
2
2
  import { isOrgScopeKey, isSharedMemoryDocumentPath, isSharedMemoryStoreId, SHARED_MEMORY_READ_CAP_BYTES, SharedMemoryStoreError, } from "@sema-agent/core";
3
3
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
4
4
  import { isDupKeyError } from "./sql-errors.js";
5
+ import { sealStoreClass, sealStoreObject } from "./store-boundary.js";
5
6
  const isDupKey = isDupKeyError;
6
7
  export const SHARED_MEMORY_SCOPE_TABLE = "shared_memory_scope";
7
8
  export const SHARED_MEMORY_STORE_TABLE = "shared_memory_store";
@@ -159,10 +160,10 @@ export class SqlSharedMemoryStore {
159
160
  };
160
161
  }
161
162
  readerFor(storeId, scopeKey) {
162
- return {
163
+ return sealStoreObject({
163
164
  list: (prefix, opts) => this.listDocuments(storeId, scopeKey, prefix, opts),
164
165
  read: (path, opts) => this.readDocument(storeId, scopeKey, path, opts),
165
- };
166
+ }, "sql-shared-memory-reader", { list: "exit", read: "exit" });
166
167
  }
167
168
  async listDocuments(storeId, scopeKey, prefix, opts) {
168
169
  throwIfAborted(opts.signal);
@@ -363,6 +364,9 @@ export class SqlSharedMemoryStore {
363
364
  }
364
365
  }
365
366
  }
367
+ sealStoreClass(SqlSharedMemoryStore, "sql-shared-memory-store", {
368
+ snapshot: "exit", putScopeState: "exit", putStore: "exit", putDocument: "exit", deleteDocument: "exit", deleteStore: "exit",
369
+ });
366
370
  export class TiDBSharedMemoryStore extends SqlSharedMemoryStore {
367
371
  constructor(pool, authorizer, opts) {
368
372
  super(mysqlDriver(pool), authorizer, opts);
@@ -423,7 +423,8 @@ export declare function connectJudged<R>(db: SqlDriver, judge: (facts: SqlEngine
423
423
  * (`getConnection` → 守卫租约上执行 → 归还,被守卫销毁的不归还)。语句契约不变:参数原样到连接,结果是 mysql2 的 `[rows, fields]` 元组,`{ sql, timeout }` 对象形
424
424
  * 一并支持(原样交给连接);池的其余成员(`end` / `execute` / `on` …)原样转给底池,返回底池自身的方法(`on` 之类)返回本包装体 —— 链式调用不会逃出守卫。
425
425
  * 生产池(`createTidbPool`)恒是它;mysql2 自己的 `pool.query` 对只读类错误(1290 / 1792 / 1836)的销毁收在同一张表里({@link isConnectionUnusableError}),不丢。
426
- * 不覆盖 `pool.execute`(预备语句:今天只有任务附件店一处,语句是小定长行,碰不到语句墙)。
426
+ * 不覆盖 `pool.execute`(预备语句:今天只有任务附件店一处,语句是小定长行,碰不到语句墙)—— 它与其余原样转给底池的成员一样,只给拒绝打驱动出身记号。
427
+ * **驱动出身记号**(`error-origin.ts` 的 `driver` 一词)是这只池座的第二件事:借连接、守卫租约上的语句与事务动词、原样转给底池的承诺,它们的拒绝都打记号(只记,不改对象、不记日志、不改控制流)。
427
428
  */
428
429
  export declare function createCloseAwarePool(pool: MySqlPool): MySqlPool;
429
430
  /** MySQL-protocol driver (TiDB / MySQL / MariaDB — `dialect` is the SQL TEXT axis, `facts.engine` the
@@ -3,6 +3,8 @@ import { recordFailOpen } from "../observability/fail-open.js";
3
3
  import { errorKindOf } from "../observability/err-text.js";
4
4
  import { writeLogText } from "../observability/logger.js";
5
5
  import { isConnectionUnusableError } from "./sql-errors.js";
6
+ import { markErrorOrigin } from "./error-origin.js";
7
+ import { storeBoundaryFault } from "../terminal.js";
6
8
  export class SqlStatementDeadlineExceeded extends Error {
7
9
  deadlineMs;
8
10
  constructor(deadlineMs) {
@@ -245,7 +247,8 @@ async function initMySqlSession(c) {
245
247
  await c.query("SET SESSION tidb_txn_mode = 'pessimistic'");
246
248
  }
247
249
  catch (err) {
248
- throw refuseSession(`this TiDB session is in "${txnMode}" transaction mode and SET SESSION tidb_txn_mode='pessimistic' was rejected (${err.message})`);
250
+ storeBoundaryFault(err, "sql-driver.initMySqlSession");
251
+ throw refuseSession(`this TiDB session is in "${txnMode}" transaction mode and SET SESSION tidb_txn_mode='pessimistic' was rejected`);
249
252
  }
250
253
  txnMode = await mysqlVar(c, "SELECT @@tidb_txn_mode AS v");
251
254
  if (txnMode !== "pessimistic") {
@@ -342,6 +345,7 @@ function leaseOf(raw) {
342
345
  return await run();
343
346
  }
344
347
  catch (err) {
348
+ markErrorOrigin(err, "driver");
345
349
  if (isConnectionUnusableError(err)) {
346
350
  destroy();
347
351
  BROKEN_LEASES.set(lease, { error: err });
@@ -375,7 +379,9 @@ function leaseOf(raw) {
375
379
  return lease;
376
380
  }
377
381
  export function createCloseAwarePool(pool) {
378
- const lend = async () => leaseOf(await pool.getConnection());
382
+ const lend = async () => leaseOf(await pool.getConnection().catch((err) => {
383
+ throw markErrorOrigin(err, "driver");
384
+ }));
379
385
  const query = async (...args) => {
380
386
  const c = await lend();
381
387
  try {
@@ -396,12 +402,21 @@ export function createCloseAwarePool(pool) {
396
402
  return member;
397
403
  return (...args) => {
398
404
  const result = member.apply(target, args);
399
- return result === target ? wrapper : result;
405
+ if (result === target)
406
+ return wrapper;
407
+ return isThenable(result)
408
+ ? result.then(undefined, (err) => {
409
+ throw markErrorOrigin(err, "driver");
410
+ })
411
+ : result;
400
412
  };
401
413
  },
402
414
  });
403
415
  return wrapper;
404
416
  }
417
+ function isThenable(v) {
418
+ return v !== null && (typeof v === "object" || typeof v === "function") && typeof v.then === "function";
419
+ }
405
420
  export function mysqlDriver(pool) {
406
421
  const session = poolSession(pool);
407
422
  return {
@@ -109,7 +109,13 @@ export declare function isDeadlockError(dialect: SqlDialect, err: unknown): bool
109
109
  /** 引擎仲裁之后整段重来的**唯一**上界(全仓每一条「撞死锁就重来」的腿共用这一个数)。8 = 锁形并发套的并发度:缺行首写的 N 路竞争每轮至多多走一轮([ref] 读数 1 成 7 死锁)。 */
110
110
  export declare const DEADLOCK_RETRY_LIMIT = 8;
111
111
  /**
112
- * 「引擎判了死锁 ⇒ 整段重来」的**唯一**写法:有界({@link DEADLOCK_RETRY_LIMIT}),判据 {@link isDeadlockError};用尽 / 其它错误原样抛。
112
+ * 语句源在一只**别人开的**事务里输了仲裁(例:文件历史的 scope 哨兵在锁读那一刻物理消失)—— 它不许自己回滚重开那只事务(段首的锁与判会随之丢掉),
113
+ * 只抛这一只;{@link retryOnDeadlock} 把它与死锁同读,从事务主人的段首整段重来(落定段、店自己的图事务同一条)。
114
+ */
115
+ export declare class SqlArbitrationLostError extends Error {
116
+ }
117
+ /**
118
+ * 「引擎判了死锁 ⇒ 整段重来」的**唯一**写法:有界({@link DEADLOCK_RETRY_LIMIT}),判据 {@link isDeadlockError} ∪ {@link SqlArbitrationLostError};用尽 / 其它错误原样抛。
113
119
  * `attempt` 必须整段重做语义等价(读-判-写全在里面的一只事务,或一条幂等语句)。`alsoArbitrated`:店自己认的另一种仲裁产物(例:缺行首写撞主键 ——
114
120
  * 并发的另一只首写赢了,重读即走更新腿),与死锁同族、同一个上界。锁等待超时不在任何一族里(延迟信号,见 {@link isDeadlockError} 的注):它照抛。
115
121
  */
@@ -241,17 +247,16 @@ export declare function isDeploymentFaultError(err: unknown): boolean;
241
247
  */
242
248
  export declare function databaseAnswerCodesOf(dialect: SqlDialect, err: unknown): string | undefined;
243
249
  /**
244
- * 检查点店的两笔写(续跑输入 `putCtx` / 停驻行 `put`)被引擎以「一行装不下」拒 ⇒ **这一只**带码错误(码 `store.row_too_large`),不以裸驱动错上抛
245
- * (其余店写今天仍是各自的驱动错 —— 未收,登记在案)。
246
- * `row` = 哪一张表的哪一格、为谁写(进句子与运维日志);`wall` = 拒它的那堵墙({@link rowTooLargeWallOf} 的读数;提交腿按它选 413 的出路);
247
- * `dbMessage` = 引擎自己的那句(TiDB 的句子带着上限与这一行的实际字节数),截到 200 字符。
250
+ * 检查点店的两笔写(续跑输入 `putCtx` / 停驻行 `put`)被引擎以「一行装不下」拒 ⇒ **这一只**带码错误(码 `store.row_too_large`),不以驱动错上抛。
251
+ * `row` = 哪一张表的哪一格、为谁写(进句子);`wall` = 拒它的那堵墙({@link rowTooLargeWallOf} 的读数;提交腿按它选 413 的出路)。
252
+ * 句子是本仓的固定句,**不带**库的原句(店的出口只带原码 + 稳定句);库的那句(TiDB 的句子带上限与这一行的实际字节数)挂在 `cause` 上 ——
253
+ * 它出店时由店边界(`terminal.ts` 的 `storeBoundaryFault`:带词法内码的 typed 错原样过,`cause` 链上的驱动错就地换成边界形)记进 `at=store` 那一行、换成边界形。
248
254
  * 提交腿把它答成 413 `request.payload_too_large`(`http/submit-ctx.ts`);停驻那一拍它经引擎的错误座落进运维日志。
249
255
  */
250
256
  export declare class StoreRowTooLargeError extends Error {
251
257
  readonly row: string;
252
258
  readonly wall: RowTooLargeWall;
253
259
  readonly code: "store.row_too_large";
254
- readonly dbMessage: string;
255
260
  constructor(row: string, cause: unknown, wall: RowTooLargeWall);
256
261
  }
257
262
  /** 店写口的翻译:{@link rowTooLargeWallOf} 认得出 ⇒ {@link StoreRowTooLargeError}(带那堵墙);其余原样交回(调用方 `throw` 它)。 */
@@ -55,13 +55,15 @@ export function isDeadlockError(dialect, err) {
55
55
  return keyOf(err, "errno") === MYSQL_ER_LOCK_DEADLOCK_ERRNO || keyOf(err, "code") === MYSQL_ER_LOCK_DEADLOCK_CODE;
56
56
  }
57
57
  export const DEADLOCK_RETRY_LIMIT = 8;
58
+ export class SqlArbitrationLostError extends Error {
59
+ }
58
60
  export async function retryOnDeadlock(dialect, attempt, alsoArbitrated) {
59
61
  for (let tries = 0;; tries++) {
60
62
  try {
61
63
  return await attempt();
62
64
  }
63
65
  catch (err) {
64
- if (tries < DEADLOCK_RETRY_LIMIT && (isDeadlockError(dialect, err) || alsoArbitrated?.(err) === true))
66
+ if (tries < DEADLOCK_RETRY_LIMIT && (isDeadlockError(dialect, err) || err instanceof SqlArbitrationLostError || alsoArbitrated?.(err) === true))
65
67
  continue;
66
68
  throw err;
67
69
  }
@@ -214,16 +216,13 @@ export class StoreRowTooLargeError extends Error {
214
216
  row;
215
217
  wall;
216
218
  code = "store.row_too_large";
217
- dbMessage;
218
219
  constructor(row, cause, wall) {
219
- const dbMessage = (cause instanceof Error ? cause.message : String(cause)).replace(/[\u0000-\u001f\u007f]+/g, " ").slice(0, 200);
220
- super(`${row} was refused by the database: the row is larger than the database stores in one row (${dbMessage}). ` +
220
+ super(`${row} was refused by the database: the row is larger than the database stores in one row. ` +
221
221
  "Shrink what the request carries inline (images / userAttachments), or raise the database's single-row limit " +
222
- "(TiDB: performance.txn-entry-size-limit, or the tidb_txn_entry_size_limit variable on TiDB 7.6+; MySQL / MariaDB: max_allowed_packet)");
222
+ "(TiDB: performance.txn-entry-size-limit, or the tidb_txn_entry_size_limit variable on TiDB 7.6+; MySQL / MariaDB: max_allowed_packet)", { cause });
223
223
  this.row = row;
224
224
  this.wall = wall;
225
225
  this.name = "StoreRowTooLargeError";
226
- this.dbMessage = dbMessage;
227
226
  }
228
227
  }
229
228
  export function rowTooLargeOrSelf(dialect, err, row) {
@@ -2,13 +2,14 @@
2
2
  * 「停-迁-起」那一段的**唯一属主**。
3
3
  *
4
4
  * 一次性迁移里有几条(登记表 `stopThenStart` 一格点名的)改的是更早版本的副本还会继续写的形:改完之后它再写一笔,这一笔读不出 / 丢掉。它们没有运行期开关,
5
- * 起服时由守卫判 —— 库上有「不知道这条迁移的副本还在服务」的证据 ⇒ 拒启、一字节不写。证据只有一种读法(`one-time-migrations.ts` 的 `readOlderReplicaEvidence`):
5
+ * 起服时由守卫段判 —— 库上有「不知道这条迁移的副本还在服务」的证据 ⇒ 拒启、一字节不写。证据只有一种读法(`stop-then-start.ts` 的 `readOlderReplicaEvidence`):
6
6
  * 判活窗内(`REAP_RUN_STALE_SEC`,收割器判 run 持有者还活着的同一个窗)心跳过的 `running` run,持有者没有登记为知道这条迁移(`replica-registration.ts`)。
7
7
  * 四只守卫(M40 · M41 · M42 · M51)的拒句和起服列册对账的升级清单**说同一件事**:先停光更早版本的副本、等满判活窗、再起本版。
8
8
  * 这件事的句子只在这里写一遍:证据一段({@link olderReplicaEvidenceSentence})与动作一段({@link stopThenStartAction});各守卫只讲「为什么这一条要停」。
9
- * 起服准入(`serving-requirement.ts`:库上记着本构建没有的能力 ⇒ 拒启)的拒句同属这一族,也在这里({@link servingRequirementUnmetSentence})。
9
+ * 起服准入(`serving-requirement.ts`:库上记着本构建没有的能力 ⇒ 拒启)的拒句同属这一族,也在这里({@link servingRequirementUnmetSentence});
10
+ * 起服拒句升级清单里「这只库要求本构建没有的能力」一步的句子同在这里({@link unmetRequirementChecklistStep})。
10
11
  *
11
- * 叶子模块:零 import(守卫所在的一次性迁移文件、M42 的拒句文件、起服准入与列册对账都静态 import 它,不成环)。
12
+ * 叶子模块:零 import(守卫段、各条的拒句、M42 的拒句文件、起服准入与列册对账都静态 import 它,不成环)。
12
13
  */
13
14
  /** 证据里一只登记过、却没有这条迁移能力的持有者(归因用:点名它是谁)。 */
14
15
  export interface OlderReplicaHolder {
@@ -47,9 +48,14 @@ export declare function stopThenStartAction(after?: string): string;
47
48
  /** 起服准入里一行本构建没有的要求(`state` = 判读的态,词表外的词读作 `in_force`;`word` = 库里存的原词)。 */
48
49
  export interface UnmetServingRequirement {
49
50
  readonly migration: string;
50
- readonly state: "pending" | "in_force";
51
+ readonly state: "in_force";
51
52
  readonly word: string;
52
53
  }
54
+ /**
55
+ * 起服拒句**升级清单**里的那一步(排在 `ALTER` 一步之前;多行,第一行是步骤首句):这只库要求本构建没有的能力 —— 照清单补完语句也不会让本构建能在这里服务。
56
+ * `unmet` 空 ⇒ 调用方不出这一步。`unreadable` 在 ⇒ 读要求表抛了:照出这一步并如实说没读到(多停一次,不漏停)。
57
+ */
58
+ export declare function unmetRequirementChecklistStep(unmet: readonly UnmetServingRequirement[], unreadable?: string): string[];
53
59
  /** 起服准入的拒句(`reason` 闭集两词;码由调用方交,本模块零 import)。 */
54
60
  export declare function servingRequirementUnmetSentence(code: string, reason: "capability_missing" | "table_absent", unmet: readonly UnmetServingRequirement[], absentTables: readonly string[], releaseLine: string): string;
55
61
  //# sourceMappingURL=stop-then-start-sentence.d.ts.map
@@ -15,11 +15,9 @@ export function stopThenStartAction(after) {
15
15
  }
16
16
  function requirementClause(r) {
17
17
  switch (r.state) {
18
- case "pending":
19
- return `${r.migration} (pending: a newer build recorded it and has not changed any data for it yet)`;
20
18
  case "in_force":
21
19
  return r.word === "in_force"
22
- ? `${r.migration} (in force: data has been changed for it)`
20
+ ? `${r.migration} (in force: a build that has it was admitted on this database and may have changed data for it)`
23
21
  : `${r.migration} (state "${r.word}", which this build does not know — read as in force)`;
24
22
  default: {
25
23
  const unhandled = r.state;
@@ -27,17 +25,23 @@ function requirementClause(r) {
27
25
  }
28
26
  }
29
27
  }
28
+ const rollbackDelete = (migration) => `DELETE FROM serving_requirement WHERE migration_id = '${migration}';`;
29
+ export function unmetRequirementChecklistStep(unmet, unreadable) {
30
+ if (unreadable !== undefined) {
31
+ return [`Whether this database requires a capability this build lacks could not be read (${unreadable}); read serving_requirement before going on — a row this build does not have means it must not serve here.`];
32
+ }
33
+ return [
34
+ `This database requires every serving replica to have ${unmet.map(requirementClause).join(", ")}; this build does not, and must not serve on it — the statements below do not make it safe.`,
35
+ `Start a build that has ${unmet.map((r) => r.migration).join(", ")} instead. Only to roll back on purpose, after handling the data of that migration: ${unmet.map((r) => rollbackDelete(r.migration)).join(" ")}`,
36
+ ];
37
+ }
30
38
  export function servingRequirementUnmetSentence(code, reason, unmet, absentTables, releaseLine) {
31
39
  switch (reason) {
32
- case "capability_missing": {
33
- const inForce = unmet.filter((r) => r.state === "in_force").map((r) => r.migration);
40
+ case "capability_missing":
34
41
  return (`${code} (reason: capability_missing): this database requires every serving replica to have ${unmet.map(requirementClause).join(", ")}; this build (release ${releaseLine}) does not. ` +
35
42
  "No one-time migration ran and no stored data was changed (the section head only registered this replica and removed registrations of other replicas older than 90 days). " +
36
- `Start a build that has ${unmet.map((r) => r.migration).join(", ")}.` +
37
- (inForce.length === 0
38
- ? ""
39
- : ` Only to roll back on purpose, after handling the data of that migration: ${inForce.map((m) => `DELETE FROM serving_requirement WHERE migration_id = '${m}';`).join(" ")}`));
40
- }
43
+ `Start a build that has ${unmet.map((r) => r.migration).join(", ")}. ` +
44
+ `Only to roll back on purpose, after handling the data of that migration: ${unmet.map((r) => rollbackDelete(r.migration)).join(" ")}`);
41
45
  case "table_absent":
42
46
  return (`${code} (reason: table_absent): this database has no ${absentTables.join(" / ")} table — the serving requirements recorded there cannot be read, ` +
43
47
  'and an unreadable requirement is not "no requirement", so this build refuses to start. Nothing was written. ' +
@@ -0,0 +1,109 @@
1
+ /**
2
+ * **停-迁-起**(SQL 两方言;常设,不带窗)—— 一次性迁移登记表里每一条停-迁-起行的声明型、它唯一的判官、守卫段与证据读法。
3
+ *
4
+ * ## 它回答哪一问(只这一问)
5
+ * 「一只**已在服务**、不认迁移 M 的副本,在 M 之后写下的那一笔不兼容写,由谁挡?」—— 答案是一格闭集 {@link StopThenStartFence}(围栏类):
6
+ * · `structural` —— 库挡:M 的第一笔改动是 DDL(就地改名 / 删退役列 / 按建表语句重建),更早副本点名旧对象的每一句不兼容写当场失败(先围后搬);
7
+ * · `per-write` —— 写句挡:从 `fencedSince` 起的构建,点名的那几句写在同一句 / 同一事务里读起服要求行;`fencedSince` 严格早于 M 的引入版
8
+ * (逐写围栏只对带这段代码的构建生效,所以必须早至少一版装入);
9
+ * · `accepted` —— 没有人挡:`residual` 一句写明残余(MIGRATION 原样引),停光旧副本是唯一的补偿。
10
+ * 类取「从第一笔不兼容写起就成立」的那一种:晚于任何一笔搬运的 DDL 围栏不使本条成 `structural`;`per-write` 与 `structural` 同时成立 ⇒ 取 `structural`。
11
+ * **围栏类没有起服期的分臂**:三类在起服时同一只序 —— 守卫段读证据(有 ⇒ 拒)、各条在自己的第一笔不兼容写之前记一行起服要求(`serving-requirement.ts`)。
12
+ * 类只决定放行判据({@link judgeStopThenStart})、残余的披露与作者该选哪一形。
13
+ *
14
+ * ## 一份声明、一只判官
15
+ * 登记表(`one-time-migrations.ts` 的 `ONE_TIME_MIGRATIONS`)每一行**显式**写 `stopThenStart: StopThenStartRow | null`(缺键 = tsc 红,形门再红一次);
16
+ * `null` 只说「本条不声明停-迁-起」(不拒启、不进升级清单的停-迁-起一步),**不是**混跑安全的证明。判官只答「这份声明合不合本类的放行判据」;
17
+ * 读者两处:形门(全表,非空 ⇒ 红)与起服({@link assertStopThenStartDeclarations}:SQL 一次性迁移段**取连接之前**判本构建的每一条停-迁-起行,非空 ⇒ 抛构建缺陷,一句库语句都没发)。
18
+ *
19
+ * ## 守卫段在一切写之前
20
+ * {@link guardOwedStopThenStart} 在 SQL 一次性迁移段的锁内、准入复核之后、第一步迁移之前跑(`runSqlOneTimeMigrations`):按登记序逐条问「这只库还欠不欠」,
21
+ * 欠的读证据,任一有证据 ⇒ 抛那一条自己的拒(码与句是那一条的)—— 迁移段一字节没写,各拒句里的「Nothing was changed」由构造成立。迁移步里不再读证据。
22
+ *
23
+ * ## 证据读法(常设)
24
+ * {@link readOlderReplicaEvidence}:判活窗内心跳过的 `running` run(谓词 = run 店的 `runningHeartbeatPredicate` 的 `live` 一侧,与死亡判决同一只构造、同一个窗),
25
+ * 按持有者分组,减去登记为知道 M 的持有者(`replica-registration.ts`)。早于副本登记的副本不登记,只经它们持有的 run 心跳可见;没登记的持有者照算(保守)——
26
+ * 回滚到早于副本登记的构建、跳过公开序列的嵌入方、登记行过了留存视界的长寿进程会一直可能存在,所以这一读法不是过渡、没有删除日期。
27
+ * 它是观察谓词,不是存活证明:空闲的(零 `running` run)副本看不见;刚停的副本留下的 run 在窗内仍算(拒句给「全停之后何时再起」)。
28
+ * 「库上的副本够不够新」(服务下限)就是这一读法读空,不另立读口、不加版本常量。
29
+ *
30
+ * local 车道:数据根独占(起服锁只许一个进程)⇒ 没有混跑窗,守卫段与证据读法只在 SQL 腿。
31
+ */
32
+ import type { StoreBootRefusalError } from "./store-contracts.js";
33
+ import type { SchemaProbeRead } from "./schema-live-columns.js";
34
+ import type { SqlDialect } from "./sql-driver.js";
35
+ import type { PendingShapeChange } from "./schema-consistency.js";
36
+ import type { OlderReplicaEvidence } from "./stop-then-start-sentence.js";
37
+ import { type ReplicaCapability } from "./replica-registration.js";
38
+ /** 已在服务、不认本条的副本在本条之后那一笔不兼容写由谁挡(闭集;停-迁-起行必填)。 */
39
+ export type StopThenStartFence =
40
+ /** 库挡:`fencedBy` 是本行 `shapeChanges` 里的一项(`rename` / `drop` / `rebuild`,不许 `add`),且是本条的第一笔改动。 */
41
+ {
42
+ readonly kind: "structural";
43
+ readonly fencedBy: PendingShapeChange;
44
+ }
45
+ /** 写句挡:从 `fencedSince`(x.y.z,严格早于本行 `introducedIn`)起的构建,`statements` 点名的那几句写读起服要求行,`(本条, in_force)` 在 ⇒ 不写、响亮。 */
46
+ | {
47
+ readonly kind: "per-write";
48
+ readonly fencedSince: string;
49
+ readonly statements: string;
50
+ }
51
+ /** 没人挡:`residual` 一句写明残余(MIGRATION 原样引);停光旧副本是唯一的补偿。 */
52
+ | {
53
+ readonly kind: "accepted";
54
+ readonly residual: string;
55
+ };
56
+ /**
57
+ * 一条停-迁-起迁移的登记格(登记表里只写引用,判据与拒句的码在本条的迁移步旁边):
58
+ * · `reason` —— 一句英文:更早版本的副本在迁移之后还会写什么、写了会怎样(= 为什么不能两版滚动;升级清单原样列给运维);
59
+ * · `owedOn` —— **这只库还欠不欠本条**的唯一读法(只读、无参语句):守卫段先问它(不欠 ⇒ 不读证据),升级清单的停-迁-起一步只列它答「欠」的行;
60
+ * · `refuse` —— 有证据时本条的拒(本条自己的码与句;`read` = 锁连接上的无参读口,拒句尾的库况 / 本条的计数从它读);守卫段调它,迁移步里不再读证据;
61
+ * · `fence` —— 围栏类({@link StopThenStartFence})。
62
+ */
63
+ export interface StopThenStartRow {
64
+ readonly reason: string;
65
+ readonly owedOn: (read: SchemaProbeRead, dialect: SqlDialect) => Promise<boolean>;
66
+ readonly refuse: (evidence: OlderReplicaEvidence, read: SchemaProbeRead, dialect: SqlDialect) => Promise<StoreBootRefusalError>;
67
+ readonly fence: StopThenStartFence;
68
+ }
69
+ /** 一条停-迁-起行连同判官要读的两格(登记表行的 `id` / `introducedIn` / `shapeChanges`;一次性迁移文件的 `stopThenStartMigrations()` 交出这一形)。 */
70
+ export interface StopThenStartEntry extends StopThenStartRow {
71
+ readonly id: string;
72
+ readonly introducedIn: string;
73
+ readonly shapeChanges?: readonly PendingShapeChange[];
74
+ }
75
+ /** 停-迁-起证据读法的判活窗输入:= 本部署的 `REAP_RUN_STALE_SEC`(收割器判「这条 run 的持有者还活着」用的同一个窗)。 */
76
+ export interface LiveRunWindowInput {
77
+ readonly liveRunWindowMs: number;
78
+ }
79
+ /** 证据读法要的读口(一次性迁移段的锁连接;只读)。 */
80
+ export interface EvidenceDb {
81
+ rows(sql: string, params?: readonly unknown[]): Promise<Record<string, unknown>[]>;
82
+ }
83
+ /**
84
+ * **唯一判官**:这份停-迁-起声明自相矛盾的清单(空 = 放行)。三类各自的放行判据(穷尽 `switch`,加第四类不处理 = tsc 红),外加三类共同的两条
85
+ * (名在能力名单里 —— 守卫读证据 ⇔ 名在名单里;缘由句不空)。纯函数。
86
+ */
87
+ export declare function judgeStopThenStart(entry: StopThenStartEntry): readonly string[];
88
+ /**
89
+ * **停-迁-起证据的唯一读法**(守卫段与 M43 的启用判同读;拒句与读数的句子属主 = `stop-then-start-sentence.ts`):判活窗内心跳过的 `running` run
90
+ * (run 店的 {@link runningHeartbeatPredicate} `live` 一侧 —— 收割器判活的同一只构造、同一个窗),按持有者(`task_run.instance_id`,建行铸、认领接管)分组,
91
+ * **减去登记为知道 `migrationId` 的持有者**(`knowsCapability`,与起服准入同一只判据)—— 剩下的 run 就是证据:持有者没有登记行(早于副本登记的副本、`NULL` 身份、
92
+ * 自传身份的嵌入方)或登记了而能力名单里没有这条迁移,照算(保守)。登记一半只作归因(点名持有者),**不判活**。
93
+ * 本进程此刻还在迁移段里、一条 run 都没起过 ⇒ 证据里只有别的进程。`task_run` 不在 ⇒ `undefined`。
94
+ * `migrationId` 在型上就要在能力名单里(`ReplicaCapability`):一条迁移读证据 ⇔ 它的名在名单里。
95
+ */
96
+ export declare function readOlderReplicaEvidence(db: EvidenceDb, dialect: SqlDialect, input: LiveRunWindowInput, migrationId: ReplicaCapability): Promise<OlderReplicaEvidence | undefined>;
97
+ /**
98
+ * 起服时的判官读者:本构建的每一条停-迁-起声明过 {@link judgeStopThenStart},任一条自相矛盾 ⇒ 抛(构建缺陷)。纯函数;唯一调用点 = SQL 一次性迁移段的入口
99
+ * (`runSqlOneTimeMigrations`),**在取连接之前** —— 抛出时这一次起服对库一句语句都没发(段头的登记、准入、取锁都还没开始)。
100
+ */
101
+ export declare function assertStopThenStartDeclarations(entries: readonly StopThenStartEntry[]): void;
102
+ /**
103
+ * **守卫段**(一次性迁移段的锁内、准入复核之后、第一步迁移之前;锁连接上;`entries` 已由调用方在取连接之前过了 {@link assertStopThenStartDeclarations}):
104
+ * 按登记序逐条 —— 不欠 ⇒ 跳过(不读证据);欠 ⇒ 读证据,有 ⇒ 抛那一条自己的拒({@link StopThenStartRow.refuse})。拒 ⇒ 迁移段一字节没写。
105
+ * 放行之后,各条在它**自己的**第一笔不兼容写之前记一行起服要求(`serving-requirement.ts` 的 `recordServingRequirement`;三类同记)—— 不在这里记:
106
+ * 本条若在本次起服里不欠,就没有那一笔写,也就不记。
107
+ */
108
+ export declare function guardOwedStopThenStart(db: EvidenceDb, dialect: SqlDialect, input: LiveRunWindowInput, entries: readonly StopThenStartEntry[]): Promise<void>;
109
+ //# sourceMappingURL=stop-then-start.d.ts.map