@sema-agent/server 7.82.1 → 7.84.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/dist/bench/s1/arms.d.ts +4 -4
- package/dist/bench/s1/arms.js +1 -2
- package/dist/bench/s1/live-deps.js +0 -1
- package/dist/bench/s1/runner-ctx.d.ts +3 -3
- package/dist/bench/s1/tasks.d.ts +8 -4
- package/dist/boot/resolve-spec.js +9 -5
- package/dist/capabilities/memory-notice.d.ts +6 -4
- package/dist/capabilities/memory-notice.js +1 -1
- package/dist/config-catalog.js +0 -1
- package/dist/config.js +1 -0
- package/dist/http/admission.js +7 -3
- package/dist/http/wire-types.d.ts +22 -0
- package/dist/leader/endpoint.js +2 -0
- package/dist/leader/leader.d.ts +33 -6
- package/dist/leader/leader.js +54 -45
- package/dist/leader/merge.d.ts +38 -29
- package/dist/leader/merge.js +45 -38
- package/dist/leader/wire.d.ts +2 -17
- package/dist/leader/wire.js +5 -58
- package/dist/memory-scope.d.ts +33 -5
- package/dist/memory-scope.js +10 -4
- package/dist/observability/fail-open.d.ts +5 -5
- package/dist/observability/fail-open.js +5 -2
- package/dist/observability/run-terminal-log.d.ts +2 -7
- package/dist/observability/run-terminal-log.js +119 -21
- package/dist/plugins/adoption-log-sql.d.ts +3 -0
- package/dist/plugins/adoption-log-sql.js +5 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
- package/dist/plugins/approval-ask-store-sql.js +9 -5
- package/dist/plugins/background-agent-store-sql.d.ts +3 -0
- package/dist/plugins/background-agent-store-sql.js +7 -2
- package/dist/plugins/device-store-sql.d.ts +3 -0
- package/dist/plugins/device-store-sql.js +9 -4
- package/dist/plugins/ensure-index.d.ts +42 -0
- package/dist/plugins/ensure-index.js +81 -0
- package/dist/plugins/ensure-schema-lock-policy.d.ts +29 -0
- package/dist/plugins/ensure-schema-lock-policy.js +31 -0
- package/dist/plugins/image-bake-store-sql.d.ts +3 -0
- package/dist/plugins/image-bake-store-sql.js +6 -2
- package/dist/plugins/image-index-sql.d.ts +3 -0
- package/dist/plugins/image-index-sql.js +8 -4
- package/dist/plugins/leader-run-store-sql.d.ts +3 -0
- package/dist/plugins/leader-run-store-sql.js +6 -2
- package/dist/plugins/mailbox-store-sql.d.ts +3 -0
- package/dist/plugins/mailbox-store-sql.js +6 -1
- package/dist/plugins/memory-engine-pg.d.ts +4 -0
- package/dist/plugins/memory-engine-pg.js +6 -6
- package/dist/plugins/memory-engine-tidb.js +5 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +5 -0
- package/dist/plugins/memory-sync-store-pg.js +9 -2
- package/dist/plugins/memory-sync-store-tidb.js +6 -0
- package/dist/plugins/one-time-migrations.d.ts +1 -1
- package/dist/plugins/one-time-migrations.js +7 -14
- package/dist/plugins/outcome-ledger-sql.d.ts +3 -0
- package/dist/plugins/outcome-ledger-sql.js +7 -3
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +9 -5
- package/dist/plugins/pg-cost-quota.d.ts +3 -0
- package/dist/plugins/pg-cost-quota.js +5 -1
- package/dist/plugins/pg-pool.d.ts +23 -2
- package/dist/plugins/pg-pool.js +22 -37
- package/dist/plugins/pg-rate-limiter.d.ts +3 -0
- package/dist/plugins/pg-rate-limiter.js +5 -1
- package/dist/plugins/pg-session-storage.d.ts +3 -0
- package/dist/plugins/pg-session-storage.js +8 -4
- package/dist/plugins/retention-store-sql.d.ts +3 -0
- package/dist/plugins/retention-store-sql.js +7 -3
- package/dist/plugins/roster-store-sql.d.ts +3 -0
- package/dist/plugins/roster-store-sql.js +6 -1
- package/dist/plugins/schema-indexes.d.ts +21 -0
- package/dist/plugins/schema-indexes.js +25 -0
- package/dist/plugins/task-attachment-store.d.ts +3 -0
- package/dist/plugins/task-attachment-store.js +7 -2
- package/dist/plugins/tidb-pool.d.ts +15 -3
- package/dist/plugins/tidb-pool.js +38 -15
- package/dist/plugins/tool-result-store-sql.d.ts +3 -0
- package/dist/plugins/tool-result-store-sql.js +5 -1
- package/dist/request-key-closure.d.ts +7 -0
- package/dist/request-key-closure.js +1 -1
- package/dist/request-value-closure.d.ts +31 -0
- package/dist/request-value-closure.js +18 -0
- package/dist/trace/project.d.ts +2 -1
- package/dist/trace/redact.d.ts +6 -0
- package/package.json +1 -1
package/dist/plugins/pg-pool.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import pg from "pg";
|
|
2
2
|
import { recordFailOpen } from "../observability/fail-open.js";
|
|
3
|
+
import { pollUntilLocked } from "./ensure-schema-lock-policy.js";
|
|
3
4
|
import { ensureSchema as ensureSessionSchema } from "./pg-session-storage.js";
|
|
4
5
|
import { ensureSchema as ensureToolResultSchema } from "./tool-result-store-sql.js";
|
|
5
6
|
import { ensureSchema as ensureCostQuotaSchema } from "./pg-cost-quota.js";
|
|
@@ -15,6 +16,8 @@ import { ensurePgAdoptionLogSchema } from "./adoption-log-sql.js";
|
|
|
15
16
|
import { ensurePgPermissionRuleSchema } from "./permission-rule-store-sql.js";
|
|
16
17
|
import { ensurePgLeaderRunSchema } from "./leader-run-store-sql.js";
|
|
17
18
|
import { ensurePgRetentionSchema } from "./retention-store-sql.js";
|
|
19
|
+
import { ensureIndexes } from "./ensure-index.js";
|
|
20
|
+
import { CENTRAL_SCHEMA_INDEXES } from "./schema-indexes.js";
|
|
18
21
|
export const PG_SCHEMA_STATEMENTS = [
|
|
19
22
|
`CREATE TABLE IF NOT EXISTS task_run (
|
|
20
23
|
task_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -43,10 +46,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
43
46
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
44
47
|
PRIMARY KEY (task_id)
|
|
45
48
|
)`,
|
|
46
|
-
`CREATE INDEX IF NOT EXISTS idx_task_run_session_status ON task_run (session_id, status)`,
|
|
47
|
-
`CREATE INDEX IF NOT EXISTS idx_task_run_owner ON task_run (owner)`,
|
|
48
|
-
`CREATE INDEX IF NOT EXISTS idx_task_run_created ON task_run (created_at)`,
|
|
49
|
-
`CREATE INDEX IF NOT EXISTS idx_task_run_job ON task_run (job_id)`,
|
|
50
49
|
`CREATE TABLE IF NOT EXISTS task_event (
|
|
51
50
|
task_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
52
51
|
seq BIGINT NOT NULL,
|
|
@@ -61,7 +60,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
61
60
|
since TIMESTAMPTZ(3) NOT NULL,
|
|
62
61
|
PRIMARY KEY (session_id)
|
|
63
62
|
)`,
|
|
64
|
-
`CREATE INDEX IF NOT EXISTS idx_task_active_task ON task_active (task_id)`,
|
|
65
63
|
`CREATE TABLE IF NOT EXISTS checkpoint (
|
|
66
64
|
token VARCHAR(120) COLLATE "C" NOT NULL,
|
|
67
65
|
scope VARCHAR(190) COLLATE "C" NOT NULL,
|
|
@@ -101,8 +99,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
101
99
|
execution_at_ms BIGINT,
|
|
102
100
|
PRIMARY KEY (token)
|
|
103
101
|
)`,
|
|
104
|
-
`CREATE INDEX IF NOT EXISTS idx_checkpoint_status_deadline ON checkpoint (status, deadline)`,
|
|
105
|
-
`CREATE INDEX IF NOT EXISTS idx_checkpoint_session_status ON checkpoint (session_id, status)`,
|
|
106
102
|
`CREATE TABLE IF NOT EXISTS checkpoint_ctx (
|
|
107
103
|
session_id VARCHAR(190) COLLATE "C" NOT NULL,
|
|
108
104
|
ctx JSONB NOT NULL,
|
|
@@ -117,7 +113,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
117
113
|
created_at TIMESTAMPTZ(3) NOT NULL,
|
|
118
114
|
PRIMARY KEY (session_id, event_id)
|
|
119
115
|
)`,
|
|
120
|
-
`CREATE INDEX IF NOT EXISTS idx_resume_anchor_session ON resume_anchor (session_id)`,
|
|
121
116
|
`CREATE TABLE IF NOT EXISTS approval_exemption (
|
|
122
117
|
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
123
118
|
tool_name VARCHAR(190) COLLATE "C" NOT NULL,
|
|
@@ -132,7 +127,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
132
127
|
expires_at_ms BIGINT NOT NULL,
|
|
133
128
|
PRIMARY KEY (session_id, nonce)
|
|
134
129
|
)`,
|
|
135
|
-
`CREATE INDEX IF NOT EXISTS idx_approval_nonce_expires ON approval_nonce (expires_at_ms)`,
|
|
136
130
|
`CREATE TABLE IF NOT EXISTS sendfile_link (
|
|
137
131
|
id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
138
132
|
scope VARCHAR(190) COLLATE "C" NOT NULL,
|
|
@@ -147,7 +141,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
147
141
|
created_at TIMESTAMPTZ(3) NOT NULL,
|
|
148
142
|
PRIMARY KEY (id)
|
|
149
143
|
)`,
|
|
150
|
-
`CREATE INDEX IF NOT EXISTS idx_sendfile_link_scope ON sendfile_link (scope, id)`,
|
|
151
144
|
`CREATE TABLE IF NOT EXISTS session_policy (
|
|
152
145
|
policy_key CHAR(64) COLLATE "C" NOT NULL,
|
|
153
146
|
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -161,7 +154,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
161
154
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
162
155
|
PRIMARY KEY (policy_key)
|
|
163
156
|
)`,
|
|
164
|
-
`CREATE INDEX IF NOT EXISTS idx_session_policy_session ON session_policy (session_id)`,
|
|
165
157
|
`CREATE TABLE IF NOT EXISTS snapshot_blob (
|
|
166
158
|
blob_hash CHAR(64) COLLATE "C" PRIMARY KEY,
|
|
167
159
|
byte_len BIGINT NOT NULL,
|
|
@@ -174,7 +166,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
174
166
|
bytes BYTEA NULL,
|
|
175
167
|
created_at TIMESTAMPTZ(3) NOT NULL
|
|
176
168
|
)`,
|
|
177
|
-
`CREATE INDEX IF NOT EXISTS idx_snapshot_blob_created ON snapshot_blob (created_at)`,
|
|
178
169
|
`CREATE TABLE IF NOT EXISTS file_history_scope (
|
|
179
170
|
scope VARCHAR(64) COLLATE "C" NOT NULL,
|
|
180
171
|
created_at_ms BIGINT NOT NULL,
|
|
@@ -201,7 +192,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
201
192
|
backup_time VARCHAR(40) COLLATE "C" NOT NULL,
|
|
202
193
|
PRIMARY KEY (scope, key_hash, version)
|
|
203
194
|
)`,
|
|
204
|
-
`CREATE INDEX IF NOT EXISTS idx_file_history_version_blob ON file_history_version (blob_hash)`,
|
|
205
195
|
`CREATE TABLE IF NOT EXISTS file_history_boundary (
|
|
206
196
|
scope VARCHAR(64) COLLATE "C" NOT NULL,
|
|
207
197
|
entry_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -217,7 +207,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
217
207
|
version INT NOT NULL,
|
|
218
208
|
PRIMARY KEY (scope, entry_id, key_hash)
|
|
219
209
|
)`,
|
|
220
|
-
`CREATE INDEX IF NOT EXISTS idx_file_history_bf_key ON file_history_boundary_file (scope, key_hash)`,
|
|
221
210
|
`CREATE TABLE IF NOT EXISTS file_history_reaped_entry (
|
|
222
211
|
scope VARCHAR(64) COLLATE "C" NOT NULL,
|
|
223
212
|
entry_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -231,7 +220,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
231
220
|
bytes BYTEA NULL,
|
|
232
221
|
created_at TIMESTAMPTZ(3) NOT NULL
|
|
233
222
|
)`,
|
|
234
|
-
`CREATE INDEX IF NOT EXISTS idx_file_history_blob_created ON file_history_blob (created_at)`,
|
|
235
223
|
`CREATE TABLE IF NOT EXISTS workflow_journal (
|
|
236
224
|
run_id VARCHAR(191) COLLATE "C" NOT NULL,
|
|
237
225
|
ordinal INT NOT NULL,
|
|
@@ -263,8 +251,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
263
251
|
ended_at_ms BIGINT,
|
|
264
252
|
PRIMARY KEY (id)
|
|
265
253
|
)`,
|
|
266
|
-
`CREATE INDEX IF NOT EXISTS idx_wfrun_scope_created ON workflow_run (scope, created_at_ms)`,
|
|
267
|
-
`CREATE INDEX IF NOT EXISTS idx_wfrun_scope_status ON workflow_run (scope, status)`,
|
|
268
254
|
`CREATE TABLE IF NOT EXISTS workflow_agent_session (
|
|
269
255
|
session_id VARCHAR(190) COLLATE "C" NOT NULL,
|
|
270
256
|
run_id VARCHAR(191) COLLATE "C" NOT NULL,
|
|
@@ -272,7 +258,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
272
258
|
scope VARCHAR(190) COLLATE "C" NOT NULL,
|
|
273
259
|
PRIMARY KEY (session_id)
|
|
274
260
|
)`,
|
|
275
|
-
`CREATE INDEX IF NOT EXISTS idx_wfagentsess_run ON workflow_agent_session (run_id)`,
|
|
276
261
|
`CREATE TABLE IF NOT EXISTS workflow_completion_inbox (
|
|
277
262
|
seq BIGSERIAL,
|
|
278
263
|
session_id VARCHAR(190) COLLATE "C" NOT NULL,
|
|
@@ -295,7 +280,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
295
280
|
-- becomes MACHINE-ENFORCED rather than merely conventional, in both engines.
|
|
296
281
|
CONSTRAINT uq_wfinbox_seq UNIQUE (seq)
|
|
297
282
|
)`,
|
|
298
|
-
`CREATE INDEX IF NOT EXISTS idx_wfinbox_session_seq ON workflow_completion_inbox (session_id, seq)`,
|
|
299
283
|
`CREATE TABLE IF NOT EXISTS workflow_inbox_fence (
|
|
300
284
|
session_id VARCHAR(190) COLLATE "C" NOT NULL,
|
|
301
285
|
kind VARCHAR(8) COLLATE "C" NOT NULL,
|
|
@@ -305,7 +289,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
305
289
|
until_ms BIGINT NOT NULL,
|
|
306
290
|
PRIMARY KEY (session_id, kind, run_id)
|
|
307
291
|
)`,
|
|
308
|
-
`CREATE INDEX IF NOT EXISTS idx_wffence_until ON workflow_inbox_fence (until_ms)`,
|
|
309
292
|
`CREATE TABLE IF NOT EXISTS workflow_notify_journal (
|
|
310
293
|
run_id VARCHAR(191) COLLATE "C" NOT NULL,
|
|
311
294
|
scope VARCHAR(190) COLLATE "C" NOT NULL,
|
|
@@ -316,7 +299,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
316
299
|
acked_at_ms BIGINT,
|
|
317
300
|
PRIMARY KEY (run_id)
|
|
318
301
|
)`,
|
|
319
|
-
`CREATE INDEX IF NOT EXISTS idx_wfnotify_acked ON workflow_notify_journal (acked)`,
|
|
320
302
|
`CREATE TABLE IF NOT EXISTS shared_memory_scope (
|
|
321
303
|
scope_key VARCHAR(190) COLLATE "C" NOT NULL,
|
|
322
304
|
state VARCHAR(16) COLLATE "C" NOT NULL,
|
|
@@ -332,7 +314,6 @@ export const PG_SCHEMA_STATEMENTS = [
|
|
|
332
314
|
updated_at_ms BIGINT NOT NULL,
|
|
333
315
|
PRIMARY KEY (store_id)
|
|
334
316
|
)`,
|
|
335
|
-
`CREATE INDEX IF NOT EXISTS idx_shared_memory_store_scope ON shared_memory_store (scope_key, store_id)`,
|
|
336
317
|
`CREATE TABLE IF NOT EXISTS shared_memory_entry (
|
|
337
318
|
store_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
338
319
|
path VARCHAR(512) COLLATE "C" NOT NULL,
|
|
@@ -400,17 +381,26 @@ export function createPgPool(opts, onIdleClientError) {
|
|
|
400
381
|
return pool;
|
|
401
382
|
}
|
|
402
383
|
export const ENSURE_SCHEMA_LOCK_KEY = 4906;
|
|
384
|
+
export async function acquirePgEnsureSchemaLock(client, tag) {
|
|
385
|
+
await pollUntilLocked(async () => {
|
|
386
|
+
const res = await client.query("SELECT pg_try_advisory_lock($1) AS got", [ENSURE_SCHEMA_LOCK_KEY]);
|
|
387
|
+
return res.rows[0]?.got === true;
|
|
388
|
+
}, { tag, label: `pg advisory lock ${ENSURE_SCHEMA_LOCK_KEY}` });
|
|
389
|
+
}
|
|
390
|
+
export async function releasePgEnsureSchemaLock(client, tag) {
|
|
391
|
+
try {
|
|
392
|
+
await client.query("SELECT pg_advisory_unlock($1)", [ENSURE_SCHEMA_LOCK_KEY]);
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
console.warn(`[${tag}] pg_advisory_unlock(${ENSURE_SCHEMA_LOCK_KEY}) failed (lock auto-frees on connection close):`, err);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
403
398
|
export async function ensurePgSchema(pool) {
|
|
404
399
|
const client = await pool.connect();
|
|
405
400
|
let locked = false;
|
|
406
401
|
try {
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
locked = true;
|
|
410
|
-
}
|
|
411
|
-
catch (err) {
|
|
412
|
-
console.warn(`[ensurePgSchema] pg_advisory_lock(${ENSURE_SCHEMA_LOCK_KEY}) failed; running DDL unserialized:`, err);
|
|
413
|
-
}
|
|
402
|
+
await acquirePgEnsureSchemaLock(client, "ensurePgSchema");
|
|
403
|
+
locked = true;
|
|
414
404
|
for (const stmt of PG_SCHEMA_STATEMENTS)
|
|
415
405
|
await client.query(stmt);
|
|
416
406
|
await ensureSessionSchema(client);
|
|
@@ -428,16 +418,11 @@ export async function ensurePgSchema(pool) {
|
|
|
428
418
|
await ensurePgPermissionRuleSchema((text, params) => client.query(text, params));
|
|
429
419
|
await ensurePgLeaderRunSchema((text, params) => client.query(text, params));
|
|
430
420
|
await ensurePgRetentionSchema((text, params) => client.query(text, params));
|
|
421
|
+
await ensureIndexes((text, params) => client.query(text, params), "pg", CENTRAL_SCHEMA_INDEXES);
|
|
431
422
|
}
|
|
432
423
|
finally {
|
|
433
|
-
if (locked)
|
|
434
|
-
|
|
435
|
-
await client.query("SELECT pg_advisory_unlock($1)", [ENSURE_SCHEMA_LOCK_KEY]);
|
|
436
|
-
}
|
|
437
|
-
catch (err) {
|
|
438
|
-
console.warn(`[ensurePgSchema] pg_advisory_unlock(${ENSURE_SCHEMA_LOCK_KEY}) failed (lock auto-frees on connection close):`, err);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
424
|
+
if (locked)
|
|
425
|
+
await releasePgEnsureSchemaLock(client, "ensurePgSchema");
|
|
441
426
|
client.release();
|
|
442
427
|
}
|
|
443
428
|
}
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import type { Pool, PoolClient } from "pg";
|
|
17
17
|
import type { RateDecision, RateGate, RateLimits } from "../observability/rate-limit.js";
|
|
18
18
|
import { type CounterDegradeHook } from "./write-behind-counter.js";
|
|
19
|
+
import { type IndexSpec } from "./ensure-index.js";
|
|
19
20
|
export declare class PgRateLimiter implements RateGate {
|
|
20
21
|
private readonly counter;
|
|
21
22
|
/** [ref]:与 TiDB 孪生逐字同语义(限额=比较参数;关断哨兵停/起刷新环)。 */
|
|
@@ -39,6 +40,8 @@ export declare class PgRateLimiter implements RateGate {
|
|
|
39
40
|
* atomic ON CONFLICT increment.
|
|
40
41
|
*/
|
|
41
42
|
export declare const PG_RATE_LIMITER_SCHEMA: string[];
|
|
43
|
+
/** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensureSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
44
|
+
export declare const RATE_LIMIT_INDEXES: readonly IndexSpec[];
|
|
42
45
|
/** Self-contained schema bootstrap for tests (the rate_limit table is disjoint from other stores). */
|
|
43
46
|
export declare function ensureSchema(pool: Pool | PoolClient): Promise<void>;
|
|
44
47
|
//# sourceMappingURL=pg-rate-limiter.d.ts.map
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PgWriteBehindCounter } from "./write-behind-counter.js";
|
|
2
|
+
import { ensureIndexes } from "./ensure-index.js";
|
|
2
3
|
const RATE_LIMIT_TABLE = { table: "rate_limit", keyCol: "limit_key", valCol: "hits" };
|
|
3
4
|
export class PgRateLimiter {
|
|
4
5
|
counter;
|
|
@@ -57,10 +58,13 @@ export const PG_RATE_LIMITER_SCHEMA = [
|
|
|
57
58
|
updated_at TIMESTAMPTZ(3) NOT NULL,
|
|
58
59
|
PRIMARY KEY (limit_key, window_bucket)
|
|
59
60
|
)`,
|
|
60
|
-
|
|
61
|
+
];
|
|
62
|
+
export const RATE_LIMIT_INDEXES = [
|
|
63
|
+
{ table: "rate_limit", name: "idx_rate_limit_window", columns: ["window_bucket"] },
|
|
61
64
|
];
|
|
62
65
|
export async function ensureSchema(pool) {
|
|
63
66
|
for (const stmt of PG_RATE_LIMITER_SCHEMA)
|
|
64
67
|
await pool.query(stmt);
|
|
68
|
+
await ensureIndexes((t, p) => pool.query(t, p), "pg", RATE_LIMIT_INDEXES);
|
|
65
69
|
}
|
|
66
70
|
//# sourceMappingURL=pg-rate-limiter.js.map
|
|
@@ -3,6 +3,7 @@ import type { Pool, PoolClient } from "pg";
|
|
|
3
3
|
import type { SessionSummary } from "./store-contracts.js";
|
|
4
4
|
import { type PlacementAcquireOpts } from "./session-placement.js";
|
|
5
5
|
import { type StagingHandle } from "../session-sync-kernel.js";
|
|
6
|
+
import { type IndexSpec } from "./ensure-index.js";
|
|
6
7
|
/**
|
|
7
8
|
* PG-dialect DDL for the L1 session log, translated from tidb-pool.ts SCHEMA_STATEMENTS:
|
|
8
9
|
* JSON→JSONB, DATETIME(3)→TIMESTAMPTZ(3), inline `KEY`/`UNIQUE KEY`→separate CREATE INDEX (UNIQUE for uq_session_event_entry).
|
|
@@ -11,6 +12,8 @@ import { type StagingHandle } from "../session-sync-kernel.js";
|
|
|
11
12
|
* drop-and-recreate, and NO new `ALTER TABLE` seams are added here; fold into the CREATE instead.
|
|
12
13
|
*/
|
|
13
14
|
export declare const PG_SESSION_SCHEMA: string[];
|
|
15
|
+
/** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensureSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
16
|
+
export declare const SESSION_INDEXES: readonly IndexSpec[];
|
|
14
17
|
/** Idempotent schema apply for this store's tables (the test calls this; production aggregation is separate). */
|
|
15
18
|
export declare function ensureSchema(pool: Pool | PoolClient): Promise<void>;
|
|
16
19
|
/**
|
|
@@ -8,6 +8,7 @@ import { contentForkRelation, fastForwardSharedContentDiverged } from "../sessio
|
|
|
8
8
|
import { classifySyncRelationshipByIds, SyncConflictError, stagingIdFor, STAGING_ID_MARKER, STAGING_GC_GRACE_MS, } from "../session-sync-kernel.js";
|
|
9
9
|
import { isPgUniqueViolation } from "./sql-errors.js";
|
|
10
10
|
import { MANAGED_RETENTION } from "./retention-store-sql.js";
|
|
11
|
+
import { ensureIndexes } from "./ensure-index.js";
|
|
11
12
|
const isDupKey = isPgUniqueViolation;
|
|
12
13
|
function parseEntry(p) {
|
|
13
14
|
return (typeof p === "string" ? JSON.parse(p) : p);
|
|
@@ -36,8 +37,6 @@ export const PG_SESSION_SCHEMA = [
|
|
|
36
37
|
placed_at TIMESTAMPTZ(3),
|
|
37
38
|
PRIMARY KEY (session_id)
|
|
38
39
|
)`,
|
|
39
|
-
`CREATE INDEX IF NOT EXISTS idx_session_meta_owner ON session_meta (owner)`,
|
|
40
|
-
`CREATE INDEX IF NOT EXISTS idx_session_meta_placement ON session_meta (placement_kind, placement_scope, placed_at)`,
|
|
41
40
|
`CREATE TABLE IF NOT EXISTS session_event (
|
|
42
41
|
session_id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
43
42
|
seq BIGINT NOT NULL,
|
|
@@ -48,12 +47,17 @@ export const PG_SESSION_SCHEMA = [
|
|
|
48
47
|
ts TIMESTAMPTZ(3) NOT NULL,
|
|
49
48
|
PRIMARY KEY (session_id, seq)
|
|
50
49
|
)`,
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
];
|
|
51
|
+
export const SESSION_INDEXES = [
|
|
52
|
+
{ table: "session_meta", name: "idx_session_meta_owner", columns: ["owner"] },
|
|
53
|
+
{ table: "session_meta", name: "idx_session_meta_placement", columns: ["placement_kind", "placement_scope", "placed_at"] },
|
|
54
|
+
{ table: "session_event", name: "uq_session_event_entry", columns: ["session_id", "entry_id"], unique: true },
|
|
55
|
+
{ table: "session_event", name: "idx_session_event_type", columns: ["session_id", "type", "seq"] },
|
|
53
56
|
];
|
|
54
57
|
export async function ensureSchema(pool) {
|
|
55
58
|
for (const stmt of PG_SESSION_SCHEMA)
|
|
56
59
|
await pool.query(stmt);
|
|
60
|
+
await ensureIndexes((t, p) => pool.query(t, p), "pg", SESSION_INDEXES);
|
|
57
61
|
}
|
|
58
62
|
export class PgSessionStorage extends BaseSessionStorage {
|
|
59
63
|
pool;
|
|
@@ -49,6 +49,7 @@ import type { ManagedRetentionCapability, RetentionDeclaration, RetentionReceipt
|
|
|
49
49
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
50
50
|
import type { Pool as PgPool } from "pg";
|
|
51
51
|
import { type SqlDriver, type SqlTxConn } from "./sql-driver.js";
|
|
52
|
+
import { type IndexSpec } from "./ensure-index.js";
|
|
52
53
|
/**
|
|
53
54
|
* 三只**被托管**的 SQL 店(session / checkpoint / tool-result)共用的 `retention` 声明常量。
|
|
54
55
|
*
|
|
@@ -90,6 +91,8 @@ export declare const TIDB_RETENTION_STATEMENTS: readonly string[];
|
|
|
90
91
|
/** PG 方言的建表语句(MySQL 孪生的逐条翻译:AUTO_INCREMENT→BIGSERIAL,逐列 `COLLATE "C"`,内联 KEY→独立
|
|
91
92
|
* CREATE INDEX)。语义逐字见 MySQL 孪生的行内注,此处不复述(两份注释迟早分叉)。 */
|
|
92
93
|
export declare const PG_RETENTION_SCHEMA: readonly string[];
|
|
94
|
+
/** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgRetentionSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
95
|
+
export declare const RETENTION_INDEXES: readonly IndexSpec[];
|
|
93
96
|
/** PG 侧的幂等 schema apply(由 `pg-pool.ts` 的中央 `ensurePgSchema` 组合;裸 `PgQueryFn` 形,与
|
|
94
97
|
* approval-ask / leader-run 同姿势 —— DDL 仍跑在中央那条 client 上,advisory lock 的 session 语义不受影响)。 */
|
|
95
98
|
export declare function ensurePgRetentionSchema(query: (text: string, params?: unknown[]) => Promise<unknown>): Promise<void>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
2
2
|
import { TASK_LIST_ITEM_TABLE, TASK_LIST_META_TABLE, taskListKeyFor } from "./task-list-store-sql.js";
|
|
3
|
+
import { ensureIndexes } from "./ensure-index.js";
|
|
3
4
|
export const MANAGED_RETENTION = "managed";
|
|
4
5
|
export const UNMANAGED_RETENTION = "none";
|
|
5
6
|
export const RETENTION_AUDIT_TABLE = "retention_audit";
|
|
@@ -93,8 +94,6 @@ export const PG_RETENTION_SCHEMA = [
|
|
|
93
94
|
error TEXT COLLATE "C" NULL,
|
|
94
95
|
PRIMARY KEY (id)
|
|
95
96
|
)`,
|
|
96
|
-
`CREATE INDEX IF NOT EXISTS idx_retention_audit_domain ON ${RETENTION_AUDIT_TABLE} (domain, id)`,
|
|
97
|
-
`CREATE INDEX IF NOT EXISTS idx_retention_audit_executed ON ${RETENTION_AUDIT_TABLE} (executed_at_ms)`,
|
|
98
97
|
`CREATE TABLE IF NOT EXISTS ${RETENTION_HOLD_TABLE} (
|
|
99
98
|
domain VARCHAR(190) COLLATE "C" NOT NULL,
|
|
100
99
|
-- held:1 = 冻结中,0 = 只是那把域级互斥行锁的哨兵。完整判据(为什么 presence 形不成立)见 MySQL 孪生。
|
|
@@ -119,11 +118,16 @@ export const PG_RETENTION_SCHEMA = [
|
|
|
119
118
|
-- 主键带 domain(理由逐字见 MySQL 孪生:会话 id 是调用方可自选的,跨租户同名合法)。
|
|
120
119
|
PRIMARY KEY (domain, kind, row_key)
|
|
121
120
|
)`,
|
|
122
|
-
|
|
121
|
+
];
|
|
122
|
+
export const RETENTION_INDEXES = [
|
|
123
|
+
{ table: `${RETENTION_AUDIT_TABLE}`, name: "idx_retention_audit_domain", columns: ["domain", "id"] },
|
|
124
|
+
{ table: `${RETENTION_AUDIT_TABLE}`, name: "idx_retention_audit_executed", columns: ["executed_at_ms"] },
|
|
125
|
+
{ table: `${RETENTION_TOMBSTONE_TABLE}`, name: "idx_retention_tombstone_key", columns: ["kind", "row_key"] },
|
|
123
126
|
];
|
|
124
127
|
export async function ensurePgRetentionSchema(query) {
|
|
125
128
|
for (const stmt of PG_RETENTION_SCHEMA)
|
|
126
129
|
await query(stmt);
|
|
130
|
+
await ensureIndexes(query, "pg", RETENTION_INDEXES);
|
|
127
131
|
}
|
|
128
132
|
class RetentionAborted extends Error {
|
|
129
133
|
why;
|
|
@@ -23,8 +23,11 @@
|
|
|
23
23
|
import type { Pool } from "mysql2/promise";
|
|
24
24
|
import { type RosterStore, type RosterEntry, type RosterAccess } from "@sema-agent/core";
|
|
25
25
|
import type { PgQueryFn } from "./pg-query.js";
|
|
26
|
+
import { type IndexSpec } from "./ensure-index.js";
|
|
26
27
|
export declare const ROSTER_TABLE = "agent_roster";
|
|
27
28
|
export declare function ensureTiDBRosterSchema(pool: Pool): Promise<void>;
|
|
29
|
+
/** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgRosterSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
30
|
+
export declare const ROSTER_INDEXES: readonly IndexSpec[];
|
|
28
31
|
export declare function ensurePgRosterSchema(q: PgQueryFn): Promise<void>;
|
|
29
32
|
export declare class TiDBRosterStore implements RosterStore {
|
|
30
33
|
private readonly pool;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeAgentName } from "@sema-agent/core";
|
|
2
|
+
import { ensureIndexes } from "./ensure-index.js";
|
|
2
3
|
export const ROSTER_TABLE = "agent_roster";
|
|
3
4
|
function rowToEntry(r) {
|
|
4
5
|
return {
|
|
@@ -42,7 +43,11 @@ export async function ensureTiDBRosterSchema(pool) {
|
|
|
42
43
|
PRIMARY KEY (name_key, owner_key, scope_key),
|
|
43
44
|
KEY idx_roster_agent (agent_id)
|
|
44
45
|
) COLLATE utf8mb4_bin`);
|
|
46
|
+
await ensureIndexes((text, params) => pool.query(text, params), "mysql", ROSTER_INDEXES);
|
|
45
47
|
}
|
|
48
|
+
export const ROSTER_INDEXES = [
|
|
49
|
+
{ table: `${ROSTER_TABLE}`, name: "idx_roster_agent", columns: ["agent_id"] },
|
|
50
|
+
];
|
|
46
51
|
export async function ensurePgRosterSchema(q) {
|
|
47
52
|
await q(`CREATE TABLE IF NOT EXISTS ${ROSTER_TABLE} (
|
|
48
53
|
name_key VARCHAR(190) COLLATE "C" NOT NULL,
|
|
@@ -66,7 +71,7 @@ export async function ensurePgRosterSchema(q) {
|
|
|
66
71
|
recorded_at_ms BIGINT NOT NULL,
|
|
67
72
|
PRIMARY KEY (name_key, owner_key, scope_key)
|
|
68
73
|
)`);
|
|
69
|
-
await q
|
|
74
|
+
await ensureIndexes(q, "pg", ROSTER_INDEXES);
|
|
70
75
|
}
|
|
71
76
|
function upsertParams(entry, now) {
|
|
72
77
|
return [
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 中央 schema(`tidb-pool.ts` 的 `SCHEMA_STATEMENTS` ⇄ `pg-pool.ts` 的 `PG_SCHEMA_STATEMENTS` 这一对孪生)
|
|
3
|
+
* 所拥有的那批表的索引**声明** —— S-287,两方言**共用同一份**。
|
|
4
|
+
*
|
|
5
|
+
* ## 为什么自成一个模块
|
|
6
|
+
* 两只池各自 `import` 了自己的驱动(`mysql2/promise` / `pg`,都是**值**导入)。若把这份声明放进其中一只,
|
|
7
|
+
* 另一只就得把对方的驱动一起拖进进程 —— 单后端部署因此多驮一个它永远用不到的驱动。声明是纯数据、零驱动
|
|
8
|
+
* 依赖,所以它住在两只池**之外**,谁都可以白拿。
|
|
9
|
+
*
|
|
10
|
+
* ## 谁应用它
|
|
11
|
+
* · **PG**:`pg-pool.ts` 的 `ensurePgSchema`,在所有 `CREATE TABLE` 之后(PG 方言没有内联 `KEY`);
|
|
12
|
+
* · **MySQL/TiDB**:`tidb-pool.ts` 的 `ensureSchema`,同样在所有 `CREATE TABLE` 之后 —— 新建库上这批索引
|
|
13
|
+
* 早已随 `CREATE TABLE` 的内联 `KEY` 到货,`ensureIndex` 的探测因此**一条 DDL 都不发**;只有存量库
|
|
14
|
+
* (建表那天还没有这条索引)才真的发一次 `ALTER TABLE … ADD INDEX`。
|
|
15
|
+
*
|
|
16
|
+
* 🔴 每条声明的**列序**必须与 `tidb-pool.ts` 那张表的内联 `KEY` 逐字一致(等值前缀在前、排序列在后);
|
|
17
|
+
* 两处列序分叉 = 两方言的访问路径分叉,而探测只看索引名、看不出列序不同。
|
|
18
|
+
*/
|
|
19
|
+
import type { IndexSpec } from "./ensure-index.js";
|
|
20
|
+
export declare const CENTRAL_SCHEMA_INDEXES: readonly IndexSpec[];
|
|
21
|
+
//# sourceMappingURL=schema-indexes.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const CENTRAL_SCHEMA_INDEXES = [
|
|
2
|
+
{ table: "task_run", name: "idx_task_run_session_status", columns: ["session_id", "status"] },
|
|
3
|
+
{ table: "task_run", name: "idx_task_run_owner", columns: ["owner"] },
|
|
4
|
+
{ table: "task_run", name: "idx_task_run_created", columns: ["created_at"] },
|
|
5
|
+
{ table: "task_run", name: "idx_task_run_job", columns: ["job_id"] },
|
|
6
|
+
{ table: "task_active", name: "idx_task_active_task", columns: ["task_id"] },
|
|
7
|
+
{ table: "checkpoint", name: "idx_checkpoint_status_deadline", columns: ["status", "deadline"] },
|
|
8
|
+
{ table: "checkpoint", name: "idx_checkpoint_session_status", columns: ["session_id", "status"] },
|
|
9
|
+
{ table: "resume_anchor", name: "idx_resume_anchor_session", columns: ["session_id"] },
|
|
10
|
+
{ table: "approval_nonce", name: "idx_approval_nonce_expires", columns: ["expires_at_ms"] },
|
|
11
|
+
{ table: "sendfile_link", name: "idx_sendfile_link_scope", columns: ["scope", "id"] },
|
|
12
|
+
{ table: "session_policy", name: "idx_session_policy_session", columns: ["session_id"] },
|
|
13
|
+
{ table: "snapshot_blob", name: "idx_snapshot_blob_created", columns: ["created_at"] },
|
|
14
|
+
{ table: "file_history_version", name: "idx_file_history_version_blob", columns: ["blob_hash"] },
|
|
15
|
+
{ table: "file_history_boundary_file", name: "idx_file_history_bf_key", columns: ["scope", "key_hash"] },
|
|
16
|
+
{ table: "file_history_blob", name: "idx_file_history_blob_created", columns: ["created_at"] },
|
|
17
|
+
{ table: "workflow_run", name: "idx_wfrun_scope_created", columns: ["scope", "created_at_ms"] },
|
|
18
|
+
{ table: "workflow_run", name: "idx_wfrun_scope_status", columns: ["scope", "status"] },
|
|
19
|
+
{ table: "workflow_agent_session", name: "idx_wfagentsess_run", columns: ["run_id"] },
|
|
20
|
+
{ table: "workflow_completion_inbox", name: "idx_wfinbox_session_seq", columns: ["session_id", "seq"] },
|
|
21
|
+
{ table: "workflow_inbox_fence", name: "idx_wffence_until", columns: ["until_ms"] },
|
|
22
|
+
{ table: "workflow_notify_journal", name: "idx_wfnotify_acked", columns: ["acked"] },
|
|
23
|
+
{ table: "shared_memory_store", name: "idx_shared_memory_store_scope", columns: ["scope_key", "store_id"] },
|
|
24
|
+
];
|
|
25
|
+
//# sourceMappingURL=schema-indexes.js.map
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import type { Pool } from "mysql2/promise";
|
|
28
28
|
import type { PgQueryFn } from "./pg-query.js";
|
|
29
|
+
import { type IndexSpec } from "./ensure-index.js";
|
|
29
30
|
export interface TaskAttachmentRecord {
|
|
30
31
|
id: string;
|
|
31
32
|
owner: string;
|
|
@@ -116,6 +117,8 @@ export declare function materializeAttachmentsInto(env: {
|
|
|
116
117
|
}>;
|
|
117
118
|
}, store: TaskAttachmentStore, sessionId: string): Promise<number>;
|
|
118
119
|
export declare function ensureTiDBTaskAttachmentSchema(pool: Pool): Promise<void>;
|
|
120
|
+
/** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensurePgTaskAttachmentSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
121
|
+
export declare const TASK_ATTACHMENT_INDEXES: readonly IndexSpec[];
|
|
119
122
|
export declare function ensurePgTaskAttachmentSchema(q: PgQueryFn): Promise<void>;
|
|
120
123
|
/** bytes → 4 MiB 块序列(共享切块器,两方言 + 完整性校验共用)。 */
|
|
121
124
|
export declare class TiDBTaskAttachmentStore implements TaskAttachmentStore {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ensureIndexes } from "./ensure-index.js";
|
|
1
2
|
export const TASK_ATTACHMENT_TABLE = "task_attachment";
|
|
2
3
|
export function materializedRelPaths(atts) {
|
|
3
4
|
const used = new Set();
|
|
@@ -76,7 +77,12 @@ export async function ensureTiDBTaskAttachmentSchema(pool) {
|
|
|
76
77
|
KEY idx_att_session (session_id),
|
|
77
78
|
KEY idx_att_unbound (created_at_ms)
|
|
78
79
|
) COLLATE utf8mb4_bin`);
|
|
80
|
+
await ensureIndexes((text, params) => pool.query(text, params), "mysql", TASK_ATTACHMENT_INDEXES);
|
|
79
81
|
}
|
|
82
|
+
export const TASK_ATTACHMENT_INDEXES = [
|
|
83
|
+
{ table: `${TASK_ATTACHMENT_TABLE}`, name: "idx_att_session", columns: ["session_id"] },
|
|
84
|
+
{ table: `${TASK_ATTACHMENT_TABLE}`, name: "idx_att_unbound", columns: ["created_at_ms"] },
|
|
85
|
+
];
|
|
80
86
|
export async function ensurePgTaskAttachmentSchema(q) {
|
|
81
87
|
await q(`CREATE TABLE IF NOT EXISTS ${TASK_ATTACHMENT_TABLE} (
|
|
82
88
|
id VARCHAR(64) COLLATE "C" NOT NULL,
|
|
@@ -91,8 +97,7 @@ export async function ensurePgTaskAttachmentSchema(q) {
|
|
|
91
97
|
bound_at_ms BIGINT,
|
|
92
98
|
PRIMARY KEY (id)
|
|
93
99
|
)`);
|
|
94
|
-
await q
|
|
95
|
-
await q(`CREATE INDEX IF NOT EXISTS idx_att_unbound ON ${TASK_ATTACHMENT_TABLE} (created_at_ms)`);
|
|
100
|
+
await ensureIndexes(q, "pg", TASK_ATTACHMENT_INDEXES);
|
|
96
101
|
}
|
|
97
102
|
export class TiDBTaskAttachmentStore {
|
|
98
103
|
pool;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import mysql from "mysql2/promise";
|
|
2
2
|
import type { Pool, PoolConnection } from "mysql2/promise";
|
|
3
3
|
import type { ServiceConfig } from "../config-types.js";
|
|
4
|
+
import { type IndexSpec } from "./ensure-index.js";
|
|
4
5
|
/**
|
|
5
6
|
* Shared TiDB (MySQL wire-compatible) connection pool for the durable Session center (L1).
|
|
6
7
|
* Owned by `main.ts`; closed on shutdown.
|
|
@@ -57,11 +58,22 @@ export declare const ENSURE_SCHEMA_LOCK = "sema_ensure_schema";
|
|
|
57
58
|
* lock row — which both exhausts TiDB's "pessimistic lock retry limit" (surfacing as a thrown error) AND starves the
|
|
58
59
|
* app's OWN `SELECT … FOR UPDATE` paths (image-bake admission/lease) of pessimistic-lock budget. So we acquire
|
|
59
60
|
* NON-BLOCKING (GET_LOCK(name, 0) returns 0 immediately if held) and POLL with jittered backoff: one holder, the
|
|
60
|
-
* rest sleeping (not blocked in-DB).
|
|
61
|
-
*
|
|
62
|
-
export declare function acquireEnsureSchemaLock(conn: PoolConnection, tag?: string): Promise<
|
|
61
|
+
* rest sleeping (not blocked in-DB). Polls until acquired (7.83.0: never degrades to unserialized — the S-287
|
|
62
|
+
* `ADD INDEX` leg is not race-safe); a connection whose probe keeps throwing surfaces as a boot failure. */
|
|
63
|
+
export declare function acquireEnsureSchemaLock(conn: PoolConnection, tag?: string): Promise<void>;
|
|
63
64
|
/** Release {@link ENSURE_SCHEMA_LOCK} on `conn` (best-effort — the lock also auto-frees when the connection closes). */
|
|
64
65
|
export declare function releaseEnsureSchemaLock(conn: PoolConnection, tag?: string): Promise<void>;
|
|
66
|
+
/**
|
|
67
|
+
* S-287:`SCHEMA_STATEMENTS` 建的那批表**在存量库上可能缺的索引**(声明与 PG 侧逐条共用同一份 —— 见
|
|
68
|
+
* `plugins/ensure-index.ts` 头注与 `plugins/schema-indexes.ts`)。
|
|
69
|
+
*
|
|
70
|
+
* 组合口径与 `SCHEMA_STATEMENTS` **一一对应**:凡是它建的表,索引声明就进这个数组;它不建的表
|
|
71
|
+
* (device / roster / background-agent / mailbox / task-attachment / memory 两族……由 `boot/stores.ts`
|
|
72
|
+
* 各自的 `ensureTiDB*Schema` 腿建)由那条腿自己应用自己的声明 —— 否则这里会对一张还不存在的表发 ALTER。
|
|
73
|
+
*
|
|
74
|
+
* 新建库上这批索引早已随 `CREATE TABLE` 的内联 `KEY` 到货 ⇒ `ensureIndex` 的探测全部命中、**零 DDL**。
|
|
75
|
+
*/
|
|
76
|
+
export declare const SCHEMA_INDEXES: readonly IndexSpec[];
|
|
65
77
|
/** Create tables if absent (+ seed the image-bake admission sentinel). Safe to call on every startup.
|
|
66
78
|
*
|
|
67
79
|
* Per the SCHEMA POLICY on {@link SCHEMA_STATEMENTS} this is a pure CREATE run — there are NO additive
|
|
@@ -6,6 +6,21 @@ import { TIDB_LEADER_RUN_STATEMENTS } from "./leader-run-store-sql.js";
|
|
|
6
6
|
import { TIDB_ADOPTION_LOG_STATEMENTS } from "./adoption-log-sql.js";
|
|
7
7
|
import { TIDB_PERMISSION_RULE_STATEMENTS } from "./permission-rule-store-sql.js";
|
|
8
8
|
import { TIDB_RETENTION_STATEMENTS } from "./retention-store-sql.js";
|
|
9
|
+
import { ensureIndexes } from "./ensure-index.js";
|
|
10
|
+
import { CENTRAL_SCHEMA_INDEXES } from "./schema-indexes.js";
|
|
11
|
+
import { SESSION_INDEXES } from "./pg-session-storage.js";
|
|
12
|
+
import { TOOL_RESULT_INDEXES } from "./tool-result-store-sql.js";
|
|
13
|
+
import { COST_QUOTA_INDEXES } from "./pg-cost-quota.js";
|
|
14
|
+
import { RATE_LIMIT_INDEXES } from "./pg-rate-limiter.js";
|
|
15
|
+
import { OUTCOME_LEDGER_INDEXES } from "./outcome-ledger-sql.js";
|
|
16
|
+
import { IMAGE_INDEX_INDEXES } from "./image-index-sql.js";
|
|
17
|
+
import { IMAGE_BAKE_INDEXES } from "./image-bake-store-sql.js";
|
|
18
|
+
import { APPROVAL_ASK_INDEXES } from "./approval-ask-store-sql.js";
|
|
19
|
+
import { ADOPTION_LOG_INDEXES } from "./adoption-log-sql.js";
|
|
20
|
+
import { PERMISSION_RULE_INDEXES } from "./permission-rule-store-sql.js";
|
|
21
|
+
import { RETENTION_INDEXES } from "./retention-store-sql.js";
|
|
22
|
+
import { LEADER_RUN_INDEXES } from "./leader-run-store-sql.js";
|
|
23
|
+
import { pollUntilLocked } from "./ensure-schema-lock-policy.js";
|
|
9
24
|
export function tidbPoolOptions(tidb) {
|
|
10
25
|
const { host, port, user, password, database, connectionLimit } = tidb;
|
|
11
26
|
const limit = connectionLimit ?? 10;
|
|
@@ -670,20 +685,10 @@ export const SCHEMA_STATEMENTS = [
|
|
|
670
685
|
];
|
|
671
686
|
export const ENSURE_SCHEMA_LOCK = "sema_ensure_schema";
|
|
672
687
|
export async function acquireEnsureSchemaLock(conn, tag = "ensureSchema") {
|
|
673
|
-
await
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
if (Number(rows[0]?.got) === 1)
|
|
678
|
-
return true;
|
|
679
|
-
}
|
|
680
|
-
catch (err) {
|
|
681
|
-
console.warn(`[${tag}] GET_LOCK('${ENSURE_SCHEMA_LOCK}') attempt ${attempt + 1}/40:`, err?.message ?? err);
|
|
682
|
-
}
|
|
683
|
-
await new Promise((r) => setTimeout(r, 100 + Math.min(attempt, 3) * 100 + Math.floor(Math.random() * 150)));
|
|
684
|
-
}
|
|
685
|
-
console.warn(`[${tag}] could not acquire '${ENSURE_SCHEMA_LOCK}' after retries`);
|
|
686
|
-
return false;
|
|
688
|
+
await pollUntilLocked(async () => {
|
|
689
|
+
const [rows] = await conn.query("SELECT GET_LOCK(?, 0) AS got", [ENSURE_SCHEMA_LOCK]);
|
|
690
|
+
return Number(rows[0]?.got) === 1;
|
|
691
|
+
}, { tag, label: `GET_LOCK('${ENSURE_SCHEMA_LOCK}')` });
|
|
687
692
|
}
|
|
688
693
|
export async function releaseEnsureSchemaLock(conn, tag = "ensureSchema") {
|
|
689
694
|
try {
|
|
@@ -693,14 +698,32 @@ export async function releaseEnsureSchemaLock(conn, tag = "ensureSchema") {
|
|
|
693
698
|
console.warn(`[${tag}] RELEASE_LOCK('${ENSURE_SCHEMA_LOCK}') failed (lock auto-frees on connection close):`, err);
|
|
694
699
|
}
|
|
695
700
|
}
|
|
701
|
+
export const SCHEMA_INDEXES = [
|
|
702
|
+
...CENTRAL_SCHEMA_INDEXES,
|
|
703
|
+
...SESSION_INDEXES,
|
|
704
|
+
...TOOL_RESULT_INDEXES,
|
|
705
|
+
...COST_QUOTA_INDEXES,
|
|
706
|
+
...RATE_LIMIT_INDEXES,
|
|
707
|
+
...OUTCOME_LEDGER_INDEXES,
|
|
708
|
+
...IMAGE_INDEX_INDEXES,
|
|
709
|
+
...IMAGE_BAKE_INDEXES,
|
|
710
|
+
...APPROVAL_ASK_INDEXES,
|
|
711
|
+
...ADOPTION_LOG_INDEXES,
|
|
712
|
+
...PERMISSION_RULE_INDEXES,
|
|
713
|
+
...RETENTION_INDEXES,
|
|
714
|
+
...LEADER_RUN_INDEXES,
|
|
715
|
+
];
|
|
696
716
|
export async function ensureSchema(pool) {
|
|
697
717
|
const conn = await pool.getConnection();
|
|
698
|
-
|
|
718
|
+
let locked = false;
|
|
699
719
|
try {
|
|
720
|
+
await acquireEnsureSchemaLock(conn);
|
|
721
|
+
locked = true;
|
|
700
722
|
for (const stmt of SCHEMA_STATEMENTS) {
|
|
701
723
|
await conn.query(stmt);
|
|
702
724
|
}
|
|
703
725
|
await conn.query("INSERT IGNORE INTO image_bake_admit (pool) VALUES ('standard')");
|
|
726
|
+
await ensureIndexes((text, params) => conn.query(text, params), "mysql", SCHEMA_INDEXES);
|
|
704
727
|
}
|
|
705
728
|
finally {
|
|
706
729
|
if (locked)
|
|
@@ -2,10 +2,13 @@ import type { ToolResultProvenance, ToolResultStore, ToolResultSlice, ToolResult
|
|
|
2
2
|
import type { Pool as MySqlPool } from "mysql2/promise";
|
|
3
3
|
import type { Pool as PgPool, PoolClient as PgPoolClient } from "pg";
|
|
4
4
|
import { type SqlDriver } from "./sql-driver.js";
|
|
5
|
+
import { type IndexSpec } from "./ensure-index.js";
|
|
5
6
|
/** PG schema for the tool_result table — the PG translation of tidb-pool.ts's tool_result DDL
|
|
6
7
|
* (LONGTEXT→TEXT, DATETIME(3)→TIMESTAMPTZ(3), inline KEY→separate CREATE INDEX). Disjoint from other stores'
|
|
7
8
|
* tables, so a self-contained ensureSchema is safe (central pg-pool aggregation is done separately). */
|
|
8
9
|
export declare const PG_TOOL_RESULT_SCHEMA: string[];
|
|
10
|
+
/** S-287:本 store 的索引**声明**(两方言共用一份)。PG 侧由下面的 `ensureSchema` 应用,MySQL 侧由 `tidb-pool.ts` 的中央 `ensureSchema` 应用(那里内联 `KEY` 已在 `CREATE TABLE` 里 ⇒ 新建库探到即零 DDL,存量库缺谁补谁)。加索引以外的 schema 变更仍归运维,见 `plugins/ensure-index.ts` 头注。 */
|
|
11
|
+
export declare const TOOL_RESULT_INDEXES: readonly IndexSpec[];
|
|
9
12
|
/** Idempotent schema apply for the tool_result table (for the integration test to call). */
|
|
10
13
|
export declare function ensureSchema(pool: PgPool | PgPoolClient): Promise<void>;
|
|
11
14
|
/**
|
|
@@ -2,6 +2,7 @@ import { MAX_MINTED_TOOL_RESULT_REF_CHARS, assertSafeToolResultRef, assertToolRe
|
|
|
2
2
|
import { pgSanitizeText } from "./pg-safe-json.js";
|
|
3
3
|
import { mysqlDriver, pgDriver } from "./sql-driver.js";
|
|
4
4
|
import { MANAGED_RETENTION } from "./retention-store-sql.js";
|
|
5
|
+
import { ensureIndexes } from "./ensure-index.js";
|
|
5
6
|
export const PG_TOOL_RESULT_SCHEMA = [
|
|
6
7
|
`CREATE TABLE IF NOT EXISTS tool_result (
|
|
7
8
|
ref VARCHAR(518) COLLATE "C" NOT NULL,
|
|
@@ -11,11 +12,14 @@ export const PG_TOOL_RESULT_SCHEMA = [
|
|
|
11
12
|
created_at TIMESTAMPTZ(3) NOT NULL,
|
|
12
13
|
PRIMARY KEY (ref)
|
|
13
14
|
)`,
|
|
14
|
-
|
|
15
|
+
];
|
|
16
|
+
export const TOOL_RESULT_INDEXES = [
|
|
17
|
+
{ table: "tool_result", name: "idx_tool_result_created", columns: ["created_at"] },
|
|
15
18
|
];
|
|
16
19
|
export async function ensureSchema(pool) {
|
|
17
20
|
for (const stmt of PG_TOOL_RESULT_SCHEMA)
|
|
18
21
|
await pool.query(stmt);
|
|
22
|
+
await ensureIndexes((t, p) => pool.query(t, p), "pg", TOOL_RESULT_INDEXES);
|
|
19
23
|
}
|
|
20
24
|
export async function assertToolResultProvenanceSchema(query, dialect) {
|
|
21
25
|
const widthRow = await query(dialect === "tidb"
|