@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,42 +1,13 @@
1
- // TiDB (MySQL wire) dialect twin of `memory-engine-pg.ts` — the THIRD MemoryBackend implementation
2
- // (边界重切:core 养 File backend+seam+memoryBackendContract,SQL 方言归 service;先例=
3
- // TiDBRunStore/PgRunStore 方言孪生对)。Same tables, same contract, mysql2 posture throughout.
4
- //
5
- // 方言改造点(core 指引,与 Pg 版逐条对照):
6
- // - `$n` 占位符 → `?`;`jsonb` → `JSON`(mysql2 对 JSON 列返回已解析对象,fromJson 防御 string 形);
7
- // - `UPDATE/DELETE … WHERE rev = ? RETURNING` → MySQL 无 RETURNING:条件写后看 affectedRows;零行=
8
- // conflict,且 currentRev 必须 SELECT 现值(freshRev 语义——绝不把 pre-race SELECT 的旧值当 currentRev)。
9
- // ⚠️ affectedRows 语义(归因修正:mysql2 默认 flags 含 FOUND_ROWS ⇒ affectedRows=
10
- // MATCHED 行数;此前注释写反)。真库契约在真默认语义下全绿;下述 fresh-rev 比对分支让两种语义都正确:matched-but-
11
- // identical 也是 0——零行后用 freshRev 消歧(现 rev==新 rev ⇒ 内容已是目标态=幂等成功,非 conflict);
12
- // - add 路径 Pg 用 `INSERT … ON CONFLICT (id) DO UPDATE`(冲突靶=仅 PK)。MySQL 的 ON DUPLICATE KEY
13
- // UPDATE 会被 **任意** 唯一键触发——(scope,slug) 撞键时它会把竞争对手的行改写成本 entry 的 id(数据毁),
14
- // 不能用;拆成「不存在→INSERT / 已存在→UPDATE by id」,两条腿的 ER_DUP_ENTRY 都按键名分诊(PRIMARY=
15
- // 并发同 id add → 转 UPDATE 腿;uq_agent_memory_engine_entry_scope_slug=并发 slug 竞争 → 下一个 -n 后缀重试);
16
- // - unique violation 判定:ER_DUP_ENTRY(errno 1062),不是 Pg 的 23505;
17
- // - pg-mem 伪影 workaround(索引列 ANY 零行/重复占位符重复行)不抄——MySQL 本就走 IN-list,dedupe 保留
18
- // 是因为它同时是诚实语义(重复 key 问的是同一个 entry,契约锁死 once-per-entry);
19
- // - (scope, slug) UNIQUE 约束照立(竞态正确性:两实例各自探得 free 同时 INSERT,DB 约束是唯一仲裁);
20
- // - 表名与 Pg 版同名(两方言不会同库;与退役 MemoryStore 的 `agent_memory*` 面刻意不相交);
21
- // - v1 lexical only:embedder/vector 面无 TiDB 对应(Pg 的 pgvector rung 是 Pg 专属);构造签名保留
22
- // opts 但传入 embedder=fail-loud 拒绝,search 恒走 jaccard 词面,haystack 与 Pg/File 逐字节同构
23
- // (`${name ?? slug} ${description} ${body}`——memoryBackendContract 的 search 等价断言依赖)。
24
1
  import { jaccardDistance, termSet } from "@sema-agent/core";
25
2
  import { computeEntryRev, serializeEntryFile } from "@sema-agent/core";
26
3
  import { pgHasUnstorable } from "./pg-safe-json.js";
27
- import { assertMemoryScopeWidth, memoryEntryKeyWidthRefusal } from "./memory-key-guards.js"; // R5 批γ + #271 件1:scope/slug 写前宽守卫
28
- // #307 件6(core 5.46.0 design/336 §2.3-2):external-origin 不可变律 —— 与 Pg 孪生共用**一份**判据。
4
+ import { assertMemoryScopeWidth, memoryEntryKeyWidthRefusal } from "./memory-key-guards.js";
29
5
  import { ambiguousOriginRefusal, createBatchCommittedBaseline, exposureBandOf, exposureCarriage, originWhitewashRefusal, rememberBatchCommitted, distilledWhitewashRefusal, distilledCarriage, unknownPatchOpRefusal, } from "./memory-origin-law.js";
30
6
  import { isMysqlDupKeyError } from "./sql-errors.js";
31
- /** Table names (single source) — SAME names as PG_MEMORY_ENGINE_TABLES (the two dialects never share
32
- * one database), deliberately DISJOINT from the legacy `agent_memory*` MemoryStore plane. */
33
7
  export const TIDB_MEMORY_ENGINE_TABLES = {
34
8
  entry: "agent_memory_engine_entry",
35
9
  cursor: "agent_memory_engine_cursor",
36
10
  };
37
- /** Idempotent DDL for the entry plane (TiDB dialect). Call once at startup.
38
- * Key lengths: utf8mb4 = 4 bytes/char and the default index-key cap is 3072 bytes, so the
39
- * (scope, slug) unique key budget is scope 190 + slug 512 = 702 chars ⇒ 2808 bytes ≤ 3072. */
40
11
  export async function ensureTiDBMemoryEngineSchema(pool) {
41
12
  await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_ENGINE_TABLES.entry} (
42
13
  id VARCHAR(64) NOT NULL,
@@ -52,23 +23,16 @@ export async function ensureTiDBMemoryEngineSchema(pool) {
52
23
  KEY idx_agent_memory_engine_entry_scope (scope),
53
24
  UNIQUE KEY uq_agent_memory_engine_entry_scope_slug (scope, slug)
54
25
  ) COLLATE utf8mb4_bin`);
55
- // The (scope, slug) UNIQUE key above is CORRECTNESS, not perf (Pg 版同注):two
56
- // instances racing an add of one scope+slug (different ids) can both probe "free" and both insert;
57
- // the DB constraint is the only true arbiter and the add path retries the next suffix on violation.
58
26
  await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_ENGINE_TABLES.cursor} (
59
27
  scope VARCHAR(190) NOT NULL,
60
28
  \`cursor\` TEXT NOT NULL,
61
29
  PRIMARY KEY (scope)
62
30
  ) COLLATE utf8mb4_bin`);
63
31
  }
64
- /** 判据属主 = `sql-errors.ts`(A-032 P1-①;本站点旧形已是并集形,收编后语义不变)。 */
65
32
  const isDupEntry = isMysqlDupKeyError;
66
- /** ER_DUP_ENTRY messages name the violated key (`… for key 'tbl.PRIMARY'` / `… for key 'tbl.uq_agent_memory_engine_entry_scope_slug'`)
67
- * — the add path uses this to tell "id already exists (→ overwrite leg)" from "slug race (→ -n retry)". */
68
33
  function isPrimaryKeyDup(err) {
69
34
  return isDupEntry(err) && /for key '[^']*PRIMARY'/i.test(String(err.message ?? ""));
70
35
  }
71
- /** `?, ?, …` for a dynamic IN list (MySQL has no ANY(param); plain IN-list on both engines). */
72
36
  function inPlaceholders(count) {
73
37
  return Array.from({ length: count }, () => "?").join(", ");
74
38
  }
@@ -79,9 +43,7 @@ export class TiDBMemoryEngineBackend {
79
43
  pool;
80
44
  clock;
81
45
  history;
82
- /** Cumulative lost-row count — audit-line breakage must itself be auditable. */
83
46
  historyRowsLost = 0;
84
- /** design/81 rung indicator — TiDB v1 sits on the lexical floor (Pg twin parity surface). */
85
47
  get vectorMode() {
86
48
  return "lexical";
87
49
  }
@@ -101,7 +63,6 @@ export class TiDBMemoryEngineBackend {
101
63
  const [res] = await this.pool.query(sql, params);
102
64
  return res;
103
65
  }
104
- /** The search haystack — BYTE-EQUAL to the File/Pg backends' lexical haystack (contract equivalence). */
105
66
  haystackOf(slug, fm, body) {
106
67
  return `${fm.name ?? slug} ${fm.description ?? ""} ${body}`;
107
68
  }
@@ -117,9 +78,7 @@ export class TiDBMemoryEngineBackend {
117
78
  mtimeMs: Number(r.mtime_ms),
118
79
  rev: r.rev,
119
80
  sizeBytes: Number(r.size_bytes),
120
- // #319(design/336 §5.2,Pg 孪生同注):committed external-origin 事实随每一个头面;归一化同源。
121
81
  ...exposureCarriage(fm),
122
- // #337(conformance c38,Pg 孪生同注):distilled 事实(carrierRev + supersedes)随每一个头面。
123
82
  ...distilledCarriage(fm),
124
83
  };
125
84
  }
@@ -134,8 +93,6 @@ export class TiDBMemoryEngineBackend {
134
93
  };
135
94
  }
136
95
  async listHeaders(scopes) {
137
- // Dedupe (all three read paths): honest semantic — a caller repeating a key asks about ONE entry
138
- // (contract-locked once-per-entry; real IN-lists don't duplicate anyway).
139
96
  const uniq = [...new Set(scopes)];
140
97
  if (uniq.length === 0)
141
98
  return [];
@@ -147,18 +104,14 @@ export class TiDBMemoryEngineBackend {
147
104
  if (uniq.length === 0)
148
105
  return [];
149
106
  const rows = await this.selectRows(`SELECT id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes FROM ${TIDB_MEMORY_ENGINE_TABLES.entry} WHERE id IN (${inPlaceholders(uniq.length)}) ORDER BY id`, uniq);
150
- return rows.map((r) => this.entryFromRow(r)); // unknown ids: silently absent
107
+ return rows.map((r) => this.entryFromRow(r));
151
108
  }
152
109
  async search(query, scopes, opts) {
153
- // #319(design/336 §5.2,Pg 孪生同注):两带序 —— 未标记先,**带序在截断之前**。本方言只有词面档,
154
- // 整档在进程内排完再切片,所以「带序先于截断」天然成立(无 SQL LIMIT 可绕过)。
155
110
  const bands = opts?.exposureBands === true;
156
- // Limit normalization mirrors the Pg twin verbatim (floor semantics; NaN rejected; Infinity = no cap).
157
111
  const rawLimit = opts?.limit ?? 20;
158
112
  const limit = Number.isFinite(rawLimit) ? Math.floor(rawLimit) : rawLimit;
159
113
  if (scopes.length === 0 || !(limit > 0))
160
114
  return [];
161
- // EMPTY-QUERY contract is backend-uniform: no terms → [] (File/Pg parity).
162
115
  const q = termSet(query);
163
116
  if (q.size === 0)
164
117
  return [];
@@ -170,25 +123,17 @@ export class TiDBMemoryEngineBackend {
170
123
  const fm = fromJson(r.frontmatter);
171
124
  const d = jaccardDistance(q, this.haystackOf(r.slug, fm, r.body));
172
125
  if (d === null)
173
- continue; // zero-overlap: not a candidate
126
+ continue;
174
127
  scored.push({ ...this.headerFromRow(r), score: d });
175
128
  }
176
- // Deterministic id tie-break (opus 审 C4,Pg 版同注): tied scores must not order by row-arrival
177
- // accident — the cross-backend equivalence assertion compares ordered projections.
178
129
  scored.sort((a, b) => (bands ? exposureBandOf(a) - exposureBandOf(b) : 0) || a.score - b.score || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
179
130
  return scored.slice(0, limit);
180
131
  }
181
132
  async applyPatches(patches) {
182
133
  const report = { applied: [], conflicts: [] };
183
- // At most ONE non-delete op per id per batch (file-backend parity — a COPY keeping its frontmatter
184
- // id would leave the projection↔applied binding ambiguous; refuse the later one).
185
134
  const nonDeleteIds = new Set();
186
- // In-batch (scope, slug) claims so two adds of one slug in one batch suffix deterministically.
187
135
  const plannedSlugs = new Set();
188
- // 142-S5.1: one history row per APPLIED patch, flushed as one batch below (append-only audit face).
189
136
  const historyRows = [];
190
- // #307 件6(design/336 §2.3-2):external-origin 不可变律的**批次基线**(Pg 版同注)。逐条 patch 直打
191
- // 真库 ⇒ 同批 delete 之后再 add 时库里已无该行,只看当下会误判成「合法新生命」。
192
137
  const originBaseline = createBatchCommittedBaseline();
193
138
  for (const patch of patches) {
194
139
  try {
@@ -205,17 +150,11 @@ export class TiDBMemoryEngineBackend {
205
150
  report.conflicts.push({ op: patch.op, id: patch.id, reason: `io error: ${err instanceof Error ? err.message : String(err)}` });
206
151
  }
207
152
  }
208
- // BEST-EFFORT history append (see TiDBMemoryEngineBackendOptions.history for the honest trade-off:
209
- // the entry writes above are already committed; a failed audit append must not fail the report —
210
- // swallow + warn, never throw. appendHistory is (id,rev)-idempotent, so a caller retry is safe.
211
153
  if (this.history && historyRows.length > 0) {
212
154
  try {
213
155
  await this.history.appendHistory(historyRows);
214
156
  }
215
157
  catch (err) {
216
- // Every warn carries the CUMULATIVE lost count — a persistent silent failure
217
- // (history table dropped, grant flipped) shows as a growing number, not a one-off line.
218
- // The metrics counter leg lives in the main.ts sink wrapper (memory_history_append_failed_total).
219
158
  this.historyRowsLost += historyRows.length;
220
159
  console.warn(`TiDBMemoryEngineBackend: best-effort history append failed (${historyRows.length} row(s) lost, ${this.historyRowsLost} cumulative — audit face only, entries committed): ${err instanceof Error ? err.message : String(err)}`);
221
160
  }
@@ -224,8 +163,6 @@ export class TiDBMemoryEngineBackend {
224
163
  }
225
164
  async applyOne(patch, report, plannedSlugs, historyRows, originBaseline) {
226
165
  const T = TIDB_MEMORY_ENGINE_TABLES.entry;
227
- // #307 件6(codex R1-[high],Pg 版同注):op 闭集**前置**判 —— 词表外的 op 会跳过两道白洗守卫、
228
- // 落进 update 的 SQL 并报成一次 applied。零 I/O 拒绝。
229
166
  {
230
167
  const refusal = unknownPatchOpRefusal(patch.op);
231
168
  if (refusal !== undefined) {
@@ -233,15 +170,10 @@ export class TiDBMemoryEngineBackend {
233
170
  return;
234
171
  }
235
172
  }
236
- // A patch whose entry carries a DIFFERENT id than the patch addresses is malformed — refused up
237
- // front, all backends alike (Pg 版同注).
238
173
  if (patch.op !== "delete" && patch.entry !== undefined && patch.entry.id !== patch.id) {
239
174
  report.conflicts.push({ op: patch.op, id: patch.id, reason: `patch id ${JSON.stringify(patch.id)} != entry id ${JSON.stringify(patch.entry.id)} (malformed patch refused)` });
240
175
  return;
241
176
  }
242
- // #271 件1(R-1):键宽在任何 I/O 之前判,add/update 两条腿一处收(Pg 版同注)。本方言尤其承重
243
- // ——非严格 MySQL 对超宽键**静默截断**(只发 warning),截断后的 scope/slug 指向**另一个**键:
244
- // 两租户的盘折叠成一个、两个条目互串,写入侧不炸、读回侧才发现串了。拒绝式,零写。
245
177
  if (patch.op !== "delete" && patch.entry !== undefined) {
246
178
  const refusal = memoryEntryKeyWidthRefusal(patch.entry.scope, patch.entry.slug);
247
179
  if (refusal !== undefined) {
@@ -249,9 +181,6 @@ export class TiDBMemoryEngineBackend {
249
181
  return;
250
182
  }
251
183
  }
252
- // #319 增量([4768] 勘误 + core `1a5ebaa1`,Pg 孪生同注):**歧义 origin 表示**零写拒。判位与
253
- // File 的 planOne 逐字同(表示合法性先于状态比较,且在任何 I/O 之前);判据用 core 导出的谓词
254
- // (禁手抄——解 extra 里的 origin 形块那只解析器一旦解得比 core 宽松,就是给洗白开后门)。
255
184
  if (patch.op !== "delete" && patch.entry !== undefined) {
256
185
  const refusal = ambiguousOriginRefusal(patch.entry.frontmatter);
257
186
  if (refusal !== undefined) {
@@ -265,25 +194,13 @@ export class TiDBMemoryEngineBackend {
265
194
  report.conflicts.push({ op: "add", id: patch.id, reason: "add patch without an entry" });
266
195
  return;
267
196
  }
268
- // Reject-not-rewrite for bytes SQL cannot store(Pg 版 applyOne add 分支同一守卫,复用同一
269
- // pgHasUnstorable——不是重派生一份近似正则):NUL / 孤立 UTF-16 代理不是 PG 独有的病,mysql2/
270
- // utf8mb4 对同样的字节没有 PG 的 22P05 fail-loud 报错,驱动或服务端会静默 mangle(丢字节/替换为
271
- // U+FFFD 视驱动版本而定),写入侧不炸、读回侧才发现内容偏离——这比 PG 的写时报错更隐蔽。三后端
272
- // (pg/tidb/local)必须对同一输入给出同一行为,否则同一份 sync 数据在不同部署形态下悄悄分叉。
273
197
  if (pgHasUnstorable(entry.frontmatter) || pgHasUnstorable(entry.body) || pgHasUnstorable(entry.slug)) {
274
198
  report.conflicts.push({ op: "add", id: entry.id, reason: "unstorable_bytes (utf8mb4 cannot store NUL/lone surrogates without mangling; strip them at the source — the store never rewrites content)" });
275
199
  return;
276
200
  }
277
- // (R5 的 add-only slug 守卫已上提到 applyOne 顶部的键宽块 —— add/update 同判,见那里的注。)
278
- // Cross-scope add refusal (opus 审 C3,Pg 版同注): an add whose id already lives in a DIFFERENT
279
- // scope must not silently MOVE the row; same-scope re-add stays the idempotent overwrite.
280
- // One probe serves BOTH the E-02 guard and the cross-scope refusal (File/Pg parity).
281
201
  const prior = await this.selectRows(`SELECT scope, slug, rev, frontmatter FROM ${T} WHERE id = ?`, [entry.id]);
282
202
  const priorRow = prior.length > 0 ? prior[0] : undefined;
283
203
  let existsById = priorRow !== undefined;
284
- // #307 件6(design/336 §2.3-2)—— external-origin 不可变律,**判在 guard 之前**(Pg 版同注:core
285
- // File backend 的 planOne 同序;顺序反了带 guard 的洗白 add 会先报 add_guard_absent_conflict,
286
- // 而契约试剂盒要的是 /malformed patch refused/,同一输入两后端两个答复)。
287
204
  {
288
205
  const committedBaseFm = rememberBatchCommitted(originBaseline, entry.id, priorRow !== undefined ? fromJson(priorRow.frontmatter) : undefined);
289
206
  const refusal = originWhitewashRefusal("add", committedBaseFm, entry.frontmatter);
@@ -291,17 +208,12 @@ export class TiDBMemoryEngineBackend {
291
208
  report.conflicts.push({ op: "add", id: entry.id, reason: refusal });
292
209
  return;
293
210
  }
294
- // #337(conformance c36,design/339):distilled 不可变律的 add 腿 —— origin 律同基线同判位
295
- // (guard 之前;同批 delete+re-add 靠基线拒,已提交墓碑后的 add 放行)。
296
211
  const dRefusal = distilledWhitewashRefusal("add", committedBaseFm, entry.frontmatter);
297
212
  if (dRefusal !== undefined) {
298
213
  report.conflicts.push({ op: "add", id: entry.id, reason: dRefusal });
299
214
  return;
300
215
  }
301
216
  }
302
- // E-02 add-if-absent guard (core 1.275.1/1.276.0 contract clause; caught live by the contract
303
- // suite on real TiDB 2026-07-13): same rev AND same (scope, slug) projection = idempotent
304
- // fall-through; different rev OR projection = REPORTED add_guard_absent_conflict + currentRev.
305
217
  if (patch.guard === "absent" && priorRow) {
306
218
  const currentRev = String(priorRow.rev);
307
219
  if (currentRev !== computeEntryRev(entry)) {
@@ -312,7 +224,6 @@ export class TiDBMemoryEngineBackend {
312
224
  report.conflicts.push({ op: "add", id: entry.id, reason: `add_guard_absent_conflict (id already exists at the same rev but a different projection ${JSON.stringify(`${String(priorRow.scope)}/${String(priorRow.slug)}`)}; add-if-absent refuses to duplicate)`, currentRev });
313
225
  return;
314
226
  }
315
- // same rev AND same projection ⇒ idempotent; fall through to the normal add (no-op re-write).
316
227
  }
317
228
  if (priorRow) {
318
229
  const held = String(priorRow.scope);
@@ -321,15 +232,8 @@ export class TiDBMemoryEngineBackend {
321
232
  return;
322
233
  }
323
234
  }
324
- // Slug collision with a DIFFERENT entry → deterministic `-n` suffix (file-backend parity); the
325
- // probe is only an optimization — the write INSIDE the loop is arbitrated by uq_agent_memory_engine_entry_scope_slug, and
326
- // a concurrent loser retries the next suffix (Pg 版同注).
327
235
  let slug = entry.slug;
328
236
  for (let n = 2;; n++) {
329
- // #271 件1(codex 对抗复审 H1,验真属实):**派生**候选也要吃守卫。顶部那道只判调用方给的原始
330
- // slug,而本循环在撞键时派生 `${slug}-${n}` —— 511 字符的合法 slug 撞一次就派生出 513 字符,
331
- // 绕过守卫直奔 INSERT(非严格 MySQL 截成另一个 512 字符键、还照报未截断的 slug=最坏形)。
332
- // 判在循环**顶**=任何探针/写之前;后缀装不下 ⇒ 可分类 conflict,不是静默截断也不是死循环。
333
237
  const derivedRefusal = memoryEntryKeyWidthRefusal(entry.scope, slug);
334
238
  if (derivedRefusal !== undefined) {
335
239
  report.conflicts.push({ op: "add", id: entry.id, reason: `${derivedRefusal} (slug collision suffix "-${n - 1}" no longer fits the column — shorten the entry name)` });
@@ -345,18 +249,12 @@ export class TiDBMemoryEngineBackend {
345
249
  const sizeBytes = Buffer.byteLength(serializeEntryFile(stored), "utf8");
346
250
  try {
347
251
  if (existsById) {
348
- // Idempotent overwrite leg (Pg 的 ON CONFLICT(id) DO UPDATE 对应面;见文件头:MySQL 的
349
- // ON DUPLICATE KEY UPDATE 会被 (scope,slug) 键劫持改写他人行,禁用)。
350
252
  const res = await this.write(`UPDATE ${T} SET scope = ?, slug = ?, frontmatter = ?, body = ?, rev = ?, mtime_ms = ?, size_bytes = ?, embedding = NULL WHERE id = ?`, [stored.scope, stored.slug, JSON.stringify(stored.frontmatter), stored.body, rev, this.clock(), sizeBytes, stored.id]);
351
253
  if (res.affectedRows === 0) {
352
- // affectedRows: under mysql2 default (FOUND_ROWS ⇒ matched count) 0 = row gone;
353
- // under changed-only semantics 0 could also mean already-at-target — the fresh-rev branch below covers BOTH: 0 = either the row already
354
- // holds exactly this content (idempotent no-op = success) or it vanished under a
355
- // concurrent delete (→ fall back to the INSERT leg, same slug).
356
254
  const still = await this.selectRows(`SELECT id FROM ${T} WHERE id = ?`, [stored.id]);
357
255
  if (still.length === 0) {
358
256
  existsById = false;
359
- continue; // row deleted concurrently — retry this slug via the INSERT leg
257
+ continue;
360
258
  }
361
259
  }
362
260
  }
@@ -367,18 +265,16 @@ export class TiDBMemoryEngineBackend {
367
265
  catch (err) {
368
266
  if (isDupEntry(err)) {
369
267
  if (isPrimaryKeyDup(err)) {
370
- existsById = true; // concurrent same-id add won the PK — retry this slug via the overwrite leg
268
+ existsById = true;
371
269
  continue;
372
270
  }
373
- slug = `${entry.slug}-${n}`; // concurrent winner took this (scope,slug) — next suffix
271
+ slug = `${entry.slug}-${n}`;
374
272
  continue;
375
273
  }
376
274
  throw err;
377
275
  }
378
276
  plannedSlugs.add(key);
379
277
  report.applied.push({ op: "add", id: stored.id, slug: stored.slug });
380
- // History: chain head — prevRev absent by contract (a same-scope idempotent re-add of
381
- // identical content dedupes on the (id, rev) PK; changed content starts a fresh head).
382
278
  if (this.history)
383
279
  historyRows.push({ id: stored.id, rev, op: "add", scope: stored.scope, snapshot: { ...stored, rev }, writtenAtMs: this.clock() });
384
280
  return;
@@ -387,8 +283,6 @@ export class TiDBMemoryEngineBackend {
387
283
  slug = `${entry.slug}-${n}`;
388
284
  }
389
285
  }
390
- // update/delete address an EXISTING entry by immutable id. (scope in the projection = the
391
- // history row's scope for delete — a tombstone can't be joined back to the deleted row.)
392
286
  const cur = await this.selectRows(`SELECT slug, rev, scope, frontmatter FROM ${T} WHERE id = ?`, [patch.id]);
393
287
  if (cur.length === 0) {
394
288
  report.conflicts.push({ op: patch.op, id: patch.id, reason: "unknown id (entry not found in backend)" });
@@ -397,13 +291,7 @@ export class TiDBMemoryEngineBackend {
397
291
  const currentRev = String(cur[0].rev);
398
292
  const currentSlug = String(cur[0].slug);
399
293
  const currentScope = String(cur[0].scope);
400
- // Read-side whitewash refusal (core 1.302 contract I-2, File-backend parity incl. ordering: judged
401
- // BEFORE the rev CAS): an update may not strip provenance/trust off a repo-ingested entry. Judged on
402
- // the committed frontmatter of THIS SELECT — a write racing past it flips rev and the conditional
403
- // write below re-reports as a CAS conflict, so the window stays closed.
404
294
  const committedFm = fromJson(cur[0].frontmatter);
405
- // #307 件6:delete 腿也要记基线(Pg 版同注)—— 同批「delete 掉带标记的行、再 add 一条不带标记的」
406
- // 正是洗白的最直接写法,而 delete 一旦落库,后面那条 add 的 SELECT 什么都看不到。
407
295
  const committedBaseFm = rememberBatchCommitted(originBaseline, patch.id, committedFm);
408
296
  if (patch.op === "update" && patch.entry !== undefined) {
409
297
  if (committedFm.provenance?.kind === "repo_file") {
@@ -419,14 +307,11 @@ export class TiDBMemoryEngineBackend {
419
307
  return;
420
308
  }
421
309
  }
422
- // #307 件6:origin 不可变律的 update 腿(Pg 版同注:repo_file 在前、origin 在后、两者都在 rev CAS 之前)。
423
310
  const refusal = originWhitewashRefusal("update", committedBaseFm, patch.entry.frontmatter);
424
311
  if (refusal !== undefined) {
425
312
  report.conflicts.push({ op: "update", id: patch.id, reason: refusal });
426
313
  return;
427
314
  }
428
- // #337(conformance c36/c37):distilled 不可变律的 update 腿 —— 同样先于 rev CAS(c37:骑着
429
- // stale baseRev 的 strip 答 malformed refusal,不是 rev mismatch)。
430
315
  const dRefusal = distilledWhitewashRefusal("update", committedBaseFm, patch.entry.frontmatter);
431
316
  if (dRefusal !== undefined) {
432
317
  report.conflicts.push({ op: "update", id: patch.id, reason: dRefusal });
@@ -434,26 +319,16 @@ export class TiDBMemoryEngineBackend {
434
319
  }
435
320
  }
436
321
  if (patch.baseRev !== undefined && patch.baseRev !== currentRev) {
437
- // Per-id CAS (§2.4 并发): NEVER blind-write over a concurrent change. ABA note shared with the
438
- // File/Pg backends (rev is a content hash — an A→B→A round-trip re-arms a stale baseRev; benign
439
- // by construction, same canonical content).
440
322
  report.conflicts.push({ op: patch.op, id: patch.id, reason: "rev mismatch (concurrent change)", baseRev: patch.baseRev, currentRev });
441
323
  return;
442
324
  }
443
325
  if (patch.op === "delete") {
444
- // Conditional on the rev we just judged — a write landing between the SELECT and this DELETE
445
- // re-reports as the same CAS conflict instead of silently deleting the newer committed state.
446
- // DELETE's affectedRows counts deleted rows (FOUND_ROWS only affects UPDATE): 0 = the row changed/vanished.
447
326
  const res = await this.write(`DELETE FROM ${T} WHERE id = ? AND rev = ?`, [patch.id, currentRev]);
448
327
  if (res.affectedRows === 0) {
449
328
  report.conflicts.push({ op: "delete", id: patch.id, reason: "rev mismatch (concurrent change)", baseRev: patch.baseRev, currentRev: await this.freshRev(patch.id, currentRev) });
450
329
  return;
451
330
  }
452
331
  report.applied.push({ op: "delete", id: patch.id, slug: currentSlug });
453
- // History tombstone. rev must be DISTINCT from the deleted version's rev — the (id, rev) PK
454
- // already holds a row for currentRev (the add/update that committed it), and the idempotent
455
- // no-op ODKU would silently drop a colliding tombstone. Deterministic derived key: revs are
456
- // 16-hex content hashes (computeEntryRev), so the prefixed form stays well under VARCHAR(128).
457
332
  if (this.history)
458
333
  historyRows.push({ id: patch.id, rev: `tombstone:${currentRev}`, prevRev: currentRev, op: "delete", scope: currentScope, writtenAtMs: this.clock() });
459
334
  return;
@@ -463,8 +338,6 @@ export class TiDBMemoryEngineBackend {
463
338
  report.conflicts.push({ op: "update", id: patch.id, reason: "update patch without an entry" });
464
339
  return;
465
340
  }
466
- // Same reject-not-rewrite guard as the add leg above(Pg 版 applyOne update 分支同一守卫)—
467
- // an update carrying unstorable bytes must refuse before the CAS write, not mangle silently.
468
341
  if (pgHasUnstorable(entry.frontmatter) || pgHasUnstorable(entry.body) || pgHasUnstorable(entry.slug)) {
469
342
  report.conflicts.push({ op: "update", id: patch.id, reason: "unstorable_bytes (utf8mb4 cannot store NUL/lone surrogates without mangling; strip them at the source — the store never rewrites content)" });
470
343
  return;
@@ -472,22 +345,9 @@ export class TiDBMemoryEngineBackend {
472
345
  const rev = computeEntryRev(entry);
473
346
  const res = await this.write(`UPDATE ${T} SET scope = ?, slug = ?, frontmatter = ?, body = ?, rev = ?, mtime_ms = ?, size_bytes = ?, embedding = NULL WHERE id = ? AND rev = ?`, [entry.scope, entry.slug, JSON.stringify(entry.frontmatter), entry.body, rev, this.clock(), Buffer.byteLength(serializeEntryFile(entry), "utf8"), entry.id, currentRev]);
474
347
  if (res.affectedRows === 0) {
475
- // Zero AFFECTED rows is ambiguous under the default protocol (changed-rows counting): the row may
476
- // hold rev==currentRev with byte-identical values (matched but unchanged). Disambiguate on the
477
- // fresh rev: the row must STILL EXIST and already be at the NEW rev ⇒ the content is exactly the
478
- // target state (idempotent success — also covers a concurrent writer landing the same hash);
479
- // anything else ⇒ CAS conflict with the rev NOW.
480
- // 🔴 codex lens-3: the row-EXISTENCE check is load-bearing. When the row was concurrently DELETED
481
- // between the SELECT and this UPDATE, affectedRows is also 0; a fallback that returned the pre-race
482
- // rev would make `fresh === rev` true for a byte-identical update and FALSELY report `applied` on a
483
- // row that no longer exists — while the Pg twin (UPDATE … RETURNING → zero rows) correctly reports a
484
- // conflict. `freshRevOrMissing` returns undefined for a gone row, so it falls through to conflict,
485
- // restoring dialect parity (never accept a lost write as applied).
486
348
  const fresh = await this.freshRevOrMissing(entry.id);
487
349
  if (fresh === rev) {
488
350
  report.applied.push({ op: "update", id: entry.id, slug: entry.slug });
489
- // History: the idempotent already-at-target success is a committed version too — the
490
- // (id, rev) idempotent append dedupes if this exact version was already recorded.
491
351
  if (this.history)
492
352
  historyRows.push({ id: entry.id, rev, prevRev: currentRev, op: "update", scope: entry.scope, snapshot: { ...entry, rev }, writtenAtMs: this.clock() });
493
353
  return;
@@ -496,19 +356,13 @@ export class TiDBMemoryEngineBackend {
496
356
  return;
497
357
  }
498
358
  report.applied.push({ op: "update", id: entry.id, slug: entry.slug });
499
- // History: rev-chain link — snapshot carries the committed entry (rev = the NEW content hash).
500
359
  if (this.history)
501
360
  historyRows.push({ id: entry.id, rev, prevRev: currentRev, op: "update", scope: entry.scope, snapshot: { ...entry, rev }, writtenAtMs: this.clock() });
502
361
  }
503
- /** The row's rev NOW, or undefined if the row is gone. The existence distinction is the whole point
504
- * (codex lens-3): a concurrent delete must not be mistaken for an idempotent already-at-target write. */
505
362
  async freshRevOrMissing(id) {
506
363
  const rows = await this.selectRows(`SELECT rev FROM ${TIDB_MEMORY_ENGINE_TABLES.entry} WHERE id = ?`, [id]);
507
364
  return rows.length > 0 ? String(rows[0].rev) : undefined;
508
365
  }
509
- /** When the conditional write reports zero rows (a write landed inside the SELECT→write window), the
510
- * report's `currentRev` must be the rev NOW — re-reporting the pre-race SELECT value could claim
511
- * `baseRev === currentRev` alongside a "rev mismatch" reason. Row gone ⇒ fallback (Pg parity). */
512
366
  async freshRev(id, fallback) {
513
367
  return (await this.freshRevOrMissing(id)) ?? fallback;
514
368
  }
@@ -517,9 +371,7 @@ export class TiDBMemoryEngineBackend {
517
371
  return rows.length > 0 ? String(rows[0].cursor) : undefined;
518
372
  }
519
373
  async setConsolidationCursor(scope, cursor) {
520
- assertMemoryScopeWidth(scope); // #271 件1:游标表 PK 也是 VARCHAR(190) —— 截断=把两个盘的整合游标折叠成一个
521
- // Single unique key (the PK) on this table — ON DUPLICATE KEY UPDATE is unambiguous here,
522
- // unlike the two-unique-key entries table (see the file-head dialect note).
374
+ assertMemoryScopeWidth(scope);
523
375
  await this.write(`INSERT INTO ${TIDB_MEMORY_ENGINE_TABLES.cursor} (scope, \`cursor\`) VALUES (?, ?) ON DUPLICATE KEY UPDATE \`cursor\` = VALUES(\`cursor\`)`, [scope, cursor]);
524
376
  }
525
377
  }
@@ -1,17 +1,7 @@
1
- /** memory-engine 方言实现共用的纯工具函数。三个距离/词集函数(termSet/jaccardDistance/cosineDistance)
2
- * 在 core(1.268.0 起公开导出,单一语义源)——消费者直引 core([2354] 兼容面全清:此处转口已删)。
3
- * `isUniqueViolation` 重新内联:它当年随 core 的 pg **示例适配器**导出面走,core 1.424 按 clay 包卫生令
4
- * 把整个 examples 面退出 npm 包([1803] BREAKING)⇒ re-export 断源。原为逐字抄自 core 1.423
5
- * `examples/adapters/pg-adapter.js`(纯谓词,无 byte-equal 契约约束);A-032 P1-① 后**码面判据**
6
- * 改走属主 `sql-errors.ts`,只留 message 补充臂(理由在函数体内)。 */
7
1
  import { isPgUniqueViolation } from "./sql-errors.js";
8
2
  export function isUniqueViolation(err) {
9
- // 码面判据 = 属主谓词(A-032 P1-①,`plugins/sql-errors.ts`)。
10
3
  if (isPgUniqueViolation(err))
11
4
  return true;
12
- // 🔴 message 正则臂是**本站点的显式补充**,不上提属主:memory-engine 的 pg 腿吃的是**裸 PgQueryFn**
13
- // (调用方自带的连接封装),错误对象未必是 node-pg 的原生错误类——中间层可能只把 message 传下来。
14
- // 其余各店都攥着自己的驱动/池,拿得到带 SQLSTATE 的原错误,不需要也不该按文案分支(#90 门②)。
15
5
  if (typeof err !== "object" || err === null)
16
6
  return false;
17
7
  return /duplicate key|unique constraint|already exists/i.test(String(err.message ?? ""));
@@ -1,29 +1,5 @@
1
- /** R5(车A [3191] 欠账,批γ 落地;#271 件1 补齐写口):memory 面的键宽写前守卫。
2
- * 列宽收窄后,模型/调用方可控的键(条目名 slug、租户盘 scope)超宽此前落裸 SQL 错(PG
3
- * `value too long`)或非严格 MySQL 静默截断(截断=两个不同的键折叠成同一行=**条目互串 / 两个租户
4
- * 的盘塌成一个**,最危险形)。写前响亮拒,错误可分类。
5
- * 列宽同源门=test/key-width-guards.test.ts(守卫常量 vs 两方言 DDL 逐字对表);写口在场门=同文件
6
- * 「#271 件1」组(超宽键一条写语句都不许发出 seam)。
7
- *
8
- * **本文件是这两个宽度的唯一属主**(#271 件1):scope 守卫此前住在 `src/memory-scope.ts`,只被
9
- * `memoryScopeFor` 的产出处消费,而 store 写口全无——同一个列宽有两个居所、两半消费面,正是
10
- * 「守卫在位≠写口接上」那类缺口的温床。`memory-scope.ts` 现在只**再导出**本文件的这两个符号
11
- * (旧 import 路径逐字不变)。 */
12
- /** memory-engine entry 表 `slug` 列宽(两方言 VARCHAR(512) 同宽;(scope,slug) UNIQUE 键预算注在 DDL)。 */
13
1
  export const MEMORY_SLUG_COLUMN_CHARS = 512;
14
- /** memory 面 `scope` 列宽(两方言 VARCHAR(190) 同宽:engine 的 entry/cursor 两表 + memory-sync 的
15
- * sync_cursor/push_queue/history)。`formatUserScope`/`formatProjScope` 的段编码(百分号转义)会
16
- * **膨胀**——非 ASCII principal 编码后可超列宽,故产出处与写口两道都要判。 */
17
2
  export const MEMORY_SCOPE_COLUMN_CHARS = 190;
18
- /**
19
- * 键宽的**计数口径**(codex 对抗复审 M1,#271 件1 验真后修):两方言的 `VARCHAR(n)` 数的都是
20
- * **字符 = Unicode 码点**(PG varchar(n) / MySQL-protocol utf8mb4 VARCHAR(n) 同口径,后者每字符
21
- * 最多 4 字节、astral 字符仍算 1)。而 JS 的 `string.length` 数的是 **UTF-16 code unit** —— 一个
22
- * emoji 算 2。照 `.length` 判会把「库里只占 96 字符」的 scope 按 192 拒掉:守卫从「防截断」变成
23
- * 「误伤合法窄键」,方向反了。
24
- *
25
- * (孤立代理项这种**不可存**字节不归本函数管——那是 `pgHasUnstorable` 的拒绝式,两条判据各司其职。)
26
- */
27
3
  function widthChars(v) {
28
4
  return [...v].length;
29
5
  }
@@ -41,16 +17,6 @@ export function assertMemoryScopeWidth(scope) {
41
17
  `both SQL dialects pin); got ${n} — use a shorter principal/projectId (non-ASCII characters expand ~9x when encoded)`);
42
18
  }
43
19
  }
44
- /**
45
- * 一个 entry 写(add / update)的两个键宽一起判 —— 两方言的 `applyOne` 在**任何 I/O 之前**调用它。
46
- *
47
- * 为什么两条腿共用一个入口而不是各自展开:add 与 update 写的是**同两列**(update 的
48
- * `SET scope = …, slug = …` 会改盘也会改名),R5 那批只护住了 add ⇒ 一次改名就能把超宽 slug 送进
49
- * DB。一个入口=两条腿不会再各自漂。
50
- *
51
- * 返回**字符串**而不是抛:两方言的 `applyPatches` 契约是「冲突进 report,不抛」(与
52
- * `unstorable_bytes` 的拒绝式同族——后端能力差异诚实暴露,重试恒同答=幂等成立)。`undefined` = 过。
53
- */
54
20
  export function memoryEntryKeyWidthRefusal(scope, slug) {
55
21
  try {
56
22
  assertMemoryScopeWidth(scope);