@sema-agent/server 7.43.0 → 7.44.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 (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,28 +1,14 @@
1
1
  import { SHARED_MEMORY_READ_CAP_BYTES, SharedMemoryStoreError, } from "@sema-agent/core";
2
2
  import { mysqlDriver, pgDriver } from "./sql-driver.js";
3
3
  import { isDupKeyError } from "./sql-errors.js";
4
- /** 唯一键冲突的方言判别 —— 判据属主 = `sql-errors.ts`(A-032 P1-①:本站点原是全仓唯一的硬化形,
5
- * 收编时把它**升格成属主**,其余六处按它对齐;`Reflect.get` 走原型链的理由见属主头注)。
6
- * 只用来把并发登记撞 PK 翻成一句人话,不让驱动错误对象漏给调用方。 */
7
4
  const isDupKey = isDupKeyError;
8
- /** Per-org plane state (`connecting` / `unavailable` / `connected`). One row per org scope. */
9
5
  export const SHARED_MEMORY_SCOPE_TABLE = "shared_memory_scope";
10
- /** The model-facing store registry: one row per browsable library. */
11
6
  export const SHARED_MEMORY_STORE_TABLE = "shared_memory_store";
12
- /** The documents. One row per (store, path). */
13
7
  export const SHARED_MEMORY_ENTRY_TABLE = "shared_memory_entry";
14
- /** core's store-id rule, restated on the WRITE side: `/^[A-Za-z0-9_.-]{1,64}$/` and no `__` (the
15
- * protocol table's namespace separator). A row failing it would be dropped from every listing — an id
16
- * the model cannot echo back is worse than an absent one — so we refuse to store it in the first place. */
17
8
  const STORE_ID_RE = /^[A-Za-z0-9_.-]{1,64}$/;
18
- /** core's org scope shape, shared with org-memory-admission. */
19
9
  const ORG_SCOPE_RE = /^org:\S{1,185}$/;
20
- /** core's FORBIDDEN_PATH_CHARS, restated. A path carrying any of these is dropped by core's listing gate. */
21
10
  const FORBIDDEN_PATH_CHARS = /[\p{Cc}\p{Cf}\p{Co}\p{Cn}\p{Default_Ignorable_Code_Point}\u2028\u2029\\]/u;
22
- /** core's DOCUMENT_SUFFIXES, restated. */
23
11
  const DOCUMENT_SUFFIXES = [".md", ".txt", ".json", ".jsonl"];
24
- /** The `path` column width. core's own bound is 1024 UTF-16 units; the column is the tighter of the two
25
- * and is enforced here so an over-long path fails LOUDLY instead of being truncated by the engine. */
26
12
  const PATH_MAX_CHARS = 512;
27
13
  const DESCRIPTION_MAX_CHARS = 512;
28
14
  const DEFAULT_MAX_LIST_LIMIT = 500;
@@ -36,11 +22,6 @@ export function assertScopeKey(scopeKey) {
36
22
  throw new Error(`shared-memory scope key ${JSON.stringify(scopeKey)} is not usable: it must match /^org:\\S{1,185}$/`);
37
23
  }
38
24
  }
39
- /**
40
- * WRITE-side document-path admission. Deliberately at least as strict as core's READ-side listing gate
41
- * (`isListablePath`): a row core would silently drop is refused here where an operator can see it. The
42
- * ONE rule that is stricter on purpose is the non-BMP refusal — see the header's ORDERING section.
43
- */
44
25
  export function assertDocumentPath(path) {
45
26
  const refuse = (why) => {
46
27
  throw new Error(`shared-memory document path ${JSON.stringify(path)} is not usable: ${why}`);
@@ -57,27 +38,17 @@ export function assertDocumentPath(path) {
57
38
  const last = segments.at(-1);
58
39
  if (last === undefined || !DOCUMENT_SUFFIXES.some((s) => last.endsWith(s)))
59
40
  refuse(`the filename must end in ${DOCUMENT_SUFFIXES.join(", ")}`);
60
- // Non-BMP refusal: SQL orders by code point, core's cursor progress check compares in UTF-16 code
61
- // units, and the two disagree ONLY above the BMP. Refusing here keeps "the listing is complete" a
62
- // structural fact instead of a probabilistic one.
63
41
  for (const ch of path) {
64
42
  if (ch.codePointAt(0) > 0xffff)
65
43
  refuse("it carries a supplementary-plane character, which would make SQL ordering and core's UTF-16 cursor ordering disagree");
66
44
  }
67
45
  }
68
- /** `path` values that start with `prefix` live in `[prefix, succ(prefix))`. `undefined` upper bound ⇒
69
- * the range degenerates to `>= prefix`, which is still a legal SUPERSET (core re-filters), just wider. */
70
46
  function prefixUpperBound(prefix) {
71
47
  const lastUnit = prefix.charCodeAt(prefix.length - 1);
72
- // Stop short of the surrogate block: incrementing into 0xD800 would build a lone surrogate, which is
73
- // not encodable as UTF-8 and would be rejected by both drivers.
74
48
  if (!Number.isFinite(lastUnit) || lastUnit >= 0xd7ff)
75
49
  return undefined;
76
50
  return prefix.slice(0, -1) + String.fromCharCode(lastUnit + 1);
77
51
  }
78
- /** `unavailable` beats `connecting` beats `connected`. A caller whose orgs are PARTLY down is told the
79
- * plane is down rather than served a silently partial store set — a partial set is indistinguishable
80
- * from "your team's library has these three documents", which is the failure this state exists to name. */
81
52
  function foldPlaneState(states) {
82
53
  let sawConnecting = false;
83
54
  for (const state of states) {
@@ -93,8 +64,6 @@ function foldPlaneState(states) {
93
64
  }
94
65
  return sawConnecting ? "connecting" : "connected";
95
66
  }
96
- /** The stored word, re-read. An unknown word in the column is a corrupted/foreign write: refuse the
97
- * whole snapshot rather than guess (this is the authorization/availability axis — no silent default). */
98
67
  function planeStateOf(raw) {
99
68
  const word = String(raw);
100
69
  switch (word) {
@@ -115,12 +84,6 @@ function throwIfAborted(signal) {
115
84
  const reason = signal.reason;
116
85
  throw reason instanceof Error ? reason : new Error("shared-memory read aborted");
117
86
  }
118
- /**
119
- * 回滚,但**绝不让回滚的失败顶掉主错**(codex 轮2 收尾建议)。裸 `await conn.rollback()` 放在 catch 里,
120
- * 一旦连接已经坏掉(回滚本身抛),抛出去的就变成了回滚错误,真正的失败原因当场丢失 —— 那是排障时最贵的
121
- * 一种信息损失。这里吞的只有**次要**错误,而且吞得出声(warn 带上主错);连接由调用方的 finally 归还,
122
- * 池会在下次取用时体检它。
123
- */
124
87
  async function rollbackPreservingError(conn, primary) {
125
88
  try {
126
89
  await conn.rollback();
@@ -130,16 +93,11 @@ async function rollbackPreservingError(conn, primary) {
130
93
  }
131
94
  }
132
95
  function storeGone(storeId) {
133
- // core folds `store_not_found` to "The memory store was not found — it may not be provisioned yet."
134
- // The alternative (an empty listing) would read to the model as "your team's library is empty".
135
96
  return new SharedMemoryStoreError("store_not_found", { message: `shared memory store ${JSON.stringify(storeId)} is no longer provisioned` });
136
97
  }
137
98
  function rowToStoreInfo(r) {
138
99
  return { id: String(r.store_id), description: String(r.description), writable: Boolean(Number(r.writable)) };
139
100
  }
140
- /** 列举行。`sizeBytes` 取 `size_bytes` 列 —— 这是**展示用提示**,不是安全判据:该列独立可写,一个外来
141
- * 写者可以让它与真实长度不符。任何**决定**(读帽)都必须现算 `OCTET_LENGTH(content)`(见 readDocument);
142
- * 列举不现算是刻意的取舍:对一整窗 LONGTEXT 逐行算字节等于把每次列举变成全文扫描。 */
143
101
  function rowToEntry(r) {
144
102
  const updatedAt = strOrNull(r.updated_at_iso);
145
103
  return {
@@ -148,10 +106,6 @@ function rowToEntry(r) {
148
106
  ...(updatedAt !== null ? { updatedAt } : {}),
149
107
  };
150
108
  }
151
- /**
152
- * The dual-dialect implementation. Every statement is written out in BOTH texts at its call site
153
- * (design/158 A12 判据:方言差异必须显式) — the `q(tidbSql, pgSql)` helper never builds one text.
154
- */
155
109
  export class SqlSharedMemoryStore {
156
110
  db;
157
111
  authorizer;
@@ -166,31 +120,17 @@ export class SqlSharedMemoryStore {
166
120
  q(tidb, pg) {
167
121
  return this.db.dialect === "tidb" ? tidb : pg;
168
122
  }
169
- // ───────────────────────────── the core-facing half (read only) ─────────────────────────────
170
- /**
171
- * ONE registry-binding snapshot per tool call, as core's seam requires: the state, the store identity
172
- * set and each reader binding all come from this one call, so the set cannot change between "list the
173
- * stores" and "resolve one". Document state stays live — which is why a store can still vanish between
174
- * binding and read, the case {@link storeGone} names.
175
- */
176
123
  async snapshot(ctx, opts) {
177
124
  throwIfAborted(opts.signal);
178
125
  const grant = await this.authorizer.resolve(ctx.principal);
179
126
  if (grant.kind === "unavailable") {
180
- // Host-authored reason, bounded and folded to one line by core. NOT "no stores" — see header.
181
127
  return { state: "unavailable", message: `shared memory authorization is unavailable (${grant.reason})` };
182
128
  }
183
129
  const scopes = [...new Set(grant.scopes)];
184
130
  if (scopes.length === 0)
185
131
  return { state: "connected", stores: [] };
186
132
  throwIfAborted(opts.signal);
187
- // 🔴 codex 复审轮2 F3:盘状态与库登记必须来自**同一个数据库快照**。两条独立的池查询之间,一次
188
- // 导入可以把 scope 翻成 connecting 再发布新库 —— 请求于是看到"老状态 connected + 新库",一个
189
- // 数据库瞬间都不曾出现过的组合,而 connecting 这道门的全部意义正是拦住半截导入的库。
190
- // 事务成本(一次连接借出 + 两次往返)由 core 每个工具调用取一次快照的频次承担;这是授权/可见性
191
- // 面,一致性优先于省一次借出。
192
133
  const { scopeRows, storeRows } = await this.readBinding(scopes);
193
- // A scope with no row has never been provisioned — that is "connected with nothing in it", not a fault.
194
134
  const folded = foldPlaneState(scopeRows.map((r) => planeStateOf(r.state)));
195
135
  if (folded === "unavailable") {
196
136
  const message = strOrNull(scopeRows.find((r) => planeStateOf(r.state) === "unavailable")?.message);
@@ -202,23 +142,10 @@ export class SqlSharedMemoryStore {
202
142
  state: "connected",
203
143
  stores: storeRows.map((r) => ({
204
144
  info: rowToStoreInfo(r),
205
- // The reader is PINNED to (store, scope) taken at binding time: a store re-homed to another org
206
- // between snapshot and read cannot be reached through the stale binding (it reads as vanished).
207
145
  reader: this.readerFor(String(r.store_id), String(r.scope_key)),
208
146
  })),
209
147
  };
210
148
  }
211
- /**
212
- * 盘状态 + 库登记,**一个事务一个快照**(轮2 F3 修)。
213
- *
214
- * 🔴 A-010.11(两臂不对称,验真后修):此前 PG 臂显式抬隔离级别、**MySQL 臂只靠服务端默认值**,
215
- * 注里写的是「MySQL/TiDB 的默认隔离级别就是 REPEATABLE READ ⇒ 天然同快照」。那句话对**默认配置**
216
- * 为真,但 `transaction_isolation` 是可设的服务端/会话变量:一台跑 READ COMMITTED 的实例(托管
217
- * MySQL 的厂商默认、中间层代理、运维手改)会把「一个事务一个快照」悄悄降成「每条语句各自取快照」,
218
- * 而本方法**不会报任何错**——它只是读到一对撕裂的 scope/store 行。「靠默认值成立」不是结构保证。
219
- * 两臂现在都走 {@link SqlTxConn.beginRepeatableRead}(语句文本与**摆放位置**的方言分歧写在那里:
220
- * MySQL 必须在 BEGIN **之前**发、PG 必须在 BEGIN **之后**发)。
221
- */
222
149
  async readBinding(scopes) {
223
150
  const conn = await this.db.connect();
224
151
  try {
@@ -242,14 +169,9 @@ export class SqlSharedMemoryStore {
242
169
  read: (path, opts) => this.readDocument(storeId, scopeKey, path, opts),
243
170
  };
244
171
  }
245
- /** One keyset window. Always the PAGED shape — a large library must never be materialized per call. */
246
172
  async listDocuments(storeId, scopeKey, prefix, opts) {
247
173
  throwIfAborted(opts.signal);
248
174
  const limit = Math.max(1, Math.min(Math.floor(opts.limit ?? this.maxListLimit) || 1, this.maxListLimit));
249
- // The predicate set is genuinely dynamic (cursor and prefix are each optional and independent), so
250
- // the two texts are built from ONE clause list rather than written out twice — but the divergence
251
- // stays visible: `ph()` is the only thing that differs, and both texts are logged side by side by
252
- // the parity harness. Column list and ORDER BY are byte-identical on both dialects.
253
175
  const clauses = [];
254
176
  const params = [];
255
177
  const bind = (value, clause) => {
@@ -257,27 +179,19 @@ export class SqlSharedMemoryStore {
257
179
  const position = params.length;
258
180
  clauses.push((ph) => clause(ph(position)));
259
181
  };
260
- // 🔴 codex 复审轮1 F4(真缺陷,已红先复现):列举必须**在主查询里**要求绑定时的 scope,不能只在
261
- // 空结果时补查。原式只按 store_id 过滤 ⇒ 一个 id 被回收给另一个 org 之后,旧 reader 只要新属主有
262
- // 文档就能列出对方的 path/大小/时间(正文读那条腿一直核 scope,所以这是一次纯元数据泄漏——更隐蔽)。
263
182
  bind(scopeKey, (p) => `s.scope_key = ${p}`);
264
183
  bind(storeId, (p) => `e.store_id = ${p}`);
265
- // EXCLUSIVE lower bound — core's cursor vocabulary, unchanged.
266
184
  if (opts.cursor !== undefined)
267
185
  bind(opts.cursor, (p) => `e.path > ${p}`);
268
186
  if (prefix !== undefined && prefix !== "" && prefix !== "/") {
269
187
  bind(prefix, (p) => `e.path >= ${p}`);
270
188
  const upper = prefixUpperBound(prefix);
271
- // Absent upper bound ⇒ the range degenerates to `>= prefix`: a WIDER superset, which core
272
- // re-filters. Never a miss, so this arm needs no compensating knob.
273
189
  if (upper !== undefined)
274
190
  bind(upper, (p) => `e.path < ${p}`);
275
191
  }
276
192
  const whereFor = (ph) => clauses.map((c) => c(ph)).join(" AND ");
277
193
  const limitPosition = params.length + 1;
278
194
  const from = `FROM ${SHARED_MEMORY_ENTRY_TABLE} e JOIN ${SHARED_MEMORY_STORE_TABLE} s ON s.store_id = e.store_id`;
279
- // Fetch one past the window: the extra row IS the "more remain" evidence, so `nextCursor` is a fact
280
- // rather than a guess (core consumes it as a HAS-MORE declaration).
281
195
  const res = await this.db.query(this.q(`SELECT e.path AS path, e.size_bytes AS size_bytes, e.updated_at_iso AS updated_at_iso ${from} WHERE ${whereFor(() => "?")} ORDER BY e.path LIMIT ?`, `SELECT e.path AS path, e.size_bytes AS size_bytes, e.updated_at_iso AS updated_at_iso ${from} WHERE ${whereFor((n) => `$${n}`)} ORDER BY e.path LIMIT $${limitPosition}`), [...params, limit + 1]);
282
196
  if (res.rows.length === 0 && !(await this.storeExists(storeId, scopeKey)))
283
197
  throw storeGone(storeId);
@@ -286,21 +200,8 @@ export class SqlSharedMemoryStore {
286
200
  const last = window.at(-1);
287
201
  return { entries: window, ...(hasMore && last !== undefined ? { nextCursor: last.path } : {}) };
288
202
  }
289
- /** `null` = the document does not exist. Absence is a VALUE here; only a VANISHED STORE is an error. */
290
203
  async readDocument(storeId, scopeKey, path, opts) {
291
204
  throwIfAborted(opts.signal);
292
- // One statement answers both questions: the LEFT JOIN keeps the store row even when the document is
293
- // absent, so zero rows means the STORE is gone (error) and a row with a NULL path means the DOCUMENT
294
- // is gone (value). Two separate SELECTs would leave a window where they disagree.
295
- //
296
- // 🔴 codex 轮1 F2b + 轮2 F1:`content` 走一层 CASE 门,超帽的行**在数据库里就不发字节** —— 否则一份
297
- // LONGTEXT 级的文档每次读都要整个物化进进程内存,而它到了 core 那里横竖是被拒的。写面已经把帽挡在
298
- // 入库(见 putDocument),这道是第二层:接管旧版/别的写者留下的行。
299
- //
300
- // 判据取 `OCTET_LENGTH(e.content)` 而**不是** `size_bytes` 列(轮2 修):那一列是独立可写的元数据,
301
- // 没有任何约束把它绑在真实内容长度上。**低报**方向才是危险的那个 —— 一行"小 size_bytes + 巨大
302
- // content"会让 CASE 门整个失效并把全文物化。真实字节数由引擎现算,元数据说什么都不影响安全判定
303
- // (`size_bytes` 因此降级为纯展示用的列举提示,见 rowToEntry)。
304
205
  const res = await this.db.query(this.q(`SELECT e.path AS path, OCTET_LENGTH(e.content) AS content_bytes, CASE WHEN OCTET_LENGTH(e.content) <= ? THEN e.content ELSE NULL END AS content, e.updated_at_iso AS updated_at_iso
305
206
  FROM ${SHARED_MEMORY_STORE_TABLE} s
306
207
  LEFT JOIN ${SHARED_MEMORY_ENTRY_TABLE} e ON e.store_id = s.store_id AND e.path = ?
@@ -314,25 +215,18 @@ export class SqlSharedMemoryStore {
314
215
  if (strOrNull(row.path) === null)
315
216
  return null;
316
217
  if (Number(row.content_bytes) > SHARED_MEMORY_READ_CAP_BYTES) {
317
- // A typed refusal, not a truncation and not a `null`: core folds `refused` with the host reason,
318
- // and the model is told the document exists but cannot be served — which is true. The byte count
319
- // in the message is the ENGINE's, so it is right even when the metadata column lies.
320
218
  throw new SharedMemoryStoreError("refused", {
321
219
  reason: "document_too_large",
322
220
  message: `shared memory document ${JSON.stringify(path)} is ${String(row.content_bytes)} bytes, over the ${SHARED_MEMORY_READ_CAP_BYTES}-byte read cap`,
323
221
  });
324
222
  }
325
223
  const updatedAt = strOrNull(row.updated_at_iso);
326
- // Content is returned code-unit EXACT: no normalization, no scrubbing. core owns sanitizing, and a
327
- // provider that pre-sanitized would make the model read different bytes than the library holds.
328
224
  return { content: String(row.content), ...(updatedAt !== null ? { updatedAt } : {}) };
329
225
  }
330
226
  async storeExists(storeId, scopeKey) {
331
227
  const res = await this.db.query(this.q(`SELECT store_id FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = ? AND scope_key = ?`, `SELECT store_id FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = $1 AND scope_key = $2`), [storeId, scopeKey]);
332
228
  return res.rows.length > 0;
333
229
  }
334
- // ───────────────────────── the DEPLOYMENT admin half (never model-facing) ─────────────────────────
335
- /** Declare an org's plane state. Absent row ⇒ `connected` (a never-provisioned org simply has no stores). */
336
230
  async putScopeState(scopeKey, state, message) {
337
231
  assertScopeKey(scopeKey);
338
232
  const now = this.now();
@@ -340,18 +234,6 @@ export class SqlSharedMemoryStore {
340
234
  ON DUPLICATE KEY UPDATE state = VALUES(state), message = VALUES(message), updated_at_ms = VALUES(updated_at_ms)`, `INSERT INTO ${SHARED_MEMORY_SCOPE_TABLE} (scope_key, state, message, updated_at_ms) VALUES ($1, $2, $3, $4)
341
235
  ON CONFLICT (scope_key) DO UPDATE SET state = EXCLUDED.state, message = EXCLUDED.message, updated_at_ms = EXCLUDED.updated_at_ms`), [scopeKey, state, message ?? null, now]);
342
236
  }
343
- /**
344
- * Register (or re-describe) one browsable store.
345
- *
346
- * 🔴 codex 复审轮1 F1(真缺陷,已红先复现):**属主 org 不可变**。原式是一条 upsert,`scope_key` 跟着
347
- * 被改写 —— 而文档行只按 `store_id` 编键、原地不动。于是一次撞 id 的导入(管理面手滑、重试、坏配置,
348
- * 零竞态)就把 A 的整座库连内容一起交给了 B,同时 A 失去它。所以这里改成:已存在的 id 只允许它自己的
349
- * org 更新描述/writable 位,换 org 一律响亮拒。真要迁库,那是一次需要显式审计的搬迁(先删再建,内容
350
- * 由搬迁方决定重导还是丢弃),不能是一条 upsert 的副作用。
351
- *
352
- * 事务 + 行锁:属主判定与写入之间不留窗口(同一把锁 {@link deleteStore} 也拿)。并发到达的两个新建
353
- * 会有一个撞 PK —— 那条也翻成同一句拒绝,不让驱动错误对象漏给调用方。
354
- */
355
237
  async putStore(record) {
356
238
  assertStoreId(record.storeId);
357
239
  assertScopeKey(record.scopeKey);
@@ -359,11 +241,10 @@ export class SqlSharedMemoryStore {
359
241
  throw new Error(`shared-memory store ${JSON.stringify(record.storeId)} description is ${record.description.length} chars, over the ${DESCRIPTION_MAX_CHARS} the column stores`);
360
242
  }
361
243
  const now = this.now();
362
- // 冲突事实在事务里查明、在事务外抛:避免"分支里 rollback + catch 里再 rollback"的双回滚吞异常形。
363
244
  let ownedBy;
364
245
  const conn = await this.db.connect();
365
246
  try {
366
- await conn.begin(); // 为什么不是 beginPessimistic:见 {@link SqlSharedMemoryStore} 头注的"事务与锁"段
247
+ await conn.begin();
367
248
  const existing = await conn.query(this.q(`SELECT scope_key FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = ? FOR UPDATE`, `SELECT scope_key FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = $1 FOR UPDATE`), [record.storeId]);
368
249
  const owner = existing.rows[0] === undefined ? undefined : String(existing.rows[0].scope_key);
369
250
  if (owner !== undefined && owner !== record.scopeKey) {
@@ -393,18 +274,6 @@ export class SqlSharedMemoryStore {
393
274
  throw new Error(`shared-memory store id ${JSON.stringify(record.storeId)} is already owned by ${JSON.stringify(ownedBy)} — a store's org is IMMUTABLE (re-homing it would hand every document already written under it to ${JSON.stringify(record.scopeKey)}). Delete it explicitly first if a transfer is really intended.`);
394
275
  }
395
276
  }
396
- /**
397
- * Write one document. Refuses when the store is not registered — an orphan document is unreachable
398
- * through the read face, so accepting it would be a silent no-op the writer never learns about, AND a
399
- * live landmine: whoever re-registers that id later inherits it.
400
- *
401
- * 🔴 codex 复审轮1 F2/F3 两修:
402
- * · 帽在**入库**成立 —— 超 `SHARED_MEMORY_READ_CAP_BYTES` 的文档 core 横竖恒拒(从不截断),存下来
403
- * 只是一行永远读不出来的死数据,却让每次读都要物化它。写面拒 = 那个帽在结构上为真。库里真有超大
404
- * 文档的话,导入方分块——这是它自己就得做的事,因为模型面本来也读不到整份。
405
- * · 存在性检查与写入**同事务同行锁**(与 {@link deleteStore} 争同一把):否则一次并发删除可以从检查
406
- * 与插入之间穿过去,留下一行孤儿文档 —— 被删的内容在 id 复用时复活。
407
- */
408
277
  async putDocument(record) {
409
278
  assertStoreId(record.storeId);
410
279
  assertScopeKey(record.scopeKey);
@@ -417,7 +286,7 @@ export class SqlSharedMemoryStore {
417
286
  let unregistered = false;
418
287
  const conn = await this.db.connect();
419
288
  try {
420
- await conn.begin(); // 为什么不是 beginPessimistic:见 {@link SqlSharedMemoryStore} 头注的"事务与锁"段
289
+ await conn.begin();
421
290
  const registered = await conn.query(this.q(`SELECT store_id FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = ? AND scope_key = ? FOR UPDATE`, `SELECT store_id FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = $1 AND scope_key = $2 FOR UPDATE`), [record.storeId, record.scopeKey]);
422
291
  if (registered.rows.length === 0) {
423
292
  unregistered = true;
@@ -441,20 +310,6 @@ export class SqlSharedMemoryStore {
441
310
  throw new Error(`shared-memory store ${JSON.stringify(record.storeId)} is not registered under ${JSON.stringify(record.scopeKey)} — either it was never created, or the id has since been recycled by another org (call putStore() first; a stale retry must NOT land in the new tenant's library)`);
442
311
  }
443
312
  }
444
- /**
445
- * Remove one document. Scope-fenced like {@link putDocument} (a stale delete must not reach a
446
- * successor tenant's library). Returns whether a row was actually there.
447
- *
448
- * 🔴 A-010.14(验真后修):此前围栏是**两条独立语句** —— `assertOwnedBy()` 先查一次登记表,然后另
449
- * 起一条 DELETE。它自称与 `putDocument` 对齐,但 `putDocument` 的检查与写是**同事务同行锁**
450
- * (`FOR UPDATE`),而这里的两条语句之间有一道真窗:登记检查通过之后、DELETE 发出之前,另一个 org
451
- * 完成 `deleteStore` + `putStore` 的 id 复用,这条 DELETE 就落进**继任租户**的库里删掉他们的文档。
452
- * 那正是 `putDocument` 的注里写明要挡住的那一形。现在逐字照它:同一事务、对登记行 `FOR UPDATE`、
453
- * 拿到锁之后再删。
454
- *
455
- * 「未登记 ⇒ 响亮抛」与「登记了但没这条文档 ⇒ 回 false」两件事仍然分家(调用方据此分支),所以
456
- * 不能收成一条 `DELETE … WHERE EXISTS(…)`:那样 `affected === 0` 会把两种结局压成同一个读数。
457
- */
458
313
  async deleteDocument(storeId, scopeKey, path) {
459
314
  assertStoreId(storeId);
460
315
  assertScopeKey(scopeKey);
@@ -462,7 +317,7 @@ export class SqlSharedMemoryStore {
462
317
  let deleted = false;
463
318
  const conn = await this.db.connect();
464
319
  try {
465
- await conn.begin(); // 为什么不是 beginPessimistic:见本类头注的"事务与锁"段(与 putDocument 同判)
320
+ await conn.begin();
466
321
  const registered = await conn.query(this.q(`SELECT store_id FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = ? AND scope_key = ? FOR UPDATE`, `SELECT store_id FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = $1 AND scope_key = $2 FOR UPDATE`), [storeId, scopeKey]);
467
322
  if (registered.rows.length === 0) {
468
323
  unregistered = true;
@@ -482,33 +337,17 @@ export class SqlSharedMemoryStore {
482
337
  conn.release();
483
338
  }
484
339
  if (unregistered) {
485
- // 文案逐字同 assertOwnedBy(调用方/测试按它对表);围栏的**执行面**改了,拒绝的**说法**没改。
486
340
  throw new Error(`shared-memory store ${JSON.stringify(storeId)} is not registered under ${JSON.stringify(scopeKey)} — either it does not exist, or the id has since been recycled by another org`);
487
341
  }
488
342
  return deleted;
489
343
  }
490
- // 属主围栏的共用断言 `assertOwnedBy()` 曾住在这里(轮3 F1)。A-010.14 把它**唯一**的消费者
491
- // (`deleteDocument`)改成「同事务 + FOR UPDATE」之后它就没有调用点了 —— 留着一个自带围栏语义却
492
- // 无人调用的私有方法,下一个人会以为「围栏在这儿,照着调就行」,而它恰恰是被判定为不够强的那一版。
493
- // 边界的如实说明随之搬进 `deleteDocument` / `putDocument`:两者**都不**覆盖同一个 org 内的
494
- // "删了又建"代际重用 —— 那不是跨租户面,真要挡住需要一枚不可变的 generation token。
495
- /**
496
- * De-register a store AND its documents, ATOMICALLY.
497
- *
498
- * 🔴 codex 复审轮1 F3(真缺陷):原式是两条独立语句。崩在中间会留下「登记着但被悄悄清空」的库,而
499
- * 并发的 {@link putDocument} 能从检查与插入之间穿过去留下孤儿行 —— 被删的内容会在这个 id 被重新登记时
500
- * 复活。现在两条删除同事务,并且**先拿注册行的行锁**:写面争的是同一把锁,两者因此串行。
501
- *
502
- * 🔴 轮3 F1 同族:必须带 `scopeKey` 围栏。id 删后可复用,而一条**迟到的** deleteStore 若只按 id 寻址,
503
- * 会把继任租户刚建好的库连内容一起抹掉 —— 一次歧义的重试变成一次跨租户的破坏性操作。
504
- */
505
344
  async deleteStore(storeId, scopeKey) {
506
345
  assertStoreId(storeId);
507
346
  assertScopeKey(scopeKey);
508
347
  let notOwned = false;
509
348
  const conn = await this.db.connect();
510
349
  try {
511
- await conn.begin(); // 为什么不是 beginPessimistic:见 {@link SqlSharedMemoryStore} 头注的"事务与锁"段
350
+ await conn.begin();
512
351
  const owned = await conn.query(this.q(`SELECT store_id FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = ? AND scope_key = ? FOR UPDATE`, `SELECT store_id FROM ${SHARED_MEMORY_STORE_TABLE} WHERE store_id = $1 AND scope_key = $2 FOR UPDATE`), [storeId, scopeKey]);
513
352
  if (owned.rows.length === 0) {
514
353
  notOwned = true;
@@ -532,13 +371,11 @@ export class SqlSharedMemoryStore {
532
371
  }
533
372
  }
534
373
  }
535
- /** MySQL-protocol (MySQL / TiDB) binding. */
536
374
  export class TiDBSharedMemoryStore extends SqlSharedMemoryStore {
537
375
  constructor(pool, authorizer, opts) {
538
376
  super(mysqlDriver(pool), authorizer, opts);
539
377
  }
540
378
  }
541
- /** PostgreSQL binding. */
542
379
  export class PgSharedMemoryStore extends SqlSharedMemoryStore {
543
380
  constructor(pool, authorizer, opts) {
544
381
  super(pgDriver(pool), authorizer, opts);
@@ -1,5 +1,4 @@
1
1
  import { pgSafeJsonStringify, pgProtocolJsonStringify } from "./pg-safe-json.js";
2
- /** mysql2 hands back `[rows]` for a SELECT and `[ResultSetHeader]` for a write. */
3
2
  function mysqlResult(r) {
4
3
  if (Array.isArray(r))
5
4
  return { rows: r, affected: 0 };
@@ -10,8 +9,6 @@ async function mysqlQuery(q, sql, params) {
10
9
  const [r] = (await q.query(sql, params ?? []));
11
10
  return mysqlResult(r);
12
11
  }
13
- /** MySQL-protocol (TiDB) driver. Transaction control uses mysql2's NATIVE methods — the historical
14
- * call shape the fake-pool unit suites script around. */
15
12
  export function mysqlDriver(pool) {
16
13
  return {
17
14
  dialect: "tidb",
@@ -24,8 +21,6 @@ export function mysqlDriver(pool) {
24
21
  beginPessimistic: async () => {
25
22
  await c.query("BEGIN PESSIMISTIC");
26
23
  },
27
- // Scope-less `SET TRANSACTION …` = next-transaction only, so it goes BEFORE the BEGIN and
28
- // leaves the pooled connection's session default untouched for whoever gets it next.
29
24
  beginRepeatableRead: async () => {
30
25
  await c.query("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
31
26
  await c.beginTransaction();
@@ -41,7 +36,6 @@ async function pgQuery(q, sql, params) {
41
36
  const res = await q.query(sql, params ?? []);
42
37
  return { rows: (res.rows ?? []), affected: res.rowCount ?? 0 };
43
38
  }
44
- /** PostgreSQL driver. Transaction control is issued as STATEMENTS (node-pg has no native verbs). */
45
39
  export function pgDriver(pool) {
46
40
  return {
47
41
  dialect: "pg",
@@ -56,8 +50,6 @@ export function pgDriver(pool) {
56
50
  beginPessimistic: async () => {
57
51
  await c.query("BEGIN");
58
52
  },
59
- // PG takes it INSIDE the transaction (and only before its first query) — the mirror image of
60
- // the MySQL arm above. Same verb, deliberately different placement.
61
53
  beginRepeatableRead: async () => {
62
54
  await c.query("BEGIN");
63
55
  await c.query("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ");
@@ -73,15 +65,6 @@ export function pgDriver(pool) {
73
65
  },
74
66
  };
75
67
  }
76
- /** design/158 redesign① — the "safe" JSON-column encoder DISPATCH (run-store/outcome-ledger's identical
77
- * ternary, collected): TiDB stores `JSON.stringify` verbatim; PG goes through `pgSafeJsonStringify`'s
78
- * protocol-byte defenses (a raw NUL/lone-surrogate would otherwise throw mid-write). Returns a 1-arg
79
- * encoder; call sites invoke `dialectJsonEncoder(this.db.dialect)(value)` inline — the dispatch is the
80
- * collected thing, not a cached field. */
81
68
  export const dialectJsonEncoder = (dialect) => dialect === "tidb" ? JSON.stringify : pgSafeJsonStringify;
82
- /** design/158 redesign① — the "protocol" JSON-column encoder DISPATCH (approval-store/checkpoint-store's
83
- * identical ternary, collected): TiDB stores `JSON.stringify` verbatim (the `label` arg is ignored on that
84
- * arm — TiDB has no protocol-byte envelope to name); PG goes through `pgProtocolJsonStringify(v, label)`,
85
- * whose `label` names the field in its error text on an unstorable-byte reject. */
86
69
  export const dialectProtocolJsonEncoder = (dialect) => dialect === "tidb" ? (v) => JSON.stringify(v) : pgProtocolJsonStringify;
87
70
  //# sourceMappingURL=sql-driver.js.map
@@ -1,24 +1,17 @@
1
- /** MySQL/TiDB `ER_DUP_ENTRY` 的数字 errno。 */
2
1
  export const MYSQL_ER_DUP_ENTRY_ERRNO = 1062;
3
- /** MySQL/TiDB `ER_DUP_ENTRY` 的字符串错误码(mysql2 的 `code`)。 */
4
2
  export const MYSQL_ER_DUP_ENTRY_CODE = "ER_DUP_ENTRY";
5
- /** PostgreSQL `unique_violation` 的 SQLSTATE。 */
6
3
  export const PG_UNIQUE_VIOLATION_SQLSTATE = "23505";
7
- /** 非对象(null / undefined / 字符串 / 数字)一律否——谓词自身绝不成为新的失败源。 */
8
4
  function keyOf(err, key) {
9
5
  if (err === null || typeof err !== "object")
10
6
  return undefined;
11
7
  return Reflect.get(err, key);
12
8
  }
13
- /** MySQL/TiDB 的唯一键冲突:`code === "ER_DUP_ENTRY"` **或** `errno === 1062`(同一件事的两种写法)。 */
14
9
  export function isMysqlDupKeyError(err) {
15
10
  return String(keyOf(err, "code")) === MYSQL_ER_DUP_ENTRY_CODE || Number(keyOf(err, "errno")) === MYSQL_ER_DUP_ENTRY_ERRNO;
16
11
  }
17
- /** PostgreSQL 的唯一键冲突:SQLSTATE `23505`(node-pg 挂在 `code` 上)。 */
18
12
  export function isPgUniqueViolation(err) {
19
13
  return String(keyOf(err, "code")) === PG_UNIQUE_VIOLATION_SQLSTATE;
20
14
  }
21
- /** 方言分派口——双方言店的调用形(`isDupKeyError(this.db.dialect, e)`)。 */
22
15
  export function isDupKeyError(dialect, err) {
23
16
  return dialect === "tidb" ? isMysqlDupKeyError(err) : isPgUniqueViolation(err);
24
17
  }
@@ -1,11 +1,3 @@
1
- /**
2
- * Backend-NEUTRAL SQL string escaping (design/158 A3 沉底).
3
- *
4
- * Lives in its own leaf so no adapter has to load another dialect's adapter module to get a pure
5
- * string transform (the PG stores used to runtime-import the TiDB tool-result store for exactly
6
- * this one function). Zero dialect deps by construction — keep it that way.
7
- */
8
- /** Escape SQL LIKE metacharacters (`\`, `%`, `_`) so a value is matched literally (default `\` escape char). */
9
1
  export function escapeLike(s) {
10
2
  return s.replace(/[\\%_]/g, (ch) => `\\${ch}`);
11
3
  }
@@ -1,17 +1,3 @@
1
- /**
2
- * Shared pure row-mapping helpers for the SQL store twins (design/158 S8 纯 helper 归位).
3
- *
4
- * These were duplicated verbatim across the pg-/tidb- store twin pairs; each store imports the flavor
5
- * it was written against (usually under its historical local alias, e.g. `parseJsonStrict as
6
- * parseJson`) so call sites and semantics are byte-identical to before.
7
- *
8
- * THREE parseJson flavors exist ON PURPOSE — they differ in what a corrupted/legacy JSON column
9
- * reads back as (lenient → null / strict → throw / fallback value), and the TWO nullable date→ISO
10
- * helpers differ in how a non-Date cell is rendered (String() passthrough vs `new Date()` re-parse).
11
- * Do NOT collapse them into one.
12
- */
13
- /** JSON column → value; a corrupted/legacy JSON column reads back as `null` instead of failing the
14
- * read. Non-strings pass through (node-pg parses jsonb → object already). */
15
1
  export function parseJsonLenient(v) {
16
2
  if (v == null)
17
3
  return null;
@@ -24,16 +10,11 @@ export function parseJsonLenient(v) {
24
10
  return null;
25
11
  }
26
12
  }
27
- /** JSON column → value; malformed JSON THROWS. For stores whose write arm keeps the column clean
28
- * (R7 终形:协议面拒绝式,库内恒干净,直读). Non-strings pass through (node-pg parses jsonb →
29
- * object already). */
30
13
  export function parseJsonStrict(v) {
31
14
  if (v == null)
32
15
  return null;
33
16
  return (typeof v === "string" ? JSON.parse(v) : v);
34
17
  }
35
- /** JSON column → value with a caller-supplied fallback for NULL/corrupted cells. Non-strings pass
36
- * through (node-pg parses jsonb → object already; tolerate a string just in case). */
37
18
  export function parseJsonOr(v, fallback) {
38
19
  if (v == null)
39
20
  return fallback;
@@ -46,20 +27,14 @@ export function parseJsonOr(v, fallback) {
46
27
  return fallback;
47
28
  }
48
29
  }
49
- /** NOT NULL datetime column → ISO string (drivers return Date; a string cell passes through via
50
- * String() — NOT re-parsed). */
51
30
  export function toIso(v) {
52
31
  return v instanceof Date ? v.toISOString() : String(v);
53
32
  }
54
- /** Nullable twin of {@link toIso} (string cells still pass through verbatim). */
55
33
  export function toIsoOrNull(v) {
56
34
  if (v == null)
57
35
  return null;
58
36
  return v instanceof Date ? v.toISOString() : String(v);
59
37
  }
60
- /** Nullable datetime column → ISO string, RE-PARSING a string cell via `new Date()` (normalizes a
61
- * raw DB datetime string like "2026-01-01 00:00:00" to ISO — unlike {@link toIsoOrNull}, which
62
- * passes it through unchanged). */
63
38
  export function normalizeIsoOrNull(v) {
64
39
  if (v == null)
65
40
  return null;