@sema-agent/server 7.4.0 → 7.6.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/LICENSE +1 -1
- package/README.md +18 -3
- package/README.zh-CN.md +14 -3
- package/USAGE.md +80 -1
- package/dist/approval-card.d.ts +15 -3
- package/dist/approval-card.js +41 -7
- package/dist/approval-reconciler.d.ts +109 -12
- package/dist/approval-reconciler.js +152 -24
- package/dist/boot/config-center.js +15 -2
- package/dist/boot/coordinators.js +10 -2
- package/dist/boot/execution-env.js +1 -1
- package/dist/boot/org-memory.d.ts +6 -0
- package/dist/boot/org-memory.js +1 -1
- package/dist/boot/parked-revive-gate.d.ts +78 -0
- package/dist/boot/parked-revive-gate.js +114 -0
- package/dist/boot/reapers.d.ts +2 -0
- package/dist/boot/reapers.js +11 -4
- package/dist/boot/resolve-spec.d.ts +3 -19
- package/dist/boot/resolve-spec.js +73 -67
- package/dist/boot/runner-deps.d.ts +23 -1
- package/dist/boot/runner-deps.js +8 -11
- package/dist/boot/workflow-orchestration.d.ts +8 -3
- package/dist/boot/workflow-orchestration.js +23 -1
- package/dist/budget.d.ts +1 -1
- package/dist/budget.js +1 -1
- package/dist/capabilities/repo-tools.d.ts +1 -1
- package/dist/capabilities/repo-tools.js +8 -2
- package/dist/config-center/apply-effective.js +33 -10
- package/dist/config-provider.d.ts +1 -0
- package/dist/config-provider.js +23 -3
- package/dist/config-types.d.ts +27 -9
- package/dist/config.d.ts +6 -1
- package/dist/config.js +61 -15
- package/dist/deployment-governance.d.ts +168 -0
- package/dist/deployment-governance.js +206 -0
- package/dist/env-facts.d.ts +3 -1
- package/dist/env-facts.js +3 -1
- package/dist/fleet/fleet-bus.d.ts +17 -2
- package/dist/fleet/fleet-bus.js +68 -3
- package/dist/fleet/fleet-terminal-window.d.ts +98 -0
- package/dist/fleet/fleet-terminal-window.js +316 -0
- package/dist/governance-ask-marks.d.ts +31 -0
- package/dist/governance-ask-marks.js +122 -0
- package/dist/hooks/hook-runner.d.ts +28 -0
- package/dist/hooks/hook-runner.js +149 -25
- package/dist/http/routes/approvals-assistant.js +6 -7
- package/dist/http/routes/diagnostics.js +10 -5
- package/dist/http/routes/fleet.js +160 -14
- package/dist/http/routes/memory-policy.d.ts +2 -1
- package/dist/http/routes/memory-policy.js +77 -13
- package/dist/http/routes/runs.js +6 -2
- package/dist/http/routes/tasks.js +59 -22
- package/dist/http/routes/trace-usage.js +3 -4
- package/dist/http/send.d.ts +23 -0
- package/dist/http/send.js +23 -0
- package/dist/http/server.d.ts +9 -0
- package/dist/http/server.js +28 -14
- package/dist/http/sse-log.js +3 -4
- package/dist/http/wire-types.d.ts +7 -2
- package/dist/leader/diffout.d.ts +10 -0
- package/dist/leader/diffout.js +14 -2
- package/dist/leader/diffup.js +3 -2
- package/dist/leader/planner.js +7 -0
- package/dist/main.js +39 -31
- package/dist/observability/fail-open.d.ts +17 -2
- package/dist/observability/fail-open.js +19 -4
- package/dist/observability/prompt-manifest.d.ts +5 -1
- package/dist/orchestration/workflow-notify-journal.d.ts +58 -2
- package/dist/orchestration/workflow-notify-journal.js +130 -45
- package/dist/parked-decide.js +9 -4
- package/dist/plugins/approval-ask-store-memory.d.ts +2 -2
- package/dist/plugins/approval-ask-store-memory.js +3 -2
- package/dist/plugins/approval-ask-store-sql.d.ts +60 -5
- package/dist/plugins/approval-ask-store-sql.js +75 -35
- package/dist/plugins/background-agent-store-sql.js +16 -16
- package/dist/plugins/background-shell-support.d.ts +1 -1
- package/dist/plugins/background-shell-support.js +2 -2
- package/dist/plugins/breaker-state-sql.js +2 -2
- package/dist/plugins/checkpoint-store-sql.d.ts +67 -8
- package/dist/plugins/checkpoint-store-sql.js +76 -13
- package/dist/plugins/image-bake-store-sql.d.ts +1 -1
- package/dist/plugins/image-bake-store-sql.js +27 -27
- package/dist/plugins/image-index-sql.js +15 -15
- package/dist/plugins/local-checkpoint-store.d.ts +20 -1
- package/dist/plugins/local-checkpoint-store.js +19 -0
- package/dist/plugins/mailbox-store-sql.d.ts +4 -10
- package/dist/plugins/mailbox-store-sql.js +59 -6
- package/dist/plugins/memory-engine-pg.js +9 -9
- package/dist/plugins/memory-engine-tidb.js +7 -7
- package/dist/plugins/memory-sync-store-pg.js +13 -13
- package/dist/plugins/memory-sync-store-tidb.js +5 -5
- package/dist/plugins/outcome-ledger-sql.js +7 -7
- package/dist/plugins/pg-cost-quota.js +3 -3
- package/dist/plugins/pg-pool.js +84 -75
- package/dist/plugins/pg-rate-limiter.js +3 -3
- package/dist/plugins/pg-session-storage.d.ts +1 -1
- package/dist/plugins/pg-session-storage.js +12 -13
- package/dist/plugins/remote-env-host.js +3 -1
- package/dist/plugins/remote-env-local-docker.js +6 -3
- package/dist/plugins/remote-env-ssh.d.ts +13 -1
- package/dist/plugins/roster-store-sql.js +8 -8
- package/dist/plugins/store-contracts.d.ts +19 -0
- package/dist/plugins/store-contracts.js +42 -0
- package/dist/plugins/task-attachment-store.js +5 -5
- package/dist/plugins/task-list-store-sql.js +1 -1
- package/dist/plugins/tidb-cost-quota.js +1 -1
- package/dist/plugins/tidb-pool.js +83 -60
- package/dist/plugins/tidb-rate-limiter.js +1 -1
- package/dist/plugins/tidb-session-store.js +2 -5
- package/dist/plugins/tool-result-store-sql.js +2 -2
- package/dist/plugins/usage-window-store-sql.js +13 -13
- package/dist/plugins/write-behind-counter.d.ts +10 -2
- package/dist/plugins/write-behind-counter.js +13 -3
- package/dist/resource-suspend.d.ts +3 -1
- package/dist/resource-suspend.js +3 -1
- package/dist/run-local.d.ts +73 -1
- package/dist/run-local.js +146 -5
- package/dist/runs.d.ts +11 -1
- package/dist/runs.js +18 -3
- package/dist/runtime-governance.d.ts +18 -0
- package/dist/runtime-governance.js +90 -3
- package/dist/security.d.ts +12 -0
- package/dist/security.js +12 -0
- package/dist/session-sync-kernel.d.ts +13 -0
- package/dist/session-sync-kernel.js +13 -0
- package/dist/task-settings.d.ts +3 -9
- package/dist/task-settings.js +16 -13
- package/dist/tool-approval.d.ts +33 -6
- package/dist/tool-approval.js +80 -23
- package/dist/trace/core-keyset-guard.d.ts +18 -4
- package/dist/trace/project.d.ts +10 -1
- package/dist/trace/project.js +31 -0
- package/package.json +3 -3
- package/dist/boot/lexical-path-env.d.ts +0 -10
- package/dist/boot/lexical-path-env.js +0 -88
- package/dist/capabilities/oa-tools.d.ts +0 -15
- package/dist/capabilities/oa-tools.js +0 -54
- package/dist/finance/cost-taxonomy.d.ts +0 -34
- package/dist/finance/cost-taxonomy.js +0 -26
- package/dist/plugins/approval-store-sql.d.ts +0 -116
- package/dist/plugins/approval-store-sql.js +0 -151
- package/dist/plugins/file-workflow-journal-store.d.ts +0 -12
- package/dist/plugins/file-workflow-journal-store.js +0 -12
- package/dist/plugins/pg-approval-store.d.ts +0 -9
- package/dist/plugins/pg-approval-store.js +0 -9
- package/dist/plugins/pg-breaker-state.d.ts +0 -8
- package/dist/plugins/pg-breaker-state.js +0 -8
- package/dist/plugins/pg-checkpoint-store.d.ts +0 -10
- package/dist/plugins/pg-checkpoint-store.js +0 -10
- package/dist/plugins/pg-file-snapshot-store.d.ts +0 -8
- package/dist/plugins/pg-file-snapshot-store.js +0 -8
- package/dist/plugins/pg-image-bake.d.ts +0 -12
- package/dist/plugins/pg-image-bake.js +0 -11
- package/dist/plugins/pg-image-index.d.ts +0 -12
- package/dist/plugins/pg-image-index.js +0 -11
- package/dist/plugins/pg-outcome-ledger.d.ts +0 -12
- package/dist/plugins/pg-outcome-ledger.js +0 -11
- package/dist/plugins/pg-resume-anchor-store.d.ts +0 -7
- package/dist/plugins/pg-resume-anchor-store.js +0 -7
- package/dist/plugins/pg-run-store.d.ts +0 -9
- package/dist/plugins/pg-run-store.js +0 -9
- package/dist/plugins/pg-session-policy-store.d.ts +0 -7
- package/dist/plugins/pg-session-policy-store.js +0 -7
- package/dist/plugins/pg-session-store.d.ts +0 -12
- package/dist/plugins/pg-session-store.js +0 -12
- package/dist/plugins/pg-tool-result-store.d.ts +0 -9
- package/dist/plugins/pg-tool-result-store.js +0 -9
- package/dist/plugins/pg-workflow-journal-store.d.ts +0 -9
- package/dist/plugins/pg-workflow-journal-store.js +0 -9
- package/dist/plugins/pg-workflow-run-store.d.ts +0 -9
- package/dist/plugins/pg-workflow-run-store.js +0 -9
- package/dist/plugins/tidb-approval-store.d.ts +0 -8
- package/dist/plugins/tidb-approval-store.js +0 -8
- package/dist/plugins/tidb-breaker-state.d.ts +0 -7
- package/dist/plugins/tidb-breaker-state.js +0 -7
- package/dist/plugins/tidb-checkpoint-store.d.ts +0 -9
- package/dist/plugins/tidb-checkpoint-store.js +0 -9
- package/dist/plugins/tidb-file-snapshot-store.d.ts +0 -8
- package/dist/plugins/tidb-file-snapshot-store.js +0 -8
- package/dist/plugins/tidb-image-bake.d.ts +0 -12
- package/dist/plugins/tidb-image-bake.js +0 -11
- package/dist/plugins/tidb-image-index.d.ts +0 -12
- package/dist/plugins/tidb-image-index.js +0 -11
- package/dist/plugins/tidb-outcome-ledger.d.ts +0 -12
- package/dist/plugins/tidb-outcome-ledger.js +0 -12
- package/dist/plugins/tidb-resume-anchor-store.d.ts +0 -7
- package/dist/plugins/tidb-resume-anchor-store.js +0 -7
- package/dist/plugins/tidb-run-store.d.ts +0 -10
- package/dist/plugins/tidb-run-store.js +0 -9
- package/dist/plugins/tidb-session-policy-store.d.ts +0 -7
- package/dist/plugins/tidb-session-policy-store.js +0 -7
- package/dist/plugins/tidb-tool-result-store.d.ts +0 -8
- package/dist/plugins/tidb-tool-result-store.js +0 -10
- package/dist/plugins/tidb-workflow-journal-store.d.ts +0 -9
- package/dist/plugins/tidb-workflow-journal-store.js +0 -9
- package/dist/plugins/tidb-workflow-run-store.d.ts +0 -10
- package/dist/plugins/tidb-workflow-run-store.js +0 -10
- package/dist/plugins/workflow-journal-limits.d.ts +0 -12
- package/dist/plugins/workflow-journal-limits.js +0 -12
- package/dist/sema-registry.d.ts +0 -41
- package/dist/sema-registry.js +0 -40
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* - `?` placeholders vs `$n` - `CAST(? AS JSON)` vs `$n::jsonb` (argv equality)
|
|
27
27
|
* - `INSERT IGNORE` vs `ON CONFLICT DO NOTHING` - affectedRows vs rowCount (via SqlDriver)
|
|
28
28
|
* - dup-key `ER_DUP_ENTRY` vs SQLSTATE `23505` - which UNIQUE collided: mysql2 `sqlMessage` text
|
|
29
|
-
* vs pg `err.constraint` (
|
|
29
|
+
* vs pg `err.constraint` (#192 A5 归一后两条方言同名 uq_image_bake_event_line)
|
|
30
30
|
* - `BEGIN PESSIMISTIC` (TiDB current read) vs `BEGIN` (PG READ COMMITTED under the sentinel lock)
|
|
31
31
|
* - the ADMISSION statement itself differs (see createBakeIfIdle) — both are correct under the
|
|
32
32
|
* sentinel lock, and both are preserved verbatim because the real-DB suites are the oracle
|
|
@@ -46,34 +46,34 @@ import { mapBakeRow as mapRow, BAKE_SELECT_COLS as SELECT_COLS, } from "./store-
|
|
|
46
46
|
* TINYINT(1) 归一(clay 批 2026-07-26):布尔列的 PG 映射统一 SMALLINT 0/1(此前本家族用 BOOLEAN)——
|
|
47
47
|
* 两方言驱动读回同为 number ⇒ 不再存在「忘了配的适配器」这个病族;写入参与 SQL 字面量一律 0/1,
|
|
48
48
|
* 读侧 `!!`/`Boolean()` 保持,store 出口 JS 类型逐字节不变(真双库套件的严格值断言为证)。
|
|
49
|
-
* The
|
|
49
|
+
* The line UNIQUE is given the explicit name uq_image_bake_event_line so a 23505 can be attributed to it via err.constraint. */
|
|
50
50
|
export const PG_IMAGE_BAKE_SCHEMA = [
|
|
51
51
|
`CREATE TABLE IF NOT EXISTS image_bake (
|
|
52
|
-
bake_id VARCHAR(64) NOT NULL,
|
|
53
|
-
profile VARCHAR(128) NOT NULL,
|
|
52
|
+
bake_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
53
|
+
profile VARCHAR(128) COLLATE "C" NOT NULL,
|
|
54
54
|
bands JSONB NULL,
|
|
55
|
-
base_ref VARCHAR(255) NULL,
|
|
55
|
+
base_ref VARCHAR(255) COLLATE "C" NULL,
|
|
56
56
|
push SMALLINT NOT NULL DEFAULT 0,
|
|
57
57
|
dry_run SMALLINT NOT NULL DEFAULT 0,
|
|
58
58
|
logs SMALLINT NOT NULL DEFAULT 0,
|
|
59
59
|
argv JSONB NOT NULL,
|
|
60
|
-
status VARCHAR(16) NOT NULL,
|
|
61
|
-
state VARCHAR(16) NULL,
|
|
62
|
-
digest VARCHAR(255) NULL,
|
|
63
|
-
repo VARCHAR(255) NULL,
|
|
64
|
-
ref VARCHAR(320) NULL,
|
|
65
|
-
index_id VARCHAR(64) NULL,
|
|
60
|
+
status VARCHAR(16) COLLATE "C" NOT NULL,
|
|
61
|
+
state VARCHAR(16) COLLATE "C" NULL,
|
|
62
|
+
digest VARCHAR(255) COLLATE "C" NULL,
|
|
63
|
+
repo VARCHAR(255) COLLATE "C" NULL,
|
|
64
|
+
ref VARCHAR(320) COLLATE "C" NULL,
|
|
65
|
+
index_id VARCHAR(64) COLLATE "C" NULL,
|
|
66
66
|
exit_code INT NULL,
|
|
67
|
-
error TEXT NULL,
|
|
68
|
-
error_code VARCHAR(32) NULL,
|
|
69
|
-
manifest_sha VARCHAR(64) NULL,
|
|
70
|
-
tag VARCHAR(255) NULL,
|
|
71
|
-
idem_key VARCHAR(255) NULL,
|
|
72
|
-
ingest_secret VARCHAR(64) NULL,
|
|
73
|
-
runner_id VARCHAR(64) NULL,
|
|
67
|
+
error TEXT COLLATE "C" NULL,
|
|
68
|
+
error_code VARCHAR(32) COLLATE "C" NULL,
|
|
69
|
+
manifest_sha VARCHAR(64) COLLATE "C" NULL,
|
|
70
|
+
tag VARCHAR(255) COLLATE "C" NULL,
|
|
71
|
+
idem_key VARCHAR(255) COLLATE "C" NULL,
|
|
72
|
+
ingest_secret VARCHAR(64) COLLATE "C" NULL,
|
|
73
|
+
runner_id VARCHAR(64) COLLATE "C" NULL,
|
|
74
74
|
lease_until TIMESTAMPTZ(3) NULL,
|
|
75
75
|
cancel_requested SMALLINT NOT NULL DEFAULT 0,
|
|
76
|
-
requested_by VARCHAR(128) NULL,
|
|
76
|
+
requested_by VARCHAR(128) COLLATE "C" NULL,
|
|
77
77
|
created_at TIMESTAMPTZ(3) NOT NULL,
|
|
78
78
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
79
79
|
PRIMARY KEY (bake_id),
|
|
@@ -82,19 +82,19 @@ export const PG_IMAGE_BAKE_SCHEMA = [
|
|
|
82
82
|
`CREATE INDEX IF NOT EXISTS idx_image_bake_status ON image_bake (status)`,
|
|
83
83
|
`CREATE INDEX IF NOT EXISTS idx_image_bake_created ON image_bake (created_at)`,
|
|
84
84
|
`CREATE TABLE IF NOT EXISTS image_bake_event (
|
|
85
|
-
bake_id VARCHAR(64) NOT NULL,
|
|
85
|
+
bake_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
86
86
|
seq INT NOT NULL,
|
|
87
87
|
line_ord INT NULL,
|
|
88
|
-
kind VARCHAR(24) NOT NULL,
|
|
89
|
-
level VARCHAR(8) NULL,
|
|
88
|
+
kind VARCHAR(24) COLLATE "C" NOT NULL,
|
|
89
|
+
level VARCHAR(8) COLLATE "C" NULL,
|
|
90
90
|
data JSONB NULL,
|
|
91
91
|
ts TIMESTAMPTZ(3) NOT NULL,
|
|
92
92
|
PRIMARY KEY (bake_id, seq),
|
|
93
93
|
CONSTRAINT uq_image_bake_event_line UNIQUE (bake_id, line_ord)
|
|
94
94
|
)`,
|
|
95
95
|
`CREATE TABLE IF NOT EXISTS image_bake_lease (
|
|
96
|
-
pool VARCHAR(32) NOT NULL DEFAULT 'standard',
|
|
97
|
-
bake_id VARCHAR(64) NOT NULL,
|
|
96
|
+
pool VARCHAR(32) COLLATE "C" NOT NULL DEFAULT 'standard',
|
|
97
|
+
bake_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
98
98
|
acquired_at TIMESTAMPTZ(3) NOT NULL,
|
|
99
99
|
expires_at TIMESTAMPTZ(3) NOT NULL,
|
|
100
100
|
PRIMARY KEY (pool)
|
|
@@ -104,7 +104,7 @@ export const PG_IMAGE_BAKE_SCHEMA = [
|
|
|
104
104
|
// NOT EXISTS that two concurrent READ COMMITTED txns both pass — see createBakeIfIdle). Dialect-uniform with
|
|
105
105
|
// the TiDB twin so a future divergence FAILS LOUD in the shared real-DB suite.
|
|
106
106
|
`CREATE TABLE IF NOT EXISTS image_bake_admit (
|
|
107
|
-
pool VARCHAR(32) NOT NULL,
|
|
107
|
+
pool VARCHAR(32) COLLATE "C" NOT NULL,
|
|
108
108
|
PRIMARY KEY (pool)
|
|
109
109
|
)`,
|
|
110
110
|
];
|
|
@@ -121,7 +121,7 @@ function isDupKey(dialect, err) {
|
|
|
121
121
|
return Boolean(err) && code === (dialect === "tidb" ? "ER_DUP_ENTRY" : "23505");
|
|
122
122
|
}
|
|
123
123
|
/** Which UNIQUE key did the dup collide on? The ATTRIBUTION CHANNEL differs by engine: mysql2 names the key in
|
|
124
|
-
* `sqlMessage` ("Duplicate entry '…' for key 'image_bake_event.
|
|
124
|
+
* `sqlMessage` ("Duplicate entry '…' for key 'image_bake_event.uq_image_bake_event_line'" vs "…'image_bake_event.PRIMARY'");
|
|
125
125
|
* PG names the violated constraint in `err.constraint` (the PK is auto-named `image_bake_event_pkey`, the line
|
|
126
126
|
* UNIQUE is explicitly named `uq_image_bake_event_line` so it is attributable at all).
|
|
127
127
|
* M2: a line-unique dup is a BENIGN at-least-once re-send (already stored → no-op); a `PRIMARY` (bake_id,seq)
|
|
@@ -129,7 +129,7 @@ function isDupKey(dialect, err) {
|
|
|
129
129
|
function dupKeyName(dialect, err) {
|
|
130
130
|
if (dialect === "tidb") {
|
|
131
131
|
const msg = String(err?.sqlMessage ?? err?.message ?? "");
|
|
132
|
-
if (/
|
|
132
|
+
if (/uq_image_bake_event_line/i.test(msg))
|
|
133
133
|
return "uq_line";
|
|
134
134
|
if (/PRIMARY/i.test(msg))
|
|
135
135
|
return "primary";
|
|
@@ -52,33 +52,33 @@ import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
|
52
52
|
* 读侧 `!!` 保持,出口 JS 类型不变), inline UNIQUE KEY/KEY → separate CREATE UNIQUE INDEX / CREATE INDEX). */
|
|
53
53
|
export const PG_IMAGE_INDEX_SCHEMA = [
|
|
54
54
|
`CREATE TABLE IF NOT EXISTS sandbox_image_index (
|
|
55
|
-
id VARCHAR(64) NOT NULL,
|
|
56
|
-
profile VARCHAR(64) NOT NULL,
|
|
55
|
+
id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
56
|
+
profile VARCHAR(64) COLLATE "C" NOT NULL,
|
|
57
57
|
bands JSONB,
|
|
58
|
-
repo VARCHAR(255) NOT NULL,
|
|
59
|
-
tag VARCHAR(128) NOT NULL,
|
|
60
|
-
digest VARCHAR(80) NOT NULL,
|
|
58
|
+
repo VARCHAR(255) COLLATE "C" NOT NULL,
|
|
59
|
+
tag VARCHAR(128) COLLATE "C" NOT NULL,
|
|
60
|
+
digest VARCHAR(80) COLLATE "C" NOT NULL,
|
|
61
61
|
toolchain_versions JSONB,
|
|
62
62
|
capabilities JSONB,
|
|
63
63
|
pod_contract JSONB,
|
|
64
64
|
size_bytes BIGINT,
|
|
65
|
-
status VARCHAR(16) NOT NULL DEFAULT 'building',
|
|
66
|
-
visibility VARCHAR(16) NOT NULL DEFAULT 'public',
|
|
67
|
-
tenant_id VARCHAR(64),
|
|
68
|
-
manifest_sha VARCHAR(64),
|
|
69
|
-
recipe_git_sha VARCHAR(64),
|
|
70
|
-
generator_version VARCHAR(32),
|
|
65
|
+
status VARCHAR(16) COLLATE "C" NOT NULL DEFAULT 'building',
|
|
66
|
+
visibility VARCHAR(16) COLLATE "C" NOT NULL DEFAULT 'public',
|
|
67
|
+
tenant_id VARCHAR(64) COLLATE "C",
|
|
68
|
+
manifest_sha VARCHAR(64) COLLATE "C",
|
|
69
|
+
recipe_git_sha VARCHAR(64) COLLATE "C",
|
|
70
|
+
generator_version VARCHAR(32) COLLATE "C",
|
|
71
71
|
build_date TIMESTAMPTZ(3),
|
|
72
|
-
supersedes VARCHAR(64),
|
|
72
|
+
supersedes VARCHAR(64) COLLATE "C",
|
|
73
73
|
signed SMALLINT NOT NULL DEFAULT 0,
|
|
74
74
|
created_at TIMESTAMPTZ(3) NOT NULL,
|
|
75
75
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
76
76
|
PRIMARY KEY (id)
|
|
77
77
|
)`,
|
|
78
78
|
`CREATE UNIQUE INDEX IF NOT EXISTS uq_repo_digest ON sandbox_image_index (repo, digest)`,
|
|
79
|
-
`CREATE INDEX IF NOT EXISTS
|
|
80
|
-
`CREATE INDEX IF NOT EXISTS
|
|
81
|
-
`CREATE INDEX IF NOT EXISTS
|
|
79
|
+
`CREATE INDEX IF NOT EXISTS idx_sandbox_image_index_profile_status ON sandbox_image_index (profile, status, visibility)`,
|
|
80
|
+
`CREATE INDEX IF NOT EXISTS idx_sandbox_image_index_digest ON sandbox_image_index (digest)`,
|
|
81
|
+
`CREATE INDEX IF NOT EXISTS idx_sandbox_image_index_tenant ON sandbox_image_index (tenant_id, status)`,
|
|
82
82
|
];
|
|
83
83
|
/** Idempotent self-contained schema apply (for the integration test; central aggregation is done separately). */
|
|
84
84
|
export async function ensureSchema(pool) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Checkpoint, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ReopenReason, type ResolveExpectation, type ResumeOutcome } from "@sema-agent/core";
|
|
1
|
+
import { type Checkpoint, type CheckpointSummary, type CheckpointToken, type PendingSteerInput, type ReopenReason, type ResolveExpectation, type ResumeOutcome, type StoreDurability, type StoreFidelity } from "@sema-agent/core";
|
|
2
2
|
import { type PendingCheckpoint } from "./checkpoint-store-sql.js";
|
|
3
3
|
export interface LocalCheckpointStoreOptions {
|
|
4
4
|
/** taskId join for the pending card (the local FileRunStore's `getActiveTaskId`). Absent ⇒ `taskId: null`. */
|
|
@@ -8,6 +8,25 @@ export interface LocalCheckpointStoreOptions {
|
|
|
8
8
|
ownerOf?: (sessionId: string) => Promise<string | null | undefined>;
|
|
9
9
|
}
|
|
10
10
|
export declare class LocalCheckpointStore {
|
|
11
|
+
/**
|
|
12
|
+
* `CheckpointStore.durability` 声明(#167 欠账,#168 件5)——**如实按内核判**:本类的内核是 core 的
|
|
13
|
+
* `FileCheckpointStore`(crash-safe ledger + snapshot,自身声明 `"durable"`),本地 scope 注册表也落
|
|
14
|
+
* 在同一个盘上目录里 ⇒ 进程重启后 park 全部还在、还赎得回,`"durable"` 是诚实读数而不是抬举。
|
|
15
|
+
*
|
|
16
|
+
* 它与「session store 的 local 形声明 durable 就拒启」不矛盾:那条判据管的是**声明 vs 介质**,而这里
|
|
17
|
+
* 的介质就是盘。真正的 process-local 形(`InMemoryCheckpointStore`)由 core 自己声明 `"process-local"`。
|
|
18
|
+
*/
|
|
19
|
+
readonly durability: StoreDurability;
|
|
20
|
+
/**
|
|
21
|
+
* `CheckpointStore.fidelity` 声明(core 5.17.0 [3052] 提货批 #172)——**如实按内核判**,与 durability
|
|
22
|
+
* 同一条判据:本类的读写内核是 core 的 `FileCheckpointStore`,它自己声明 `"json"`(盘上是 JSON 行),
|
|
23
|
+
* 包装层只加 scope/session 索引与 ctx 附属文件,不改这条 round-trip 的宽度 ⇒ 抄内核的读数是诚实的。
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ 包装类**不继承**被包装者的声明(本类是委派不是子类),所以这一格必须自己写。它与内核那一格
|
|
26
|
+
* 是否仍然一致由测试盯着(`wiring-governance-operator.test.ts` 的 #172 组直接读内核那一格对表)——
|
|
27
|
+
* 内核哪天改宽度,包装层的声明会被那条钉当场揪出来,而不是靠人记得同步。
|
|
28
|
+
*/
|
|
29
|
+
readonly fidelity: StoreFidelity;
|
|
11
30
|
private readonly inner;
|
|
12
31
|
private readonly scopesPath;
|
|
13
32
|
private readonly sessionTokensPath;
|
|
@@ -34,6 +34,25 @@ function ctxFileOf(sessionId) {
|
|
|
34
34
|
return `${sanitizePathComponent(sessionId).slice(0, 48)}-${createHash("sha256").update(sessionId).digest("hex").slice(0, 12)}.json`;
|
|
35
35
|
}
|
|
36
36
|
export class LocalCheckpointStore {
|
|
37
|
+
/**
|
|
38
|
+
* `CheckpointStore.durability` 声明(#167 欠账,#168 件5)——**如实按内核判**:本类的内核是 core 的
|
|
39
|
+
* `FileCheckpointStore`(crash-safe ledger + snapshot,自身声明 `"durable"`),本地 scope 注册表也落
|
|
40
|
+
* 在同一个盘上目录里 ⇒ 进程重启后 park 全部还在、还赎得回,`"durable"` 是诚实读数而不是抬举。
|
|
41
|
+
*
|
|
42
|
+
* 它与「session store 的 local 形声明 durable 就拒启」不矛盾:那条判据管的是**声明 vs 介质**,而这里
|
|
43
|
+
* 的介质就是盘。真正的 process-local 形(`InMemoryCheckpointStore`)由 core 自己声明 `"process-local"`。
|
|
44
|
+
*/
|
|
45
|
+
durability = "durable";
|
|
46
|
+
/**
|
|
47
|
+
* `CheckpointStore.fidelity` 声明(core 5.17.0 [3052] 提货批 #172)——**如实按内核判**,与 durability
|
|
48
|
+
* 同一条判据:本类的读写内核是 core 的 `FileCheckpointStore`,它自己声明 `"json"`(盘上是 JSON 行),
|
|
49
|
+
* 包装层只加 scope/session 索引与 ctx 附属文件,不改这条 round-trip 的宽度 ⇒ 抄内核的读数是诚实的。
|
|
50
|
+
*
|
|
51
|
+
* ⚠️ 包装类**不继承**被包装者的声明(本类是委派不是子类),所以这一格必须自己写。它与内核那一格
|
|
52
|
+
* 是否仍然一致由测试盯着(`wiring-governance-operator.test.ts` 的 #172 组直接读内核那一格对表)——
|
|
53
|
+
* 内核哪天改宽度,包装层的声明会被那条钉当场揪出来,而不是靠人记得同步。
|
|
54
|
+
*/
|
|
55
|
+
fidelity = "json";
|
|
37
56
|
inner;
|
|
38
57
|
scopesPath;
|
|
39
58
|
sessionTokensPath;
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
39
39
|
import type { Pool as PgPool } from "pg";
|
|
40
40
|
import type { MailboxLease, MailboxStore } from "@sema-agent/core";
|
|
41
|
+
type MailboxAppendMessage = Parameters<MailboxStore["append"]>[2];
|
|
41
42
|
export declare const MAILBOX_TABLE = "mailboxes";
|
|
42
43
|
export declare const MAILBOX_MSG_TABLE = "mailbox_messages";
|
|
43
44
|
export declare function ensureTiDBMailboxSchema(pool: MySqlPool): Promise<void>;
|
|
@@ -46,11 +47,7 @@ export declare class TiDBMailboxStore implements MailboxStore {
|
|
|
46
47
|
private readonly pool;
|
|
47
48
|
constructor(pool: MySqlPool);
|
|
48
49
|
private tx;
|
|
49
|
-
append(scope: string, handle: string, msg:
|
|
50
|
-
from?: string;
|
|
51
|
-
content: string;
|
|
52
|
-
sentAt: number;
|
|
53
|
-
}): Promise<number>;
|
|
50
|
+
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
54
51
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
55
52
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
56
53
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
@@ -66,11 +63,7 @@ export declare class PgMailboxStore implements MailboxStore {
|
|
|
66
63
|
private tx;
|
|
67
64
|
/** 身份键拒绝式([1439] 协议纪律):scope/handle/owner/from 清洗形变 = 路由/attribution 错位。 */
|
|
68
65
|
private assertIdentity;
|
|
69
|
-
append(scope: string, handle: string, msg:
|
|
70
|
-
from?: string;
|
|
71
|
-
content: string;
|
|
72
|
-
sentAt: number;
|
|
73
|
-
}): Promise<number>;
|
|
66
|
+
append(scope: string, handle: string, msg: MailboxAppendMessage): Promise<number>;
|
|
74
67
|
claimLease(scope: string, handle: string, owner: string, ttlMs: number, now?: number): Promise<MailboxLease | null>;
|
|
75
68
|
ack(scope: string, handle: string, owner: string, upToSeq: number): Promise<void>;
|
|
76
69
|
releaseLease(scope: string, handle: string, owner: string): Promise<void>;
|
|
@@ -80,4 +73,5 @@ export declare class PgMailboxStore implements MailboxStore {
|
|
|
80
73
|
maxAgeMs?: number;
|
|
81
74
|
}): Promise<number>;
|
|
82
75
|
}
|
|
76
|
+
export {};
|
|
83
77
|
//# sourceMappingURL=mailbox-store-sql.d.ts.map
|
|
@@ -25,12 +25,58 @@ function assertKeyBytes(what, v) {
|
|
|
25
25
|
function bufToStr(v) {
|
|
26
26
|
return Buffer.isBuffer(v) ? v.toString("utf8") : String(v);
|
|
27
27
|
}
|
|
28
|
+
/** `hopChain` 的 JSON 文本列上界(TiDB `TEXT` = 65535 字节)。core 侧链本就有界
|
|
29
|
+
* (`PEER_ADMISSION_DEFAULTS.maxChainLength` = 28,`PEER_HOP_CHAIN_WINDOW` = 32,元素是 peerAxisToken
|
|
30
|
+
* 短串)⇒ 正常形离这个数几个数量级远;守卫存在的理由是**关掉 MySQL 的静默截断类**(非严格模式下
|
|
31
|
+
* 超长 TEXT 写入只发 warning),截断后的 JSON 文本读侧 parse 失败 = 整条链静默变缺席,而缺席在
|
|
32
|
+
* mailbox 上是有语义的(见下 encodeHopChain 注)。同 assertKeyBytes 精神:写入面 fail-loud。 */
|
|
33
|
+
const HOP_CHAIN_MAX_BYTES = 60_000;
|
|
34
|
+
/**
|
|
35
|
+
* `hopChain` 存取(core 5.17.0 design/176)。**JSON 文本列**,不是清洗面也不是身份键面:
|
|
36
|
+
*
|
|
37
|
+
* - 为什么不清洗:链元素参与 core 的 `hop_loop` **等值比对**。内容面的 lossy 清洗(pgSanitizeText 的
|
|
38
|
+
* U+FFFD 标记)会让两个不同 token 塌成同一个(误报环路)或让一个 token 变形(漏判环路)——两个
|
|
39
|
+
* 方向都是把准入判决改错,与 `content` 那种「读者读到近似文本仍可用」的容忍面性质不同。
|
|
40
|
+
* - 为什么也不需要拒绝:`JSON.stringify` 是 **well-formed**(ES2019)——NUL 转义成 `\u0000`、lone
|
|
41
|
+
* surrogate 转义成 `\udXXX`,产物恒是 ASCII 可存字节。于是 PG 的 unstorable 面根本不会被触及,
|
|
42
|
+
* 两方言都拿到逐字 round-trip,不必像 `from`/`scope`/`handle` 那样走拒绝式。
|
|
43
|
+
* - **缺席 ≠ 空数组**:core 明示 mailbox 的 `hopChain` 在场性本身携带语义(「经 sema 准入门录入」vs
|
|
44
|
+
* 「外部/存量写入」,是整份契约里唯一允许从在场性推断的例外)。所以 NULL 读回 = 不铸键,`[]` 读回
|
|
45
|
+
* = 铸一个空数组键,两者绝不互相折叠。
|
|
46
|
+
*/
|
|
47
|
+
function encodeHopChain(hopChain) {
|
|
48
|
+
if (hopChain === undefined)
|
|
49
|
+
return null;
|
|
50
|
+
const text = JSON.stringify(hopChain);
|
|
51
|
+
if (Buffer.byteLength(text, "utf8") > HOP_CHAIN_MAX_BYTES) {
|
|
52
|
+
throw new Error(`MailboxStore: hopChain exceeds the ${HOP_CHAIN_MAX_BYTES}-byte column (refusing — a truncated chain would silently read back as ABSENT, which mailbox treats as "not gate-admitted")`);
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
}
|
|
56
|
+
/** 读侧:NULL/缺席 ⇒ undefined(不铸键);坏行(非 JSON / 非字符串数组)⇒ 同样 undefined —— 一条读不
|
|
57
|
+
* 懂的链只能诚实降级成「没有链」,绝不半解析出一条会被拿去做环路比对的残链。 */
|
|
58
|
+
function decodeHopChain(raw) {
|
|
59
|
+
if (raw === null || raw === undefined)
|
|
60
|
+
return undefined;
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(bufToStr(raw));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(parsed) || parsed.some((x) => typeof x !== "string"))
|
|
69
|
+
return undefined;
|
|
70
|
+
return parsed;
|
|
71
|
+
}
|
|
28
72
|
function rowToMessage(r) {
|
|
73
|
+
const hopChain = decodeHopChain(r.hop_chain);
|
|
29
74
|
return {
|
|
30
75
|
seq: Number(r.seq),
|
|
31
76
|
...(r.from_id !== null && r.from_id !== undefined ? { from: bufToStr(r.from_id) } : {}),
|
|
32
77
|
content: String(r.content),
|
|
33
78
|
sentAt: Number(r.sent_at_ms),
|
|
79
|
+
...(hopChain !== undefined ? { hopChain } : {}),
|
|
34
80
|
};
|
|
35
81
|
}
|
|
36
82
|
export async function ensureTiDBMailboxSchema(pool) {
|
|
@@ -53,6 +99,10 @@ export async function ensureTiDBMailboxSchema(pool) {
|
|
|
53
99
|
from_id VARBINARY(255) NULL,
|
|
54
100
|
content LONGTEXT NOT NULL,
|
|
55
101
|
sent_at_ms BIGINT NOT NULL,
|
|
102
|
+
-- hop_chain(core 5.17.0 design/176):peer 准入的 hop 链,JSON 文本;NULL = 缺席(**有语义**,
|
|
103
|
+
-- 见 encodeHopChain 头注:缺席 vs 空数组绝不互折)。TEXT 而非 LONGTEXT——链上界在 core(28 跳,
|
|
104
|
+
-- 短 token),写入面另有 HOP_CHAIN_MAX_BYTES fail-loud 守卫关掉静默截断类。
|
|
105
|
+
hop_chain TEXT NULL,
|
|
56
106
|
PRIMARY KEY (scope_key, handle, seq),
|
|
57
107
|
KEY idx_mbm_sent (scope_key, handle, sent_at_ms)
|
|
58
108
|
) COLLATE utf8mb4_bin`);
|
|
@@ -61,7 +111,7 @@ export async function ensurePgMailboxSchema(q) {
|
|
|
61
111
|
await q(`CREATE TABLE IF NOT EXISTS ${MAILBOX_TABLE} (
|
|
62
112
|
scope_key VARCHAR(190) COLLATE "C" NOT NULL,
|
|
63
113
|
handle VARCHAR(190) COLLATE "C" NOT NULL,
|
|
64
|
-
scope VARCHAR(190) NOT NULL,
|
|
114
|
+
scope VARCHAR(190) COLLATE "C" NOT NULL,
|
|
65
115
|
next_seq BIGINT NOT NULL,
|
|
66
116
|
lease_owner VARCHAR(190) COLLATE "C",
|
|
67
117
|
lease_expires_at_ms BIGINT,
|
|
@@ -73,8 +123,9 @@ export async function ensurePgMailboxSchema(q) {
|
|
|
73
123
|
handle VARCHAR(190) COLLATE "C" NOT NULL,
|
|
74
124
|
seq BIGINT NOT NULL,
|
|
75
125
|
from_id VARCHAR(255) COLLATE "C",
|
|
76
|
-
content TEXT NOT NULL,
|
|
126
|
+
content TEXT COLLATE "C" NOT NULL,
|
|
77
127
|
sent_at_ms BIGINT NOT NULL,
|
|
128
|
+
hop_chain TEXT COLLATE "C", -- core 5.17.0 design/176(TiDB twin 同注:NULL = 缺席且有语义)
|
|
78
129
|
PRIMARY KEY (scope_key, handle, seq)
|
|
79
130
|
)`);
|
|
80
131
|
await q(`CREATE INDEX IF NOT EXISTS idx_mbm_sent ON ${MAILBOX_MSG_TABLE} (scope_key, handle, sent_at_ms)`);
|
|
@@ -106,6 +157,7 @@ export class TiDBMailboxStore {
|
|
|
106
157
|
assertIdentityChars("handle", handle);
|
|
107
158
|
assertKeyBytes("scope", scope);
|
|
108
159
|
assertKeyBytes("handle", handle);
|
|
160
|
+
const hopChain = encodeHopChain(msg.hopChain); // 事务外先编码:超界 fail-loud 必须在建盒/分配 seq 之前
|
|
109
161
|
// ensure-first 形:先幂等 INSERT IGNORE 建盒(next_seq=1,首条/drop 后重建同臂)再 FOR UPDATE——
|
|
110
162
|
// 「无行 FOR UPDATE + INSERT」的并发死锁/dup 撞车形整个消掉;seq 分配与 append 同事务(定谳 2)。
|
|
111
163
|
return this.tx(async (c) => {
|
|
@@ -119,7 +171,7 @@ export class TiDBMailboxStore {
|
|
|
119
171
|
}
|
|
120
172
|
const seq = Number(rows[0].next_seq);
|
|
121
173
|
await c.query(`UPDATE ${MAILBOX_TABLE} SET next_seq = ? WHERE scope_key = ? AND handle = ?`, [seq + 1, scope, handle]);
|
|
122
|
-
await c.query(`INSERT INTO ${MAILBOX_MSG_TABLE} (scope_key, handle, seq, from_id, content, sent_at_ms) VALUES (?, ?, ?, ?, ?, ?)`, [scope, handle, seq, msg.from ?? null, msg.content, msg.sentAt]);
|
|
174
|
+
await c.query(`INSERT INTO ${MAILBOX_MSG_TABLE} (scope_key, handle, seq, from_id, content, sent_at_ms, hop_chain) VALUES (?, ?, ?, ?, ?, ?, ?)`, [scope, handle, seq, msg.from ?? null, msg.content, msg.sentAt, hopChain]);
|
|
123
175
|
return seq;
|
|
124
176
|
});
|
|
125
177
|
}
|
|
@@ -139,7 +191,7 @@ export class TiDBMailboxStore {
|
|
|
139
191
|
}
|
|
140
192
|
// FOR UPDATE = 当前读:TiDB 悲观事务的普通 SELECT 按 start_ts 快照(task-list twin 同案)——
|
|
141
193
|
// 锁到 box 行后消息集必须读「现在」,否则 drop+重建窗内老消息混进新盒 lease(maxSeq 错位)。
|
|
142
|
-
const [msgs] = (await c.query(`SELECT seq, from_id, content, sent_at_ms FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? AND handle = ? ORDER BY seq FOR UPDATE`, [scope, handle]));
|
|
194
|
+
const [msgs] = (await c.query(`SELECT seq, from_id, content, sent_at_ms, hop_chain FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = ? AND handle = ? ORDER BY seq FOR UPDATE`, [scope, handle]));
|
|
143
195
|
if (msgs.length === 0)
|
|
144
196
|
return null; // 定谳 5:空盒 claim = null(box 行在也 null)
|
|
145
197
|
const maxSeq = Number(msgs[msgs.length - 1].seq);
|
|
@@ -241,6 +293,7 @@ export class PgMailboxStore {
|
|
|
241
293
|
const content = pgSanitizeText(msg.content); // 内容面 lossy(U+FFFD 标记,与 PG 转录 payload 同族)
|
|
242
294
|
assertKeyBytes("scope", scope);
|
|
243
295
|
assertKeyBytes("handle", handle);
|
|
296
|
+
const hopChain = encodeHopChain(msg.hopChain); // TiDB 同注:事务外先编码,超界 fail-loud 不留半个盒
|
|
244
297
|
// ensure-first 形(TiDB 同注):幂等建盒再 FOR UPDATE,死锁/dup 撞车形消。
|
|
245
298
|
return this.tx(async (c) => {
|
|
246
299
|
await c.query(`INSERT INTO ${MAILBOX_TABLE} (scope_key, handle, scope, next_seq) VALUES ($1, $2, $3, 1) ON CONFLICT (scope_key, handle) DO NOTHING`, [
|
|
@@ -260,7 +313,7 @@ export class PgMailboxStore {
|
|
|
260
313
|
}
|
|
261
314
|
const seq = Number(rows[0].next_seq);
|
|
262
315
|
await c.query(`UPDATE ${MAILBOX_TABLE} SET next_seq = $1 WHERE scope_key = $2 AND handle = $3`, [seq + 1, scope, handle]);
|
|
263
|
-
await c.query(`INSERT INTO ${MAILBOX_MSG_TABLE} (scope_key, handle, seq, from_id, content, sent_at_ms) VALUES ($1, $2, $3, $4, $5, $6)`, [scope, handle, seq, msg.from ?? null, content, msg.sentAt]);
|
|
316
|
+
await c.query(`INSERT INTO ${MAILBOX_MSG_TABLE} (scope_key, handle, seq, from_id, content, sent_at_ms, hop_chain) VALUES ($1, $2, $3, $4, $5, $6, $7)`, [scope, handle, seq, msg.from ?? null, content, msg.sentAt, hopChain]);
|
|
264
317
|
return seq;
|
|
265
318
|
});
|
|
266
319
|
}
|
|
@@ -273,7 +326,7 @@ export class PgMailboxStore {
|
|
|
273
326
|
const box = boxes[0];
|
|
274
327
|
if (box.lease_owner !== null && Number(box.lease_expires_at_ms) > now && String(box.lease_owner) !== owner)
|
|
275
328
|
return null;
|
|
276
|
-
const { rows: msgs } = await c.query(`SELECT seq, from_id, content, sent_at_ms FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 AND handle = $2 ORDER BY seq FOR UPDATE`, [scope, handle]);
|
|
329
|
+
const { rows: msgs } = await c.query(`SELECT seq, from_id, content, sent_at_ms, hop_chain FROM ${MAILBOX_MSG_TABLE} WHERE scope_key = $1 AND handle = $2 ORDER BY seq FOR UPDATE`, [scope, handle]);
|
|
277
330
|
if (msgs.length === 0)
|
|
278
331
|
return null;
|
|
279
332
|
const maxSeq = Number(msgs[msgs.length - 1].seq);
|
|
@@ -42,18 +42,18 @@ export async function ensurePgMemoryEngineSchema(query, opts = {}) {
|
|
|
42
42
|
await query("CREATE EXTENSION IF NOT EXISTS vector");
|
|
43
43
|
const embeddingCol = vec?.pgvector ? `vector(${vec.dimensions})` : "jsonb";
|
|
44
44
|
await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_ENGINE_TABLES.entries} (
|
|
45
|
-
id text PRIMARY KEY,
|
|
46
|
-
scope text NOT NULL,
|
|
47
|
-
slug text NOT NULL,
|
|
45
|
+
id text COLLATE "C" PRIMARY KEY,
|
|
46
|
+
scope text COLLATE "C" NOT NULL,
|
|
47
|
+
slug text COLLATE "C" NOT NULL,
|
|
48
48
|
frontmatter jsonb NOT NULL,
|
|
49
|
-
body text NOT NULL,
|
|
50
|
-
rev text NOT NULL,
|
|
49
|
+
body text COLLATE "C" NOT NULL,
|
|
50
|
+
rev text COLLATE "C" NOT NULL,
|
|
51
51
|
mtime_ms bigint NOT NULL,
|
|
52
52
|
size_bytes integer NOT NULL,
|
|
53
53
|
embedding ${embeddingCol}
|
|
54
54
|
)`);
|
|
55
55
|
try {
|
|
56
|
-
await query(`CREATE INDEX IF NOT EXISTS ${PG_MEMORY_ENGINE_TABLES.entries}
|
|
56
|
+
await query(`CREATE INDEX IF NOT EXISTS idx_${PG_MEMORY_ENGINE_TABLES.entries}_scope ON ${PG_MEMORY_ENGINE_TABLES.entries} (scope)`);
|
|
57
57
|
}
|
|
58
58
|
catch {
|
|
59
59
|
/* pg-mem: partial CREATE INDEX support — the index is a perf add-on, never correctness */
|
|
@@ -63,10 +63,10 @@ export async function ensurePgMemoryEngineSchema(query, opts = {}) {
|
|
|
63
63
|
// insert, leaving two entries whose file projections overwrite each other at materialize. The DB
|
|
64
64
|
// constraint is the only true arbiter; the add path catches the violation and retries the next
|
|
65
65
|
// suffix. NOT wrapped in try/catch — if this constraint cannot be created, the backend is unsafe.
|
|
66
|
-
await query(`CREATE UNIQUE INDEX IF NOT EXISTS ${PG_MEMORY_ENGINE_TABLES.entries}
|
|
66
|
+
await query(`CREATE UNIQUE INDEX IF NOT EXISTS uq_${PG_MEMORY_ENGINE_TABLES.entries}_scope_slug ON ${PG_MEMORY_ENGINE_TABLES.entries} (scope, slug)`);
|
|
67
67
|
await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_ENGINE_TABLES.cursors} (
|
|
68
|
-
scope text PRIMARY KEY,
|
|
69
|
-
cursor text NOT NULL
|
|
68
|
+
scope text COLLATE "C" PRIMARY KEY,
|
|
69
|
+
cursor text COLLATE "C" NOT NULL
|
|
70
70
|
)`);
|
|
71
71
|
}
|
|
72
72
|
/** pgvector input literal — the same `[…]` string JSON.parse reads back from jsonb (one format, two columns). */
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// - add 路径 Pg 用 `INSERT … ON CONFLICT (id) DO UPDATE`(冲突靶=仅 PK)。MySQL 的 ON DUPLICATE KEY
|
|
13
13
|
// UPDATE 会被 **任意** 唯一键触发——(scope,slug) 撞键时它会把竞争对手的行改写成本 entry 的 id(数据毁),
|
|
14
14
|
// 不能用;拆成「不存在→INSERT / 已存在→UPDATE by id」,两条腿的 ER_DUP_ENTRY 都按键名分诊(PRIMARY=
|
|
15
|
-
// 并发同 id add → 转 UPDATE 腿;
|
|
15
|
+
// 并发同 id add → 转 UPDATE 腿;uq_agent_memory_engine_entries_scope_slug=并发 slug 竞争 → 下一个 -n 后缀重试);
|
|
16
16
|
// - unique violation 判定:ER_DUP_ENTRY(errno 1062),不是 Pg 的 23505;
|
|
17
17
|
// - pg-mem 伪影 workaround(索引列 ANY 零行/重复占位符重复行)不抄——MySQL 本就走 IN-list,dedupe 保留
|
|
18
18
|
// 是因为它同时是诚实语义(重复 key 问的是同一个 entry,契约锁死 once-per-entry);
|
|
@@ -45,9 +45,9 @@ export async function ensureTiDBMemoryEngineSchema(pool) {
|
|
|
45
45
|
size_bytes INT NOT NULL,
|
|
46
46
|
embedding JSON NULL,
|
|
47
47
|
PRIMARY KEY (id),
|
|
48
|
-
KEY
|
|
49
|
-
UNIQUE KEY
|
|
50
|
-
)`);
|
|
48
|
+
KEY idx_agent_memory_engine_entries_scope (scope),
|
|
49
|
+
UNIQUE KEY uq_agent_memory_engine_entries_scope_slug (scope, slug)
|
|
50
|
+
) COLLATE utf8mb4_bin`);
|
|
51
51
|
// The (scope, slug) UNIQUE key above is CORRECTNESS, not perf (Pg 版同注):two
|
|
52
52
|
// instances racing an add of one scope+slug (different ids) can both probe "free" and both insert;
|
|
53
53
|
// the DB constraint is the only true arbiter and the add path retries the next suffix on violation.
|
|
@@ -55,14 +55,14 @@ export async function ensureTiDBMemoryEngineSchema(pool) {
|
|
|
55
55
|
scope VARCHAR(190) NOT NULL,
|
|
56
56
|
\`cursor\` TEXT NOT NULL,
|
|
57
57
|
PRIMARY KEY (scope)
|
|
58
|
-
)`);
|
|
58
|
+
) COLLATE utf8mb4_bin`);
|
|
59
59
|
}
|
|
60
60
|
function isDupEntry(err) {
|
|
61
61
|
if (typeof err !== "object" || err === null)
|
|
62
62
|
return false;
|
|
63
63
|
return err.code === "ER_DUP_ENTRY" || err.errno === 1062;
|
|
64
64
|
}
|
|
65
|
-
/** ER_DUP_ENTRY messages name the violated key (`… for key 'tbl.PRIMARY'` / `… for key 'tbl.
|
|
65
|
+
/** ER_DUP_ENTRY messages name the violated key (`… for key 'tbl.PRIMARY'` / `… for key 'tbl.uq_agent_memory_engine_entries_scope_slug'`)
|
|
66
66
|
* — the add path uses this to tell "id already exists (→ overwrite leg)" from "slug race (→ -n retry)". */
|
|
67
67
|
function isPrimaryKeyDup(err) {
|
|
68
68
|
return isDupEntry(err) && /for key '[^']*PRIMARY'/i.test(String(err.message ?? ""));
|
|
@@ -263,7 +263,7 @@ export class TiDBMemoryEngineBackend {
|
|
|
263
263
|
}
|
|
264
264
|
}
|
|
265
265
|
// Slug collision with a DIFFERENT entry → deterministic `-n` suffix (file-backend parity); the
|
|
266
|
-
// probe is only an optimization — the write INSIDE the loop is arbitrated by
|
|
266
|
+
// probe is only an optimization — the write INSIDE the loop is arbitrated by uq_agent_memory_engine_entries_scope_slug, and
|
|
267
267
|
// a concurrent loser retries the next suffix (Pg 版同注).
|
|
268
268
|
let slug = entry.slug;
|
|
269
269
|
for (let n = 2;; n++) {
|
|
@@ -8,23 +8,23 @@ export const PG_MEMORY_SYNC_TABLES = {
|
|
|
8
8
|
/** Idempotent DDL for the sync plane (PG dialect). Call once at startup. */
|
|
9
9
|
export async function ensurePgMemorySyncSchema(query) {
|
|
10
10
|
await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_SYNC_TABLES.syncCursors} (
|
|
11
|
-
scope varchar(190) NOT NULL,
|
|
12
|
-
peer varchar(190) NOT NULL,
|
|
11
|
+
scope varchar(190) COLLATE "C" NOT NULL,
|
|
12
|
+
peer varchar(190) COLLATE "C" NOT NULL,
|
|
13
13
|
base_revs jsonb NOT NULL,
|
|
14
14
|
updated_at_ms bigint NOT NULL,
|
|
15
15
|
PRIMARY KEY (scope, peer)
|
|
16
16
|
)`);
|
|
17
17
|
await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_SYNC_TABLES.pushQueue} (
|
|
18
|
-
id char(36) PRIMARY KEY,
|
|
19
|
-
scope varchar(190) NOT NULL,
|
|
20
|
-
entry_id char(36) NOT NULL,
|
|
18
|
+
id char(36) COLLATE "C" PRIMARY KEY,
|
|
19
|
+
scope varchar(190) COLLATE "C" NOT NULL,
|
|
20
|
+
entry_id char(36) COLLATE "C" NOT NULL,
|
|
21
21
|
payload jsonb NOT NULL,
|
|
22
22
|
attempts integer NOT NULL DEFAULT 0,
|
|
23
23
|
next_attempt_at bigint NOT NULL,
|
|
24
|
-
last_error text NULL
|
|
24
|
+
last_error text COLLATE "C" NULL
|
|
25
25
|
)`);
|
|
26
26
|
// Scan face (§1.2): the reaper/consumer sweeps by due time. Perf add-on, never correctness.
|
|
27
|
-
await query(`CREATE INDEX IF NOT EXISTS ${PG_MEMORY_SYNC_TABLES.pushQueue}
|
|
27
|
+
await query(`CREATE INDEX IF NOT EXISTS idx_${PG_MEMORY_SYNC_TABLES.pushQueue}_next_attempt ON ${PG_MEMORY_SYNC_TABLES.pushQueue} (next_attempt_at)`);
|
|
28
28
|
}
|
|
29
29
|
/** jsonb defense: node-pg hands back parsed objects, but a text-typed round-trip (or a foreign
|
|
30
30
|
* writer) may yield a string — same posture as parseJson in run-store-sql.ts. */
|
|
@@ -135,17 +135,17 @@ export async function ensurePgMemoryHistorySchema(query) {
|
|
|
135
135
|
// (caller-injected epoch ms) instead of the draft's TIMESTAMP — the family's clock discipline
|
|
136
136
|
// (sync_cursors.updated_at_ms precedent; DB clocks never judge anything).
|
|
137
137
|
await query(`CREATE TABLE IF NOT EXISTS ${PG_MEMORY_SYNC_TABLES.history} (
|
|
138
|
-
id char(36) NOT NULL,
|
|
139
|
-
rev varchar(128) NOT NULL,
|
|
140
|
-
prev_rev varchar(128) NULL,
|
|
141
|
-
op varchar(16) NOT NULL,
|
|
142
|
-
scope varchar(190) NOT NULL,
|
|
138
|
+
id char(36) COLLATE "C" NOT NULL,
|
|
139
|
+
rev varchar(128) COLLATE "C" NOT NULL,
|
|
140
|
+
prev_rev varchar(128) COLLATE "C" NULL,
|
|
141
|
+
op varchar(16) COLLATE "C" NOT NULL,
|
|
142
|
+
scope varchar(190) COLLATE "C" NOT NULL,
|
|
143
143
|
snapshot jsonb NULL,
|
|
144
144
|
written_at_ms bigint NOT NULL,
|
|
145
145
|
PRIMARY KEY (id, rev)
|
|
146
146
|
)`);
|
|
147
147
|
// countByScope / future per-scope export-with-history scan face. Perf add-on, never correctness.
|
|
148
|
-
await query(`CREATE INDEX IF NOT EXISTS ${PG_MEMORY_SYNC_TABLES.history}
|
|
148
|
+
await query(`CREATE INDEX IF NOT EXISTS idx_${PG_MEMORY_SYNC_TABLES.history}_scope ON ${PG_MEMORY_SYNC_TABLES.history} (scope)`);
|
|
149
149
|
}
|
|
150
150
|
export class PgMemoryHistoryStore {
|
|
151
151
|
query;
|
|
@@ -15,7 +15,7 @@ export async function ensureTiDBMemorySyncSchema(pool) {
|
|
|
15
15
|
base_revs JSON NOT NULL,
|
|
16
16
|
updated_at_ms BIGINT NOT NULL,
|
|
17
17
|
PRIMARY KEY (scope, peer)
|
|
18
|
-
)`);
|
|
18
|
+
) COLLATE utf8mb4_bin`);
|
|
19
19
|
await pool.query(`CREATE TABLE IF NOT EXISTS ${TIDB_MEMORY_SYNC_TABLES.pushQueue} (
|
|
20
20
|
id CHAR(36) NOT NULL,
|
|
21
21
|
scope VARCHAR(190) NOT NULL,
|
|
@@ -25,8 +25,8 @@ export async function ensureTiDBMemorySyncSchema(pool) {
|
|
|
25
25
|
next_attempt_at BIGINT NOT NULL,
|
|
26
26
|
last_error TEXT NULL,
|
|
27
27
|
PRIMARY KEY (id),
|
|
28
|
-
KEY
|
|
29
|
-
)`);
|
|
28
|
+
KEY idx_agent_memory_engine_push_queue_next_attempt (next_attempt_at)
|
|
29
|
+
) COLLATE utf8mb4_bin`);
|
|
30
30
|
}
|
|
31
31
|
/** JSON-column defense: mysql2 normally hands back parsed objects for JSON columns, but string
|
|
32
32
|
* shapes leak through (connection flags/proxies) — same posture as memory-engine-tidb.ts. */
|
|
@@ -138,8 +138,8 @@ export async function ensureTiDBMemoryHistorySchema(pool) {
|
|
|
138
138
|
snapshot JSON NULL,
|
|
139
139
|
written_at_ms BIGINT NOT NULL,
|
|
140
140
|
PRIMARY KEY (id, rev),
|
|
141
|
-
KEY
|
|
142
|
-
)`);
|
|
141
|
+
KEY idx_agent_memory_engine_history_scope (scope)
|
|
142
|
+
) COLLATE utf8mb4_bin`);
|
|
143
143
|
}
|
|
144
144
|
export class TiDBMemoryHistoryStore {
|
|
145
145
|
pool;
|
|
@@ -113,12 +113,12 @@ export function coreOutcomeToLedgerRow(o) {
|
|
|
113
113
|
*/
|
|
114
114
|
export const PG_OUTCOME_LEDGER_SCHEMA = [
|
|
115
115
|
`CREATE TABLE IF NOT EXISTS outcome_ledger (
|
|
116
|
-
id VARCHAR(64) NOT NULL,
|
|
117
|
-
task_signature VARCHAR(190) NOT NULL,
|
|
118
|
-
model VARCHAR(190) NOT NULL,
|
|
119
|
-
task_id VARCHAR(64),
|
|
120
|
-
run_status VARCHAR(20) NOT NULL DEFAULT 'completed-graded',
|
|
121
|
-
route VARCHAR(16),
|
|
116
|
+
id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
117
|
+
task_signature VARCHAR(190) COLLATE "C" NOT NULL,
|
|
118
|
+
model VARCHAR(190) COLLATE "C" NOT NULL,
|
|
119
|
+
task_id VARCHAR(64) COLLATE "C",
|
|
120
|
+
run_status VARCHAR(20) COLLATE "C" NOT NULL DEFAULT 'completed-graded',
|
|
121
|
+
route VARCHAR(16) COLLATE "C",
|
|
122
122
|
worker_count INTEGER,
|
|
123
123
|
oracle_passed INTEGER,
|
|
124
124
|
oracle_total INTEGER,
|
|
@@ -128,7 +128,7 @@ export const PG_OUTCOME_LEDGER_SCHEMA = [
|
|
|
128
128
|
replan_triggers JSONB,
|
|
129
129
|
cost_micro_usd BIGINT,
|
|
130
130
|
turns INTEGER,
|
|
131
|
-
outcome VARCHAR(8),
|
|
131
|
+
outcome VARCHAR(8) COLLATE "C",
|
|
132
132
|
llm_assisted JSONB,
|
|
133
133
|
signature_inputs JSONB,
|
|
134
134
|
-- design/73 §1 bridge: the VERBATIM core TaskOutcome fact (oracleHadRedRun / status / oracle blob) as it
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { PgWriteBehindCounter } from "./write-behind-counter.js";
|
|
2
2
|
/** The counter's table binding for the cumulative cost quota (mirror of the TiDB twin's binding). */
|
|
3
|
-
const COST_QUOTA_TABLE = { table: "cost_quota", keyCol: "scope_key", valCol: "
|
|
3
|
+
const COST_QUOTA_TABLE = { table: "cost_quota", keyCol: "scope_key", valCol: "micro_usd" };
|
|
4
4
|
export class PgCostQuota {
|
|
5
5
|
limitMicroUsd;
|
|
6
6
|
counter;
|
|
@@ -39,9 +39,9 @@ export class PgCostQuota {
|
|
|
39
39
|
* tables, so a self-contained ensureSchema is safe (the central pg-pool aggregation is done separately). */
|
|
40
40
|
export const PG_COST_QUOTA_SCHEMA = [
|
|
41
41
|
`CREATE TABLE IF NOT EXISTS cost_quota (
|
|
42
|
-
scope_key VARCHAR(190) NOT NULL,
|
|
42
|
+
scope_key VARCHAR(190) COLLATE "C" NOT NULL,
|
|
43
43
|
window_bucket BIGINT NOT NULL,
|
|
44
|
-
|
|
44
|
+
micro_usd BIGINT NOT NULL DEFAULT 0,
|
|
45
45
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
46
46
|
PRIMARY KEY (scope_key, window_bucket)
|
|
47
47
|
)`,
|