@sema-agent/server 7.144.0 → 7.146.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/MIGRATION.md +621 -0
- package/README.md +3 -0
- package/README.zh-CN.md +1 -0
- package/USAGE.md +233 -54
- package/deploy/facts/CHANGES.json +25 -0
- package/deploy/facts/README.md +57 -0
- package/deploy/facts/lanes.json +54 -0
- package/deploy/facts/lanes.schema.json +57 -0
- package/deploy/facts/sandbox-rbac.json +113 -0
- package/deploy/facts/sandbox-rbac.schema.json +74 -0
- package/deploy/facts/sealed-key.json +9 -0
- package/deploy/facts/sealed-key.schema.json +52 -0
- package/deploy/sema-up/chart/templates/_helpers.tpl +19 -0
- package/deploy/sema-up/chart/templates/sandbox-rbac.yaml +47 -7
- package/deploy/sema-up/chart/templates/server-v4pro.yaml +4 -0
- package/deploy/sema-up/chart/templates/server.yaml +4 -0
- package/deploy/sema-up/chart/values.yaml +7 -0
- package/deploy/sema-up/stack/sema-stack-cluster.yaml +30 -14
- package/dist/adoption/import-limits.d.ts +6 -0
- package/dist/adoption/import-limits.js +7 -0
- package/dist/adoption/import.d.ts +62 -0
- package/dist/adoption/import.js +107 -0
- package/dist/adoption/wire.d.ts +52 -0
- package/dist/adoption/wire.js +9 -0
- package/dist/approval-ask-machine.d.ts +2 -2
- package/dist/approval-card.d.ts +3 -12
- package/dist/approval-card.js +0 -4
- package/dist/approval-gate-currency.d.ts +1 -1
- package/dist/approval-gate-currency.js +1 -1
- package/dist/approval-reconciler.d.ts +1 -1
- package/dist/audit.d.ts +2 -21
- package/dist/audit.js +0 -9
- package/dist/boot/deployment-mods.d.ts +11 -1
- package/dist/boot/deployment-mods.js +15 -2
- package/dist/boot/device-lane.js +2 -0
- package/dist/boot/engine-lease.d.ts +193 -21
- package/dist/boot/engine-lease.js +146 -20
- package/dist/boot/execution-env.d.ts +4 -0
- package/dist/boot/execution-env.js +12 -0
- package/dist/boot/exit-boundary.d.ts +50 -0
- package/dist/boot/exit-boundary.js +32 -0
- package/dist/boot/exit-last.d.ts +20 -4
- package/dist/boot/exit-last.js +21 -4
- package/dist/boot/governance-seams.js +1 -0
- package/dist/boot/memory-consolidation.d.ts +29 -17
- package/dist/boot/memory-consolidation.js +26 -15
- package/dist/boot/reapers.d.ts +12 -4
- package/dist/boot/reapers.js +39 -15
- package/dist/boot/resolve-spec.d.ts +2 -0
- package/dist/boot/resolve-spec.js +38 -15
- package/dist/boot/runner-deps.js +1 -1
- package/dist/boot/session-faces.d.ts +1 -1
- package/dist/boot/session-faces.js +6 -4
- package/dist/boot/shutdown.js +27 -9
- package/dist/boot/stage-01-config.js +1 -1
- package/dist/boot/stage-03-store-backend.d.ts +1 -0
- package/dist/boot/stage-03-store-backend.js +2 -2
- package/dist/boot/stage-04-stores.d.ts +1 -0
- package/dist/boot/stage-05-execution-env.d.ts +3 -0
- package/dist/boot/stage-05-execution-env.js +3 -3
- package/dist/boot/stage-06-runners.d.ts +3 -0
- package/dist/boot/stage-06-runners.js +11 -10
- package/dist/boot/stage-07-capability-layer.d.ts +3 -0
- package/dist/boot/stage-07-capability-layer.js +7 -2
- package/dist/boot/stage-08-reapers.d.ts +3 -0
- package/dist/boot/stage-08-reapers.js +3 -3
- package/dist/boot/stage-09-leader.d.ts +4 -1
- package/dist/boot/stage-10-http-server.d.ts +4 -1
- package/dist/boot/stage-10-http-server.js +4 -3
- package/dist/boot/stores.d.ts +1 -0
- package/dist/boot/stores.js +35 -19
- package/dist/boot/workflow-orchestration.d.ts +1 -1
- package/dist/boot/workflow-orchestration.js +2 -1
- package/dist/capabilities/composite-members.d.ts +3 -1
- package/dist/capabilities/composite-members.js +3 -1
- package/dist/capabilities/scenarios.d.ts +2 -2
- package/dist/capability-facts.d.ts +18 -1
- package/dist/capability-facts.js +25 -7
- package/dist/checkpoint-ctx-seal-m28.d.ts +1 -1
- package/dist/config-catalog.js +13 -4
- package/dist/config-d-migration.d.ts +3 -2
- package/dist/config-provider.d.ts +2 -2
- package/dist/config-types.d.ts +43 -4
- package/dist/config.d.ts +6 -1
- package/dist/config.js +67 -11
- package/dist/deleted-session-ids.d.ts +19 -0
- package/dist/deleted-session-ids.js +26 -0
- package/dist/deploy-facts.d.ts +155 -0
- package/dist/deploy-facts.js +94 -0
- package/dist/device-ws-hub.d.ts +48 -3
- package/dist/device-ws-hub.js +48 -2
- package/dist/error-code-family.d.ts +35 -0
- package/dist/error-code-family.js +26 -0
- package/dist/fleet/drain-entry.d.ts +39 -3
- package/dist/fleet/drain-entry.js +6 -0
- package/dist/fleet/fleet-bus.d.ts +15 -4
- package/dist/fleet/fleet-bus.js +17 -3
- package/dist/hooks/branch-transcript.d.ts +1 -1
- package/dist/hooks/branch-transcript.js +1 -0
- package/dist/hooks/classic-stations.d.ts +25 -8
- package/dist/hooks/classic-stations.js +29 -3
- package/dist/hooks/hook-runner.d.ts +33 -16
- package/dist/hooks/hook-runner.js +166 -73
- package/dist/hooks/hook-source.d.ts +14 -2
- package/dist/hooks/hook-source.js +8 -0
- package/dist/http/active-run-conflict.d.ts +1 -1
- package/dist/http/active-run-conflict.js +1 -1
- package/dist/http/admission.js +13 -2
- package/dist/http/checkpoint-ctx.d.ts +60 -71
- package/dist/http/checkpoint-ctx.js +35 -40
- package/dist/http/dispatch.js +18 -2
- package/dist/http/durable-run-leg.d.ts +5 -0
- package/dist/http/durable-run-leg.js +8 -6
- package/dist/http/execution-context.d.ts +4 -0
- package/dist/http/execution-context.js +9 -0
- package/dist/http/health-self-report.d.ts +6 -2
- package/dist/http/health-self-report.js +3 -2
- package/dist/http/memory-compliance-failure-reply.d.ts +1 -1
- package/dist/http/memory-compliance-failure-reply.js +2 -15
- package/dist/http/memory-control-plane-corrupt-reply.d.ts +25 -0
- package/dist/http/memory-control-plane-corrupt-reply.js +19 -0
- package/dist/http/principal-gate.d.ts +35 -12
- package/dist/http/principal-gate.js +16 -12
- package/dist/http/request-guards.d.ts +1 -1
- package/dist/http/request-guards.js +5 -5
- package/dist/http/resume-legs.d.ts +22 -0
- package/dist/http/resume-legs.js +31 -20
- package/dist/http/route-ctx.d.ts +26 -10
- package/dist/http/route-table.js +2 -0
- package/dist/http/routes/adoption.d.ts +11 -0
- package/dist/http/routes/adoption.js +80 -6
- package/dist/http/routes/agents-roster.js +1 -0
- package/dist/http/routes/approvals-assistant.d.ts +20 -1
- package/dist/http/routes/approvals-assistant.js +29 -7
- package/dist/http/routes/background.js +1 -0
- package/dist/http/routes/capabilities.js +4 -1
- package/dist/http/routes/diagnostics.d.ts +9 -14
- package/dist/http/routes/diagnostics.js +10 -20
- package/dist/http/routes/engine.d.ts +31 -0
- package/dist/http/routes/engine.js +81 -0
- package/dist/http/routes/memory-bundle.js +3 -0
- package/dist/http/routes/memory-consolidation.js +3 -5
- package/dist/http/routes/memory-origin.d.ts +1 -1
- package/dist/http/routes/memory-origin.js +2 -4
- package/dist/http/routes/memory-policy.js +5 -1
- package/dist/http/routes/observability.js +3 -6
- package/dist/http/routes/rules.d.ts +1 -1
- package/dist/http/routes/rules.js +5 -2
- package/dist/http/routes/runs.js +7 -3
- package/dist/http/routes/session-sync.js +24 -25
- package/dist/http/routes/sessions-list.d.ts +1 -1
- package/dist/http/routes/sessions.js +4 -0
- package/dist/http/routes/tasks.js +35 -14
- package/dist/http/routes/trace-usage.js +1 -1
- package/dist/http/run-concurrency-cap.d.ts +86 -0
- package/dist/http/run-concurrency-cap.js +54 -0
- package/dist/http/run-meta.d.ts +14 -7
- package/dist/http/run-meta.js +5 -2
- package/dist/http/server.d.ts +27 -16
- package/dist/http/server.js +21 -3
- package/dist/http/stored-body-replay.d.ts +9 -6
- package/dist/http/stored-body-replay.js +19 -5
- package/dist/http/submit-ctx.d.ts +12 -0
- package/dist/http/submit-ctx.js +23 -0
- package/dist/http/wire-types.d.ts +8 -3
- package/dist/json-body-limit.d.ts +8 -0
- package/dist/json-body-limit.js +2 -0
- package/dist/leader/diffout.d.ts +1 -1
- package/dist/local-data-root.d.ts +2 -2
- package/dist/memory-operator-faces.d.ts +9 -4
- package/dist/memory-scope-stores.d.ts +9 -7
- package/dist/memory-scope-stores.js +3 -14
- package/dist/memory-scope.d.ts +36 -0
- package/dist/memory-scope.js +32 -1
- package/dist/net-address.d.ts +53 -0
- package/dist/net-address.js +111 -0
- package/dist/observability/fail-open.d.ts +28 -12
- package/dist/observability/fail-open.js +28 -12
- package/dist/orchestration/workflow-notify-journal.js +2 -3
- package/dist/org-delivery-gate.js +1 -1
- package/dist/peer-directory.d.ts +6 -12
- package/dist/peer-directory.js +5 -1
- package/dist/plugins/approval-ask-store-file.d.ts +16 -2
- package/dist/plugins/approval-ask-store-file.js +21 -1
- package/dist/plugins/approval-ask-store-memory.d.ts +8 -2
- package/dist/plugins/approval-ask-store-memory.js +11 -8
- package/dist/plugins/caching-session-store.d.ts +6 -1
- package/dist/plugins/caching-session-store.js +6 -0
- package/dist/plugins/checkpoint-store-face.d.ts +426 -0
- package/dist/plugins/checkpoint-store-face.js +152 -0
- package/dist/plugins/file-run-store.d.ts +13 -7
- package/dist/plugins/file-run-store.js +12 -13
- package/dist/plugins/fork-routing-session-store.d.ts +6 -2
- package/dist/plugins/fork-routing-session-store.js +2 -2
- package/dist/plugins/imported-layer-root-retirement.d.ts +10 -8
- package/dist/plugins/local-checkpoint-store.d.ts +16 -5
- package/dist/plugins/local-checkpoint-store.js +25 -6
- package/dist/plugins/local-session-store.d.ts +80 -21
- package/dist/plugins/local-session-store.js +79 -22
- package/dist/plugins/memory-engine-pg.d.ts +29 -6
- package/dist/plugins/memory-engine-pg.js +96 -214
- package/dist/plugins/memory-engine-sql-apply.d.ts +121 -0
- package/dist/plugins/memory-engine-sql-apply.js +358 -0
- package/dist/plugins/memory-engine-tidb.d.ts +35 -9
- package/dist/plugins/memory-engine-tidb.js +97 -227
- package/dist/plugins/memory-key-guards.d.ts +1 -1
- package/dist/plugins/memory-origin-law.d.ts +3 -3
- package/dist/plugins/memory-run-store.d.ts +2 -0
- package/dist/plugins/memory-run-store.js +6 -0
- package/dist/plugins/one-time-migrations.d.ts +32 -56
- package/dist/plugins/one-time-migrations.js +27 -119
- package/dist/plugins/permission-rule-store-file.d.ts +17 -3
- package/dist/plugins/permission-rule-store-file.js +8 -1
- package/dist/plugins/pg-pool.js +1 -1
- package/dist/plugins/pg-session-storage.d.ts +17 -4
- package/dist/plugins/pg-session-storage.js +8 -4
- package/dist/plugins/run-store-sql.d.ts +4 -0
- package/dist/plugins/run-store-sql.js +5 -2
- package/dist/plugins/schema-ensure-legs.js +2 -2
- package/dist/plugins/session-fork.d.ts +31 -9
- package/dist/plugins/session-fork.js +3 -2
- package/dist/plugins/session-placement.d.ts +4 -2
- package/dist/plugins/session-store.d.ts +1 -1
- package/dist/plugins/sql-json-column.d.ts +5 -1
- package/dist/plugins/store-backend.d.ts +11 -8
- package/dist/plugins/store-backend.js +8 -7
- package/dist/plugins/store-contracts.d.ts +1 -11
- package/dist/plugins/store-contracts.js +0 -9
- package/dist/plugins/store-sweep.d.ts +63 -0
- package/dist/plugins/store-sweep.js +48 -0
- package/dist/plugins/tidb-pool.js +1 -1
- package/dist/plugins/tidb-session-store.d.ts +16 -3
- package/dist/plugins/tidb-session-store.js +8 -4
- package/dist/prompt-screen.d.ts +18 -0
- package/dist/prompt-screen.js +5 -0
- package/dist/rules-consent.d.ts +56 -51
- package/dist/rules-consent.js +86 -14
- package/dist/run-acceptance.d.ts +40 -10
- package/dist/run-acceptance.js +38 -5
- package/dist/run-cancel-context.d.ts +4 -1
- package/dist/run-cancel-context.js +2 -1
- package/dist/runs.js +2 -2
- package/dist/sandbox-egress-policy.js +6 -11
- package/dist/sandbox-rbac-rules.d.ts +88 -0
- package/dist/sandbox-rbac-rules.js +80 -0
- package/dist/sealed-key-layout.d.ts +17 -0
- package/dist/sealed-key-layout.js +4 -0
- package/dist/sealed-key.js +5 -5
- package/dist/security.d.ts +48 -9
- package/dist/security.js +9 -2
- package/dist/session-agent.d.ts +39 -0
- package/dist/session-agent.js +16 -0
- package/dist/session-error-wire.d.ts +4 -4
- package/dist/session-notify-door.d.ts +5 -3
- package/dist/session-resident-background.d.ts +57 -0
- package/dist/session-resident-background.js +20 -0
- package/dist/session-sync-kernel.d.ts +53 -14
- package/dist/session-sync-kernel.js +70 -1
- package/dist/session-sync.d.ts +5 -3
- package/dist/session-workspace.d.ts +1 -0
- package/dist/session-workspace.js +1 -0
- package/dist/sighup-idle.d.ts +9 -7
- package/dist/sighup-idle.js +4 -5
- package/dist/spec-fields.d.ts +16 -0
- package/dist/spec-fields.js +7 -0
- package/dist/stored-request-env-credentials-m38.d.ts +3 -0
- package/dist/stored-request-env-credentials-m38.js +8 -0
- package/dist/stored-request-prompt-profile-m30.d.ts +1 -1
- package/dist/{plugins → stores-sql}/checkpoint-store-sql.d.ts +33 -429
- package/dist/{plugins → stores-sql}/checkpoint-store-sql.js +70 -209
- package/dist/{plugins → stores-sql}/mailbox-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/mailbox-store-sql.js +4 -4
- package/dist/{plugins → stores-sql}/roster-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/roster-store-sql.js +1 -1
- package/dist/{plugins → stores-sql}/tool-result-store-sql.d.ts +2 -2
- package/dist/{plugins → stores-sql}/tool-result-store-sql.js +6 -6
- package/dist/{plugins → stores-sql}/workflow-journal-store-sql.d.ts +7 -12
- package/dist/{plugins → stores-sql}/workflow-journal-store-sql.js +3 -8
- package/dist/task-settings.d.ts +59 -4
- package/dist/task-settings.js +50 -4
- package/dist/tool-approval.d.ts +2 -2
- package/dist/trace/core-keyset-guard.d.ts +8 -7
- package/dist/trace/engine-notice-wire.d.ts +1 -1
- package/dist/trace/engine-notice-wire.js +2 -0
- package/dist/trace/exit-seat.d.ts +6 -0
- package/dist/trace/exit-seat.js +1 -0
- package/dist/trace/model-segment.d.ts +3 -2
- package/dist/trace/model-segment.js +16 -8
- package/dist/trace/project.js +9 -0
- package/dist/trace/redact.d.ts +79 -4
- package/dist/trace/redact.js +523 -123
- package/dist/trace/self-minted-wire.d.ts +391 -1
- package/dist/trace/self-minted-wire.js +145 -0
- package/dist/trace/wire-projection-faces.d.ts +1 -1
- package/dist/trace/wire-projection-faces.js +1 -1
- package/dist/usage-analytics.d.ts +4 -0
- package/package.json +12 -4
- package/scripts/drain-check-local.mjs +8 -29
- package/scripts/drain-check.mysql.sql +3 -24
- package/scripts/drain-check.pg.sql +3 -24
- package/skills/update-config.md +1 -1
- package/wire-changes/server@7.145.0.json +146 -0
- package/wire-changes/server@7.146.0.json +3221 -0
- package/dist/approval-batch-census.d.ts +0 -27
- package/dist/approval-batch-census.js +0 -27
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { MemoryHistoryAppendRow } from "./memory-sync-store-pg.js";
|
|
2
2
|
import type { PgEmbedder, PgQueryFn } from "./pg-query.js";
|
|
3
|
+
import { type SqlApplyOpts } from "./memory-engine-sql-apply.js";
|
|
3
4
|
import type { MemoryBackend, MemoryEntry, MemoryEntryHeader, NotePatch, PatchReport, ScoredMemoryEntry } from "@sema-agent/core";
|
|
4
5
|
import { type IndexSpec } from "./ensure-index.js";
|
|
5
6
|
/** Table names (single source). Deliberately DISJOINT from the legacy `agent_memory*` tables —
|
|
@@ -54,6 +55,8 @@ export declare class PgMemoryEngineBackend implements MemoryBackend {
|
|
|
54
55
|
private readonly history?;
|
|
55
56
|
/** Cumulative lost-row count — audit-line breakage must itself be auditable. */
|
|
56
57
|
private historyRowsLost;
|
|
58
|
+
/** 交给共享驱动器的读口 / 写口 / history 冲刷(构造末尾铸一次)。 */
|
|
59
|
+
private readonly leg;
|
|
57
60
|
/** [ref] rung indicator — same semantics as PgMemoryStore.vectorMode. */
|
|
58
61
|
get vectorMode(): "native" | "portable" | "lexical";
|
|
59
62
|
constructor(query: PgQueryFn, opts?: PgMemoryEngineBackendOptions);
|
|
@@ -70,12 +73,32 @@ export declare class PgMemoryEngineBackend implements MemoryBackend {
|
|
|
70
73
|
limit?: number;
|
|
71
74
|
exposureBands?: boolean;
|
|
72
75
|
}): Promise<ScoredMemoryEntry[]>;
|
|
73
|
-
applyPatches(
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
private
|
|
76
|
+
/** core 落点问询协议的能力位:`applyPatches` 兑现 `opts.admit` / `opts.confirm`(读法见 `memory-engine-sql-apply.ts` 头注)。 */
|
|
77
|
+
readonly admitsLandings: true;
|
|
78
|
+
/** 判与驱动器只在 `memory-engine-sql-apply.ts` 一处(两只 SQL 腿共用);本腿只供读口、PG 方言写口(写语句绑定计划的投影)与 history 冲刷。
|
|
79
|
+
* `opts.anchoredRoot` 是带目录的后端的事,本腿没有目录 ⇒ 不读(core 契约原文:a backend without a directory ignores it)。 */
|
|
80
|
+
applyPatches(patches: readonly NotePatch[], opts?: SqlApplyOpts): Promise<PatchReport>;
|
|
81
|
+
private rowOf;
|
|
82
|
+
private idsAt;
|
|
83
|
+
/** 一个 patch 第一次要写之前算一次它的向量参数(撞名 / 被挪重试复用,不重调 embedder —— 改前 add 也只在后缀循环外算一次)。
|
|
84
|
+
* haystack 用调用方给的原形(add 的原名,不是撞名后缀),与改前同。 */
|
|
85
|
+
private prepareWrite;
|
|
86
|
+
/** 写不落之后的现读:这个 id 的行此刻的 rev 与投影(没了 ⇒ undefined)。 */
|
|
87
|
+
private freshRow;
|
|
88
|
+
/**
|
|
89
|
+
* add:`INSERT … ON CONFLICT (id)`,冲突靶 = 仅主键。计划时这个 id 已有行 ⇒ `DO UPDATE … WHERE` 行仍在计划的投影上(同 scope re-add 的幂等
|
|
90
|
+
* 覆盖;行被挪走就不覆盖 —— 被挪去的那个拼写没问过);计划时没有 ⇒ `DO NOTHING`(同 id 的行在判与写之间被别的副本插入就不覆盖)。
|
|
91
|
+
* 两种「不覆盖」都让 `RETURNING` 交零行 ⇒ 现读确认后答 `moved`(驱动器按现状重判)。剩下能撞的唯一键只有 `(scope, slug)` ⇒ `slug-taken`。
|
|
92
|
+
*/
|
|
93
|
+
private writeAdd;
|
|
94
|
+
/** update:条件 = 判过的 rev **且**判过的投影(rev 是内容哈希、不含 scope / slug)。零行 ⇒ 现读:内容未变而投影变了 = `moved`;其余 =
|
|
95
|
+
* CAS 冲突,currentRev 报现值(行没了 ⇒ 判时的值)。 */
|
|
96
|
+
private writeUpdate;
|
|
97
|
+
/** delete:条件同 update。 */
|
|
98
|
+
private writeDelete;
|
|
99
|
+
/** BEST-EFFORT history append(见 PgMemoryEngineBackendOptions.history 的如实取舍):entry 写已提交,审计行追加失败不许让报告失败 ——
|
|
100
|
+
* 吞 + warn,永不抛;appendHistory 按 (id, rev) 幂等,调用方重试安全。 */
|
|
101
|
+
private appendHistory;
|
|
79
102
|
getConsolidationCursor(scope: string): Promise<string | undefined>;
|
|
80
103
|
setConsolidationCursor(scope: string, cursor: string): Promise<void>;
|
|
81
104
|
}
|
|
@@ -2,10 +2,11 @@ import { cosineDistance, jaccardDistance, termSet } from "@sema-agent/core";
|
|
|
2
2
|
import { isUniqueViolation, readEmbeddingColumn, readFrontmatterColumn } from "./memory-engine-vector-util.js";
|
|
3
3
|
import { decodeJsonColumn, decodeNullableJsonColumn, foldJsonColumn, isJsonColumnBodyProblem, jsonColumnText } from "./sql-json-column.js";
|
|
4
4
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
5
|
-
import {
|
|
6
|
-
import { pgSafeJsonStringify
|
|
7
|
-
import { assertMemoryScopeWidth
|
|
8
|
-
import {
|
|
5
|
+
import { serializeEntryFile } from "@sema-agent/core";
|
|
6
|
+
import { pgSafeJsonStringify } from "./pg-safe-json.js";
|
|
7
|
+
import { assertMemoryScopeWidth } from "./memory-key-guards.js";
|
|
8
|
+
import { exposureBandOf, exposureCarriage, originMarkedSqlPredicate, distilledCarriage } from "./memory-origin-law.js";
|
|
9
|
+
import { applySqlPatches, } from "./memory-engine-sql-apply.js";
|
|
9
10
|
import { ensureIndexes } from "./ensure-index.js";
|
|
10
11
|
export const PG_MEMORY_ENGINE_TABLES = {
|
|
11
12
|
entry: "agent_memory_engine_entry",
|
|
@@ -72,6 +73,7 @@ export class PgMemoryEngineBackend {
|
|
|
72
73
|
clock;
|
|
73
74
|
history;
|
|
74
75
|
historyRowsLost = 0;
|
|
76
|
+
leg;
|
|
75
77
|
get vectorMode() {
|
|
76
78
|
return this.pgvectorSql ? "native" : this.embedder ? "portable" : "lexical";
|
|
77
79
|
}
|
|
@@ -83,6 +85,18 @@ export class PgMemoryEngineBackend {
|
|
|
83
85
|
this.history = opts.history;
|
|
84
86
|
if (this.pgvectorSql && !this.embedder)
|
|
85
87
|
throw new Error("PgMemoryEngineBackend: pgvectorSql requires an embedder");
|
|
88
|
+
this.leg = {
|
|
89
|
+
reads: { rowOf: (id) => this.rowOf(id), idsAt: (scope, slug) => this.idsAt(scope, slug) },
|
|
90
|
+
writes: {
|
|
91
|
+
prepare: (entry) => this.prepareWrite(entry),
|
|
92
|
+
add: (w, embedding, confirm) => this.writeAdd(w, embedding, confirm),
|
|
93
|
+
update: (w, embedding, confirm) => this.writeUpdate(w, embedding, confirm),
|
|
94
|
+
remove: (w, confirm) => this.writeDelete(w, confirm),
|
|
95
|
+
},
|
|
96
|
+
unstorableReason: "unstorable_bytes (PG cannot store NUL/lone surrogates; strip them at the source — the store never rewrites content)",
|
|
97
|
+
now: () => this.clock(),
|
|
98
|
+
...(this.history !== undefined ? { appendHistory: (rows) => this.appendHistory(rows) } : {}),
|
|
99
|
+
};
|
|
86
100
|
}
|
|
87
101
|
haystackOf(slug, fm, body) {
|
|
88
102
|
return `${fm.name ?? slug} ${fm.description ?? ""} ${body}`;
|
|
@@ -181,224 +195,92 @@ export class PgMemoryEngineBackend {
|
|
|
181
195
|
scored.sort(contractOrder);
|
|
182
196
|
return scored.slice(0, limit);
|
|
183
197
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
catch (err) {
|
|
202
|
-
report.conflicts.push({ op: patch.op, id: patch.id, reason: `io error: ${err instanceof Error ? err.message : String(err)}` });
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
if (this.history && historyRows.length > 0) {
|
|
206
|
-
try {
|
|
207
|
-
await this.history.appendHistory(historyRows);
|
|
208
|
-
}
|
|
209
|
-
catch (err) {
|
|
210
|
-
this.historyRowsLost += historyRows.length;
|
|
211
|
-
console.warn(`PgMemoryEngineBackend: 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)}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
return report;
|
|
198
|
+
admitsLandings = true;
|
|
199
|
+
async applyPatches(patches, opts) {
|
|
200
|
+
return applySqlPatches(patches, opts, this.leg);
|
|
201
|
+
}
|
|
202
|
+
async rowOf(id) {
|
|
203
|
+
const res = await this.query(`SELECT slug, rev, scope, ${jsonColumnText("pg", "frontmatter")} FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1`, [id]);
|
|
204
|
+
const r = res.rows[0];
|
|
205
|
+
if (r === undefined)
|
|
206
|
+
return undefined;
|
|
207
|
+
return { scope: String(r.scope), slug: String(r.slug), rev: String(r.rev), frontmatter: readFrontmatter(r.frontmatter, id) };
|
|
208
|
+
}
|
|
209
|
+
async idsAt(scope, slug) {
|
|
210
|
+
const res = await this.query(`SELECT id FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE scope = $1 AND slug = $2`, [scope, slug]);
|
|
211
|
+
return res.rows.map((r) => String(r.id));
|
|
212
|
+
}
|
|
213
|
+
prepareWrite(entry) {
|
|
214
|
+
return this.embeddingParam(this.haystackOf(entry.slug, entry.frontmatter, entry.body));
|
|
215
215
|
}
|
|
216
|
-
async
|
|
216
|
+
async freshRow(id) {
|
|
217
|
+
const res = await this.query(`SELECT rev, scope, slug FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1`, [id]);
|
|
218
|
+
const r = res.rows[0];
|
|
219
|
+
return r === undefined ? undefined : { rev: String(r.rev), scope: String(r.scope), slug: String(r.slug) };
|
|
220
|
+
}
|
|
221
|
+
async writeAdd({ stored, rev, prior }, embedding, confirm) {
|
|
217
222
|
const T = PG_MEMORY_ENGINE_TABLES.entry;
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
223
|
+
const params = [stored.id, stored.scope, stored.slug, pgSafeJsonStringify(stored.frontmatter), stored.body, rev, this.clock(), Buffer.byteLength(serializeEntryFile(stored), "utf8"), embedding];
|
|
224
|
+
const onConflict = prior !== undefined
|
|
225
|
+
? `ON CONFLICT (id) DO UPDATE SET scope = $2, slug = $3, frontmatter = $4, body = $5, rev = $6, mtime_ms = $7, size_bytes = $8, embedding = $9 WHERE ${T}.scope = $10 AND ${T}.slug = $11`
|
|
226
|
+
: "ON CONFLICT (id) DO NOTHING";
|
|
227
|
+
if (prior !== undefined)
|
|
228
|
+
params.push(prior.scope, prior.slug);
|
|
229
|
+
confirm();
|
|
230
|
+
let landed;
|
|
231
|
+
try {
|
|
232
|
+
const res = await this.query(`INSERT INTO ${T} (id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes, embedding)
|
|
233
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
234
|
+
${onConflict}
|
|
235
|
+
RETURNING id`, params);
|
|
236
|
+
landed = res.rows.length > 0;
|
|
224
237
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
238
|
+
catch (err) {
|
|
239
|
+
if (isUniqueViolation(err))
|
|
240
|
+
return { kind: "slug-taken" };
|
|
241
|
+
throw err;
|
|
228
242
|
}
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
report.conflicts.push({ op: patch.op, id: patch.id, reason: refusal });
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (patch.op === "add") {
|
|
244
|
-
const rawEntry = patch.entry;
|
|
245
|
-
if (!rawEntry) {
|
|
246
|
-
report.conflicts.push({ op: "add", id: patch.id, reason: "add patch without an entry" });
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
if (pgHasUnstorable(rawEntry.frontmatter) || pgHasUnstorable(rawEntry.body) || pgHasUnstorable(rawEntry.slug)) {
|
|
250
|
-
report.conflicts.push({ op: "add", id: rawEntry.id, reason: "unstorable_bytes (PG cannot store NUL/lone surrogates; strip them at the source — the store never rewrites content)" });
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
const entry = rawEntry;
|
|
254
|
-
const prior = await this.query(`SELECT scope, slug, rev, ${jsonColumnText("pg", "frontmatter")} FROM ${T} WHERE id = $1`, [entry.id]);
|
|
255
|
-
const priorRow = prior.rows.length > 0 ? prior.rows[0] : undefined;
|
|
256
|
-
{
|
|
257
|
-
const committedBaseFm = rememberBatchCommitted(originBaseline, entry.id, priorRow !== undefined ? readFrontmatter(priorRow.frontmatter, entry.id) : undefined);
|
|
258
|
-
const refusal = originWhitewashRefusal("add", committedBaseFm, entry.frontmatter);
|
|
259
|
-
if (refusal !== undefined) {
|
|
260
|
-
report.conflicts.push({ op: "add", id: entry.id, reason: refusal });
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
const dRefusal = distilledWhitewashRefusal("add", committedBaseFm, entry.frontmatter);
|
|
264
|
-
if (dRefusal !== undefined) {
|
|
265
|
-
report.conflicts.push({ op: "add", id: entry.id, reason: dRefusal });
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
if (patch.guard === "absent" && priorRow) {
|
|
270
|
-
const currentRev = String(priorRow.rev);
|
|
271
|
-
if (currentRev !== computeEntryRev(entry)) {
|
|
272
|
-
report.conflicts.push({ op: "add", id: entry.id, reason: "add_guard_absent_conflict (id already exists at a different rev; add-if-absent refuses to overwrite)", currentRev });
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
if (String(priorRow.scope) !== entry.scope || String(priorRow.slug) !== entry.slug) {
|
|
276
|
-
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 });
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
if (priorRow) {
|
|
281
|
-
const held = String(priorRow.scope);
|
|
282
|
-
if (held !== entry.scope) {
|
|
283
|
-
report.conflicts.push({ op: "add", id: entry.id, reason: `id already exists in scope ${JSON.stringify(held)} (cross-scope add refused)` });
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
const cEntry = entry;
|
|
288
|
-
const embedding = await this.embeddingParam(this.haystackOf(cEntry.slug, cEntry.frontmatter, cEntry.body));
|
|
289
|
-
let slug = cEntry.slug;
|
|
290
|
-
for (let n = 2;; n++) {
|
|
291
|
-
const derivedRefusal = memoryEntryKeyWidthRefusal(entry.scope, slug);
|
|
292
|
-
if (derivedRefusal !== undefined) {
|
|
293
|
-
report.conflicts.push({ op: "add", id: entry.id, reason: `${derivedRefusal} (slug collision suffix "-${n - 1}" no longer fits the column — shorten the entry name)` });
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
const key = `${entry.scope}|${slug}`;
|
|
297
|
-
if (!plannedSlugs.has(key)) {
|
|
298
|
-
const taken = await this.query(`SELECT id FROM ${T} WHERE scope = $1 AND slug = $2`, [entry.scope, slug]);
|
|
299
|
-
const otherId = taken.rows.find((r) => String(r.id) !== entry.id);
|
|
300
|
-
if (!otherId) {
|
|
301
|
-
const stored = { ...cEntry, slug };
|
|
302
|
-
const rev = computeEntryRev(stored);
|
|
303
|
-
try {
|
|
304
|
-
await this.query(`INSERT INTO ${T} (id, scope, slug, frontmatter, body, rev, mtime_ms, size_bytes, embedding)
|
|
305
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
306
|
-
ON CONFLICT (id) DO UPDATE SET scope = $2, slug = $3, frontmatter = $4, body = $5, rev = $6, mtime_ms = $7, size_bytes = $8, embedding = $9`, [stored.id, stored.scope, stored.slug, pgSafeJsonStringify(stored.frontmatter), stored.body, rev, this.clock(), Buffer.byteLength(serializeEntryFile(stored), "utf8"), embedding]);
|
|
307
|
-
}
|
|
308
|
-
catch (err) {
|
|
309
|
-
if (isUniqueViolation(err)) {
|
|
310
|
-
slug = `${entry.slug}-${n}`;
|
|
311
|
-
continue;
|
|
312
|
-
}
|
|
313
|
-
throw err;
|
|
314
|
-
}
|
|
315
|
-
plannedSlugs.add(key);
|
|
316
|
-
report.applied.push({ op: "add", id: stored.id, slug: stored.slug });
|
|
317
|
-
if (this.history)
|
|
318
|
-
historyRows.push({ id: stored.id, rev, op: "add", scope: stored.scope, snapshot: { ...stored, rev }, writtenAtMs: this.clock() });
|
|
319
|
-
return;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
slug = `${entry.slug}-${n}`;
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
const cur = await this.query(`SELECT slug, rev, scope, ${jsonColumnText("pg", "frontmatter")} FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1`, [patch.id]);
|
|
326
|
-
if (cur.rows.length === 0) {
|
|
327
|
-
report.conflicts.push({ op: patch.op, id: patch.id, reason: "unknown id (entry not found in backend)" });
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
const currentRev = String(cur.rows[0].rev);
|
|
331
|
-
const currentSlug = String(cur.rows[0].slug);
|
|
332
|
-
const currentScope = String(cur.rows[0].scope);
|
|
333
|
-
const committedFm = readFrontmatter(cur.rows[0].frontmatter, patch.id);
|
|
334
|
-
const committedBaseFm = rememberBatchCommitted(originBaseline, patch.id, committedFm);
|
|
335
|
-
if (patch.op === "update" && patch.entry !== undefined) {
|
|
336
|
-
if (committedFm.provenance?.kind === "repo_file") {
|
|
337
|
-
const next = patch.entry.frontmatter;
|
|
338
|
-
const stripsProvenance = next.provenance === undefined;
|
|
339
|
-
const stripsTrust = committedFm.trust !== undefined && next.trust === undefined;
|
|
340
|
-
if (stripsProvenance || stripsTrust) {
|
|
341
|
-
report.conflicts.push({
|
|
342
|
-
op: "update",
|
|
343
|
-
id: patch.id,
|
|
344
|
-
reason: `repo_file provenance whitewash refused: update drops ${stripsProvenance ? "provenance" : "trust"} from a repo-ingested entry (malformed patch refused)`,
|
|
345
|
-
});
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
const refusal = originWhitewashRefusal("update", committedBaseFm, patch.entry.frontmatter);
|
|
350
|
-
if (refusal !== undefined) {
|
|
351
|
-
report.conflicts.push({ op: "update", id: patch.id, reason: refusal });
|
|
352
|
-
return;
|
|
353
|
-
}
|
|
354
|
-
const dRefusal = distilledWhitewashRefusal("update", committedBaseFm, patch.entry.frontmatter);
|
|
355
|
-
if (dRefusal !== undefined) {
|
|
356
|
-
report.conflicts.push({ op: "update", id: patch.id, reason: dRefusal });
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
if (patch.baseRev !== undefined && patch.baseRev !== currentRev) {
|
|
361
|
-
report.conflicts.push({ op: patch.op, id: patch.id, reason: "rev mismatch (concurrent change)", baseRev: patch.baseRev, currentRev });
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
if (patch.op === "delete") {
|
|
365
|
-
const res = await this.query(`DELETE FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1 AND rev = $2 RETURNING id`, [patch.id, currentRev]);
|
|
366
|
-
if (res.rows.length === 0) {
|
|
367
|
-
report.conflicts.push({ op: "delete", id: patch.id, reason: "rev mismatch (concurrent change)", baseRev: patch.baseRev, currentRev: await this.freshRev(patch.id, currentRev) });
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
report.applied.push({ op: "delete", id: patch.id, slug: currentSlug });
|
|
371
|
-
if (this.history)
|
|
372
|
-
historyRows.push({ id: patch.id, rev: `tombstone:${currentRev}`, prevRev: currentRev, op: "delete", scope: currentScope, writtenAtMs: this.clock() });
|
|
373
|
-
return;
|
|
374
|
-
}
|
|
375
|
-
const entry = patch.entry;
|
|
376
|
-
if (!entry) {
|
|
377
|
-
report.conflicts.push({ op: "update", id: patch.id, reason: "update patch without an entry" });
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
if (pgHasUnstorable(entry.frontmatter) || pgHasUnstorable(entry.body) || pgHasUnstorable(entry.slug)) {
|
|
381
|
-
report.conflicts.push({ op: "update", id: patch.id, reason: "unstorable_bytes (PG cannot store NUL/lone surrogates; strip them at the source — the store never rewrites content)" });
|
|
382
|
-
return;
|
|
383
|
-
}
|
|
384
|
-
const cEntry = entry;
|
|
385
|
-
const rev = computeEntryRev(cEntry);
|
|
386
|
-
const embedding = await this.embeddingParam(this.haystackOf(cEntry.slug, cEntry.frontmatter, cEntry.body));
|
|
243
|
+
if (landed)
|
|
244
|
+
return { kind: "committed" };
|
|
245
|
+
const fresh = await this.freshRow(stored.id);
|
|
246
|
+
if (fresh !== undefined && (prior === undefined || fresh.scope !== prior.scope || fresh.slug !== prior.slug))
|
|
247
|
+
return { kind: "moved" };
|
|
248
|
+
throw new Error(`memory-engine: the add of ${JSON.stringify(stored.id)} wrote no row, yet the row is ${fresh === undefined ? "absent" : "still at the planned projection"} — refusing to report it either way`);
|
|
249
|
+
}
|
|
250
|
+
async writeUpdate({ entry, rev, currentRev, current, baseRev }, embedding, confirm) {
|
|
251
|
+
const params = [entry.id, entry.scope, entry.slug, pgSafeJsonStringify(entry.frontmatter), entry.body, rev, this.clock(), Buffer.byteLength(serializeEntryFile(entry), "utf8"), embedding, currentRev, current.scope, current.slug];
|
|
252
|
+
confirm();
|
|
387
253
|
const res = await this.query(`UPDATE ${PG_MEMORY_ENGINE_TABLES.entry}
|
|
388
254
|
SET scope = $2, slug = $3, frontmatter = $4, body = $5, rev = $6, mtime_ms = $7, size_bytes = $8, embedding = $9
|
|
389
|
-
WHERE id = $1 AND rev = $10
|
|
390
|
-
RETURNING id`,
|
|
391
|
-
if (res.rows.length
|
|
392
|
-
|
|
255
|
+
WHERE id = $1 AND rev = $10 AND scope = $11 AND slug = $12
|
|
256
|
+
RETURNING id`, params);
|
|
257
|
+
if (res.rows.length > 0)
|
|
258
|
+
return { kind: "committed" };
|
|
259
|
+
const fresh = await this.freshRow(entry.id);
|
|
260
|
+
if (fresh !== undefined && fresh.rev === currentRev && (fresh.scope !== current.scope || fresh.slug !== current.slug))
|
|
261
|
+
return { kind: "moved" };
|
|
262
|
+
return { kind: "lost", conflict: { op: "update", id: entry.id, reason: "rev mismatch (concurrent change)", baseRev, currentRev: fresh?.rev ?? currentRev } };
|
|
263
|
+
}
|
|
264
|
+
async writeDelete({ id, currentRev, current, baseRev }, confirm) {
|
|
265
|
+
confirm();
|
|
266
|
+
const res = await this.query(`DELETE FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1 AND rev = $2 AND scope = $3 AND slug = $4 RETURNING id`, [id, currentRev, current.scope, current.slug]);
|
|
267
|
+
if (res.rows.length > 0)
|
|
268
|
+
return { kind: "committed" };
|
|
269
|
+
const fresh = await this.freshRow(id);
|
|
270
|
+
if (fresh !== undefined && fresh.rev === currentRev && (fresh.scope !== current.scope || fresh.slug !== current.slug))
|
|
271
|
+
return { kind: "moved" };
|
|
272
|
+
return { kind: "lost", conflict: { op: "delete", id, reason: "rev mismatch (concurrent change)", baseRev, currentRev: fresh?.rev ?? currentRev } };
|
|
273
|
+
}
|
|
274
|
+
async appendHistory(rows) {
|
|
275
|
+
if (!this.history)
|
|
393
276
|
return;
|
|
277
|
+
try {
|
|
278
|
+
await this.history.appendHistory(rows);
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
this.historyRowsLost += rows.length;
|
|
282
|
+
console.warn(`PgMemoryEngineBackend: best-effort history append failed (${rows.length} row(s) lost, ${this.historyRowsLost} cumulative — audit face only, entries committed): ${err instanceof Error ? err.message : String(err)}`);
|
|
394
283
|
}
|
|
395
|
-
report.applied.push({ op: "update", id: entry.id, slug: entry.slug });
|
|
396
|
-
if (this.history)
|
|
397
|
-
historyRows.push({ id: entry.id, rev, prevRev: currentRev, op: "update", scope: entry.scope, snapshot: { ...entry, rev }, writtenAtMs: this.clock() });
|
|
398
|
-
}
|
|
399
|
-
async freshRev(id, fallback) {
|
|
400
|
-
const res = await this.query(`SELECT rev FROM ${PG_MEMORY_ENGINE_TABLES.entry} WHERE id = $1`, [id]);
|
|
401
|
-
return res.rows.length > 0 ? String(res.rows[0].rev) : fallback;
|
|
402
284
|
}
|
|
403
285
|
async getConsolidationCursor(scope) {
|
|
404
286
|
const res = await this.query(`SELECT cursor FROM ${PG_MEMORY_ENGINE_TABLES.cursor} WHERE scope = $1`, [scope]);
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 两只 SQL 记忆腿(`memory-engine-pg.ts` / `memory-engine-tidb.ts`)的 `applyPatches` —— **一份判、一个驱动器**。
|
|
3
|
+
*
|
|
4
|
+
* ## 为什么是一个共享模块
|
|
5
|
+
*
|
|
6
|
+
* 两腿此前各抄一份 `applyOne`(op 闭集 / 键宽 / 歧义 origin / 不可变律 / guard / 跨 scope / 撞名后缀 / rev CAS),逐段「Pg 版同注」,
|
|
7
|
+
* 且「判」与「写」交织在一条循环里。core 的落点问询协议(`MemoryBackend.applyPatches` 的 `opts.admit` / `opts.confirm` 与能力位
|
|
8
|
+
* `admitsLandings`,协议原文 = core `landing-admission.d.ts` 头注)要求后端**先判出整批的落点、问过、再写** —— 交织形做不到,两份
|
|
9
|
+
* 各拆一遍就是两个写者写同一条协议。所以判只有一份({@link planSqlPatch},只读,产出决定 + 落点),驱动器只有一个
|
|
10
|
+
* ({@link applySqlPatches}),两腿各自只剩**读口**(两只 SELECT)、**方言写口**(add / update / delete 的 CAS 写)与 history 冲刷。
|
|
11
|
+
*
|
|
12
|
+
* ## 协议在 SQL 上的读法
|
|
13
|
+
*
|
|
14
|
+
* 与 core File 腿(`file-backend.js` 的 `applyPatches` / `applyPatchesLocked`)的差别都出自「SQL 腿没有事务锁」这一个结构事实:
|
|
15
|
+
* ① File 在锁内计划、出锁问、再进锁重计划,直到整批每个落点都问过才开始提交;这里没有锁可出,计划是对库的只读预测,问过之后
|
|
16
|
+
* **再整批预测一遍**(File 的「再计划」),仍有没问过的就再问 —— 所以 `admit` 的每一次调用都在本次 apply 的第一笔写之前,拒 ⇒ 一字未写。
|
|
17
|
+
* 「问、记下问过的、轮数预算、超限的码与句」整圈是 core 的 `admittingLandings`(File 腿同一只),本模块只交它一次尝试(预测 + 提交);
|
|
18
|
+
* 「这些落点问过没有」只问 core 的 `requireLandingsAdmitted`(问过的集合与它的键都是 core 的,本模块不持第二份);
|
|
19
|
+
* ② 提交恒为一 patch 一条写语句(两态都不会一次提交两个 patch 的落点)。每条写之前现判一次:落点在最后一次预测之后才变(并发写者抢名 /
|
|
20
|
+
* 挪走行)且变出了没问过的拼写 ⇒ 本次 apply 还一笔没写时回去再问;已经写过 ⇒ 不再问(再问就可能「拒了却已写了前缀」),这一条报冲突、
|
|
21
|
+
* 一字不写,下次 apply 再问 —— 与 File「崩在两个落点之间,后一个留给下次 apply 再问」同一语义(变去的拼写都问过 ⇒ 照写);
|
|
22
|
+
* ③ File 的 confirm 在事务锁下调;这里在写语句之前同步调,不持锁。
|
|
23
|
+
*
|
|
24
|
+
* · LANDING = 一个 patch 写下或移走的每个拼写 `{ id, scope, slug }`:add 的终拼(含撞名后缀)、同 scope 改名留下的旧拼、update 的目标
|
|
25
|
+
* 与移走的旧拼、delete 移走的拼。
|
|
26
|
+
* · 写语句把**计划的投影**也绑进条件(rev 是内容哈希、不含 scope / slug,单靠 `WHERE id AND rev` 绑不住落点):行在计划之后被挪到别的拼写
|
|
27
|
+
* ⇒ 写不落,方言写口按现读答 `moved`,驱动器重判(新拼写同样要问过才写);名被占(唯一键冲突)⇒ `slug-taken`,重判从下一个后缀起
|
|
28
|
+
* (与改前「撞键即退下一个后缀」同一序列)。
|
|
29
|
+
* · 问的次数有上限(core `MAX_LANDING_ADMISSION_ROUNDS`),超了 core 抛 `memory.landing_unstable`(此时恒一字未写)。
|
|
30
|
+
* · `confirm` = 调用方的最后一句话:方言写口在**每条** entry 写语句发出之前同步调它(与写语句之间没有 await)。SQL 腿不持事务锁 ——
|
|
31
|
+
* confirm 与写语句之间仍有一段竞争窗,这是两腿「单语句 CAS、无事务」的既有结构事实(MIGRATION / USAGE 的已知限制一句)。
|
|
32
|
+
* · 抛错分诊:per-patch 的 catch 只把读 / 写 I/O 失败记成 `io error` 冲突(改前同);`confirm` 的抛被包成 {@link CallerThrow} 越过这道
|
|
33
|
+
* catch,出驱动器时还原成调用方抛出的**同一个值**;`admit` 根本不在 catch 内。已提交 patch 的 history 在 `finally` 里照冲刷。
|
|
34
|
+
* · 「已经写过」按**可能写过**算:一条写语句发出之后以 I/O 失败收场,库里落没落不知道 ⇒ 当作写过(此后不再问),否则后面一次被拒的重问
|
|
35
|
+
* 就可能发生在一笔其实已落下的写之后。写语句的三种「没落」结局(唯一键冲突 / 零行 + 现读)是库给的确答,不算写过。
|
|
36
|
+
* · 不传 opts ⇒ 没有计划阶段、`confirm` 是空操作;常规路径与改前逐字同。只在并发竞争路径上收紧:行在判与写之间被挪走 ⇒ 按现状重判
|
|
37
|
+
* 再写(此前按 id 盲写);同 id 的行在判与写之间被别的副本插入 ⇒ 重判(guard / 跨 scope 拒照判,此前按 id 盲覆盖)。
|
|
38
|
+
*/
|
|
39
|
+
import { type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type NotePatch, type PatchReport } from "@sema-agent/core";
|
|
40
|
+
import type { MemoryHistoryAppendRow } from "./memory-sync-store-pg.js";
|
|
41
|
+
/** `applyPatches` 的第二参 —— 从 core 的 `MemoryBackend` 接口推导,不手抄。 */
|
|
42
|
+
export type SqlApplyOpts = NonNullable<Parameters<MemoryBackend["applyPatches"]>[1]>;
|
|
43
|
+
export type SqlPatchConflict = PatchReport["conflicts"][number];
|
|
44
|
+
/** 计划读到的一行已提交 entry(frontmatter 已解码)。 */
|
|
45
|
+
export interface SqlEntryRow {
|
|
46
|
+
readonly scope: string;
|
|
47
|
+
readonly slug: string;
|
|
48
|
+
readonly rev: string;
|
|
49
|
+
readonly frontmatter: MemoryEntryFrontmatter;
|
|
50
|
+
}
|
|
51
|
+
/** 判的两只读口(方言自供)。 */
|
|
52
|
+
export interface SqlEntryReads {
|
|
53
|
+
rowOf(id: string): Promise<SqlEntryRow | undefined>;
|
|
54
|
+
/** 占着 `(scope, slug)` 的 id(唯一键 ⇒ 至多一个;数组形让叠层视图能并入计划中的行)。 */
|
|
55
|
+
idsAt(scope: string, slug: string): Promise<readonly string[]>;
|
|
56
|
+
}
|
|
57
|
+
/** 一个拼写在库里的位置(写语句绑定的投影)。 */
|
|
58
|
+
export interface SqlProjection {
|
|
59
|
+
readonly scope: string;
|
|
60
|
+
readonly slug: string;
|
|
61
|
+
}
|
|
62
|
+
/** add 的写入单:`stored` = 终拼形(含后缀)。`prior` = 计划时这个 id 的行所在的投影(缺席 = 计划时没有这一行):写语句只覆盖仍在那里的行,
|
|
63
|
+
* 没有 prior 时只插入 —— 行在判与写之间被挪走 / 被别的副本插入都答 `moved`。 */
|
|
64
|
+
export interface SqlAddWrite {
|
|
65
|
+
readonly stored: MemoryEntry;
|
|
66
|
+
readonly rev: string;
|
|
67
|
+
readonly prior: SqlProjection | undefined;
|
|
68
|
+
}
|
|
69
|
+
/** update 的写入单:条件 = 判过的 rev **且**判过的投影 `current`。 */
|
|
70
|
+
export interface SqlUpdateWrite {
|
|
71
|
+
readonly entry: MemoryEntry;
|
|
72
|
+
readonly rev: string;
|
|
73
|
+
readonly currentRev: string;
|
|
74
|
+
readonly current: SqlProjection;
|
|
75
|
+
readonly baseRev: string | undefined;
|
|
76
|
+
}
|
|
77
|
+
/** delete 的写入单:条件同 update。 */
|
|
78
|
+
export interface SqlDeleteWrite {
|
|
79
|
+
readonly id: string;
|
|
80
|
+
readonly currentRev: string;
|
|
81
|
+
readonly current: SqlProjection;
|
|
82
|
+
readonly baseRev: string | undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 一次写的结局:`committed` = 落了;`slug-taken` = 唯一键 `(scope, slug)` 冲突(名在计划之后被别的副本占了);`moved` = 写不落而现读显示
|
|
86
|
+
* 这个 id 的行已不在计划的投影上(或计划时没有、现在有了);`lost` = rev CAS 输了(冲突里的 currentRev 是现值)。
|
|
87
|
+
*/
|
|
88
|
+
export type SqlWriteOutcome = {
|
|
89
|
+
readonly kind: "committed";
|
|
90
|
+
} | {
|
|
91
|
+
readonly kind: "slug-taken";
|
|
92
|
+
} | {
|
|
93
|
+
readonly kind: "moved";
|
|
94
|
+
} | {
|
|
95
|
+
readonly kind: "lost";
|
|
96
|
+
readonly conflict: SqlPatchConflict;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* 方言写口。每只都必须在它发出的**每条** entry 写语句之前同步调 `confirm()`(中间不许有 await);只把「这条写没落成」的形翻译成结局,
|
|
100
|
+
* 其余 I/O 失败原样抛。`prepare` 在一个 patch 第一次要写之前调一次,结果交给它的每一次写(撞名 / 被挪重试不重算 —— PG 的向量)。
|
|
101
|
+
*/
|
|
102
|
+
export interface SqlEntryWrites<P> {
|
|
103
|
+
prepare(entry: MemoryEntry): Promise<P>;
|
|
104
|
+
add(w: SqlAddWrite, prepared: P, confirm: () => void): Promise<SqlWriteOutcome>;
|
|
105
|
+
update(w: SqlUpdateWrite, prepared: P, confirm: () => void): Promise<SqlWriteOutcome>;
|
|
106
|
+
remove(w: SqlDeleteWrite, confirm: () => void): Promise<SqlWriteOutcome>;
|
|
107
|
+
}
|
|
108
|
+
/** 一只 SQL 腿交给驱动器的全部东西。 */
|
|
109
|
+
export interface SqlMemoryLeg<P> {
|
|
110
|
+
readonly reads: SqlEntryReads;
|
|
111
|
+
readonly writes: SqlEntryWrites<P>;
|
|
112
|
+
/** 本方言的 `unstorable_bytes` 拒绝文案(两方言措辞不同,判据同一只 `pgHasUnstorable`)。 */
|
|
113
|
+
readonly unstorableReason: string;
|
|
114
|
+
/** history 行的落笔时刻(只在 `appendHistory` 在场时调,与改前的时钟调用次数同)。 */
|
|
115
|
+
readonly now: () => number;
|
|
116
|
+
/** best-effort history 冲刷(自己吞错计数,永不抛);缺席 ⇒ 不攒 history 行。 */
|
|
117
|
+
readonly appendHistory?: (rows: readonly MemoryHistoryAppendRow[]) => Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
/** 两只 SQL 腿的 `applyPatches`。读法见文件头注。 */
|
|
120
|
+
export declare function applySqlPatches<P>(patches: readonly NotePatch[], opts: SqlApplyOpts | undefined, leg: SqlMemoryLeg<P>): Promise<PatchReport>;
|
|
121
|
+
//# sourceMappingURL=memory-engine-sql-apply.d.ts.map
|