@sema-agent/server 7.131.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 (509) hide show
  1. package/MIGRATION.md +575 -0
  2. package/README.md +12 -9
  3. package/README.zh-CN.md +9 -6
  4. package/USAGE.md +326 -98
  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 +82 -20
  24. package/dist/approval-card.js +46 -16
  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 +33 -19
  29. package/dist/audit.js +12 -16
  30. package/dist/auth-bridge.d.ts +4 -0
  31. package/dist/auth-bridge.js +10 -11
  32. package/dist/bake-runner/main.js +3 -2
  33. package/dist/boot/deferred-sandbox-path-env.js +1 -1
  34. package/dist/boot/deployment-mods.d.ts +31 -0
  35. package/dist/boot/deployment-mods.js +26 -0
  36. package/dist/boot/execution-env.d.ts +1 -1
  37. package/dist/boot/execution-env.js +27 -12
  38. package/dist/boot/execution-lane.js +11 -0
  39. package/dist/boot/leader.js +2 -0
  40. package/dist/boot/memory-boundary.d.ts +2 -2
  41. package/dist/boot/memory-boundary.js +2 -2
  42. package/dist/boot/memory-consolidation.d.ts +5 -4
  43. package/dist/boot/memory-consolidation.js +7 -6
  44. package/dist/boot/org-delivery.d.ts +28 -0
  45. package/dist/boot/org-delivery.js +20 -0
  46. package/dist/boot/read-deny-anchor-audit.d.ts +17 -0
  47. package/dist/boot/read-deny-anchor-audit.js +36 -0
  48. package/dist/boot/reapers.d.ts +3 -0
  49. package/dist/boot/reapers.js +21 -1
  50. package/dist/boot/resolve-spec.d.ts +4 -5
  51. package/dist/boot/resolve-spec.js +74 -123
  52. package/dist/boot/runner-deps.d.ts +19 -8
  53. package/dist/boot/runner-deps.js +15 -2
  54. package/dist/boot/session-faces.d.ts +2 -7
  55. package/dist/boot/session-faces.js +4 -2
  56. package/dist/boot/stage-02-web-admin-pull.js +4 -1
  57. package/dist/boot/stage-03-store-backend.d.ts +1 -1
  58. package/dist/boot/stage-03-store-backend.js +2 -2
  59. package/dist/boot/stage-04-stores.d.ts +1 -1
  60. package/dist/boot/stage-05-execution-env.d.ts +4 -4
  61. package/dist/boot/stage-05-execution-env.js +7 -10
  62. package/dist/boot/stage-06-runners.d.ts +4 -2
  63. package/dist/boot/stage-06-runners.js +11 -7
  64. package/dist/boot/stage-07-capability-layer.d.ts +4 -2
  65. package/dist/boot/stage-07-capability-layer.js +27 -6
  66. package/dist/boot/stage-08-reapers.d.ts +4 -2
  67. package/dist/boot/stage-08-reapers.js +2 -2
  68. package/dist/boot/stage-09-leader.d.ts +6 -9
  69. package/dist/boot/stage-10-http-server.d.ts +6 -9
  70. package/dist/boot/stage-10-http-server.js +7 -9
  71. package/dist/boot/stores.d.ts +1 -1
  72. package/dist/boot/stores.js +7 -8
  73. package/dist/boot/web-admin.d.ts +4 -3
  74. package/dist/boot/web-admin.js +186 -98
  75. package/dist/boot/workflow-orchestration.d.ts +2 -0
  76. package/dist/boot/workflow-orchestration.js +2 -1
  77. package/dist/brain.js +25 -25
  78. package/dist/breaker-key.d.ts +6 -0
  79. package/dist/breaker-key.js +29 -0
  80. package/dist/budget.js +5 -4
  81. package/dist/caller-text-echo.d.ts +23 -0
  82. package/dist/caller-text-echo.js +21 -0
  83. package/dist/capabilities/brand-identity.d.ts +26 -0
  84. package/dist/capabilities/brand-identity.js +27 -0
  85. package/dist/capabilities/center-plugins.d.ts +7 -0
  86. package/dist/capabilities/center-plugins.js +1 -1
  87. package/dist/capabilities/center-prompts.d.ts +32 -59
  88. package/dist/capabilities/center-prompts.js +18 -32
  89. package/dist/capabilities/composite-members.d.ts +1 -1
  90. package/dist/capabilities/composite-members.js +1 -1
  91. package/dist/capabilities/engine-capabilities.js +2 -1
  92. package/dist/capabilities/execution-lane.js +2 -2
  93. package/dist/capabilities/hands-lane.d.ts +30 -6
  94. package/dist/capabilities/hands-lane.js +11 -1
  95. package/dist/capabilities/mcp-probe.d.ts +48 -3
  96. package/dist/capabilities/mcp-probe.js +47 -135
  97. package/dist/capabilities/scenarios.d.ts +95 -45
  98. package/dist/capabilities/scenarios.js +53 -38
  99. package/dist/capabilities/skills.d.ts +19 -4
  100. package/dist/capabilities/skills.js +30 -10
  101. package/dist/capability-facts.d.ts +34 -4
  102. package/dist/capability-facts.js +28 -2
  103. package/dist/center-config-tier.d.ts +69 -0
  104. package/dist/center-config-tier.js +42 -0
  105. package/dist/checkpoint-identity.d.ts +16 -0
  106. package/dist/checkpoint-identity.js +19 -0
  107. package/dist/config-catalog.d.ts +17 -10
  108. package/dist/config-catalog.js +47 -32
  109. package/dist/config-d-migration.d.ts +3 -1
  110. package/dist/config-d-migration.js +2 -2
  111. package/dist/config-dir-trust.d.ts +114 -0
  112. package/dist/config-dir-trust.js +388 -0
  113. package/dist/config-invariants.d.ts +18 -5
  114. package/dist/config-invariants.js +35 -3
  115. package/dist/config-lkg.d.ts +9 -9
  116. package/dist/config-lkg.js +20 -4
  117. package/dist/config-provider.d.ts +9 -2
  118. package/dist/config-provider.js +18 -6
  119. package/dist/config-types.d.ts +75 -20
  120. package/dist/config-types.js +8 -0
  121. package/dist/config.d.ts +13 -0
  122. package/dist/config.js +137 -62
  123. package/dist/delegated-session-owner.d.ts +185 -0
  124. package/dist/delegated-session-owner.js +119 -0
  125. package/dist/device-protocol.d.ts +5 -3
  126. package/dist/device-protocol.js +1 -1
  127. package/dist/device-ws-hub.js +21 -2
  128. package/dist/device-ws-protocol.d.ts +70 -6
  129. package/dist/device-ws-protocol.js +50 -22
  130. package/dist/elicitation.js +3 -2
  131. package/dist/entry-problems.d.ts +34 -0
  132. package/dist/entry-problems.js +14 -0
  133. package/dist/env-name-allowlist-knobs.d.ts +12 -2
  134. package/dist/env-name-allowlist-knobs.js +5 -4
  135. package/dist/exec-bounds.d.ts +15 -3
  136. package/dist/exec-bounds.js +4 -0
  137. package/dist/execution-lane-caps.d.ts +56 -10
  138. package/dist/execution-lane-caps.js +50 -0
  139. package/dist/field-conflict.d.ts +43 -0
  140. package/dist/field-conflict.js +20 -0
  141. package/dist/fleet/fleet-bus.d.ts +80 -6
  142. package/dist/fleet/fleet-bus.js +41 -9
  143. package/dist/hooks/classic-stations.d.ts +91 -0
  144. package/dist/hooks/classic-stations.js +27 -0
  145. package/dist/hooks/command-spawn.d.ts +57 -0
  146. package/dist/hooks/command-spawn.js +139 -0
  147. package/dist/hooks/hook-llm.d.ts +8 -5
  148. package/dist/hooks/hook-llm.js +79 -36
  149. package/dist/hooks/hook-runner.d.ts +349 -95
  150. package/dist/hooks/hook-runner.js +855 -568
  151. package/dist/hooks/hook-source.d.ts +44 -0
  152. package/dist/hooks/hook-source.js +40 -0
  153. package/dist/http/active-run-conflict.d.ts +5 -13
  154. package/dist/http/active-run-conflict.js +17 -27
  155. package/dist/http/admission.d.ts +8 -2
  156. package/dist/http/admission.js +188 -101
  157. package/dist/http/approval-sweeps.js +99 -21
  158. package/dist/http/caller-trust.d.ts +77 -0
  159. package/dist/http/caller-trust.js +143 -0
  160. package/dist/http/checkpoint-ctx.d.ts +41 -24
  161. package/dist/http/checkpoint-ctx.js +23 -16
  162. package/dist/http/claimed-run.d.ts +14 -0
  163. package/dist/http/claimed-run.js +15 -0
  164. package/dist/http/device-placement.d.ts +2 -4
  165. package/dist/http/device-placement.js +7 -11
  166. package/dist/http/dispatch.d.ts +0 -4
  167. package/dist/http/dispatch.js +15 -13
  168. package/dist/http/durable-run-leg.d.ts +6 -33
  169. package/dist/http/durable-run-leg.js +36 -10
  170. package/dist/http/health-self-report.d.ts +7 -5
  171. package/dist/http/health-self-report.js +4 -18
  172. package/dist/http/owner-access.d.ts +56 -0
  173. package/dist/http/owner-access.js +42 -0
  174. package/dist/http/principal-gate.d.ts +70 -28
  175. package/dist/http/principal-gate.js +61 -15
  176. package/dist/http/readiness.d.ts +11 -5
  177. package/dist/http/readiness.js +10 -3
  178. package/dist/http/refusal-body.d.ts +21 -0
  179. package/dist/http/refusal-body.js +4 -0
  180. package/dist/http/request-guards.d.ts +43 -23
  181. package/dist/http/request-guards.js +53 -17
  182. package/dist/http/resume-ctx-read.d.ts +2 -5
  183. package/dist/http/resume-ctx-read.js +7 -4
  184. package/dist/http/resume-legs.d.ts +28 -4
  185. package/dist/http/resume-legs.js +169 -91
  186. package/dist/http/retired-service-deps.d.ts +43 -0
  187. package/dist/http/retired-service-deps.js +18 -0
  188. package/dist/http/route-ctx.d.ts +44 -28
  189. package/dist/http/route-ctx.js +4 -0
  190. package/dist/http/route-table.js +2 -0
  191. package/dist/http/routes/a2a-serve.js +3 -4
  192. package/dist/http/routes/admin-config-refresh.js +1 -1
  193. package/dist/http/routes/admin-drain.js +1 -1
  194. package/dist/http/routes/admin-memory-optout.js +5 -1
  195. package/dist/http/routes/adoption.js +2 -9
  196. package/dist/http/routes/approvals-assistant.js +49 -48
  197. package/dist/http/routes/background.js +3 -2
  198. package/dist/http/routes/capabilities.js +21 -5
  199. package/dist/http/routes/config-catalog.d.ts +3 -3
  200. package/dist/http/routes/config-catalog.js +1 -1
  201. package/dist/http/routes/devices.js +3 -17
  202. package/dist/http/routes/diagnostics.js +6 -2
  203. package/dist/http/routes/images.js +6 -7
  204. package/dist/http/routes/leader.js +1 -8
  205. package/dist/http/routes/mcp-reconnect.d.ts +1 -1
  206. package/dist/http/routes/mcp-reconnect.js +3 -2
  207. package/dist/http/routes/memory-bundle.js +1 -1
  208. package/dist/http/routes/memory-compliance.js +1 -1
  209. package/dist/http/routes/memory-consolidation.js +1 -1
  210. package/dist/http/routes/memory-origin.js +1 -1
  211. package/dist/http/routes/memory-policy.d.ts +1 -1
  212. package/dist/http/routes/memory-policy.js +3 -2
  213. package/dist/http/routes/notify-wake.js +8 -21
  214. package/dist/http/routes/observability.js +2 -2
  215. package/dist/http/routes/principal-inbox.d.ts +39 -0
  216. package/dist/http/routes/principal-inbox.js +76 -0
  217. package/dist/http/routes/retention-ops.js +2 -9
  218. package/dist/http/routes/rules.js +5 -18
  219. package/dist/http/routes/runs.d.ts +5 -0
  220. package/dist/http/routes/runs.js +34 -107
  221. package/dist/http/routes/session-sync.js +15 -26
  222. package/dist/http/routes/sessions.js +30 -40
  223. package/dist/http/routes/shared-memory.js +2 -13
  224. package/dist/http/routes/side-query.js +8 -7
  225. package/dist/http/routes/tasks.js +67 -21
  226. package/dist/http/routes/workflows.d.ts +351 -0
  227. package/dist/http/routes/workflows.js +153 -119
  228. package/dist/http/send.d.ts +33 -10
  229. package/dist/http/send.js +21 -4
  230. package/dist/http/server.d.ts +27 -18
  231. package/dist/http/server.js +34 -6
  232. package/dist/http/stored-body-replay.js +17 -4
  233. package/dist/http/submit-ctx.d.ts +25 -0
  234. package/dist/http/submit-ctx.js +40 -0
  235. package/dist/http/submit-replay.d.ts +4 -3
  236. package/dist/http/submit-replay.js +5 -4
  237. package/dist/http/wire-types.d.ts +11 -8
  238. package/dist/index.d.ts +2 -2
  239. package/dist/index.js +1 -2
  240. package/dist/lane-home-probe.d.ts +112 -0
  241. package/dist/lane-home-probe.js +101 -0
  242. package/dist/leader/endpoint.d.ts +4 -1
  243. package/dist/leader/endpoint.js +11 -10
  244. package/dist/limit-refusal.d.ts +59 -0
  245. package/dist/limit-refusal.js +15 -0
  246. package/dist/main.js +1 -1
  247. package/dist/mcp-assertion-header.d.ts +32 -19
  248. package/dist/mcp-assertion-header.js +10 -9
  249. package/dist/mcp-tool-faces.d.ts +5 -0
  250. package/dist/mcp-tool-faces.js +6 -4
  251. package/dist/memory-project-layer.d.ts +37 -0
  252. package/dist/memory-project-layer.js +46 -0
  253. package/dist/memory-scope.d.ts +24 -17
  254. package/dist/memory-scope.js +24 -8
  255. package/dist/memory-sync-client.d.ts +27 -13
  256. package/dist/memory-sync-client.js +107 -18
  257. package/dist/model-entry-refusal.d.ts +9 -1
  258. package/dist/model-entry-refusal.js +6 -1
  259. package/dist/model-plane-face.d.ts +51 -0
  260. package/dist/model-plane-face.js +24 -0
  261. package/dist/model-provider.d.ts +44 -12
  262. package/dist/model-provider.js +21 -2
  263. package/dist/model-route-endpoint.d.ts +9 -12
  264. package/dist/model-route-endpoint.js +8 -11
  265. package/dist/model-route-env.d.ts +22 -0
  266. package/dist/model-route-env.js +2 -0
  267. package/dist/model-route-gap.d.ts +60 -30
  268. package/dist/model-route-gap.js +21 -15
  269. package/dist/observability/corrupt-read-seat.d.ts +7 -0
  270. package/dist/observability/corrupt-read-seat.js +9 -0
  271. package/dist/observability/cost-taxonomy.d.ts +7 -7
  272. package/dist/observability/err-text.d.ts +6 -0
  273. package/dist/observability/err-text.js +18 -4
  274. package/dist/observability/fail-open.d.ts +76 -14
  275. package/dist/observability/fail-open.js +76 -14
  276. package/dist/observability/metrics.js +4 -1
  277. package/dist/observability/prompt-manifest.d.ts +33 -82
  278. package/dist/observability/prompt-manifest.js +122 -17
  279. package/dist/observability/run-terminal.js +14 -6
  280. package/dist/observability/secret-env-scrub.d.ts +13 -1
  281. package/dist/observability/secret-env-scrub.js +13 -1
  282. package/dist/observability/tool-trace.d.ts +16 -21
  283. package/dist/observability/tool-trace.js +58 -49
  284. package/dist/operator-ask.d.ts +10 -0
  285. package/dist/operator-ask.js +3 -0
  286. package/dist/orchestration/workflow-agent-session-index.d.ts +12 -12
  287. package/dist/orchestration/workflow-agent-session-index.js +9 -13
  288. package/dist/orchestration/workflow-completion-inbox.d.ts +42 -15
  289. package/dist/orchestration/workflow-completion-inbox.js +13 -0
  290. package/dist/orchestration/workflow-notify-journal.d.ts +13 -32
  291. package/dist/orchestration/workflow-notify-journal.js +27 -34
  292. package/dist/orchestration/workflow-park-decision.d.ts +89 -0
  293. package/dist/orchestration/workflow-park-decision.js +118 -0
  294. package/dist/org-delivery-gate.d.ts +81 -0
  295. package/dist/org-delivery-gate.js +59 -0
  296. package/dist/panel-tool-names.d.ts +23 -0
  297. package/dist/panel-tool-names.js +8 -0
  298. package/dist/parked-decide.d.ts +39 -6
  299. package/dist/parked-decide.js +98 -51
  300. package/dist/parked-revive-surface.d.ts +9 -2
  301. package/dist/parked-revive-surface.js +11 -8
  302. package/dist/per-task-image.d.ts +7 -1
  303. package/dist/per-task-image.js +3 -3
  304. package/dist/permission-rule-list.d.ts +1 -1
  305. package/dist/permission-rule-list.js +2 -2
  306. package/dist/plain-string-record.d.ts +25 -0
  307. package/dist/plain-string-record.js +32 -0
  308. package/dist/plugins/approval-ask-store-file.d.ts +16 -1
  309. package/dist/plugins/approval-ask-store-file.js +21 -0
  310. package/dist/plugins/approval-ask-store-memory.d.ts +18 -1
  311. package/dist/plugins/approval-ask-store-memory.js +34 -2
  312. package/dist/plugins/approval-ask-store-sql.d.ts +25 -0
  313. package/dist/plugins/approval-ask-store-sql.js +37 -1
  314. package/dist/plugins/blob-backend.d.ts +83 -15
  315. package/dist/plugins/blob-backend.js +68 -15
  316. package/dist/plugins/breaker-state-sql.d.ts +2 -2
  317. package/dist/plugins/breaker-state-sql.js +4 -4
  318. package/dist/plugins/caching-session-store.d.ts +3 -0
  319. package/dist/plugins/caching-session-store.js +3 -0
  320. package/dist/plugins/checkpoint-store-sql.d.ts +28 -4
  321. package/dist/plugins/checkpoint-store-sql.js +59 -28
  322. package/dist/plugins/file-history-store-sql.d.ts +5 -2
  323. package/dist/plugins/file-history-store-sql.js +3 -1
  324. package/dist/plugins/file-run-store.d.ts +5 -13
  325. package/dist/plugins/file-run-store.js +9 -0
  326. package/dist/plugins/fork-routing-session-store.d.ts +9 -1
  327. package/dist/plugins/fork-routing-session-store.js +12 -1
  328. package/dist/plugins/host-platform.d.ts +2 -0
  329. package/dist/plugins/host-platform.js +2 -1
  330. package/dist/plugins/k8s-bg-scripts.d.ts +11 -0
  331. package/dist/plugins/k8s-bg-scripts.js +22 -0
  332. package/dist/plugins/local-checkpoint-store.d.ts +2 -0
  333. package/dist/plugins/local-checkpoint-store.js +11 -3
  334. package/dist/plugins/local-session-store.d.ts +1 -1
  335. package/dist/plugins/local-session-store.js +1 -1
  336. package/dist/plugins/local-session-window.d.ts +39 -0
  337. package/dist/plugins/local-session-window.js +54 -0
  338. package/dist/plugins/mailbox-recipient-lifecycle.d.ts +3 -2
  339. package/dist/plugins/mailbox-store-sql.d.ts +30 -48
  340. package/dist/plugins/mailbox-store-sql.js +77 -52
  341. package/dist/plugins/memory-embedder-fingerprint.js +2 -1
  342. package/dist/plugins/memory-run-store.d.ts +5 -13
  343. package/dist/plugins/memory-run-store.js +8 -0
  344. package/dist/plugins/one-time-migrations.d.ts +245 -93
  345. package/dist/plugins/one-time-migrations.js +668 -207
  346. package/dist/plugins/org-delivery-quota-store.d.ts +123 -0
  347. package/dist/plugins/org-delivery-quota-store.js +228 -0
  348. package/dist/plugins/permission-rule-store-sql.d.ts +13 -7
  349. package/dist/plugins/permission-rule-store-sql.js +4 -18
  350. package/dist/plugins/pg-pool.js +39 -7
  351. package/dist/plugins/pg-session-storage.d.ts +20 -8
  352. package/dist/plugins/pg-session-storage.js +34 -21
  353. package/dist/plugins/remote-env-adb.d.ts +12 -12
  354. package/dist/plugins/remote-env-adb.js +21 -6
  355. package/dist/plugins/remote-env-device.js +3 -0
  356. package/dist/plugins/remote-env-e2b.d.ts +1 -1
  357. package/dist/plugins/remote-env-e2b.js +21 -9
  358. package/dist/plugins/remote-env-host.d.ts +9 -9
  359. package/dist/plugins/remote-env-host.js +10 -32
  360. package/dist/plugins/remote-env-k8s.d.ts +103 -38
  361. package/dist/plugins/remote-env-k8s.js +288 -95
  362. package/dist/plugins/remote-env-local-docker.d.ts +27 -13
  363. package/dist/plugins/remote-env-local-docker.js +82 -11
  364. package/dist/plugins/remote-env-ssh.d.ts +21 -12
  365. package/dist/plugins/remote-env-ssh.js +117 -5
  366. package/dist/plugins/remote-shell.d.ts +0 -1
  367. package/dist/plugins/remote-shell.js +1 -2
  368. package/dist/plugins/retention-store-sql.js +6 -2
  369. package/dist/plugins/run-store-sql.d.ts +16 -14
  370. package/dist/plugins/run-store-sql.js +22 -2
  371. package/dist/plugins/schema-consistency.d.ts +54 -0
  372. package/dist/plugins/schema-consistency.js +52 -0
  373. package/dist/plugins/schema-indexes.js +2 -0
  374. package/dist/plugins/send-user-file.js +2 -2
  375. package/dist/plugins/session-epoch-anchor.d.ts +39 -0
  376. package/dist/plugins/session-epoch-anchor.js +22 -0
  377. package/dist/plugins/session-store.d.ts +18 -8
  378. package/dist/plugins/session-store.js +15 -4
  379. package/dist/plugins/shared-memory-store-sql.d.ts +9 -3
  380. package/dist/plugins/shared-memory-store-sql.js +5 -16
  381. package/dist/plugins/sql-driver.d.ts +205 -1
  382. package/dist/plugins/sql-driver.js +325 -13
  383. package/dist/plugins/sql-errors.d.ts +64 -0
  384. package/dist/plugins/sql-errors.js +57 -0
  385. package/dist/plugins/store-backend.d.ts +36 -1
  386. package/dist/plugins/store-backend.js +42 -15
  387. package/dist/plugins/store-contracts.d.ts +42 -0
  388. package/dist/plugins/store-contracts.js +1 -0
  389. package/dist/plugins/tidb-pool.d.ts +6 -1
  390. package/dist/plugins/tidb-pool.js +44 -10
  391. package/dist/plugins/tidb-session-storage.d.ts +8 -11
  392. package/dist/plugins/tidb-session-storage.js +6 -16
  393. package/dist/plugins/tidb-session-store.d.ts +24 -4
  394. package/dist/plugins/tidb-session-store.js +29 -5
  395. package/dist/plugins/workflow-journal-store-sql.d.ts +73 -19
  396. package/dist/plugins/workflow-journal-store-sql.js +19 -6
  397. package/dist/plugins/workflow-run-store-sql.d.ts +30 -8
  398. package/dist/plugins/workflow-run-store-sql.js +50 -9
  399. package/dist/posture-source.d.ts +18 -10
  400. package/dist/posture-source.js +4 -1
  401. package/dist/project-identity.d.ts +45 -6
  402. package/dist/project-identity.js +162 -8
  403. package/dist/project-layer-key.d.ts +28 -0
  404. package/dist/project-layer-key.js +29 -0
  405. package/dist/prompts-domain-validate.d.ts +17 -61
  406. package/dist/prompts-domain-validate.js +9 -119
  407. package/dist/question.js +3 -2
  408. package/dist/read-deny-entry.d.ts +25 -0
  409. package/dist/read-deny-entry.js +12 -0
  410. package/dist/read-face-posture.d.ts +37 -11
  411. package/dist/read-face-posture.js +32 -3
  412. package/dist/request-key-closure.d.ts +9 -2
  413. package/dist/request-key-closure.js +11 -5
  414. package/dist/request-value-closure.d.ts +5 -0
  415. package/dist/request-value-closure.js +11 -3
  416. package/dist/rule-entry-list.d.ts +38 -11
  417. package/dist/rule-entry-list.js +106 -30
  418. package/dist/rules-consent.js +3 -4
  419. package/dist/run-acceptance.d.ts +19 -3
  420. package/dist/run-acceptance.js +13 -1
  421. package/dist/run-local.js +54 -24
  422. package/dist/runs.d.ts +15 -8
  423. package/dist/runs.js +35 -1
  424. package/dist/runtime-caps-resolver.d.ts +16 -6
  425. package/dist/runtime-caps-resolver.js +18 -5
  426. package/dist/sandbox-pkg-source.d.ts +19 -1
  427. package/dist/sandbox-pkg-source.js +29 -7
  428. package/dist/security.d.ts +87 -9
  429. package/dist/security.js +52 -9
  430. package/dist/server-secret-env.d.ts +44 -18
  431. package/dist/server-secret-env.js +49 -8
  432. package/dist/session-error-wire.d.ts +19 -2
  433. package/dist/session-error-wire.js +5 -1
  434. package/dist/session-incarnation.d.ts +15 -5
  435. package/dist/session-incarnation.js +16 -9
  436. package/dist/session-titled-frame.d.ts +33 -0
  437. package/dist/session-titled-frame.js +4 -0
  438. package/dist/session-titler.d.ts +9 -1
  439. package/dist/session-titler.js +27 -6
  440. package/dist/spec-fields.d.ts +28 -16
  441. package/dist/spec-fields.js +66 -54
  442. package/dist/sql-engine-posture.d.ts +31 -4
  443. package/dist/sql-engine-posture.js +36 -1
  444. package/dist/stored-request-tool-axes-m19.d.ts +19 -4
  445. package/dist/stored-request-tool-axes-m19.js +30 -2
  446. package/dist/task-a2a.d.ts +9 -1
  447. package/dist/task-a2a.js +73 -47
  448. package/dist/task-cwd.d.ts +1 -1
  449. package/dist/task-mcp.d.ts +46 -58
  450. package/dist/task-mcp.js +82 -107
  451. package/dist/task-settings.d.ts +95 -108
  452. package/dist/task-settings.js +77 -98
  453. package/dist/text-bidi.d.ts +1 -48
  454. package/dist/text-bidi.js +3 -5
  455. package/dist/tighten-only-seats.d.ts +150 -0
  456. package/dist/tighten-only-seats.js +196 -0
  457. package/dist/tool-approval.d.ts +304 -88
  458. package/dist/tool-approval.js +623 -239
  459. package/dist/trace/core-keyset-guard.d.ts +122 -20
  460. package/dist/trace/engine-notice-wire.d.ts +11 -3
  461. package/dist/trace/engine-notice-wire.js +8 -1
  462. package/dist/trace/exit-seat.d.ts +28 -0
  463. package/dist/trace/exit-seat.js +76 -0
  464. package/dist/trace/ledger-events.d.ts +6 -3
  465. package/dist/trace/ledger-sink.js +4 -1
  466. package/dist/trace/model-segment.js +1 -1
  467. package/dist/trace/project.d.ts +98 -17
  468. package/dist/trace/project.js +98 -50
  469. package/dist/trace/projection-table.d.ts +100 -0
  470. package/dist/trace/projection-table.js +130 -0
  471. package/dist/trace/redact.d.ts +54 -0
  472. package/dist/trace/redact.js +778 -152
  473. package/dist/trace/self-minted-wire.d.ts +345 -1
  474. package/dist/trace/self-minted-wire.js +117 -1
  475. package/dist/trace/wire-projection-faces.d.ts +377 -3
  476. package/dist/trace/wire-projection-faces.js +164 -1
  477. package/dist/url-refusal.js +2 -0
  478. package/dist/web-admin-client/apply-effective.d.ts +12 -0
  479. package/dist/web-admin-client/apply-effective.js +56 -5
  480. package/dist/web-admin-client/candidate-gate.d.ts +6 -6
  481. package/dist/web-admin-client/candidate-gate.js +3 -3
  482. package/dist/web-admin-client/facade.d.ts +1 -1
  483. package/dist/web-admin-client/facade.js +1 -1
  484. package/dist/web-admin-client/http-client.js +0 -6
  485. package/dist/web-admin-client/read-face.js +5 -9
  486. package/dist/web-admin-client/read-warnings.d.ts +13 -0
  487. package/dist/web-admin-client/read-warnings.js +10 -0
  488. package/dist/web-admin-client/skills-mcp.d.ts +3 -0
  489. package/dist/web-admin-client/skills-mcp.js +11 -9
  490. package/dist/web-admin-client/types.d.ts +2 -3
  491. package/dist/workspace-cache.d.ts +35 -0
  492. package/dist/workspace-cache.js +36 -0
  493. package/dist/workspace-space.d.ts +3 -2
  494. package/dist/workspace-space.js +2 -0
  495. package/docs/DEVICE-EXECUTOR-PROTOCOL.md +65 -10
  496. package/package.json +8 -4
  497. package/scripts/drain-check-local.mjs +34 -1
  498. package/scripts/drain-check.mysql.sql +30 -2
  499. package/scripts/drain-check.pg.sql +29 -2
  500. package/dist/capabilities/memory-notice.d.ts +0 -138
  501. package/dist/capabilities/memory-notice.js +0 -64
  502. package/dist/capabilities/prompt.d.ts +0 -17
  503. package/dist/capabilities/prompt.js +0 -6
  504. package/dist/mcp-content-origin.d.ts +0 -34
  505. package/dist/mcp-content-origin.js +0 -10
  506. package/dist/tool-axis-words.d.ts +0 -27
  507. package/dist/tool-axis-words.js +0 -9
  508. package/dist/wire-name-bound.d.ts +0 -14
  509. package/dist/wire-name-bound.js +0 -13
@@ -3,15 +3,15 @@ const REQUESTABLE_CAPS = new Set(["browser", "db", "nestedBuild"]);
3
3
  export async function resolveSandboxImageRef(opts) {
4
4
  const entry = await opts.index.latestPublished(opts.profile, opts.viewer);
5
5
  if (!entry) {
6
- return { ok: false, status: 404, message: `no published sandbox image for profile "${opts.profile}" visible to this principal` };
6
+ return { ok: false, status: 404, errorCode: "not_found.image", message: `no published sandbox image for profile "${opts.profile}" visible to this principal` };
7
7
  }
8
8
  const caps = entry.capabilities;
9
9
  for (const cap of opts.capabilitiesNeeded ?? []) {
10
10
  if (!REQUESTABLE_CAPS.has(cap)) {
11
- return { ok: false, status: 400, message: `unknown sandbox capability "${cap}" (allowed: ${[...REQUESTABLE_CAPS].join(", ")})` };
11
+ return { ok: false, status: 400, errorCode: "request.field_invalid", message: `unknown sandbox capability "${cap}" (allowed: ${[...REQUESTABLE_CAPS].join(", ")})` };
12
12
  }
13
13
  if (caps[cap] !== true) {
14
- return { ok: false, status: 404, message: `sandbox image for profile "${opts.profile}" does not provide capability "${cap}"` };
14
+ return { ok: false, status: 404, errorCode: "not_found.image", message: `sandbox image for profile "${opts.profile}" does not provide capability "${cap}"` };
15
15
  }
16
16
  }
17
17
  return { ok: true, ref: `${entry.repo}@${entry.digest}`, capabilities: entry.capabilities };
@@ -6,7 +6,7 @@
6
6
  * `TaskSpec.allowedTools`(`task-settings.ts` 的 `applyTaskSettings`),运维的 `APPROVAL_GRANT` 投 `RunnerDeps.allowedTools`
7
7
  * (`boot/runner-deps.ts` 的部署姿态座席族)。本口的 `behavior` 型直接是 core 的 `PermissionRule["behavior"]`(不手抄:core 收窄,这里跟着收窄)。
8
8
  *
9
- * 为什么住在一只叶模块里(不留在 `task-settings.ts`):本模块只 import core 与 `text-bidi.ts`(纯叶),读者引它不成环。
9
+ * 为什么住在一只叶模块里(不留在 `task-settings.ts`):本模块只 import core 与契约包 settings-schema(纯叶),读者引它不成环。
10
10
  */
11
11
  import { type PathRuleBases, type PermissionRule, type ToolPolicy } from "@sema-agent/core";
12
12
  /** 一张表里规则条数的上限 —— 请求腿 wire 门(`task-settings.ts` 的 deny / ask 两桶判形)读的也是这一个名字:core 的缺省帽是 256,
@@ -1,5 +1,5 @@
1
1
  import { createPermissionRulePolicy } from "@sema-agent/core";
2
- import { CONTROL_AND_BIDI_STRIP_RE } from "./text-bidi.js";
2
+ import { stripControlAndBidi } from "@sema-agent/settings-schema";
3
3
  export const MAX_PERMISSION_RULES_PER_LIST = 1_000;
4
4
  export function createRuleListPolicy(names, behavior, source, pathBases) {
5
5
  try {
@@ -10,7 +10,7 @@ export function createRuleListPolicy(names, behavior, source, pathBases) {
10
10
  });
11
11
  }
12
12
  catch (e) {
13
- const coreMsg = (e instanceof Error ? e.message : String(e)).replace(CONTROL_AND_BIDI_STRIP_RE, "");
13
+ const coreMsg = stripControlAndBidi(e instanceof Error ? e.message : String(e));
14
14
  throw new Error(`${source.replace("settings.", "")} has rule(s) core cannot compile — ${coreMsg}`, { cause: e });
15
15
  }
16
16
  }
@@ -0,0 +1,25 @@
1
+ /** 一个没进副本的成员:名字(调用方写的,原样;回显前由调用点清洗)与它为什么不能交(只说种类,不回显值)。 */
2
+ export interface PlainStringRecordMemberFault {
3
+ readonly name: string;
4
+ readonly fault: string;
5
+ }
6
+ /**
7
+ * {@link readPlainStringRecord} 的读数(见模块头注的律):`ok:false` = 记录不交(`fault` 只说种类;坏在某个成员时 `member` 点名它);
8
+ * `ok:true` = 交出去的纯数据副本 + 被剔掉的 `__proto__` 名成员(按记录里的顺序;恒只有这一种成员会被剔)。
9
+ * `ok:false` 的 `fault` 是接在「its member "<名>" 」后面的原因句;成员名恰是 `__proto__` 而值也坏时,这一句同时说两件事(名不许、值必须是串)。
10
+ */
11
+ export type PlainStringRecordRead = {
12
+ readonly ok: true;
13
+ readonly record: Record<string, string>;
14
+ readonly stripped: readonly PlainStringRecordMemberFault[];
15
+ } | {
16
+ readonly ok: false;
17
+ readonly fault: string;
18
+ readonly member?: string;
19
+ };
20
+ /**
21
+ * 读一份「名 → 串」记录(见模块头注的律)。成员 = `Object.getOwnPropertyNames` 的每一个名字(不可枚举的也算,与引擎收据同读法),值按属性描述读:
22
+ * 数据属性取值,访问器调一次。先判每个成员的值(`__proto__` 名的成员也判:它的值不是串 ⇒ 整只不交,与 7.131.x 同),再剔 `__proto__` 名。调用方的对象不被交出去、也不被改。
23
+ */
24
+ export declare function readPlainStringRecord(v: unknown): PlainStringRecordRead;
25
+ //# sourceMappingURL=plain-string-record.d.ts.map
@@ -0,0 +1,32 @@
1
+ import { describeValueShape } from "@sema-agent/settings-schema";
2
+ const PROTO_MEMBER_FAULT = 'no member of a declaration may be named "__proto__"';
3
+ export function readPlainStringRecord(v) {
4
+ if (v === null || typeof v !== "object" || Array.isArray(v))
5
+ return { ok: false, fault: v === null ? "got null" : Array.isArray(v) ? "got an array" : `got a ${typeof v}` };
6
+ const proto = Object.getPrototypeOf(v);
7
+ if (proto !== Object.prototype && proto !== null)
8
+ return { ok: false, fault: "got an object that is not plain data (a class instance, a Map, process.env, or a record built on a prototype; copy it into a plain object)" };
9
+ const record = {};
10
+ const stripped = [];
11
+ for (const name of Object.getOwnPropertyNames(v)) {
12
+ const d = Object.getOwnPropertyDescriptor(v, name);
13
+ let value;
14
+ let unreadable = false;
15
+ try {
16
+ value = d === undefined ? undefined : Object.hasOwn(d, "value") ? d.value : d.get === undefined ? undefined : Reflect.apply(d.get, v, []);
17
+ }
18
+ catch {
19
+ unreadable = true;
20
+ }
21
+ if (unreadable || typeof value !== "string") {
22
+ const valueFault = unreadable ? "could not be read (its getter threw)" : `must be a string — got ${describeValueShape(value)}`;
23
+ return { ok: false, fault: name === "__proto__" ? `is not allowed (${PROTO_MEMBER_FAULT}) and its value ${valueFault}` : valueFault, member: name };
24
+ }
25
+ if (name === "__proto__")
26
+ stripped.push({ name, fault: PROTO_MEMBER_FAULT });
27
+ else
28
+ record[name] = value;
29
+ }
30
+ return { ok: true, record, stripped };
31
+ }
32
+ //# sourceMappingURL=plain-string-record.js.map
@@ -22,7 +22,7 @@ export declare const ASK_JOURNAL_COMPACT_GROWTH = 2;
22
22
  * 账本的**动词闭集**([ref] 形)。运行期的词表判定与编译期的穷尽 `switch` 读同一份:
23
23
  * 往这里加一个词,{@link FileApprovalAskStore.replayOp} 的 switch 立刻编译红(漏一口不可能发布)。
24
24
  */
25
- declare const JOURNAL_OPS: readonly ["ensureAsk", "transitionAsk", "decideAsk", "claimTerminal", "claimTerminalRefuse", "expireAsk", "bindBatch", "abortBatch", "deferReconcile", "resolveProvisional", "deleteByTask"];
25
+ declare const JOURNAL_OPS: readonly ["ensureAsk", "transitionAsk", "decideAsk", "claimTerminal", "claimTerminalRefuse", "expireAsk", "bindBatch", "abortBatch", "deferReconcile", "resolveProvisional", "deleteByTask", "reapSettledAsks", "reapEmptyBatches"];
26
26
  type JournalOpWord = (typeof JOURNAL_OPS)[number];
27
27
  /** 运行期词表(上面的数组)与类型面动词维({@link JournalCall})的**双向**对表 —— 任一边加词而另一边
28
28
  * 没跟上,这个别名解析成 `never`,{@link JOURNAL_OP_TABLE_GATE} 的赋值当场编译红。 */
@@ -96,6 +96,17 @@ type JournalCall = {
96
96
  } | {
97
97
  op: "deleteByTask";
98
98
  taskId: string;
99
+ }
100
+ /** 留存律的问账两半(挂靠集落成数组;Set 不是 JSON)。更老的读者不认这两个动词 ⇒ 拒启并点名出路(与 `claimTerminalRefuse` 同一条降级纪律)。 */
101
+ | {
102
+ op: "reapSettledAsks";
103
+ cutoffMs: number;
104
+ limit: number;
105
+ keepAnchors: string[];
106
+ } | {
107
+ op: "reapEmptyBatches";
108
+ cutoffMs: number;
109
+ limit: number;
99
110
  };
100
111
  export declare class FileApprovalAskStore implements ApprovalAskStore {
101
112
  /** 语义内核(转移判定的唯一真源)。本类对它只做两件事:重放、代理。 */
@@ -171,6 +182,10 @@ export declare class FileApprovalAskStore implements ApprovalAskStore {
171
182
  deferReconcile(askId: string, expectedState: AskState, expectedRev: number, nowMs: number): Promise<boolean>;
172
183
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
173
184
  deleteByTask(taskId: string): Promise<void>;
185
+ /** 留存律的问行半场(谓词在内核,见接口注)。空的一次(内核的纯读选择为空)不落账本、不碰内存;否则照常「先落账、再翻内存」。 */
186
+ reapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): Promise<number>;
187
+ /** 留存律的批行半场(同上)。 */
188
+ reapEmptyBatches(cutoffMs: number, limit: number): Promise<number>;
174
189
  listByState(state: AskState, limit: number): Promise<AskRow[]>;
175
190
  listPendingByTask(taskId: string, signal?: AbortSignal): Promise<AskRow[]>;
176
191
  listPendingBySession(sessionId: string, owner: string | null, signal?: AbortSignal): Promise<AskRow[]>;
@@ -21,6 +21,8 @@ const JOURNAL_OPS = [
21
21
  "deferReconcile",
22
22
  "resolveProvisional",
23
23
  "deleteByTask",
24
+ "reapSettledAsks",
25
+ "reapEmptyBatches",
24
26
  ];
25
27
  const JOURNAL_OP_WORDS = new Set(JOURNAL_OPS);
26
28
  export const JOURNAL_OP_TABLE_GATE = true;
@@ -159,6 +161,11 @@ export class FileApprovalAskStore {
159
161
  refuse("call record without a clock reading");
160
162
  if (probe.op === "claimTerminal" && !JOURNAL_V1_CLAIM_INTENT_WORDS.has(String(raw.intent)))
161
163
  refuse("claimTerminal intent outside this build's journal set (a newer server wrote it)");
164
+ if (probe.op === "reapSettledAsks") {
165
+ const anchors = raw.keepAnchors;
166
+ if (!Array.isArray(anchors) || !anchors.every((a) => typeof a === "string"))
167
+ refuse("reapSettledAsks record without a string-array keepAnchors");
168
+ }
162
169
  const rec = raw;
163
170
  this.pinnedNow = rec.t;
164
171
  try {
@@ -197,6 +204,10 @@ export class FileApprovalAskStore {
197
204
  return this.core.resolveProvisional(rec.askId, rec.from, rec.to, rec.patch);
198
205
  case "deleteByTask":
199
206
  return this.core.deleteByTask(rec.taskId);
207
+ case "reapSettledAsks":
208
+ return this.core.reapSettledAsks(rec.cutoffMs, rec.limit, new Set(rec.keepAnchors));
209
+ case "reapEmptyBatches":
210
+ return this.core.reapEmptyBatches(rec.cutoffMs, rec.limit);
200
211
  default: {
201
212
  const unreachable = rec;
202
213
  throw new Error(`FileApprovalAskStore: unreachable journal op ${JSON.stringify(unreachable)}`);
@@ -244,6 +255,16 @@ export class FileApprovalAskStore {
244
255
  async deleteByTask(taskId) {
245
256
  return this.write({ op: "deleteByTask", taskId }, (c) => this.core.deleteByTask(c.taskId));
246
257
  }
258
+ async reapSettledAsks(cutoffMs, limit, keepAnchors) {
259
+ if (this.core.planReapSettledAsks(cutoffMs, limit, keepAnchors).length === 0)
260
+ return 0;
261
+ return this.write({ op: "reapSettledAsks", cutoffMs, limit, keepAnchors: [...keepAnchors].sort() }, (c) => this.core.reapSettledAsks(c.cutoffMs, c.limit, new Set(c.keepAnchors)));
262
+ }
263
+ async reapEmptyBatches(cutoffMs, limit) {
264
+ if (this.core.planReapEmptyBatches(cutoffMs, limit).length === 0)
265
+ return 0;
266
+ return this.write({ op: "reapEmptyBatches", cutoffMs, limit }, (c) => this.core.reapEmptyBatches(c.cutoffMs, c.limit));
267
+ }
247
268
  listByState(state, limit) {
248
269
  return this.core.listByState(state, limit);
249
270
  }
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * 🔴 **S-384:本类同时是 `FileApprovalAskStore` 的内核**(local 车道的持久形不再是「第二份手抄」——
8
8
  * 它把本类**原样**包起来,每次写前先把**调用本身**落 JSONL、重放时用同一只内核重放同一串调用)。
9
- * 为此本类多了三件、且**只**多这三件(全是数据面,零转移判定):
9
+ * 为此本类多了这几件、且**只**多这几件(全是数据面,零转移判定):
10
10
  * · {@link InMemoryApprovalAskStore.constructor} 的可注入钟 `now` —— 五个动词(decideAsk / claimTerminal /
11
11
  * expireAsk / bindBatch / abortBatch)原先直读 `Date.now()`,那是重放路上唯一的不确定源:同一串调用
12
12
  * 重放出来的 `updatedAtMs`/`decidedAtMs` 必须与当初**逐字节**相同,否则「重放等价」是假的。
@@ -16,6 +16,8 @@
16
16
  * · {@link InMemoryApprovalAskStore.exportRows} / {@link InMemoryApprovalAskStore.importRows} —— 压实用的
17
17
  * 整本快照口(纯搬行,不经任何谓词)。压实要把「一串调用」折成「一串终局行」,没有这一对就只能在
18
18
  * File 形里另写一套「哪些行还算数」的判据 —— 那才是第二份手抄。
19
+ * · {@link InMemoryApprovalAskStore.planReapSettledAsks} / {@link InMemoryApprovalAskStore.planReapEmptyBatches} —— 两只留存动词的
20
+ * **纯读选择**(删哪些行)。File 形据它判「这一次有没有东西可删」,空的一次不落账本;删除本身仍是接口动词,与选择共用这一只。
19
21
  *
20
22
  * 行为口径与 `SqlApprovalAskStore` 逐条对齐:
21
23
  * - ensureAsk 幂等 upsert(ask_id 已在 ⇒ 返回既有行,不改写;batch 行若不存在则以 OPEN 补齐)。
@@ -106,6 +108,21 @@ export declare class InMemoryApprovalAskStore implements ApprovalAskStore {
106
108
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
107
109
  deleteByTask(taskId: string): Promise<void>;
108
110
  getBatch(batchId: string): Promise<BatchRow | null>;
111
+ /**
112
+ * 留存律的问行半场(谓词见接口注,与 SQL twin 的 `DELETE … LIMIT` 同答)。方法体全同步(零 `await`)⇒ File 形把它当一次落账的调用
113
+ * 重放时逐字复现(cutoff / limit / 挂靠集都是调用实参,不读钟)。
114
+ */
115
+ reapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): Promise<number>;
116
+ /** 留存律的批行半场(谓词见接口注,与 SQL twin 同答;同上,全同步)。 */
117
+ reapEmptyBatches(cutoffMs: number, limit: number): Promise<number>;
118
+ /**
119
+ * 两只留存动词会删哪些行(纯读,零写)。File 形据此判「这一次有没有东西可删」—— 空的一次不落账本(否则每次清扫都往账本里写一条什么都
120
+ * 没做的调用)。与删除共用这一只选择,没有第二份谓词。选哪几只:按 `(updatedAtMs, id)` 升序取前 `limit` 只 —— 确定性只为重放,律本身
121
+ * 不依赖次序(SQL twin 不排序)。
122
+ */
123
+ planReapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): readonly string[];
124
+ /** {@link reapEmptyBatches} 的选择(同上)。 */
125
+ planReapEmptyBatches(cutoffMs: number, limit: number): readonly string[];
109
126
  }
110
127
  export type { AskDecision, AskState, BatchState };
111
128
  //# sourceMappingURL=approval-ask-store-memory.d.ts.map
@@ -1,6 +1,6 @@
1
- import { canAskTransition } from "../approval-ask-machine.js";
1
+ import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
2
2
  import { APPROVAL_ENDING_CAUSES } from "../approval-deny-reasons.js";
3
- import { askRowOnCall, assertDecideSettledBy, assertIdempotencyKeyShape, assertIntegralEpochMs, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./approval-ask-store-sql.js";
3
+ import { askRowOnCall, assertDecideSettledBy, assertIdempotencyKeyShape, assertIntegralEpochMs, assertReapBounds, ApprovalAskReadAbortedError, claimTargetState, POLICY_REFUSAL_DECISION, projectAskTerminalClaimTruth } from "./approval-ask-store-sql.js";
4
4
  function applyPatch(row, patch) {
5
5
  if (patch.decision !== undefined)
6
6
  row.decision = patch.decision;
@@ -359,5 +359,37 @@ export class InMemoryApprovalAskStore {
359
359
  const b = this.batches.get(batchId);
360
360
  return b ? { ...b } : null;
361
361
  }
362
+ async reapSettledAsks(cutoffMs, limit, keepAnchors) {
363
+ const ids = this.planReapSettledAsks(cutoffMs, limit, keepAnchors);
364
+ for (const id of ids)
365
+ this.asks.delete(id);
366
+ return ids.length;
367
+ }
368
+ async reapEmptyBatches(cutoffMs, limit) {
369
+ const ids = this.planReapEmptyBatches(cutoffMs, limit);
370
+ for (const id of ids)
371
+ this.batches.delete(id);
372
+ return ids.length;
373
+ }
374
+ planReapSettledAsks(cutoffMs, limit, keepAnchors) {
375
+ assertReapBounds(cutoffMs, limit);
376
+ const anchored = (r) => keepAnchors.has(r.taskId) || keepAnchors.has(r.sessionId) || keepAnchors.has(r.sourceTaskId);
377
+ return [...this.asks.values()]
378
+ .filter((r) => ASK_TERMINAL.has(r.state) && !r.provisional && r.updatedAtMs < cutoffMs && !anchored(r))
379
+ .sort((a, b) => a.updatedAtMs - b.updatedAtMs || (a.askId < b.askId ? -1 : a.askId > b.askId ? 1 : 0))
380
+ .slice(0, limit)
381
+ .map((r) => r.askId);
382
+ }
383
+ planReapEmptyBatches(cutoffMs, limit) {
384
+ assertReapBounds(cutoffMs, limit);
385
+ const occupied = new Set();
386
+ for (const r of this.asks.values())
387
+ occupied.add(r.batchId);
388
+ return [...this.batches.values()]
389
+ .filter((b) => b.updatedAtMs < cutoffMs && !occupied.has(b.batchId))
390
+ .sort((a, b) => a.updatedAtMs - b.updatedAtMs || (a.batchId < b.batchId ? -1 : a.batchId > b.batchId ? 1 : 0))
391
+ .slice(0, limit)
392
+ .map((b) => b.batchId);
393
+ }
362
394
  }
363
395
  //# sourceMappingURL=approval-ask-store-memory.js.map
@@ -268,6 +268,8 @@ export type DecideResult = {
268
268
  * 隐式截断 —— 两方言不同答;这里改成两方言同一句 TypeError(响亮,点名字段与值),铸点侧(`tool-approval.ts`
269
269
  * 的 deadline 铸点)负责给整数,本守卫只拦漏网。 */
270
270
  export declare function assertIntegralEpochMs(field: string, value: number): void;
271
+ /** 留存动词两只的入参门(三 twin 同判):cutoff 是整数毫秒、limit 是正整数(limit 在 SQL twin 里内联进语句)。 */
272
+ export declare function assertReapBounds(cutoffMs: number, limit: number): void;
271
273
  export declare function assertIdempotencyKeyShape(key: string): void;
272
274
  /** `settled_by` 列宽 = 这一格的唯一尺(两方言 DDL 与下面的形门读同一个常量,不手抄第二份)。 */
273
275
  export declare const MAX_SETTLED_BY_CHARS = 16;
@@ -510,6 +512,21 @@ export interface ApprovalAskStore {
510
512
  getByIdempotencyKey(taskId: string, key: string): Promise<AskRow | null>;
511
513
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
512
514
  deleteByTask(taskId: string): Promise<void>;
515
+ /**
516
+ * **审批账本留存律的问账半场之一:问行**(律、节律与「还能动的 run」一处成文在 `approval-ask-retention.ts`;维护 tick 是唯一调用方)。
517
+ * 删至多 `limit` 只**结了**的问行 —— `state ∈ ASK_TERMINAL` ∧ `provisional = false`(provisional 行还能被 {@link resolveProvisional} 改判,没结)
518
+ * ∧ `updatedAtMs < cutoffMs` ∧ 行挂靠的三格(`taskId` / `sessionId` / `sourceTaskId`)都不在 `keepAnchors`(还能动的东西的挂靠集:
519
+ * 它还会回读这些行 —— 会话授权的争议判据 {@link listByCall} 把「这次调用在腿内没有行」读成可以代答)。非终态行多老都不删(它们归对账器
520
+ * 与起服收敛)。只删不改。
521
+ * 一条语句:要么删了并交回删掉的行数,要么一行没删就抛。三形同答(InMemory = 内核;File 形把这次调用照常落成一条账本动词,
522
+ * 空的一次不落;SQL 两方言 = 一条 `DELETE … LIMIT`,行谓词,多副本同扫只是一方删到零行)。
523
+ */
524
+ reapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): Promise<number>;
525
+ /**
526
+ * **问账半场之二:批行**。删至多 `limit` 只批里已经**没有任何问行**、且自己的 `updatedAtMs < cutoffMs` 的批 —— 批是问的容器,问走光了它才走。
527
+ * 一条语句,同上;三形同答。
528
+ */
529
+ reapEmptyBatches(cutoffMs: number, limit: number): Promise<number>;
513
530
  /** 🔴 车1 追加(不在设计定稿 §4 的接口字面清单里):设计的 `ApprovalAskStore` 接口本身没有批读方法,
514
531
  * 但 §6 测试钉(3/6/7 条)要求断言批行的 state/boundAskId 变化——没有读口就无法验证 store 真做对了
515
532
  * 它自己承诺的事。加一个纯观测方法(不改变任何写路径行为),SQL 双方言 + InMemory 三形都实现,
@@ -667,6 +684,14 @@ export declare class SqlApprovalAskStore implements ApprovalAskStore {
667
684
  deferReconcile(askId: string, expectedState: AskState, expectedRev: number, nowMs: number): Promise<boolean>;
668
685
  resolveProvisional(askId: string, from: AskState, to: AskState, patch: AskTransitionPatch): Promise<boolean>;
669
686
  deleteByTask(taskId: string): Promise<void>;
687
+ /**
688
+ * 留存律的问行半场(谓词见接口注)。一条带 `LIMIT` 的 `DELETE`(PG 无 `DELETE … LIMIT` ⇒ `ctid IN (SELECT … LIMIT)`,与
689
+ * `permission-rule-store-sql.ts` 的清扫同形);终态闭集从 {@link ASK_TERMINAL} 展开成占位符,不在 SQL 里手抄态名;挂靠集对三格各一句
690
+ * (MySQL `NOT IN (?, …)` 三次展开、PG 同一只 `text[]` 参数用三次 `<> ALL`),空集不加。`limit` 先过整数门再内联(本仓常量,门只防将来的误用)。
691
+ */
692
+ reapSettledAsks(cutoffMs: number, limit: number, keepAnchors: ReadonlySet<string>): Promise<number>;
693
+ /** 留存律的批行半场(谓词见接口注):一条带 `LIMIT` 的 `DELETE`,批里有没有问 = 对问表的 `NOT EXISTS`。 */
694
+ reapEmptyBatches(cutoffMs: number, limit: number): Promise<number>;
670
695
  getBatch(batchId: string): Promise<BatchRow | null>;
671
696
  }
672
697
  /** MySQL-protocol(TiDB)绑定。 */
@@ -1,5 +1,5 @@
1
1
  import { mysqlDriver, pgDriver, dialectJsonEncoder } from "./sql-driver.js";
2
- import { canAskTransition } from "../approval-ask-machine.js";
2
+ import { ASK_TERMINAL, canAskTransition } from "../approval-ask-machine.js";
3
3
  import { APPROVAL_ENDING_CAUSES, causeOfAskRow } from "../approval-deny-reasons.js";
4
4
  import { isDupKeyError, isMissingColumnError } from "./sql-errors.js";
5
5
  import { decodeJsonColumn, foldJsonColumn, jsonColumnText, readJsonObjectColumn, readTriStateJsonValueColumn } from "./sql-json-column.js";
@@ -9,6 +9,11 @@ export function assertIntegralEpochMs(field, value) {
9
9
  throw new TypeError(`approval-ask store: ${field} must be an integer epoch-ms (BIGINT column), got ${String(value)}`);
10
10
  }
11
11
  }
12
+ export function assertReapBounds(cutoffMs, limit) {
13
+ assertIntegralEpochMs("cutoffMs", cutoffMs);
14
+ if (!Number.isSafeInteger(limit) || limit <= 0)
15
+ throw new Error(`approval-ask-store: reap limit must be a positive integer, got ${String(limit)}`);
16
+ }
12
17
  export function assertIdempotencyKeyShape(key) {
13
18
  if (key.length === 0 || key.length > 255 || key.trim() !== key) {
14
19
  throw new Error(`invalid idempotency key: must be 1-255 chars with no leading/trailing whitespace (got ${JSON.stringify(key.slice(0, 32))}${key.length > 32 ? "…" : ""}, length ${key.length})`);
@@ -722,6 +727,37 @@ export class SqlApprovalAskStore {
722
727
  await this.db.query(this.q(`DELETE FROM ${APPROVAL_ASK_TABLE} WHERE task_id = ?`, `DELETE FROM ${APPROVAL_ASK_TABLE} WHERE task_id = $1`), [taskId]);
723
728
  await this.db.query(this.q(`DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE task_id = ?`, `DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE task_id = $1`), [taskId]);
724
729
  }
730
+ async reapSettledAsks(cutoffMs, limit, keepAnchors) {
731
+ assertReapBounds(cutoffMs, limit);
732
+ const terminal = [...ASK_TERMINAL];
733
+ const keep = [...keepAnchors];
734
+ const dialect = this.db.dialect;
735
+ let n = 0;
736
+ const statePh = terminal.map(() => ph(dialect, ++n)).join(", ");
737
+ const cutPh = ph(dialect, ++n);
738
+ const ANCHOR_COLUMNS = ["task_id", "session_id", "source_task_id"];
739
+ let keepSql = "";
740
+ let keepParams = [];
741
+ if (keep.length > 0 && dialect === "tidb") {
742
+ const list = keep.map(() => "?").join(", ");
743
+ keepSql = ANCHOR_COLUMNS.map((c) => ` AND ${c} NOT IN (${list})`).join("");
744
+ keepParams = ANCHOR_COLUMNS.flatMap(() => keep);
745
+ }
746
+ else if (keep.length > 0) {
747
+ const arr = ph(dialect, ++n);
748
+ keepSql = ANCHOR_COLUMNS.map((c) => ` AND ${c} <> ALL(${arr}::text[])`).join("");
749
+ keepParams = [keep];
750
+ }
751
+ const where = `state IN (${statePh}) AND provisional = 0 AND updated_at_ms < ${cutPh}${keepSql}`;
752
+ const params = [...terminal, cutoffMs, ...keepParams];
753
+ const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_ASK_TABLE} WHERE ${where} LIMIT ${limit}`, `DELETE FROM ${APPROVAL_ASK_TABLE} WHERE ctid IN (SELECT ctid FROM ${APPROVAL_ASK_TABLE} WHERE ${where} LIMIT ${limit})`), params);
754
+ return res.affected;
755
+ }
756
+ async reapEmptyBatches(cutoffMs, limit) {
757
+ assertReapBounds(cutoffMs, limit);
758
+ const res = await this.db.query(this.q(`DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE updated_at_ms < ? AND NOT EXISTS (SELECT 1 FROM ${APPROVAL_ASK_TABLE} a WHERE a.batch_id = ${APPROVAL_BATCH_TABLE}.batch_id) LIMIT ${limit}`, `DELETE FROM ${APPROVAL_BATCH_TABLE} WHERE ctid IN (SELECT b.ctid FROM ${APPROVAL_BATCH_TABLE} b WHERE b.updated_at_ms < $1 AND NOT EXISTS (SELECT 1 FROM ${APPROVAL_ASK_TABLE} a WHERE a.batch_id = b.batch_id) LIMIT ${limit})`), [cutoffMs]);
759
+ return res.affected;
760
+ }
725
761
  async getBatch(batchId) {
726
762
  const { rows } = await this.db.query(this.q(`SELECT ${BATCH_COLS} FROM ${APPROVAL_BATCH_TABLE} WHERE batch_id = ?`, `SELECT ${BATCH_COLS} FROM ${APPROVAL_BATCH_TABLE} WHERE batch_id = $1`), [batchId]);
727
763
  return rows[0] ? mapBatchRow(rows[0]) : null;
@@ -1,19 +1,79 @@
1
1
  import type { Pool as MysqlPool } from "mysql2/promise";
2
2
  import type { Pool as PgPool } from "pg";
3
- /** (a)(clay 拍 a+c,2026-07-27)tidb 方言的默认 per-blob 帽 = **6 MiB,TiDB 自己的默认
4
- * `txn-entry-size-limit`**。两堵墙真库实测(k3s8 tidb-test v7.5.1 默认配置,2026-07-27 探针):
5
- * 7MiB=`entry too large`(entry 墙,默认 6291456)、31MiB=同、32MiB=`max_allowed_packet`(包墙,
6
- * 64MiB ÷ ~2× 文本协议膨胀)——矮墙是 entry 的 6MiB,不是先前推测的包墙 ~31MiB。帽=诚实拒绝:
7
- * 超限 blob 在 query 之前 typed 拒(消费面 HTTP 413 / capture warn 拿到可行动信息);**帽拒绝的
8
- * 在默认配置下本来也落不进去 ⇒ 零回归**。调大过 entry 限/packet 的部署用 SNAPSHOT_BLOB_SQL_MAX_BYTES
9
- * 放宽(两方言都生效)。**pg 方言默认不设帽**(扩展协议无这两堵墙,不人为降档)。D-1 附件战役同墙
10
- * 教训:task-attachment-store.ts:19。 */
11
- export declare const SQL_BLOB_DEFAULT_MAX_BYTES = 6291456;
3
+ import { type SqlEngineFacts, type SqlEngineReading, type SqlWallSource, type SqlWallsReading } from "./sql-driver.js";
4
+ import { type Logger } from "../observability/logger.js";
5
+ /**
6
+ * 文本协议的 blob 膨胀律 —— mysql2 `query()` 把 Buffer 参数写成 `X'<hex>'`,一字节两个字符,所以承载一个 blob 的语句至少是它的 2 倍。
7
+ * 包墙折成「能装下多大的 blob」只用这一只常量,推导里不手写 `/ 2`。
8
+ */
9
+ export declare const SQL_TEXT_PROTOCOL_BLOB_INFLATION = 2;
10
+ /**
11
+ * 包墙下能装多大的 blob:语句里的载荷量({@link packetWallStatementBytes})再按 {@link SQL_TEXT_PROTOCOL_BLOB_INFLATION} 折半。
12
+ * blob 以十六进制字面进语句、有 2× —— 膨胀律是 blob 腿自己的,余量不是。
13
+ */
14
+ export declare function packetWallBlobCap(maxPacketBytes: number): number;
15
+ /**
16
+ * SQL 快照 blob 的**缺省**帽(运维没设 `SNAPSHOT_BLOB_SQL_MAX_BYTES` 时)的唯一推导 —— 入参是判官 `sqlWallsOf` 给的墙(不读事实、不判缺席、不看方言),**两堵墙取最小**:
17
+ * 行墙腿(`SqlEngineFacts.maxRowBytes`,TiDB:{@link rowWallPayloadBytes} 的 `blob` = 行墙 − blob 行的余量 1 KiB —— 按这一行的真实形算,不借 JSON 提交体行的 64 KiB)
18
+ * 与包墙腿(`SqlEngineFacts.maxPacketBytes`,InnoDB 与 TiDB 都有:{@link packetWallBlobCap} = ⌊(包墙 − 1 KiB 语句余量) ÷ 2⌋)。余量只在 `sql-driver.ts` 定义一次
19
+ * (墙 × 行的形各一只),提交体受理口(`http/request-guards.ts` `refuseSubmitBody`)读同一张表的 `json` 那一只,不各抄。
20
+ * 墙的 `null` 只表示「这堵墙不存在」;两堵都不存在(PG —— 扩展协议无这两堵墙 —— 或没有 SQL 后端)⇒ `undefined` 不设帽。
21
+ * 「事实本身缺席」不是这里的入参:店的写前判读的是借出连接自己的事实(恒已建立,见 {@link SqlConnectionReading}),缺席的读法只在提交受理口读池级读口时出现(判官 `sqlWallsOf` 给墙)。
22
+ * 显式旋钮是运维的策略帽,在本推导之上(见 {@link sqlBlobCapBytes})。
23
+ *
24
+ * 两堵墙的真库读数(clay 拍 a+c,2026-07-27;k3s8 tidb-test v7.5.1 默认配置探针):7 MiB / 31 MiB = `entry too large`(行墙,默认 6291456)、
25
+ * 32 MiB = `max_allowed_packet`(包墙,64 MiB ÷ ~2× 文本协议膨胀)。帽=诚实拒绝:超限 blob 在 query 之前 typed 拒(消费面 HTTP 413 / capture warn
26
+ * 拿到可行动信息);帽与墙之间只差这一行自己的几百字节(blob 行余量),帽之下的 blob 在缺省配置下落得进。D-1 附件战役同墙教训:`task-attachment-store.ts` 头注。
27
+ */
28
+ export declare function sqlBlobDefaultCapBytes(walls: SqlWallsReading): number | undefined;
29
+ /**
30
+ * 一个 SQL 快照 blob 的帽 + 它**从哪来**。`operator` = 运维显式设的 `SNAPSHOT_BLOB_SQL_MAX_BYTES`(策略帽);`reported` / `assumed` = 缺省推导里**定下这个帽的那条腿**的墙数字出处
31
+ * ({@link SqlWallSource}:库自己报的 / 读失败或事实缺席而退的文档缺省)。机读位同 413 拒体的 `wallSource` 词,旋钮形多一个 `operator`。
32
+ */
33
+ export interface SqlBlobCapReading {
34
+ readonly capBytes: number;
35
+ readonly source: "operator" | SqlWallSource;
36
+ }
37
+ /**
38
+ * 缺省帽的推导(唯一一处,{@link sqlBlobDefaultCapBytes} 是它的字节投影):两条腿各带自己那堵墙的出处,取最小的那条;两条一样小取行墙腿。两堵墙都不存在 ⇒ `undefined`。
39
+ */
40
+ export declare function sqlBlobDefaultCapOf(walls: SqlWallsReading): SqlBlobCapReading | undefined;
41
+ /**
42
+ * 一条**借出连接**的引擎读口:方言(装配事实)+ 它自己那次初始化回读的事实。与 {@link SqlEngineReading} 的差别只有一处 —— `facts` 恒在:连接是借出时才有的,
43
+ * 借出即已初始化({@link connectJudged} 交给判决的就是借出连接的 `sqlConnectionFacts`),所以「事实缺席」在这条读法上**不可表示**(编译期拒,不是运行期一个没人走的分支)。
44
+ * 事实缺席(池上还没建立过、或最近一次初始化被拒)只出现在读**池级**读口的地方 —— 提交受理口 —— 那一格的墙由判官 `sqlWallsOf` 给,不经过帽的推导。
45
+ */
46
+ export interface SqlConnectionReading {
47
+ readonly dialect: SqlEngineReading["dialect"];
48
+ readonly facts: SqlEngineFacts;
49
+ }
50
+ /**
51
+ * 一个 SQL 快照 blob 此刻的帽 —— **显式旋钮 ?? 判官给的墙走缺省推导**的唯一一只函数。唯一读者 = 店的写前判({@link SqlBlobBackend.putBlob}),传的读口({@link SqlConnectionReading})是
52
+ * **将要执行写入的那条连接**的(方言 + 它自己那次初始化回读的事实,墙经与提交受理口同一只判官 `sqlWallsOf`):包墙是连接的会话值,所以判决只在写入的那条连接上做,
53
+ * HTTP 面不替店另算一份帽。
54
+ */
55
+ export declare function sqlBlobCapOf(explicitCapBytes: number | undefined, connection: SqlConnectionReading): SqlBlobCapReading | undefined;
56
+ /** {@link sqlBlobCapOf} 的字节投影(同一个推导,不另读)。 */
57
+ export declare function sqlBlobCapBytes(explicitCapBytes: number | undefined, connection: SqlConnectionReading): number | undefined;
58
+ /**
59
+ * 这一拒是不是**那条连接的包墙**定的:帽是缺省推导出的(不是运维旋钮)、那条连接有包墙、且包墙腿的折算恰是这个帽(包墙腿是最矮的一腿,或与行墙腿并列)。
60
+ * 只有这种拒才值得问「这条连接过时了吗」({@link connectJudged}) —— 旋钮的拒、行墙的拒与连接新旧无关。`walls` = 判出这个帽的那一份墙({@link sqlWallsOf})。
61
+ */
62
+ export declare function capBoundByPacketWall(cap: SqlBlobCapReading, walls: SqlWallsReading): boolean;
63
+ /**
64
+ * 一次 blob 超帽的拒句 —— **唯一的组句器**(店的 typed {@link BlobTooLargeError} 调它;`PUT /sync/blobs` 的 413 原样转出它的句子):句子按帽的出处({@link SqlBlobCapReading.source})
65
+ * 分三形,不把假定说成库的上限、也不把运维的旋钮说成库的上限。
66
+ * · `operator` —— 帽是运维设的 `SNAPSHOT_BLOB_SQL_MAX_BYTES`;
67
+ * · `reported` —— 帽由库报的两堵墙推出(单行上限与包墙÷文本协议膨胀的较小者);
68
+ * · `assumed` —— 库的上限没读到(事实还没建立,或这一堵读失败),帽按受支持引擎里最保守的缺省墙推出,稍后重试或由运维设旋钮。
69
+ */
70
+ export declare function sqlBlobCapRefusalText(byteLen: number, cap: SqlBlobCapReading): string;
12
71
  /** typed 判别错:blob 超过 SQL 店的承载帽。判别码供店侧结果映射/HTTP 面/日志使用——绝不能与
13
- * transient 写失败混在一类(那类是可重试的 502;这类重试永远不会成功)。 */
72
+ * transient 写失败混在一类(那类是可重试的 502;这类重试永远不会成功)。句子是 {@link sqlBlobCapRefusalText}(带帽的出处)。 */
14
73
  export declare class BlobTooLargeError extends Error {
74
+ readonly cap: SqlBlobCapReading;
15
75
  readonly code: "blob_too_large_for_sql";
16
- constructor(byteLen: number, capBytes: number);
76
+ constructor(byteLen: number, cap: SqlBlobCapReading);
17
77
  }
18
78
  /**
19
79
  * The content-addressed byte store. All four methods are keyed on the sha256 `hash` of the bytes:
@@ -47,16 +107,24 @@ export interface BlobBackend {
47
107
  export declare class SqlBlobBackend implements BlobBackend {
48
108
  private readonly dialect;
49
109
  private readonly pool;
50
- /** Effective per-blob cap: explicit ctor value wins; else tidb=SQL_BLOB_DEFAULT_MAX_BYTES, pg=uncapped. */
51
- private readonly capBytes;
110
+ /** Explicit per-blob cap (the operator's `SNAPSHOT_BLOB_SQL_MAX_BYTES`); absent ⇒ the derivation in {@link sqlBlobCapOf} over the walls of the connection each write borrows. */
111
+ private readonly explicitCapBytes;
52
112
  /** Blob-domain table ([ref] blob 域隔离:file-history 域用自己的表,reap/GC 不跨域)。代码内字面量,非用户输入。 */
53
113
  private readonly table;
54
- constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number, table?: string);
114
+ /** The write path's connection source: every `putBlob` borrows ONE connection from it (through {@link connectJudged}), judges on that connection's walls, writes on it. */
115
+ private readonly db;
116
+ /** Where a stale connection's replacement is logged ({@link connectJudged}); the boot assembly passes the service logger. */
117
+ private readonly log;
118
+ constructor(dialect: "tidb" | "pg", pool: MysqlPool | PgPool, maxBytes?: number, table?: string, log?: Pick<Logger, "warn">);
55
119
  /** The created_at-refreshing content-addressed upsert — VERBATIM the tidb/pg stores' `putBlobRow`. Refreshes
56
120
  * created_at even on a dedup hit so a re-used/just-written blob is "touched=now" and GC-grace-protected.
57
121
  * (a): an over-cap blob is REJECTED (typed {@link BlobTooLargeError}) BEFORE the driver ever sees the bytes —
58
122
  * the alternative is an inscrutable packet-limit error deep in the driver, and on the HTTP face a 502 that
59
- * invites the peer to retry a blob that can never land. */
123
+ * invites the peer to retry a blob that can never land.
124
+ * 🔴 The cap is judged ON THE CONNECTION THAT WRITES (S-1746): `max_allowed_packet` is a session value, fixed per
125
+ * connection, so the write borrows one connection through {@link connectJudged}, which reads ITS walls, judges, and — when
126
+ * the refusal is that connection's packet wall and the server's global wall is higher — replaces the stale connection and
127
+ * judges once more; the INSERT runs on the connection that passed. Never a pool-level reading, never a bare `pool.query`. */
60
128
  putBlob(hash: string, bytes: Uint8Array): Promise<void>;
61
129
  /** The bytes for `hash` (undefined if absent) — VERBATIM the tidb/pg stores' getBlob SELECT. A pool/query error
62
130
  * PROPAGATES (a transient DB fault is loud, not a silent "missing blob" — parity with the MinIO backend's throw). */
@@ -1,14 +1,54 @@
1
1
  import { createHash } from "node:crypto";
2
2
  import { presignS3Url, presignS3ListUrl } from "./s3-presign.js";
3
+ import { connectJudged, mysqlDriver, packetWallStatementBytes, pgDriver, rowWallPayloadBytes, sqlWallsOf } from "./sql-driver.js";
4
+ import { createLogger } from "../observability/logger.js";
3
5
  const BLOB_GC_GRACE_MS = 3_600_000;
4
- export const SQL_BLOB_DEFAULT_MAX_BYTES = 6_291_456;
6
+ export const SQL_TEXT_PROTOCOL_BLOB_INFLATION = 2;
7
+ export function packetWallBlobCap(maxPacketBytes) {
8
+ return Math.floor(packetWallStatementBytes(maxPacketBytes) / SQL_TEXT_PROTOCOL_BLOB_INFLATION);
9
+ }
10
+ export function sqlBlobDefaultCapBytes(walls) {
11
+ return sqlBlobDefaultCapOf(walls)?.capBytes;
12
+ }
13
+ export function sqlBlobDefaultCapOf(walls) {
14
+ const legs = [];
15
+ if (walls.maxRowBytes !== null)
16
+ legs.push({ capBytes: rowWallPayloadBytes(walls.maxRowBytes, "blob"), source: walls.sources.maxRowBytes });
17
+ if (walls.maxPacketBytes !== null)
18
+ legs.push({ capBytes: packetWallBlobCap(walls.maxPacketBytes), source: walls.sources.maxPacketBytes });
19
+ return legs.reduce((best, leg) => (best === undefined || leg.capBytes < best.capBytes ? leg : best), undefined);
20
+ }
21
+ export function sqlBlobCapOf(explicitCapBytes, connection) {
22
+ return explicitCapBytes !== undefined ? { capBytes: explicitCapBytes, source: "operator" } : sqlBlobDefaultCapOf(sqlWallsOf(connection));
23
+ }
24
+ export function sqlBlobCapBytes(explicitCapBytes, connection) {
25
+ return sqlBlobCapOf(explicitCapBytes, connection)?.capBytes;
26
+ }
27
+ export function capBoundByPacketWall(cap, walls) {
28
+ return cap.source !== "operator" && walls.maxPacketBytes !== null && packetWallBlobCap(walls.maxPacketBytes) === cap.capBytes;
29
+ }
30
+ export function sqlBlobCapRefusalText(byteLen, cap) {
31
+ const objectStorage = "configure MinIO object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot blobs";
32
+ const head = `blob of ${byteLen} bytes exceeds this deployment's SQL snapshot-blob cap (${cap.capBytes} bytes — `;
33
+ switch (cap.source) {
34
+ case "operator":
35
+ return `${head}set by the operator's SNAPSHOT_BLOB_SQL_MAX_BYTES) — ${objectStorage}, or raise SNAPSHOT_BLOB_SQL_MAX_BYTES if the database allows larger writes`;
36
+ case "reported":
37
+ return `${head}the smaller of the database's single-row limit (TiDB txn-entry-size-limit, 6MiB by default) and about half its max_allowed_packet (the text protocol doubles a blob inside the statement)) — ${objectStorage}, or raise SNAPSHOT_BLOB_SQL_MAX_BYTES if your deployment lifted those limits`;
38
+ case "assumed":
39
+ return `${head}the database's limits were not read — engine facts not read yet, or a limit could not be read — so the smallest documented default wall applies) — retry shortly, ${objectStorage}, or set SNAPSHOT_BLOB_SQL_MAX_BYTES to the limit your deployment actually has`;
40
+ default: {
41
+ const unhandled = cap.source;
42
+ throw new Error(`blob cap refusal without a sentence: ${String(unhandled)}`);
43
+ }
44
+ }
45
+ }
5
46
  export class BlobTooLargeError extends Error {
47
+ cap;
6
48
  code = "blob_too_large_for_sql";
7
- constructor(byteLen, capBytes) {
8
- super(`blob of ${byteLen} bytes exceeds the SQL snapshot-blob cap (${capBytes} bytes): a single-row INSERT this large ` +
9
- `hits TiDB's txn-entry-size-limit (default 6MiB) or the mysql-protocol max_allowed_packet (~2x text-protocol ` +
10
- `inflation). Configure object storage (MINIO_ENDPOINT/MINIO_ACCESS_KEY/MINIO_SECRET_KEY) for large snapshot ` +
11
- `blobs, or override the cap with SNAPSHOT_BLOB_SQL_MAX_BYTES if your deployment raised those limits.`);
49
+ constructor(byteLen, cap) {
50
+ super(sqlBlobCapRefusalText(byteLen, cap));
51
+ this.cap = cap;
12
52
  this.name = "BlobTooLargeError";
13
53
  }
14
54
  }
@@ -26,22 +66,35 @@ async function mapBounded(items, limit, fn) {
26
66
  export class SqlBlobBackend {
27
67
  dialect;
28
68
  pool;
29
- capBytes;
69
+ explicitCapBytes;
30
70
  table;
31
- constructor(dialect, pool, maxBytes, table = "snapshot_blob") {
71
+ db;
72
+ log;
73
+ constructor(dialect, pool, maxBytes, table = "snapshot_blob", log) {
32
74
  this.dialect = dialect;
33
75
  this.pool = pool;
34
- this.capBytes = maxBytes ?? (dialect === "tidb" ? SQL_BLOB_DEFAULT_MAX_BYTES : Number.POSITIVE_INFINITY);
76
+ this.explicitCapBytes = maxBytes;
35
77
  this.table = table;
78
+ this.db = dialect === "tidb" ? mysqlDriver(pool) : pgDriver(pool);
79
+ this.log = log ?? createLogger();
36
80
  }
37
81
  async putBlob(hash, bytes) {
38
- if (bytes.byteLength > this.capBytes)
39
- throw new BlobTooLargeError(bytes.byteLength, this.capBytes);
40
- if (this.dialect === "tidb") {
41
- await this.pool.query(`INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
82
+ const judged = await connectJudged(this.db, (facts) => {
83
+ const connection = { dialect: this.dialect, facts };
84
+ const cap = sqlBlobCapOf(this.explicitCapBytes, connection);
85
+ if (cap === undefined || bytes.byteLength <= cap.capBytes)
86
+ return undefined;
87
+ return { cap, packetBound: capBoundByPacketWall(cap, sqlWallsOf(connection)) };
88
+ }, (refusal) => refusal.packetBound, this.log);
89
+ if (judged.refusal !== undefined)
90
+ throw new BlobTooLargeError(bytes.byteLength, judged.refusal.cap);
91
+ try {
92
+ await judged.conn.query(this.dialect === "tidb"
93
+ ? `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES (?,?,?,?) ON DUPLICATE KEY UPDATE created_at = VALUES(created_at)`
94
+ : `INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
42
95
  }
43
- else {
44
- await this.pool.query(`INSERT INTO ${this.table} (blob_hash, byte_len, bytes, created_at) VALUES ($1,$2,$3,$4) ON CONFLICT (blob_hash) DO UPDATE SET created_at = EXCLUDED.created_at`, [hash, bytes.byteLength, Buffer.from(bytes), new Date()]);
96
+ finally {
97
+ judged.conn.release();
45
98
  }
46
99
  }
47
100
  async getBlob(hash) {