@sema-agent/server 7.132.0 → 7.133.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 (402) hide show
  1. package/MIGRATION.md +266 -0
  2. package/README.md +8 -9
  3. package/README.zh-CN.md +7 -6
  4. package/USAGE.md +174 -55
  5. package/deploy/sema-up/README.md +19 -1
  6. package/deploy/sema-up/chart/templates/server-v4pro.yaml +5 -2
  7. package/deploy/sema-up/chart/templates/server.yaml +5 -2
  8. package/deploy/sema-up/stack/compose.yaml +3 -0
  9. package/deploy/sema-up/stack/sema-stack-cluster.yaml +4 -2
  10. package/dist/adoption/plan.d.ts +3 -2
  11. package/dist/adoption/plan.js +7 -5
  12. package/dist/adoption/runner.js +3 -10
  13. package/dist/adoption/sql.d.ts +3 -2
  14. package/dist/adoption/sql.js +3 -5
  15. package/dist/adoption/wire.d.ts +4 -1
  16. package/dist/adoption/wire.js +1 -0
  17. package/dist/approval-ask-audit-store.d.ts +31 -13
  18. package/dist/approval-ask-audit-store.js +272 -74
  19. package/dist/approval-ask-machine.d.ts +15 -0
  20. package/dist/approval-ask-machine.js +6 -0
  21. package/dist/approval-ask-retention.d.ts +136 -0
  22. package/dist/approval-ask-retention.js +102 -0
  23. package/dist/approval-card.d.ts +79 -19
  24. package/dist/approval-card.js +44 -13
  25. package/dist/approval-deny-reasons.d.ts +1 -1
  26. package/dist/approval.d.ts +9 -0
  27. package/dist/approval.js +24 -3
  28. package/dist/audit.d.ts +27 -10
  29. package/dist/audit.js +12 -16
  30. package/dist/bake-runner/main.js +3 -2
  31. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  32. package/dist/boot/execution-env.js +9 -7
  33. package/dist/boot/execution-lane.js +11 -0
  34. package/dist/boot/leader.js +2 -0
  35. package/dist/boot/memory-boundary.js +2 -2
  36. package/dist/boot/memory-consolidation.d.ts +5 -4
  37. package/dist/boot/memory-consolidation.js +7 -6
  38. package/dist/boot/org-delivery.d.ts +28 -0
  39. package/dist/boot/org-delivery.js +20 -0
  40. package/dist/boot/reapers.d.ts +3 -0
  41. package/dist/boot/reapers.js +21 -1
  42. package/dist/boot/resolve-spec.d.ts +3 -0
  43. package/dist/boot/resolve-spec.js +60 -81
  44. package/dist/boot/runner-deps.js +1 -0
  45. package/dist/boot/session-faces.d.ts +1 -0
  46. package/dist/boot/session-faces.js +4 -2
  47. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  48. package/dist/boot/stage-06-runners.d.ts +2 -0
  49. package/dist/boot/stage-06-runners.js +6 -3
  50. package/dist/boot/stage-07-capability-layer.d.ts +2 -0
  51. package/dist/boot/stage-07-capability-layer.js +24 -3
  52. package/dist/boot/stage-08-reapers.d.ts +2 -0
  53. package/dist/boot/stage-08-reapers.js +2 -2
  54. package/dist/boot/stage-09-leader.d.ts +3 -0
  55. package/dist/boot/stage-10-http-server.d.ts +3 -0
  56. package/dist/boot/stage-10-http-server.js +5 -6
  57. package/dist/boot/stores.js +2 -2
  58. package/dist/boot/web-admin.d.ts +4 -0
  59. package/dist/boot/web-admin.js +151 -63
  60. package/dist/boot/workflow-orchestration.d.ts +2 -0
  61. package/dist/boot/workflow-orchestration.js +2 -1
  62. package/dist/brain.js +8 -3
  63. package/dist/breaker-key.d.ts +6 -0
  64. package/dist/breaker-key.js +29 -0
  65. package/dist/caller-text-echo.d.ts +9 -6
  66. package/dist/caller-text-echo.js +8 -11
  67. package/dist/capabilities/center-plugins.d.ts +7 -0
  68. package/dist/capabilities/center-plugins.js +1 -1
  69. package/dist/capabilities/composite-members.d.ts +1 -1
  70. package/dist/capabilities/composite-members.js +1 -1
  71. package/dist/capabilities/execution-lane.js +2 -2
  72. package/dist/capabilities/hands-lane.d.ts +21 -0
  73. package/dist/capabilities/hands-lane.js +10 -0
  74. package/dist/capabilities/mcp-probe.d.ts +36 -4
  75. package/dist/capabilities/mcp-probe.js +27 -4
  76. package/dist/capabilities/scenarios.d.ts +63 -7
  77. package/dist/capabilities/scenarios.js +39 -7
  78. package/dist/capability-facts.d.ts +34 -4
  79. package/dist/capability-facts.js +28 -2
  80. package/dist/center-config-tier.d.ts +69 -0
  81. package/dist/center-config-tier.js +42 -0
  82. package/dist/checkpoint-identity.d.ts +16 -0
  83. package/dist/checkpoint-identity.js +19 -0
  84. package/dist/config-catalog.d.ts +17 -10
  85. package/dist/config-catalog.js +43 -28
  86. package/dist/config-d-migration.d.ts +3 -1
  87. package/dist/config-d-migration.js +2 -2
  88. package/dist/config-dir-trust.d.ts +114 -0
  89. package/dist/config-dir-trust.js +388 -0
  90. package/dist/config-invariants.d.ts +18 -5
  91. package/dist/config-invariants.js +35 -3
  92. package/dist/config-provider.d.ts +6 -1
  93. package/dist/config-provider.js +6 -2
  94. package/dist/config-types.d.ts +71 -14
  95. package/dist/config-types.js +8 -0
  96. package/dist/config.d.ts +13 -0
  97. package/dist/config.js +121 -59
  98. package/dist/delegated-session-owner.d.ts +185 -0
  99. package/dist/delegated-session-owner.js +119 -0
  100. package/dist/device-protocol.d.ts +5 -3
  101. package/dist/device-protocol.js +1 -1
  102. package/dist/device-ws-hub.js +21 -2
  103. package/dist/device-ws-protocol.d.ts +70 -6
  104. package/dist/device-ws-protocol.js +50 -23
  105. package/dist/elicitation.js +3 -2
  106. package/dist/entry-problems.d.ts +34 -0
  107. package/dist/entry-problems.js +14 -0
  108. package/dist/execution-lane-caps.d.ts +49 -10
  109. package/dist/execution-lane-caps.js +43 -4
  110. package/dist/field-conflict.d.ts +43 -0
  111. package/dist/field-conflict.js +20 -0
  112. package/dist/fleet/fleet-bus.d.ts +80 -6
  113. package/dist/fleet/fleet-bus.js +41 -9
  114. package/dist/hooks/classic-stations.d.ts +10 -5
  115. package/dist/hooks/classic-stations.js +4 -4
  116. package/dist/hooks/command-spawn.d.ts +57 -0
  117. package/dist/hooks/command-spawn.js +139 -0
  118. package/dist/hooks/hook-llm.js +2 -2
  119. package/dist/hooks/hook-runner.d.ts +127 -25
  120. package/dist/hooks/hook-runner.js +668 -287
  121. package/dist/hooks/hook-source.d.ts +44 -0
  122. package/dist/hooks/hook-source.js +40 -0
  123. package/dist/http/active-run-conflict.d.ts +5 -13
  124. package/dist/http/active-run-conflict.js +17 -27
  125. package/dist/http/admission.js +103 -60
  126. package/dist/http/approval-sweeps.js +55 -18
  127. package/dist/http/caller-trust.d.ts +77 -0
  128. package/dist/http/caller-trust.js +143 -0
  129. package/dist/http/checkpoint-ctx.d.ts +16 -16
  130. package/dist/http/checkpoint-ctx.js +13 -10
  131. package/dist/http/claimed-run.d.ts +4 -3
  132. package/dist/http/device-placement.d.ts +2 -4
  133. package/dist/http/device-placement.js +2 -1
  134. package/dist/http/dispatch.js +10 -4
  135. package/dist/http/durable-run-leg.d.ts +4 -32
  136. package/dist/http/durable-run-leg.js +34 -10
  137. package/dist/http/owner-access.d.ts +56 -0
  138. package/dist/http/owner-access.js +42 -0
  139. package/dist/http/principal-gate.d.ts +48 -23
  140. package/dist/http/principal-gate.js +39 -12
  141. package/dist/http/readiness.d.ts +11 -5
  142. package/dist/http/readiness.js +10 -3
  143. package/dist/http/request-guards.d.ts +16 -23
  144. package/dist/http/request-guards.js +30 -15
  145. package/dist/http/resume-ctx-read.d.ts +2 -5
  146. package/dist/http/resume-ctx-read.js +7 -4
  147. package/dist/http/resume-legs.d.ts +3 -4
  148. package/dist/http/resume-legs.js +126 -69
  149. package/dist/http/retired-service-deps.d.ts +43 -0
  150. package/dist/http/retired-service-deps.js +18 -0
  151. package/dist/http/route-ctx.d.ts +44 -28
  152. package/dist/http/route-ctx.js +4 -0
  153. package/dist/http/route-table.js +2 -0
  154. package/dist/http/routes/a2a-serve.js +3 -4
  155. package/dist/http/routes/admin-config-refresh.js +1 -1
  156. package/dist/http/routes/admin-drain.js +1 -1
  157. package/dist/http/routes/admin-memory-optout.js +5 -1
  158. package/dist/http/routes/adoption.js +2 -9
  159. package/dist/http/routes/approvals-assistant.js +49 -48
  160. package/dist/http/routes/background.js +3 -2
  161. package/dist/http/routes/capabilities.js +20 -4
  162. package/dist/http/routes/config-catalog.d.ts +3 -3
  163. package/dist/http/routes/config-catalog.js +1 -1
  164. package/dist/http/routes/devices.js +3 -17
  165. package/dist/http/routes/diagnostics.js +5 -1
  166. package/dist/http/routes/images.js +6 -7
  167. package/dist/http/routes/leader.js +1 -8
  168. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  169. package/dist/http/routes/mcp-reconnect.js +3 -2
  170. package/dist/http/routes/memory-bundle.js +1 -1
  171. package/dist/http/routes/memory-compliance.js +1 -1
  172. package/dist/http/routes/memory-consolidation.js +1 -1
  173. package/dist/http/routes/memory-origin.js +1 -1
  174. package/dist/http/routes/memory-policy.d.ts +1 -1
  175. package/dist/http/routes/memory-policy.js +3 -2
  176. package/dist/http/routes/notify-wake.js +8 -21
  177. package/dist/http/routes/observability.js +2 -2
  178. package/dist/http/routes/principal-inbox.d.ts +39 -0
  179. package/dist/http/routes/principal-inbox.js +76 -0
  180. package/dist/http/routes/retention-ops.js +2 -9
  181. package/dist/http/routes/rules.js +5 -18
  182. package/dist/http/routes/runs.d.ts +5 -0
  183. package/dist/http/routes/runs.js +33 -106
  184. package/dist/http/routes/session-sync.js +15 -28
  185. package/dist/http/routes/sessions.js +30 -40
  186. package/dist/http/routes/side-query.js +6 -4
  187. package/dist/http/routes/tasks.js +50 -14
  188. package/dist/http/routes/workflows.d.ts +351 -0
  189. package/dist/http/routes/workflows.js +153 -119
  190. package/dist/http/send.d.ts +24 -3
  191. package/dist/http/send.js +14 -2
  192. package/dist/http/server.d.ts +17 -18
  193. package/dist/http/server.js +15 -13
  194. package/dist/http/stored-body-replay.js +17 -4
  195. package/dist/http/submit-ctx.js +4 -3
  196. package/dist/http/submit-replay.d.ts +4 -3
  197. package/dist/http/submit-replay.js +5 -4
  198. package/dist/http/wire-types.d.ts +10 -7
  199. package/dist/index.d.ts +1 -1
  200. package/dist/index.js +0 -1
  201. package/dist/lane-home-probe.d.ts +78 -9
  202. package/dist/lane-home-probe.js +78 -12
  203. package/dist/leader/endpoint.d.ts +4 -1
  204. package/dist/leader/endpoint.js +11 -10
  205. package/dist/limit-refusal.d.ts +17 -5
  206. package/dist/limit-refusal.js +4 -1
  207. package/dist/mcp-assertion-header.d.ts +31 -10
  208. package/dist/mcp-assertion-header.js +10 -3
  209. package/dist/mcp-tool-faces.js +1 -3
  210. package/dist/memory-project-layer.d.ts +37 -0
  211. package/dist/memory-project-layer.js +46 -0
  212. package/dist/memory-scope.d.ts +23 -16
  213. package/dist/memory-scope.js +24 -8
  214. package/dist/memory-sync-client.d.ts +27 -13
  215. package/dist/memory-sync-client.js +107 -18
  216. package/dist/model-entry-refusal.d.ts +9 -1
  217. package/dist/model-entry-refusal.js +6 -1
  218. package/dist/model-plane-face.d.ts +51 -0
  219. package/dist/model-plane-face.js +24 -0
  220. package/dist/model-provider.d.ts +5 -0
  221. package/dist/model-provider.js +4 -2
  222. package/dist/model-route-endpoint.d.ts +8 -11
  223. package/dist/model-route-endpoint.js +4 -7
  224. package/dist/model-route-env.d.ts +22 -0
  225. package/dist/model-route-env.js +2 -0
  226. package/dist/model-route-gap.d.ts +60 -30
  227. package/dist/model-route-gap.js +21 -15
  228. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  229. package/dist/observability/corrupt-read-seat.js +9 -0
  230. package/dist/observability/err-text.d.ts +6 -0
  231. package/dist/observability/err-text.js +18 -4
  232. package/dist/observability/fail-open.d.ts +35 -9
  233. package/dist/observability/fail-open.js +35 -9
  234. package/dist/observability/metrics.js +1 -0
  235. package/dist/observability/prompt-manifest.d.ts +3 -17
  236. package/dist/observability/prompt-manifest.js +45 -42
  237. package/dist/observability/tool-trace.js +9 -9
  238. package/dist/operator-ask.d.ts +10 -0
  239. package/dist/operator-ask.js +3 -0
  240. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  241. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  242. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  243. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  244. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  245. package/dist/orchestration/workflow-notify-journal.js +27 -34
  246. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  247. package/dist/orchestration/workflow-park-decision.js +118 -0
  248. package/dist/org-delivery-gate.d.ts +81 -0
  249. package/dist/org-delivery-gate.js +59 -0
  250. package/dist/panel-tool-names.d.ts +23 -0
  251. package/dist/panel-tool-names.js +8 -0
  252. package/dist/parked-decide.d.ts +39 -6
  253. package/dist/parked-decide.js +98 -51
  254. package/dist/parked-revive-surface.d.ts +9 -2
  255. package/dist/parked-revive-surface.js +11 -8
  256. package/dist/permission-rule-list.d.ts +1 -1
  257. package/dist/permission-rule-list.js +2 -2
  258. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  259. package/dist/plugins/approval-ask-store-file.js +21 -0
  260. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  261. package/dist/plugins/approval-ask-store-memory.js +34 -2
  262. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  263. package/dist/plugins/approval-ask-store-sql.js +37 -1
  264. package/dist/plugins/blob-backend.d.ts +38 -25
  265. package/dist/plugins/blob-backend.js +29 -19
  266. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  267. package/dist/plugins/breaker-state-sql.js +4 -4
  268. package/dist/plugins/caching-session-store.d.ts +3 -0
  269. package/dist/plugins/caching-session-store.js +3 -0
  270. package/dist/plugins/checkpoint-store-sql.d.ts +14 -0
  271. package/dist/plugins/checkpoint-store-sql.js +20 -2
  272. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  273. package/dist/plugins/file-history-store-sql.js +3 -1
  274. package/dist/plugins/file-run-store.d.ts +5 -13
  275. package/dist/plugins/file-run-store.js +9 -0
  276. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  277. package/dist/plugins/fork-routing-session-store.js +12 -1
  278. package/dist/plugins/host-platform.d.ts +2 -0
  279. package/dist/plugins/host-platform.js +2 -1
  280. package/dist/plugins/local-checkpoint-store.d.ts +2 -0
  281. package/dist/plugins/local-checkpoint-store.js +6 -1
  282. package/dist/plugins/local-session-store.d.ts +1 -1
  283. package/dist/plugins/local-session-window.d.ts +39 -0
  284. package/dist/plugins/local-session-window.js +54 -0
  285. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  286. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  287. package/dist/plugins/mailbox-store-sql.js +77 -52
  288. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  289. package/dist/plugins/memory-run-store.d.ts +5 -13
  290. package/dist/plugins/memory-run-store.js +8 -0
  291. package/dist/plugins/one-time-migrations.d.ts +110 -5
  292. package/dist/plugins/one-time-migrations.js +320 -8
  293. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  294. package/dist/plugins/org-delivery-quota-store.js +228 -0
  295. package/dist/plugins/pg-pool.js +39 -7
  296. package/dist/plugins/pg-session-storage.d.ts +17 -4
  297. package/dist/plugins/pg-session-storage.js +29 -6
  298. package/dist/plugins/remote-env-adb.d.ts +6 -12
  299. package/dist/plugins/remote-env-adb.js +12 -2
  300. package/dist/plugins/remote-env-k8s.d.ts +8 -12
  301. package/dist/plugins/remote-env-k8s.js +23 -5
  302. package/dist/plugins/remote-env-local-docker.d.ts +10 -15
  303. package/dist/plugins/remote-env-local-docker.js +12 -9
  304. package/dist/plugins/remote-env-ssh.d.ts +10 -16
  305. package/dist/plugins/remote-env-ssh.js +17 -11
  306. package/dist/plugins/run-store-sql.d.ts +16 -14
  307. package/dist/plugins/run-store-sql.js +22 -2
  308. package/dist/plugins/schema-indexes.js +2 -0
  309. package/dist/plugins/send-user-file.js +2 -2
  310. package/dist/plugins/session-epoch-anchor.d.ts +32 -6
  311. package/dist/plugins/session-epoch-anchor.js +2 -3
  312. package/dist/plugins/session-store.d.ts +18 -8
  313. package/dist/plugins/session-store.js +15 -4
  314. package/dist/plugins/sql-driver.d.ts +74 -11
  315. package/dist/plugins/sql-driver.js +210 -12
  316. package/dist/plugins/sql-errors.d.ts +23 -0
  317. package/dist/plugins/sql-errors.js +21 -0
  318. package/dist/plugins/store-backend.d.ts +36 -1
  319. package/dist/plugins/store-backend.js +40 -14
  320. package/dist/plugins/store-contracts.d.ts +42 -0
  321. package/dist/plugins/store-contracts.js +1 -0
  322. package/dist/plugins/tidb-pool.d.ts +6 -1
  323. package/dist/plugins/tidb-pool.js +44 -10
  324. package/dist/plugins/tidb-session-storage.d.ts +3 -2
  325. package/dist/plugins/tidb-session-storage.js +2 -2
  326. package/dist/plugins/tidb-session-store.d.ts +24 -4
  327. package/dist/plugins/tidb-session-store.js +28 -4
  328. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  329. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  330. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  331. package/dist/plugins/workflow-run-store-sql.js +50 -9
  332. package/dist/posture-source.d.ts +18 -10
  333. package/dist/posture-source.js +4 -1
  334. package/dist/project-identity.d.ts +45 -6
  335. package/dist/project-identity.js +162 -8
  336. package/dist/project-layer-key.d.ts +28 -0
  337. package/dist/project-layer-key.js +29 -0
  338. package/dist/question.js +3 -2
  339. package/dist/read-deny-entry.d.ts +25 -0
  340. package/dist/read-deny-entry.js +12 -0
  341. package/dist/read-face-posture.d.ts +37 -11
  342. package/dist/read-face-posture.js +32 -3
  343. package/dist/request-key-closure.d.ts +7 -0
  344. package/dist/request-key-closure.js +9 -1
  345. package/dist/request-value-closure.d.ts +5 -0
  346. package/dist/request-value-closure.js +11 -3
  347. package/dist/rule-entry-list.d.ts +26 -2
  348. package/dist/rule-entry-list.js +72 -12
  349. package/dist/rules-consent.js +3 -4
  350. package/dist/run-acceptance.d.ts +19 -3
  351. package/dist/run-acceptance.js +13 -1
  352. package/dist/run-local.js +36 -6
  353. package/dist/runs.d.ts +10 -9
  354. package/dist/runs.js +21 -3
  355. package/dist/runtime-caps-resolver.d.ts +16 -6
  356. package/dist/runtime-caps-resolver.js +18 -5
  357. package/dist/sandbox-pkg-source.d.ts +19 -1
  358. package/dist/sandbox-pkg-source.js +29 -7
  359. package/dist/security.d.ts +87 -9
  360. package/dist/security.js +52 -9
  361. package/dist/session-error-wire.d.ts +19 -2
  362. package/dist/session-error-wire.js +5 -1
  363. package/dist/session-titled-frame.d.ts +33 -0
  364. package/dist/session-titled-frame.js +4 -0
  365. package/dist/session-titler.d.ts +9 -1
  366. package/dist/session-titler.js +15 -4
  367. package/dist/spec-fields.js +21 -2
  368. package/dist/sql-engine-posture.d.ts +3 -1
  369. package/dist/task-cwd.d.ts +1 -1
  370. package/dist/task-mcp.d.ts +3 -3
  371. package/dist/task-mcp.js +7 -66
  372. package/dist/task-settings.d.ts +89 -88
  373. package/dist/task-settings.js +71 -63
  374. package/dist/text-bidi.d.ts +0 -4
  375. package/dist/text-bidi.js +1 -3
  376. package/dist/tighten-only-seats.d.ts +150 -0
  377. package/dist/tighten-only-seats.js +196 -0
  378. package/dist/tool-approval.d.ts +303 -87
  379. package/dist/tool-approval.js +623 -239
  380. package/dist/trace/core-keyset-guard.d.ts +104 -22
  381. package/dist/trace/exit-seat.d.ts +28 -0
  382. package/dist/trace/exit-seat.js +76 -0
  383. package/dist/trace/ledger-events.d.ts +3 -2
  384. package/dist/trace/project.d.ts +80 -12
  385. package/dist/trace/project.js +41 -28
  386. package/dist/trace/projection-table.d.ts +100 -0
  387. package/dist/trace/projection-table.js +130 -0
  388. package/dist/trace/redact.d.ts +54 -0
  389. package/dist/trace/redact.js +778 -152
  390. package/dist/trace/self-minted-wire.d.ts +345 -1
  391. package/dist/trace/self-minted-wire.js +117 -1
  392. package/dist/trace/wire-projection-faces.d.ts +298 -2
  393. package/dist/trace/wire-projection-faces.js +154 -13
  394. package/dist/url-refusal.js +2 -0
  395. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  396. package/dist/web-admin-client/apply-effective.js +56 -5
  397. package/dist/web-admin-client/facade.d.ts +1 -1
  398. package/dist/web-admin-client/facade.js +1 -1
  399. package/dist/web-admin-client/read-face.js +5 -9
  400. package/dist/web-admin-client/skills-mcp.js +4 -5
  401. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +57 -9
  402. package/package.json +7 -3
@@ -1,6 +1,8 @@
1
1
  import { type SessionStore } from "@sema-agent/core";
2
2
  import type { ServiceConfig } from "../config-types.js";
3
3
  import type { StoreBackend } from "./store-backend.js";
4
+ import { type DelegationRootWitness, type DelegationTree, type OrphanOwnerLog } from "../delegated-session-owner.js";
5
+ import type { OwnerAwareSessionStore } from "../security.js";
4
6
  /**
5
7
  * Session-store plugin seam.
6
8
  *
@@ -43,15 +45,23 @@ export declare function ensureSessionDurable(hostStore: SessionStore, sessionId:
43
45
  * [ref] 主动晋升版 ensureChildSessionDurable(cli TiDB 真机撞获:1.263 的只查不迁形在拆店生产形下
44
46
  * 恒否决 park——bg 子会话住 subRunner 私有 TTL 店、从不在 host 店,core 收 veto 即 expire checkpoint
45
47
  * (14ms~2s)+ settle failed,赎回窗口不存在):
46
- * 1. 快路:已在 host 店且有真内容 → 直接通过(幂等/重入)。
48
+ * 0. 属主(S-1770,`delegated-session-owner.ts`):按委派继承律判一次(唯一判据 `judgeDelegatedOwner`)—— 来源集 = 见证的根这一只(调用方交出的委派树根 + 调用方判断那一刻
49
+ * 读到的根行住客:请求腿 = 本请求经鉴权解析的会话与受理时那一读,`/decide` 赎回腿 = 行上的 `rootSessionId` 与 claim 前后两读的那一位;core 的挂点签名只给子代 id,
50
+ * 场景层把见证绑进闭包交下来)。此刻根行在而住客不是见证的那一位(删了、同 id 被重新登记)⇒ **拒**(`assertRootStillWitnessed`:不按新住客晋升 / 认领);
51
+ * 缺席(行上没有根)或根行此刻不在 ⇒ 孤行哨兵。
52
+ * 1. 快路:已在 host 店且有真内容 → 通过,**前提是那一行的属主按律定得下来**(`settleDelegatedOwner`:记着别的委派树 ⇒ 抛;记着这一棵 ⇒ 按它记录的根、父再判一次
53
+ * (与 M23 同读数),无主 ⇒ 认领(CAS 子行,写判断时的读数)、没落下 ⇒ 抛(只有记录委派树的 SQL 店;local 店的行不记委派树 ⇒ 不认领);此刻属主不是律给的那一只 ⇒ 那是别人的同 id 会话,
54
+ * 不是这只子代的转录 ⇒ 抛 = 停驻否决;与晋升建行前那一道同一只函数)。
47
55
  * 2. 晋升:routing.promoteToHost 把子会话整树从私店拷进 host 店(拷贝→私店 forget→路由记 host,
48
- * 三步见 ForkRoutingSessionStore.promoteToHost)。
49
- * 3. 复验:同一把 `ensureSessionDurable` 严检(leaf 非空)——晋升产物必须达到与既有校验完全相同的
50
- * durable 标准,任何一步失败=如实 reject=veto=pre-153 行为(不放大)。
56
+ * 三步见 ForkRoutingSessionStore.promoteToHost),宿主行在建行那一笔里带第 0 步的属主;写的是哨兵 ⇒ 写落下之后一行 warn。
57
+ * 3. 复验:同一把 `ensureSessionDurable` 严检(leaf 非空)+ 同一只属主定夺 —— 晋升产物必须达到与快路完全相同的
58
+ * 标准,任何一步失败=如实 reject=veto=pre-153 行为(不放大)。
59
+ * 宿主店必须读得出属主(`delegatedOwnerHostOf`:`ownerOf` + `sessionFaceOf`;记录委派树的店另交 `placementOf` + `claimOwnerless`,两只同在或同缺;生产的 SQL / local 店与缓存包装层都满足),
60
+ * 否则响亮拒。
51
61
  * 调用方纪律与 {@link ensureSessionDurable} 同款:hostStore 必须是 release 非破坏性的真 durable 店
52
- * (main.ts 的 `config.sessionBackend === "tidb"` 门不变)。
62
+ * (stage-07 的 `config.sessionBackend === "tidb"` 门不变 —— 该词在 local 后端上同样归一成 `tidb`,所以本腿在 local 宿主店上也跑)。
53
63
  */
54
- export declare function ensureChildSessionDurableWithPromotion(hostStore: SessionStore, routing: {
55
- promoteToHost(sessionId: string): Promise<void>;
56
- }, sessionId: string): Promise<void>;
64
+ export declare function ensureChildSessionDurableWithPromotion(hostStore: OwnerAwareSessionStore, routing: {
65
+ promoteToHost(sessionId: string, owner: string | null, tree: DelegationTree): Promise<void>;
66
+ }, sessionId: string, root: DelegationRootWitness | undefined, log: OrphanOwnerLog): Promise<void>;
57
67
  //# sourceMappingURL=session-store.d.ts.map
@@ -1,5 +1,6 @@
1
1
  import { TtlSessionStore } from "@sema-agent/core";
2
2
  import { CachingSessionStore } from "./caching-session-store.js";
3
+ import { assertRootStillWitnessed, delegatedOwnerHostOf, readDelegatedOwner, reportWrittenOwner, settleDelegatedOwner, sourceFacesVia, } from "../delegated-session-owner.js";
3
4
  export function createSessionStore(config, backend, metrics) {
4
5
  if (config.sessionBackend === "tidb") {
5
6
  if (!backend)
@@ -22,14 +23,24 @@ export async function ensureSessionDurable(hostStore, sessionId) {
22
23
  await hostStore.forget?.(acquired.sessionId);
23
24
  }
24
25
  }
25
- export async function ensureChildSessionDurableWithPromotion(hostStore, routing, sessionId) {
26
+ export async function ensureChildSessionDurableWithPromotion(hostStore, routing, sessionId, root, log) {
27
+ const host = delegatedOwnerHostOf(hostStore);
28
+ if (host === undefined) {
29
+ throw new Error(`session "${sessionId}" cannot be parked: the host session store cannot read a session's owner (ownerOf / sessionFaceOf), or records delegation trees without being able to claim under them (placementOf / claimOwnerless), so the delegated transcript's owner cannot be derived or checked`);
30
+ }
31
+ const judged = await readDelegatedOwner(sessionId, root === undefined ? {} : { rootSessionId: root.sessionId }, sourceFacesVia(host.sessionFaceOf));
32
+ assertRootStillWitnessed(sessionId, root, judged);
33
+ let promoted = false;
26
34
  try {
27
35
  await ensureSessionDurable(hostStore, sessionId);
28
- return;
29
36
  }
30
37
  catch {
38
+ await routing.promoteToHost(sessionId, judged.inherited.owner, judged.tree);
39
+ await ensureSessionDurable(hostStore, sessionId);
40
+ promoted = true;
31
41
  }
32
- await routing.promoteToHost(sessionId);
33
- await ensureSessionDurable(hostStore, sessionId);
42
+ const settled = await settleDelegatedOwner(host, sessionId, judged.inherited.owner, judged.tree);
43
+ if (promoted || settled.claimed)
44
+ reportWrittenOwner(log, "promotion", sessionId, settled.judged ?? judged);
34
45
  }
35
46
  //# sourceMappingURL=session-store.js.map
@@ -111,6 +111,7 @@
111
111
  */
112
112
  import type { Pool as MySqlPool } from "mysql2/promise";
113
113
  import type { Pool as PgPool } from "pg";
114
+ import type { Logger } from "../observability/logger.js";
114
115
  export type SqlDialect = "tidb" | "pg";
115
116
  export type SqlRow = Record<string, unknown>;
116
117
  /** Normalized statement result: `rows` for SELECTs, `affected` for INSERT/UPDATE/DELETE. */
@@ -175,20 +176,35 @@ export interface SqlEngineFacts {
175
176
  * `StoreRowTooLargeError` (`sql-errors.ts`): the checkpoint store's `putCtx` (resume inputs) and `put` (the INSERT of
176
177
  * a pending row). Every other write — the checkpoint store's updates (`resolve` / `reopen` / …), the session log —
177
178
  * keeps its existing driver error.
179
+ * **Level**: the TiDB config item is per TiDB instance, read as the minimum across instances (so the reading does not depend
180
+ * on which instance a connection landed on); the 7.6+ variable is read as this connection sees it at initialization. Its
181
+ * readers: the submit admission reads it off {@link SqlDriver.facts} (it has no connection in hand); the SQL snapshot-blob cap
182
+ * reads it off the connection that writes the blob ({@link sqlConnectionFacts}). This version does not change how it is read.
178
183
  */
179
184
  readonly maxRowBytes: number | null;
180
185
  /**
181
- * The largest single statement the server accepts, in bytes — the session's `@@max_allowed_packet`. A second wall,
182
- * so a second fact (one fact per wall; `null` always and only means "this engine has no such wall").
186
+ * The largest single statement the server accepts on **this connection**, in bytes — its session `@@max_allowed_packet`.
187
+ * A second wall, so a second fact (one fact per wall; `null` always and only means "this engine has no such wall").
188
+ * **Level: one physical connection.** The server copies the session value from the global one when the connection is
189
+ * established and never changes it afterwards: after a DBA changes the global value, connections opened later carry the
190
+ * new value and the ones already pooled keep theirs until the pool replaces them. So the reading lives with the connection
191
+ * that took it: read once at that connection's initialization, kept with it, and judged on it.
183
192
  * · **MySQL protocol — InnoDB and TiDB alike**: read at connection initialization, in the same place and with the
184
193
  * same unreadable disposition as {@link maxRowBytes} on TiDB: the variable unknown / the query failing / a value that
185
194
  * is not a positive integer ⇒ the documented default of that server family
186
195
  * ({@link PACKET_CAP_UNREAD_LANDING_BYTES}), recorded through `recordFailOpen("server.sql.packet-cap-unread")` (a per-hit
187
196
  * count and one warn line naming the reason). Never `null`: on these engines the wall exists, only its size may be unread.
188
197
  * · **`null`** — PostgreSQL: the extended protocol has no statement-size wall.
189
- * Its readers: the submit admission (`http/request-guards.ts` `refuseSubmitBody`) and the SQL snapshot-blob default cap
190
- * (`plugins/blob-backend.ts` `sqlBlobDefaultCapBytes`) — both through {@link packetWallStatementBytes}, and both read the walls (this one and
191
- * {@link maxRowBytes}) through the one judge {@link sqlWallsOf}, which also says what holds when the facts themselves are absent.
198
+ * Its final judge: the SQL snapshot-blob cap (`plugins/blob-backend.ts` `SqlBlobBackend.putBlob`), which borrows the connection
199
+ * it will write on through {@link connectJudged}, reads this off it ({@link sqlConnectionFacts}), judges, and writes on that
200
+ * same connection — and when it refuses on a connection opened before the DBA raised the wall, that stale connection is
201
+ * replaced and the write judged once more ({@link connectJudged}). On {@link SqlDriver.facts} the value is the most recently
202
+ * initialized connection's reading (the operator read face `GET /v1/diagnostics/wiring` → `sqlEngine.maxPacketBytes`, and
203
+ * the submit admission's early run of the same rule — `http/request-guards.ts` `refuseSubmitBody`: it has no connection in
204
+ * hand, so it pre-judges against the newest reading the pool has — a cached value that moves only when a connection is
205
+ * initialized, so right after a DBA changes the global value it can lag in either direction; the database on the writing
206
+ * connection stays the final judge of a submitted body). Both readers (the blob cap on the borrowed connection's own facts, the admission on the pool's) read the walls
207
+ * — this one and {@link maxRowBytes} — through the one judge {@link sqlWallsOf}, which also says what holds when the facts themselves are absent.
192
208
  */
193
209
  readonly maxPacketBytes: number | null;
194
210
  /**
@@ -229,19 +245,24 @@ export type RowWallRowShape = keyof typeof ROW_WALL_ALLOWANCE_BYTES;
229
245
  /**
230
246
  * 一条语句里**载荷之外**的字节余量 —— 固定语句文本 + 表名 + 64 字符哈希 + 长度 + 时间戳 + 行包装(mysql2 真格式化量过:最长表名
231
247
  * `file_history_blob` 的 blob INSERT 在 blob 之外 240 字节)。1 KiB 盖住它的 4 倍还多,又不松到压低可用量(16 MiB 包墙下少 1 KiB)。
232
- * 不扣的话恰在包墙上的载荷整条语句会超出包墙几百字节:放行、库在写那一刻拒。经 {@link packetWallStatementBytes} 被两个读者共读。
248
+ * 不扣的话恰在包墙上的载荷整条语句会超出包墙几百字节:放行、库在写那一刻拒。经 {@link packetWallStatementBytes} 被两个读者共读(同一只规则:blob 帽的终判、提交体受理口的提前一跑)。
233
249
  */
234
250
  export declare const STATEMENT_ALLOWANCE_BYTES = 1024;
235
251
  /** 行墙下一份载荷最多多少字节:`max(0, 行墙 − 这种行的余量)`({@link ROW_WALL_ALLOWANCE_BYTES})。受理口的提交体门限读 `json`、blob 缺省帽的行墙腿读 `blob`。 */
236
252
  export declare function rowWallPayloadBytes(maxRowBytes: number, row: RowWallRowShape): number;
237
- /** 包墙下一条语句里的载荷最多多少字节:`max(0, 包墙 − {@link STATEMENT_ALLOWANCE_BYTES})`。受理口的提交体门限(量的是转义后的语句字节,{@link escapedStatementBytes})与 blob 缺省帽的包墙腿(再按膨胀律折半)读它。 */
253
+ /**
254
+ * 包墙下一条语句里的载荷最多多少字节:`max(0, 包墙 − {@link STATEMENT_ALLOWANCE_BYTES})` —— 包墙这一只规则的**唯一**算式,两个读者:
255
+ * · blob 缺省帽的包墙腿(再按膨胀律折半),包墙取自将要执行写入的那条连接 —— **终判**;
256
+ * · 提交体受理口(量的是转义后的语句字节,{@link escapedStatementBytes}),包墙取自池里最近初始化的那条连接 —— 同一只规则的**提前一跑**
257
+ * (受理时手里没有将要写入的连接;拒 ⇒ 413 在任何副作用之前,收 ⇒ 写入那条连接上的库仍是终判)。
258
+ */
238
259
  export declare function packetWallStatementBytes(maxPacketBytes: number): number;
239
260
  /**
240
261
  * 一段文本作为**一只字符串字面**进 MySQL 协议语句时占的字节数(不含外层两只引号)—— 店真正发出去的字节,不是文本自己的字节数。
241
- * 店用 `pool.query(sql, [text])`,mysql2 在客户端把参数格式化进语句(`sql-escaper` 的 `escapeString`):`\0 \b \t \n \r \x1a " ' \` 九个字符各变成两个字符
262
+ * 店用 `query(sql, [text])`,mysql2 在客户端把参数格式化进语句(`sql-escaper` 的 `escapeString`):`\0 \b \t \n \r \x1a " ' \` 九个字符各变成两个字符
242
263
  * (多一个反斜杠字节),其余字符原样 —— 多字节 UTF-8 不受影响。所以一份 JSON 里引号 / 反斜杠越多,语句越比文本长(上界 2×:每个字符至多多一字节)。
243
264
  *
244
- * 受理口的包墙腿量它(`http/request-guards.ts` `refuseSubmitBody`):门承诺「写不进去的体在任何副作用之前 413」,量的就必须是语句字节。
265
+ * 受理口的包墙腿量它(`http/request-guards.ts` `refuseSubmitBody`):门承诺「按池见过的最新包墙读数写不进去的体在任何副作用之前 413」,量的就必须是语句字节。
245
266
  * 这是 mysql2 转义表的**镜像**,不是第二份读法:`test/store-row-cap.test.ts` 把它逐字钉在 mysql2 自己的 `escape` 上(`escapedStatementBytes(s) ===
246
267
  * Buffer.byteLength(mysql.escape(s)) − 2`,含全码点表扫),mysql2 升级改转义表时那格先红。不直接调 `escape` 取长度,是因为那会为一份 8 MiB 的体再造一份 8–16 MiB 的字符串。
247
268
  */
@@ -291,7 +312,7 @@ export type SqlEngineReading = Pick<SqlDriver, "dialect" | "facts">;
291
312
  export declare const SQL_FACTS_ABSENT_WALLS: SqlWallsReading;
292
313
  /**
293
314
  * 🔴 **此刻一份写入要装进的墙 —— 唯一判官**。两个读者同调它、不各读 `facts`:提交受理口(`http/request-guards.ts` `refuseSubmitBody`)与 SQL 快照 blob 的缺省帽
294
- * (`plugins/blob-backend.ts` `sqlBlobCapBytes`,店的写前帽与 `PUT /sync/blobs` 的前置 413 都经它)。输入是一只引擎读口({@link SqlEngineReading}:方言与事实同出一只驱动),一种读法:
315
+ * (`plugins/blob-backend.ts` `sqlBlobCapOf`:店的写前判,读的是**将要执行写入的那条连接**的读口 —— 方言 + 它自己那次初始化的事实;受理口读本池驱动的读口)。输入是一只引擎读口({@link SqlEngineReading}:方言与事实同出),一种读法:
295
316
  * · 读口缺席 = 没有 SQL 后端 ⇒ 没有墙(「忘了给」读不成这一臂:后端交出了 SQL 池而没给读口 ⇒ `createHttpServer` 起服拒,见 `ServiceDeps.sqlEngine`);
296
317
  * · 事实在场 ⇒ 事实里的两位,出处逐墙取事实里记的那一份({@link SqlEngineFacts.wallSources}:读到的 `reported`,读失败退文档缺省的 `assumed`);
297
318
  * · 事实缺席(`null`)⇒ 按读口的方言:PG 没有墙(扩展协议没有这两堵墙 —— 方言事实,不随引擎事实缺席);MySQL 协议 ⇒ {@link SQL_FACTS_ABSENT_WALLS}(两堵都 `assumed`;
@@ -330,7 +351,10 @@ export declare function sqlDbNowMs(db: Pick<SqlDriver, "dialect" | "query">, sub
330
351
  export interface SqlDriver extends SqlExec {
331
352
  readonly dialect: SqlDialect;
332
353
  /**
333
- * 本池上**最近一次连接初始化的结果**(状态按池存,所以共享一条池的二十多只店 driver 报同一份事实)。
354
+ * 本池上**最近一次连接初始化的结果**(状态按池存,所以共享一条池的二十多只店 driver 报同一份事实)—— 运维读面的料。
355
+ * 连接级的事实(包墙 `maxPacketBytes`:会话值,随连接不同)在这里是「最近一次初始化的那条连接的读数」—— 池见过的最新读数,只在有连接初始化时才更新(DBA 调了全局值、
356
+ * 还没有连接重新初始化时它仍是旧值):
357
+ * 提交受理口拿它跑同一只规则的**提前一跑**(手里没有将要写入的连接);终判读的是**将要执行那条语句的连接**的事实({@link sqlConnectionFacts})。
334
358
  * 三种读数,含义各不相同:
335
359
  * · `null`(还没借出过连接)—— 没有可报的东西,**不猜**;
336
360
  * · 事实 —— 最近一次初始化**回读复核**过的值。每条池连接首次借出都重跑一次初始化(记忆化按连接身份),
@@ -342,6 +366,45 @@ export interface SqlDriver extends SqlExec {
342
366
  readonly facts: SqlEngineFacts | null;
343
367
  connect(): Promise<SqlTxConn>;
344
368
  }
369
+ /**
370
+ * 一条**借出的**连接自己的引擎事实 —— 它的初始化回读到的那一份(包墙 `maxPacketBytes` 是这条连接的会话值)。判一条语句装不装得下的地方
371
+ * 只读这里:借出连接 → 读它的墙 → 判 → 在同一条连接上执行(`plugins/blob-backend.ts` `SqlBlobBackend.putBlob`)。
372
+ * 只认本模块的 `mysqlDriver` / `pgDriver` 的 `connect()` 借出的连接;别的连接(测试替身、包装体)没有经过初始化,墙没人读过 ⇒ **响亮抛**,不猜一个数。
373
+ */
374
+ export declare function sqlConnectionFacts(conn: SqlTxConn): SqlEngineFacts;
375
+ /** 旧连接被换掉时的运维日志事件名。 */
376
+ export declare const STALE_PACKET_WALL_EVENT = "sql_connection_packet_wall_stale";
377
+ /**
378
+ * 判官的**唯一借出口**:借出将要执行写入的那条连接,按它自己的事实判(`judge` 交回 `undefined` = 装得下,否则交回拒绝)。装得下 ⇒ 交回这条连接
379
+ * (调用方在它上面写、负责 `release()`)。拒绝 ⇒ 这条连接已归还,交回拒绝。
380
+ *
381
+ * **旧连接首次撞墙即自换**:包墙是会话值,连接建立时从全局拷一份、此后不变 —— DBA 调大包墙之后,池里已有的连接仍带旧值,而本仓的 MySQL 协议池
382
+ * 不按空闲回收连接(`tidb-pool.ts` `maxIdle` = 连接上限)。所以一次拒绝若由这条连接的**包墙**定(`byPacketWall(拒绝)`),先在**同一条连接**上读一次
383
+ * `@@global.max_allowed_packet`:
384
+ * · 全局值大于这条连接的读数 ⇒ 它是调墙之前建立的旧连接,这一拒不是判决:**销毁**它(池补新连接)、记一行 {@link STALE_PACKET_WALL_EVENT}(连接标识 / 旧值 / 新值),
385
+ * 再借一条**不过时**的连接**重判一次**;仍拒 ⇒ 交回拒绝。重借时,读全局值之前就已初始化、包墙又小于那个全局值的连接同样销毁记一行(不再问库)——
386
+ * 池里这样的旧连接是有限的(都在读全局值之前建立),一条条换完,下一条就是之后才初始化的连接,它带的是那之后的全局值,直接在它上面判(没有固定的跳过上限);
387
+ * · 全局值不大于(或读不出 —— 证明不了过时,拒是关闭方向)⇒ 这一拒就是判决。读不出分两种:变量不认识 / 不是正整数是库的答案,不记;
388
+ * **查询本身失败**是故障,记一行 {@link GLOBAL_PACKET_WALL_UNREAD_EVENT}(连接标识 / 这条连接的墙 / 错误类)再按拒收 —— 方向不变,只是原因不再蒸发。
389
+ * 只在拒的那一刻多一次往返,装得下的写零额外语句;读与判在同一条连接上。运维日志写不出(logger 抛)不改判决、不漏连接:先销毁、再记,
390
+ * 记的失败交给 {@link recordFailOpen}(`server.sql.packet-wall-log-unwritten`,计数 + 契约行 + 探针,不依赖那只坏掉的 logger)。
391
+ */
392
+ export declare function connectJudged<R>(db: SqlDriver, judge: (facts: SqlEngineFacts) => R | undefined, byPacketWall: (refusal: R) => boolean, log: Pick<Logger, "warn">): Promise<{
393
+ readonly conn: SqlTxConn;
394
+ readonly refusal?: undefined;
395
+ } | {
396
+ readonly conn?: undefined;
397
+ readonly refusal: R;
398
+ }>;
399
+ /**
400
+ * 守卫租约的**池**:交出去的每一条连接 —— 池上裸语句(`pool.query`)和直接 `pool.getConnection()` 的店 —— 都是 {@link leaseOf} 的守卫租约。
401
+ * mysql2 的 `pool.query` 在内部借连接、跑、归还,本仓够不着那条连接,所以库关掉的连接(上面的 1153 一类)只能照常回池;这里换成**每条语句租一条连接**
402
+ * (`getConnection` → 守卫租约上执行 → 归还,被守卫销毁的不归还)。语句契约不变:参数原样到连接,结果是 mysql2 的 `[rows, fields]` 元组,`{ sql, timeout }` 对象形
403
+ * 一并支持(原样交给连接);池的其余成员(`end` / `execute` / `on` …)原样转给底池,返回底池自身的方法(`on` 之类)返回本包装体 —— 链式调用不会逃出守卫。
404
+ * 生产池(`createTidbPool`)恒是它;mysql2 自己的 `pool.query` 对只读类错误(1290 / 1792 / 1836)的销毁收在同一张表里({@link isConnectionUnusableError}),不丢。
405
+ * 不覆盖 `pool.execute`(预备语句:今天只有任务附件店一处,语句是小定长行,碰不到语句墙)。
406
+ */
407
+ export declare function createCloseAwarePool(pool: MySqlPool): MySqlPool;
345
408
  /** MySQL-protocol driver (TiDB / MySQL / MariaDB — `dialect` is the SQL TEXT axis, `facts.engine` the
346
409
  * runtime one). Transaction control uses mysql2's NATIVE methods — the historical call shape the
347
410
  * fake-pool unit suites script around. */
@@ -1,5 +1,7 @@
1
1
  import { pgSafeJsonStringify, pgProtocolJsonStringify } from "./pg-safe-json.js";
2
2
  import { recordFailOpen } from "../observability/fail-open.js";
3
+ import { errorKindOf } from "../observability/err-text.js";
4
+ import { isConnectionUnusableError } from "./sql-errors.js";
3
5
  export const TIDB_DEFAULT_TXN_ENTRY_SIZE_LIMIT = 6_291_456;
4
6
  export const ROW_WALL_ALLOWANCE_BYTES = { json: 64 * 1024, blob: 1024 };
5
7
  export const STATEMENT_ALLOWANCE_BYTES = 1024;
@@ -84,9 +86,92 @@ const POOL_SESSIONS = new WeakMap();
84
86
  function poolSession(pool) {
85
87
  let st = POOL_SESSIONS.get(pool);
86
88
  if (st === undefined)
87
- POOL_SESSIONS.set(pool, (st = { initialized: new WeakSet(), facts: null }));
89
+ POOL_SESSIONS.set(pool, (st = { connections: new WeakMap(), facts: null }));
88
90
  return st;
89
91
  }
92
+ const LENT = new WeakMap();
93
+ export function sqlConnectionFacts(conn) {
94
+ return lentRecord(conn).facts;
95
+ }
96
+ function lentRecord(conn) {
97
+ const lent = LENT.get(conn);
98
+ if (lent === undefined) {
99
+ throw new Error("sql-driver: this connection was not lent by mysqlDriver()/pgDriver().connect(), so no connection initialization read its walls — " +
100
+ "borrow the connection a statement will run on from the driver before judging that statement's size");
101
+ }
102
+ return lent;
103
+ }
104
+ let INIT_ORDINAL = 0;
105
+ export const STALE_PACKET_WALL_EVENT = "sql_connection_packet_wall_stale";
106
+ const GLOBAL_PACKET_WALL_UNREAD_EVENT = "sql_connection_packet_wall_global_unread";
107
+ export async function connectJudged(db, judge, byPacketWall, log) {
108
+ let conn = await db.connect();
109
+ try {
110
+ let refusal = judge(sqlConnectionFacts(conn));
111
+ if (refusal === undefined)
112
+ return { conn };
113
+ if (byPacketWall(refusal)) {
114
+ const lent = lentRecord(conn);
115
+ const wall = lent.facts.maxPacketBytes;
116
+ const global = wall === null ? null : await readGlobalPacketWallLogged(lent, log);
117
+ if (wall !== null && global !== null && global > wall) {
118
+ const initializedBefore = INIT_ORDINAL;
119
+ retireStale(lent, global, log);
120
+ conn = await borrowNotStale(db, global, initializedBefore, log);
121
+ refusal = judge(sqlConnectionFacts(conn));
122
+ if (refusal === undefined)
123
+ return { conn };
124
+ }
125
+ }
126
+ conn.release();
127
+ return { refusal };
128
+ }
129
+ catch (err) {
130
+ conn.release();
131
+ throw err;
132
+ }
133
+ }
134
+ function retireStale(lent, globalWallBytes, log) {
135
+ lent.destroy();
136
+ warnPacketWall(log, STALE_PACKET_WALL_EVENT, { connection: lent.id, sessionWallBytes: lent.facts.maxPacketBytes, globalWallBytes });
137
+ }
138
+ async function readGlobalPacketWallLogged(lent, log) {
139
+ try {
140
+ return await lent.readGlobalPacketWall();
141
+ }
142
+ catch (err) {
143
+ warnPacketWall(log, GLOBAL_PACKET_WALL_UNREAD_EVENT, { connection: lent.id, sessionWallBytes: lent.facts.maxPacketBytes, errKind: errorKindOf(err) });
144
+ return null;
145
+ }
146
+ }
147
+ function warnPacketWall(log, event, fields) {
148
+ try {
149
+ log.warn(event, fields);
150
+ }
151
+ catch (err) {
152
+ recordFailOpen("server.sql.packet-wall-log-unwritten", `event=${event} err=${errorKindOf(err)}`);
153
+ }
154
+ }
155
+ async function borrowNotStale(db, globalWallBytes, initializedBefore, log) {
156
+ for (;;) {
157
+ const conn = await db.connect();
158
+ let lent;
159
+ try {
160
+ lent = lentRecord(conn);
161
+ }
162
+ catch (err) {
163
+ conn.release();
164
+ throw err;
165
+ }
166
+ const wall = lent.facts.maxPacketBytes;
167
+ if (wall === null || wall >= globalWallBytes || lent.initOrdinal > initializedBefore)
168
+ return conn;
169
+ retireStale(lent, globalWallBytes, log);
170
+ }
171
+ }
172
+ async function readGlobalMaxAllowedPacket(c) {
173
+ return positiveIntOf(await mysqlVar(c, "SELECT @@global.max_allowed_packet AS v")) ?? null;
174
+ }
90
175
  async function mysqlVar(c, sql) {
91
176
  try {
92
177
  const r = await mysqlQuery(c, sql);
@@ -105,16 +190,20 @@ function refuseSession(what) {
105
190
  `Neither is optional: every "SELECT … FOR UPDATE" decision point in this service is a CURRENT read only under them.`);
106
191
  }
107
192
  async function establishSession(session, pooled, c, init) {
108
- if (session.initialized.has(pooled))
109
- return;
193
+ const known = session.connections.get(pooled);
194
+ if (known !== undefined)
195
+ return known;
110
196
  try {
111
- session.facts = await init(c);
112
- session.initialized.add(pooled);
197
+ const facts = await init(c);
198
+ const initialized = { facts, initOrdinal: ++INIT_ORDINAL };
199
+ session.facts = facts;
200
+ session.connections.set(pooled, initialized);
201
+ return initialized;
113
202
  }
114
203
  catch (err) {
115
204
  const stale = session.facts;
116
205
  session.facts = null;
117
- session.initialized = new WeakSet();
206
+ session.connections = new WeakMap();
118
207
  if (stale !== null)
119
208
  console.warn(`[sql-driver] connection initialization REFUSED — SQL engine facts invalidated (was: ${stale ? `${stale.engine} ${stale.version} ${stale.isolation} txnMode=${stale.txnMode ?? "-"}` : "<absent>"}). GET /v1/diagnostics/wiring -> sqlEngine and GET /v1/capabilities -> sql now report ABSENT, which is the honest ` +
120
209
  `reading: this pool cannot currently establish the read semantics every "SELECT … FOR UPDATE" in this repo assumes. Cause: ${err.message}`);
@@ -203,6 +292,86 @@ async function readMaxAllowedPacket(c, engine) {
203
292
  recordFailOpen("server.sql.packet-cap-unread", `assumed=${landing} engine=${engine} ${unread}`);
204
293
  return { bytes: landing, source: "assumed" };
205
294
  }
295
+ const LEASE_STATEMENT_METHODS = new Set(["query", "execute", "beginTransaction", "commit", "rollback"]);
296
+ const GUARDED_LEASES = new WeakSet();
297
+ const BROKEN_LEASES = new WeakMap();
298
+ function leaseOf(raw) {
299
+ if (GUARDED_LEASES.has(raw))
300
+ return raw;
301
+ let destroyed = false;
302
+ const destroy = () => {
303
+ if (destroyed)
304
+ return;
305
+ destroyed = true;
306
+ raw.destroy();
307
+ };
308
+ let turn = Promise.resolve();
309
+ const guard = (run) => {
310
+ const result = turn.then(async () => {
311
+ try {
312
+ return await run();
313
+ }
314
+ catch (err) {
315
+ if (isConnectionUnusableError(err)) {
316
+ destroy();
317
+ BROKEN_LEASES.set(lease, { error: err });
318
+ }
319
+ throw err;
320
+ }
321
+ });
322
+ turn = result.then(() => undefined, () => undefined);
323
+ return result;
324
+ };
325
+ const lease = new Proxy(raw, {
326
+ get(target, prop) {
327
+ const member = Reflect.get(target, prop, target);
328
+ if (typeof member !== "function")
329
+ return member;
330
+ if (prop === "release")
331
+ return () => (destroyed ? undefined : raw.release());
332
+ if (prop === "destroy")
333
+ return destroy;
334
+ if (prop === "rollback")
335
+ return (...args) => guard(async () => (destroyed ? undefined : member.apply(target, args)));
336
+ if (LEASE_STATEMENT_METHODS.has(prop))
337
+ return (...args) => guard(() => member.apply(target, args));
338
+ return (...args) => {
339
+ const result = member.apply(target, args);
340
+ return result === target ? lease : result;
341
+ };
342
+ },
343
+ });
344
+ GUARDED_LEASES.add(lease);
345
+ return lease;
346
+ }
347
+ export function createCloseAwarePool(pool) {
348
+ const lend = async () => leaseOf(await pool.getConnection());
349
+ const query = async (...args) => {
350
+ const c = await lend();
351
+ try {
352
+ return await c.query(...args);
353
+ }
354
+ finally {
355
+ c.release();
356
+ }
357
+ };
358
+ const wrapper = new Proxy(pool, {
359
+ get(target, prop) {
360
+ if (prop === "query")
361
+ return query;
362
+ if (prop === "getConnection")
363
+ return lend;
364
+ const member = Reflect.get(target, prop, target);
365
+ if (typeof member !== "function")
366
+ return member;
367
+ return (...args) => {
368
+ const result = member.apply(target, args);
369
+ return result === target ? wrapper : result;
370
+ };
371
+ },
372
+ });
373
+ return wrapper;
374
+ }
206
375
  export function mysqlDriver(pool) {
207
376
  const session = poolSession(pool);
208
377
  return {
@@ -212,15 +381,28 @@ export function mysqlDriver(pool) {
212
381
  },
213
382
  query: (sql, params) => mysqlQuery(pool, sql, params),
214
383
  async connect() {
215
- const c = await pool.getConnection();
216
- await establishSession(session, c.connection ?? c, c, initMySqlSession);
217
- return {
384
+ const raw = await pool.getConnection();
385
+ const c = leaseOf(raw);
386
+ const { facts, initOrdinal } = await establishSession(session, raw.connection ?? raw, c, initMySqlSession);
387
+ const broken = BROKEN_LEASES.get(c);
388
+ if (broken !== undefined)
389
+ throw broken.error;
390
+ const conn = {
218
391
  query: (sql, params) => mysqlQuery(c, sql, params),
219
392
  begin: () => c.beginTransaction(),
220
393
  commit: () => c.commit(),
221
394
  rollback: () => c.rollback(),
222
395
  release: () => c.release(),
223
396
  };
397
+ const threadId = c.threadId;
398
+ LENT.set(conn, {
399
+ facts,
400
+ initOrdinal,
401
+ id: typeof threadId === "number" ? threadId : null,
402
+ destroy: () => c.destroy(),
403
+ readGlobalPacketWall: () => readGlobalMaxAllowedPacket(c),
404
+ });
405
+ return conn;
224
406
  },
225
407
  };
226
408
  }
@@ -256,8 +438,9 @@ export function pgDriver(pool) {
256
438
  query: (sql, params) => pgQuery(pool, sql, params),
257
439
  async connect() {
258
440
  const c = await pool.connect();
259
- await establishSession(session, c, c, initPgSession);
260
- return {
441
+ const { facts, initOrdinal } = await establishSession(session, c, c, initPgSession);
442
+ let destroyed = false;
443
+ const conn = {
261
444
  query: (sql, params) => pgQuery(c, sql, params),
262
445
  begin: async () => {
263
446
  await c.query("BEGIN");
@@ -268,8 +451,23 @@ export function pgDriver(pool) {
268
451
  rollback: async () => {
269
452
  await c.query("ROLLBACK");
270
453
  },
271
- release: () => c.release(),
454
+ release: () => {
455
+ if (!destroyed)
456
+ c.release();
457
+ },
272
458
  };
459
+ const pid = c.processID;
460
+ LENT.set(conn, {
461
+ facts,
462
+ initOrdinal,
463
+ id: typeof pid === "number" ? pid : null,
464
+ destroy: () => {
465
+ destroyed = true;
466
+ c.release(true);
467
+ },
468
+ readGlobalPacketWall: async () => null,
469
+ });
470
+ return conn;
273
471
  },
274
472
  };
275
473
  }
@@ -118,6 +118,29 @@ export type RowTooLargeWall = "row" | "statement" | "value";
118
118
  * MySQL 协议两条墙都认 —— 两条墙对**一条单行 INSERT** 说的是同一件事(这一行写不进),出路不同;PG 认 `54000`。认不出的一律 `undefined`(原始错误原样上抛)。
119
119
  */
120
120
  export declare function rowTooLargeWallOf(dialect: SqlDialect, err: unknown): RowTooLargeWall | undefined;
121
+ /**
122
+ * **这条连接不许再回池**的 MySQL 协议错误 —— 闭集,逐码登记「为什么」。两类原因:
123
+ * · **库发完这个错误包之后自己关掉这条连接**(或随后必关):客户端的池只看见「这条语句完了」,连接照常归还,库的 FIN / RST 晚一拍才到 ——
124
+ * 池上紧接着的下一笔借到这条半死的连接,答 `write ECONNRESET`。1153 是真库读数(k3s8 TiDB v7.5.1:超墙的续跑输入写,首笔答 1153 + 带库原句,
125
+ * 同池下一笔 `getCtx` 答 `write ECONNRESET`);其余按各引擎的服务端行为文档登记(**未在真库上逐码复现**,收车档如实标注),错放进来的代价只是一次重连;
126
+ * · **只读类**(只读模式 / 故障切换之后连接落在只读节点上):mysql2 自己的 `pool.query` 对这三个错误号同样销毁连接(`lib/base/pool.js` `isReadOnlyError`)——
127
+ * 本仓的池上裸语句改走租来的连接(`createCloseAwarePool`),不收进来就丢了这条既有行为;归因取 mysql2 的,不另作更窄的断言。
128
+ * **不在表里**的(连接仍可用,语句级错误):唯一键冲突 1062、死锁 1213、锁等待超时 1205、`KILL QUERY` 的 1317、缺表 1146、CHECK 被拒 3819,以及 **TiDB 的单行墙 8025**
129
+ * (`ErrEntryTooLarge`,事务层的语句错;真库读数:同池上 8025 写之后的读照常 —— `checkpoint-row-size-db-integration` ②③④)。
130
+ * 断连形(`ECONNRESET` / `EPIPE` / `PROTOCOL_CONNECTION_LOST`)也不在表里:那是 mysql2 的**致命错误**,连接对象自己已经 `_removeFromPool`。
131
+ * PostgreSQL 没有对应面:node-pg 的 `pool.query` 对任何错误都丢弃那条 client。
132
+ * 新增一行 = 改表 + 在 `test/db-closed-connection-never-pooled.test.ts` 的码表里加一格。
133
+ */
134
+ export declare const MYSQL_CONNECTION_UNUSABLE_ERRORS: ReadonlyArray<{
135
+ readonly errno: number;
136
+ readonly code: string;
137
+ }>;
138
+ /**
139
+ * 「抛出这个错误的那条连接还能不能回池」的单一判据:{@link MYSQL_CONNECTION_UNUSABLE_ERRORS} 里的错 ⇒ **不能** —— 持连接的一方(`sql-driver.ts` 的 `leaseOf`)
140
+ * 先销毁它、再让错误出去。两个归因键任一在场即认(同本文件其余谓词的姿势:`errno` 与 `code` 是同一件事的两种写法,代理 / 序列化往返可能只留一个)。
141
+ * 认不出的一律不是(连接照常归还;断连形由 mysql2 自己清出池)。只管 MySQL 协议的错误形:PG 的错误没有这两个键,恒 false。
142
+ */
143
+ export declare function isConnectionUnusableError(err: unknown): boolean;
121
144
  /**
122
145
  * 检查点店的两笔写(续跑输入 `putCtx` / 停驻行 `put`)被引擎以「一行装不下」拒 ⇒ **这一只**带码错误(码 `store.row_too_large`),不以裸驱动错上抛
123
146
  * (其余店写今天仍是各自的驱动错 —— 未收,登记在案)。
@@ -61,6 +61,27 @@ export function rowTooLargeWallOf(dialect, err) {
61
61
  return "statement";
62
62
  return undefined;
63
63
  }
64
+ export const MYSQL_CONNECTION_UNUSABLE_ERRORS = [
65
+ { errno: 1053, code: "ER_SERVER_SHUTDOWN" },
66
+ { errno: 1152, code: "ER_ABORTING_CONNECTION" },
67
+ { errno: 1153, code: "ER_NET_PACKET_TOO_LARGE" },
68
+ { errno: 1158, code: "ER_NET_READ_ERROR" },
69
+ { errno: 1159, code: "ER_NET_READ_INTERRUPTED" },
70
+ { errno: 1160, code: "ER_NET_ERROR_ON_WRITE" },
71
+ { errno: 1161, code: "ER_NET_WRITE_INTERRUPTED" },
72
+ { errno: 1927, code: "ER_CONNECTION_KILLED" },
73
+ { errno: 4031, code: "ER_CLIENT_INTERACTION_TIMEOUT" },
74
+ { errno: 1290, code: "ER_OPTION_PREVENTS_STATEMENT" },
75
+ { errno: 1792, code: "ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION" },
76
+ { errno: 1836, code: "ER_READ_ONLY_MODE" },
77
+ ];
78
+ const UNUSABLE_ERRNOS = new Set(MYSQL_CONNECTION_UNUSABLE_ERRORS.map((e) => e.errno));
79
+ const UNUSABLE_CODES = new Set(MYSQL_CONNECTION_UNUSABLE_ERRORS.map((e) => e.code));
80
+ export function isConnectionUnusableError(err) {
81
+ const errno = keyOf(err, "errno");
82
+ const code = keyOf(err, "code");
83
+ return (typeof errno === "number" && UNUSABLE_ERRNOS.has(errno)) || (typeof code === "string" && UNUSABLE_CODES.has(code));
84
+ }
64
85
  export class StoreRowTooLargeError extends Error {
65
86
  row;
66
87
  wall;