@axiom-lattice/pg-stores 3.3.13 → 4.0.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +208 -0
- package/dist/index.d.mts +357 -3
- package/dist/index.d.ts +357 -3
- package/dist/index.js +2337 -77
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2342 -76
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/AgentRunRetention.migrations.test.ts +41 -0
- package/src/__tests__/PostgreSQLAgentRunLedger.test.ts +305 -0
- package/src/__tests__/PostgreSQLAgentRunRetention.test.ts +126 -0
- package/src/__tests__/PostgreSQLAgentRunTaskQueue.test.ts +1375 -0
- package/src/__tests__/PostgreSQLChannelReplyJobStore.test.ts +335 -0
- package/src/__tests__/PostgreSQLCheckpointStorage.deleteAfter.test.ts +473 -0
- package/src/__tests__/PostgreSQLCheckpointStorage.test.ts +538 -0
- package/src/__tests__/PostgreSQLStore.test.ts +173 -0
- package/src/__tests__/PostgreSQLThreadStore.test.ts +80 -0
- package/src/__tests__/ThreadMessageQueueStore.integration.test.ts +21 -0
- package/src/__tests__/ThreadMessageQueueStore.test.ts +8 -3
- package/src/__tests__/createPgStoreConfig.test.ts +38 -0
- package/src/__tests__/eval-task-id.test.ts +14 -3
- package/src/createPgStoreConfig.ts +35 -2
- package/src/index.ts +7 -0
- package/src/migrations/agent_run_base_checkpoint_captured_migration.ts +34 -0
- package/src/migrations/agent_run_capability_revision_migration.ts +24 -0
- package/src/migrations/agent_run_ledger_migration.ts +51 -0
- package/src/migrations/agent_run_project_task_migration.ts +34 -0
- package/src/migrations/agent_run_project_task_running_index_migration.ts +33 -0
- package/src/migrations/agent_run_retention_migration.ts +25 -0
- package/src/migrations/agent_run_rollback_failure_migration.ts +28 -0
- package/src/migrations/agent_run_running_index_source_migration.ts +31 -0
- package/src/migrations/agent_run_task_queue_migration.ts +65 -0
- package/src/migrations/channel_reply_job_migration.ts +55 -0
- package/src/migrations/eval_migrations.ts +19 -0
- package/src/migrations/store_migrations.ts +50 -0
- package/src/migrations/task_migration.ts +17 -0
- package/src/migrations/thread_migrations.ts +25 -0
- package/src/stores/PostgreSQLAgentRunLedger.ts +1221 -0
- package/src/stores/PostgreSQLChannelReplyJobStore.ts +373 -0
- package/src/stores/PostgreSQLCheckpointStorage.ts +548 -0
- package/src/stores/PostgreSQLEvalStore.ts +17 -4
- package/src/stores/PostgreSQLStore.ts +167 -0
- package/src/stores/PostgreSQLTaskStore.ts +4 -0
- package/src/stores/PostgreSQLThreadStore.ts +49 -10
- package/src/stores/ThreadMessageQueueStore.ts +10 -5
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Migration } from "./migration";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Indexes terminal run completion times for retention cleanup.
|
|
5
|
+
*
|
|
6
|
+
* @remarks Only terminal rows are ever eligible for deletion, so the partial
|
|
7
|
+
* index keeps the bounded oldest-first cleanup scan cheap without indexing
|
|
8
|
+
* pending or running queue work.
|
|
9
|
+
*/
|
|
10
|
+
export const createAgentRunRetentionMigration: Migration = {
|
|
11
|
+
version: 201,
|
|
12
|
+
name: "add_agent_run_retention_index",
|
|
13
|
+
up: async (client) => {
|
|
14
|
+
await client.query(`
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_agent_runs_terminal_finished_at
|
|
16
|
+
ON lattice_agent_runs (finished_at)
|
|
17
|
+
WHERE status IN ('success', 'error', 'interrupted', 'cancelled', 'timeout')
|
|
18
|
+
`);
|
|
19
|
+
},
|
|
20
|
+
down: async (client) => {
|
|
21
|
+
await client.query(`
|
|
22
|
+
DROP INDEX IF EXISTS idx_lattice_agent_runs_terminal_finished_at
|
|
23
|
+
`);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Migration } from "./migration";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adds bounded rollback failure tracking to run records.
|
|
5
|
+
*
|
|
6
|
+
* @remarks Recovery increments `rollback_attempts` and stores
|
|
7
|
+
* `rollback_error`. When the attempt cap is reached the row settles `error`
|
|
8
|
+
* with `rollback_requested=false`, so join/list converge instead of retrying a
|
|
9
|
+
* permanently failing checkpoint rewind forever.
|
|
10
|
+
*/
|
|
11
|
+
export const createAgentRunRollbackFailureMigration: Migration = {
|
|
12
|
+
version: 199,
|
|
13
|
+
name: "add_agent_run_rollback_failure",
|
|
14
|
+
up: async (client) => {
|
|
15
|
+
await client.query(`
|
|
16
|
+
ALTER TABLE lattice_agent_runs
|
|
17
|
+
ADD COLUMN IF NOT EXISTS rollback_attempts INTEGER NOT NULL DEFAULT 0,
|
|
18
|
+
ADD COLUMN IF NOT EXISTS rollback_error TEXT
|
|
19
|
+
`);
|
|
20
|
+
},
|
|
21
|
+
down: async (client) => {
|
|
22
|
+
await client.query(`
|
|
23
|
+
ALTER TABLE lattice_agent_runs
|
|
24
|
+
DROP COLUMN IF EXISTS rollback_error,
|
|
25
|
+
DROP COLUMN IF EXISTS rollback_attempts
|
|
26
|
+
`);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { PoolClient } from "pg";
|
|
2
|
+
import { Migration } from "./migration";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Scope the one-running-row-per-thread index to the durable run queue.
|
|
6
|
+
*
|
|
7
|
+
* @remarks Legacy message-queue ledger rows also use `status='running'`. A
|
|
8
|
+
* crashed legacy owner can leave such a row behind; it must not make a
|
|
9
|
+
* `run_api` pending row unclaimable after the `thread-run` lease expires.
|
|
10
|
+
* Real execution exclusion remains the shared `thread-run` lease.
|
|
11
|
+
*/
|
|
12
|
+
export const agentRunRunningIndexSourceMigration: Migration = {
|
|
13
|
+
version: 202,
|
|
14
|
+
name: "scope_agent_run_running_index_to_run_api",
|
|
15
|
+
up: async (client: PoolClient) => {
|
|
16
|
+
await client.query("DROP INDEX IF EXISTS uq_lattice_agent_runs_one_running");
|
|
17
|
+
await client.query(`
|
|
18
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lattice_agent_runs_one_running
|
|
19
|
+
ON lattice_agent_runs (tenant_id, thread_id)
|
|
20
|
+
WHERE status = 'running' AND source = 'run_api'
|
|
21
|
+
`);
|
|
22
|
+
},
|
|
23
|
+
down: async (client: PoolClient) => {
|
|
24
|
+
await client.query("DROP INDEX IF EXISTS uq_lattice_agent_runs_one_running");
|
|
25
|
+
await client.query(`
|
|
26
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lattice_agent_runs_one_running
|
|
27
|
+
ON lattice_agent_runs (tenant_id, thread_id)
|
|
28
|
+
WHERE status = 'running'
|
|
29
|
+
`);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Migration } from "./migration";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Expands `lattice_agent_runs` into a durable run task queue.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* - `source` separates new run-queue work (`run_api`) from the existing
|
|
8
|
+
* observational ledger path (`legacy`, the default for old/inserted rows).
|
|
9
|
+
* - Pending rows have no owner, lease or start time, so those columns become
|
|
10
|
+
* nullable.
|
|
11
|
+
* - Payload columns are JSONB; `available_at` is the earliest claim time.
|
|
12
|
+
* - The partial unique running index is intentionally left untouched.
|
|
13
|
+
*/
|
|
14
|
+
export const createAgentRunTaskQueueMigration: Migration = {
|
|
15
|
+
version: 196,
|
|
16
|
+
name: "expand_lattice_agent_runs_task_queue",
|
|
17
|
+
up: async (client) => {
|
|
18
|
+
await client.query(`
|
|
19
|
+
ALTER TABLE lattice_agent_runs
|
|
20
|
+
ALTER COLUMN owner DROP NOT NULL,
|
|
21
|
+
ALTER COLUMN lease_expires_at DROP NOT NULL,
|
|
22
|
+
ALTER COLUMN started_at DROP NOT NULL,
|
|
23
|
+
ALTER COLUMN started_at DROP DEFAULT
|
|
24
|
+
`);
|
|
25
|
+
await client.query(`
|
|
26
|
+
ALTER TABLE lattice_agent_runs
|
|
27
|
+
ADD COLUMN IF NOT EXISTS source VARCHAR(32) NOT NULL DEFAULT 'legacy',
|
|
28
|
+
ADD COLUMN IF NOT EXISTS input JSONB,
|
|
29
|
+
ADD COLUMN IF NOT EXISTS command JSONB,
|
|
30
|
+
ADD COLUMN IF NOT EXISTS config JSONB,
|
|
31
|
+
ADD COLUMN IF NOT EXISTS metadata JSONB,
|
|
32
|
+
ADD COLUMN IF NOT EXISTS base_checkpoint_id TEXT,
|
|
33
|
+
ADD COLUMN IF NOT EXISTS available_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
|
34
|
+
ADD COLUMN IF NOT EXISTS rollback_requested BOOLEAN NOT NULL DEFAULT FALSE
|
|
35
|
+
`);
|
|
36
|
+
await client.query(`
|
|
37
|
+
ALTER TABLE lattice_agent_runs
|
|
38
|
+
ALTER COLUMN source SET DEFAULT 'legacy'
|
|
39
|
+
`);
|
|
40
|
+
await client.query(`
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_agent_runs_run_queue
|
|
42
|
+
ON lattice_agent_runs (source, status, available_at, created_at)
|
|
43
|
+
`);
|
|
44
|
+
},
|
|
45
|
+
down: async (client) => {
|
|
46
|
+
await client.query(`
|
|
47
|
+
DROP INDEX IF EXISTS idx_lattice_agent_runs_run_queue
|
|
48
|
+
`);
|
|
49
|
+
await client.query(`
|
|
50
|
+
ALTER TABLE lattice_agent_runs
|
|
51
|
+
DROP COLUMN IF EXISTS rollback_requested,
|
|
52
|
+
DROP COLUMN IF EXISTS available_at,
|
|
53
|
+
DROP COLUMN IF EXISTS base_checkpoint_id,
|
|
54
|
+
DROP COLUMN IF EXISTS metadata,
|
|
55
|
+
DROP COLUMN IF EXISTS config,
|
|
56
|
+
DROP COLUMN IF EXISTS command,
|
|
57
|
+
DROP COLUMN IF EXISTS input,
|
|
58
|
+
DROP COLUMN IF EXISTS source
|
|
59
|
+
`);
|
|
60
|
+
await client.query(`
|
|
61
|
+
ALTER TABLE lattice_agent_runs
|
|
62
|
+
ALTER COLUMN started_at SET DEFAULT now()
|
|
63
|
+
`);
|
|
64
|
+
},
|
|
65
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { PoolClient } from "pg";
|
|
2
|
+
import type { Migration } from "./migration";
|
|
3
|
+
|
|
4
|
+
const DB_CLOCK_MS =
|
|
5
|
+
"FLOOR(EXTRACT(EPOCH FROM clock_timestamp()) * 1000)::bigint";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Durable channel reply jobs used by the cross-replica reply dispatcher.
|
|
9
|
+
*
|
|
10
|
+
* @remarks Lease-based claims make the row the single source of truth for
|
|
11
|
+
* "which replica is delivering this run's channel reply". Timestamps are epoch
|
|
12
|
+
* milliseconds from the database clock.
|
|
13
|
+
*/
|
|
14
|
+
export const createChannelReplyJobsTable: Migration = {
|
|
15
|
+
version: 200,
|
|
16
|
+
name: "create_lattice_channel_reply_jobs",
|
|
17
|
+
up: async (client: PoolClient) => {
|
|
18
|
+
await client.query(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS lattice_channel_reply_jobs (
|
|
20
|
+
run_id TEXT PRIMARY KEY,
|
|
21
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
22
|
+
assistant_id TEXT NOT NULL,
|
|
23
|
+
thread_id TEXT NOT NULL,
|
|
24
|
+
adapter_channel TEXT NOT NULL,
|
|
25
|
+
channel_installation_id TEXT NOT NULL,
|
|
26
|
+
reply_target JSONB NOT NULL,
|
|
27
|
+
input_message_id TEXT NOT NULL,
|
|
28
|
+
stream_cursor TEXT NULL,
|
|
29
|
+
status TEXT NOT NULL,
|
|
30
|
+
attempts INT NOT NULL DEFAULT 0,
|
|
31
|
+
available_at BIGINT NOT NULL,
|
|
32
|
+
lease_owner TEXT NULL,
|
|
33
|
+
lease_expires_at BIGINT NULL,
|
|
34
|
+
last_error TEXT NULL,
|
|
35
|
+
output_message_id TEXT NULL,
|
|
36
|
+
created_at BIGINT NOT NULL DEFAULT (${DB_CLOCK_MS}),
|
|
37
|
+
updated_at BIGINT NOT NULL DEFAULT (${DB_CLOCK_MS}),
|
|
38
|
+
delivered_at BIGINT NULL
|
|
39
|
+
)
|
|
40
|
+
`);
|
|
41
|
+
await client.query(`
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_channel_reply_jobs_due
|
|
43
|
+
ON lattice_channel_reply_jobs (status, available_at)
|
|
44
|
+
`);
|
|
45
|
+
await client.query(`
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_channel_reply_jobs_tenant_thread
|
|
47
|
+
ON lattice_channel_reply_jobs (tenant_id, thread_id)
|
|
48
|
+
`);
|
|
49
|
+
},
|
|
50
|
+
down: async (client: PoolClient) => {
|
|
51
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_channel_reply_jobs_tenant_thread");
|
|
52
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_channel_reply_jobs_due");
|
|
53
|
+
await client.query("DROP TABLE IF EXISTS lattice_channel_reply_jobs");
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -381,6 +381,24 @@ export const addTaskIdToEvalRuns: Migration = {
|
|
|
381
381
|
},
|
|
382
382
|
};
|
|
383
383
|
|
|
384
|
+
|
|
385
|
+
/** Cooperative cancellation flag for running eval runs. */
|
|
386
|
+
export const addCancelRequestedToEvalRuns: Migration = {
|
|
387
|
+
version: 205,
|
|
388
|
+
name: "add_cancel_requested_to_eval_runs",
|
|
389
|
+
up: async (client: PoolClient) => {
|
|
390
|
+
await client.query(`
|
|
391
|
+
ALTER TABLE lattice_eval_runs
|
|
392
|
+
ADD COLUMN IF NOT EXISTS cancel_requested BOOLEAN NOT NULL DEFAULT FALSE
|
|
393
|
+
`);
|
|
394
|
+
},
|
|
395
|
+
down: async (client: PoolClient) => {
|
|
396
|
+
await client.query(`
|
|
397
|
+
ALTER TABLE lattice_eval_runs DROP COLUMN IF EXISTS cancel_requested
|
|
398
|
+
`);
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
|
|
384
402
|
/** All eval migrations in version order */
|
|
385
403
|
export const evalMigrations: Migration[] = [
|
|
386
404
|
createEvalProjectsTable,
|
|
@@ -394,4 +412,5 @@ export const evalMigrations: Migration[] = [
|
|
|
394
412
|
addInterruptPolicyToEvalCases,
|
|
395
413
|
enforceUniqueEvalProjectName,
|
|
396
414
|
addTaskIdToEvalRuns,
|
|
415
|
+
addCancelRequestedToEvalRuns,
|
|
397
416
|
];
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Namespaced KV store migrations.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { PoolClient } from "pg";
|
|
6
|
+
import { Migration } from "./migration";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Migration: create the tenant-scoped namespaced KV store table.
|
|
10
|
+
*
|
|
11
|
+
* GIN indexes support namespace prefix and JSONB containment filters; the
|
|
12
|
+
* partial expiry index supports the TTL sweeper.
|
|
13
|
+
*/
|
|
14
|
+
export const createStoreItemsTable: Migration = {
|
|
15
|
+
version: 195,
|
|
16
|
+
name: "create_lattice_store_items_table",
|
|
17
|
+
up: async (client: PoolClient) => {
|
|
18
|
+
await client.query(`
|
|
19
|
+
CREATE TABLE IF NOT EXISTS lattice_store_items (
|
|
20
|
+
tenant_id TEXT NOT NULL,
|
|
21
|
+
namespace TEXT[] NOT NULL,
|
|
22
|
+
key TEXT NOT NULL,
|
|
23
|
+
value JSONB NOT NULL,
|
|
24
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
25
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
26
|
+
expires_at TIMESTAMPTZ,
|
|
27
|
+
PRIMARY KEY (tenant_id, namespace, key)
|
|
28
|
+
)
|
|
29
|
+
`);
|
|
30
|
+
await client.query(`
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_store_items_namespace
|
|
32
|
+
ON lattice_store_items USING GIN (namespace)
|
|
33
|
+
`);
|
|
34
|
+
await client.query(`
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_store_items_value
|
|
36
|
+
ON lattice_store_items USING GIN (value)
|
|
37
|
+
`);
|
|
38
|
+
await client.query(`
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_store_items_expires_at
|
|
40
|
+
ON lattice_store_items(expires_at)
|
|
41
|
+
WHERE expires_at IS NOT NULL
|
|
42
|
+
`);
|
|
43
|
+
},
|
|
44
|
+
down: async (client: PoolClient) => {
|
|
45
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_store_items_expires_at");
|
|
46
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_store_items_value");
|
|
47
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_store_items_namespace");
|
|
48
|
+
await client.query("DROP TABLE IF EXISTS lattice_store_items");
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -112,6 +112,22 @@ export const addTaskDependenciesGinIndex: Migration = {
|
|
|
112
112
|
},
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
/** Index task execution threads for ownership checks on isolated worker threads. */
|
|
116
|
+
export const addTaskContextThreadIndex: Migration = {
|
|
117
|
+
version: 206,
|
|
118
|
+
name: "add_task_context_thread_index",
|
|
119
|
+
up: async (client) => {
|
|
120
|
+
await client.query(`
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_tasks_context_thread
|
|
122
|
+
ON lattice_tasks (tenant_id, (context->>'thread_id'))
|
|
123
|
+
WHERE context->>'thread_id' IS NOT NULL
|
|
124
|
+
`);
|
|
125
|
+
},
|
|
126
|
+
down: async (client) => {
|
|
127
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_tasks_context_thread");
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
115
131
|
/** All task migrations in version order */
|
|
116
132
|
export const taskMigrations: Migration[] = [
|
|
117
133
|
createTasksTable,
|
|
@@ -119,4 +135,5 @@ export const taskMigrations: Migration[] = [
|
|
|
119
135
|
addTaskProjectFieldsMigration,
|
|
120
136
|
addFilesToTasks,
|
|
121
137
|
addTaskDependenciesGinIndex,
|
|
138
|
+
addTaskContextThreadIndex,
|
|
122
139
|
];
|
|
@@ -40,3 +40,28 @@ export const createThreadsTable: Migration = {
|
|
|
40
40
|
await client.query("DROP TABLE IF EXISTS lattice_threads");
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Migration: Add per-thread expiry (`expires_at`) for the TTL sweeper.
|
|
46
|
+
*
|
|
47
|
+
* A partial index keeps expiry scans cheap while leaving non-TTL threads out.
|
|
48
|
+
*/
|
|
49
|
+
export const addThreadExpiresAt: Migration = {
|
|
50
|
+
version: 194,
|
|
51
|
+
name: "add_thread_expires_at",
|
|
52
|
+
up: async (client: PoolClient) => {
|
|
53
|
+
await client.query(`
|
|
54
|
+
ALTER TABLE lattice_threads
|
|
55
|
+
ADD COLUMN IF NOT EXISTS expires_at TIMESTAMPTZ
|
|
56
|
+
`);
|
|
57
|
+
await client.query(`
|
|
58
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_threads_expires_at
|
|
59
|
+
ON lattice_threads(expires_at)
|
|
60
|
+
WHERE expires_at IS NOT NULL
|
|
61
|
+
`);
|
|
62
|
+
},
|
|
63
|
+
down: async (client: PoolClient) => {
|
|
64
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_threads_expires_at");
|
|
65
|
+
await client.query("ALTER TABLE lattice_threads DROP COLUMN IF EXISTS expires_at");
|
|
66
|
+
},
|
|
67
|
+
};
|