@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.
- package/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- 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";
|
|
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));
|
|
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;
|
|
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;
|
|
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;
|
|
268
|
+
existsById = true;
|
|
371
269
|
continue;
|
|
372
270
|
}
|
|
373
|
-
slug = `${entry.slug}-${n}`;
|
|
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);
|
|
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);
|