@axiom-lattice/pg-stores 3.1.1 → 3.1.2
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 +9 -0
- package/dist/index.d.mts +195 -15
- package/dist/index.d.ts +195 -15
- package/dist/index.js +1309 -199
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1295 -191
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/ChannelBindingStore.test.ts +122 -0
- package/src/__tests__/PostgreSQLChannelInstallationStore.test.ts +16 -0
- package/src/__tests__/PostgreSQLProjectBotMembershipStore.test.ts +117 -0
- package/src/__tests__/PostgreSQLProjectMembershipStore.test.ts +235 -0
- package/src/__tests__/PostgreSQLProjectRoomMessageStore.test.ts +103 -0
- package/src/__tests__/PostgreSQLProjectRoomStore.migrations.test.ts +114 -0
- package/src/__tests__/PostgreSQLProjectRoomStore.test.ts +48 -0
- package/src/__tests__/PostgreSQLTaskStore.integration.test.ts +27 -0
- package/src/__tests__/PostgreSQLTaskStore.test.ts +57 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.integration.test.ts +74 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.migrations.test.ts +62 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.test.ts +96 -0
- package/src/__tests__/ThreadMessageQueueStore.migrations.test.ts +63 -0
- package/src/__tests__/ThreadMessageQueueStore.test.ts +113 -0
- package/src/__tests__/migration-name-version-compatibility.test.ts +67 -0
- package/src/__tests__/task-files.test.ts +4 -3
- package/src/createPgStoreConfig.ts +14 -0
- package/src/index.ts +7 -0
- package/src/migrations/add_trusted_run_context_column.ts +18 -0
- package/src/migrations/migration.ts +2 -1
- package/src/migrations/project_room_migration.ts +128 -0
- package/src/migrations/task_migration.ts +15 -0
- package/src/migrations/task_work_items_migration.ts +29 -1
- package/src/stores/ChannelBindingStore.ts +99 -59
- package/src/stores/PostgreSQLChannelInstallationStore.ts +12 -30
- package/src/stores/PostgreSQLProjectBotMembershipStore.ts +199 -0
- package/src/stores/PostgreSQLProjectMembershipStore.ts +140 -0
- package/src/stores/PostgreSQLProjectRoomMessageStore.ts +177 -0
- package/src/stores/PostgreSQLProjectRoomStore.ts +57 -0
- package/src/stores/PostgreSQLTaskStore.ts +89 -3
- package/src/stores/PostgreSQLTaskWorkItemStore.ts +159 -8
- package/src/stores/ThreadMessageQueueStore.ts +38 -9
package/dist/index.js
CHANGED
|
@@ -32,10 +32,12 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
ChannelBindingStore: () => ChannelBindingStore,
|
|
34
34
|
ChannelIdentityMappingStore: () => ChannelIdentityMappingStore,
|
|
35
|
+
DuplicateProjectMembershipError: () => DuplicateProjectMembershipError,
|
|
36
|
+
DuplicateProjectRoomMessageIdempotencyKeyError: () => DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
35
37
|
MenuStore: () => MenuStore,
|
|
36
38
|
MigrationManager: () => MigrationManager,
|
|
37
39
|
PGVectorStoreProvider: () => PGVectorStoreProvider,
|
|
38
|
-
Pool: () =>
|
|
40
|
+
Pool: () => import_pg29.Pool,
|
|
39
41
|
PostgreSQLA2AApiKeyStore: () => PostgreSQLA2AApiKeyStore,
|
|
40
42
|
PostgreSQLAgentWebAppStore: () => PostgreSQLAgentWebAppStore,
|
|
41
43
|
PostgreSQLAssistantStore: () => PostgreSQLAssistantStore,
|
|
@@ -47,9 +49,14 @@ __export(index_exports, {
|
|
|
47
49
|
PostgreSQLEvalStore: () => PostgreSQLEvalStore,
|
|
48
50
|
PostgreSQLMcpServerConfigStore: () => PostgreSQLMcpServerConfigStore,
|
|
49
51
|
PostgreSQLMetricsServerConfigStore: () => PostgreSQLMetricsServerConfigStore,
|
|
52
|
+
PostgreSQLProjectBotMembershipStore: () => PostgreSQLProjectBotMembershipStore,
|
|
53
|
+
PostgreSQLProjectMembershipStore: () => PostgreSQLProjectMembershipStore,
|
|
54
|
+
PostgreSQLProjectRoomMessageStore: () => PostgreSQLProjectRoomMessageStore,
|
|
55
|
+
PostgreSQLProjectRoomStore: () => PostgreSQLProjectRoomStore,
|
|
50
56
|
PostgreSQLProjectStore: () => PostgreSQLProjectStore,
|
|
51
57
|
PostgreSQLScheduleStorage: () => PostgreSQLScheduleStorage,
|
|
52
58
|
PostgreSQLSkillStore: () => PostgreSQLSkillStore,
|
|
59
|
+
PostgreSQLTaskWorkItemStore: () => PostgreSQLTaskWorkItemStore,
|
|
53
60
|
PostgreSQLTenantStore: () => PostgreSQLTenantStore,
|
|
54
61
|
PostgreSQLThreadStore: () => PostgreSQLThreadStore,
|
|
55
62
|
PostgreSQLUserStore: () => PostgreSQLUserStore,
|
|
@@ -57,6 +64,9 @@ __export(index_exports, {
|
|
|
57
64
|
PostgreSQLWorkflowTrackingStore: () => PostgreSQLWorkflowTrackingStore,
|
|
58
65
|
PostgreSQLWorkspaceStore: () => PostgreSQLWorkspaceStore,
|
|
59
66
|
PostgresSharedResourceStore: () => PostgresSharedResourceStore,
|
|
67
|
+
ProjectBotMembershipIdConflictError: () => ProjectBotMembershipIdConflictError,
|
|
68
|
+
ProjectMembershipIdConflictError: () => ProjectMembershipIdConflictError,
|
|
69
|
+
ProjectRoomMessageIdConflictError: () => ProjectRoomMessageIdConflictError,
|
|
60
70
|
ThreadMessageQueueStore: () => ThreadMessageQueueStore,
|
|
61
71
|
addAssistantTenantId: () => addAssistantTenantId,
|
|
62
72
|
addEnvToEvalRuns: () => addEnvToEvalRuns,
|
|
@@ -94,6 +104,7 @@ __export(index_exports, {
|
|
|
94
104
|
createMetricsConfigsTable: () => createMetricsConfigsTable,
|
|
95
105
|
createPGVectorStoreProvider: () => createPGVectorStoreProvider,
|
|
96
106
|
createPgStoreConfig: () => createPgStoreConfig,
|
|
107
|
+
createProjectRoomTables: () => createProjectRoomTables,
|
|
97
108
|
createProjectsTable: () => createProjectsTable,
|
|
98
109
|
createScheduledTasksTable: () => createScheduledTasksTable,
|
|
99
110
|
createSharedResourcesTable: () => createSharedResourcesTable,
|
|
@@ -111,10 +122,10 @@ __export(index_exports, {
|
|
|
111
122
|
safeParse: () => safeParse
|
|
112
123
|
});
|
|
113
124
|
module.exports = __toCommonJS(index_exports);
|
|
114
|
-
var
|
|
125
|
+
var import_pg29 = require("pg");
|
|
115
126
|
|
|
116
127
|
// src/createPgStoreConfig.ts
|
|
117
|
-
var
|
|
128
|
+
var import_pg26 = require("pg");
|
|
118
129
|
|
|
119
130
|
// src/migrations/migration.ts
|
|
120
131
|
var MigrationManager = class {
|
|
@@ -145,7 +156,8 @@ var MigrationManager = class {
|
|
|
145
156
|
const tableExists = await client.query(`
|
|
146
157
|
SELECT EXISTS (
|
|
147
158
|
SELECT FROM information_schema.tables
|
|
148
|
-
WHERE
|
|
159
|
+
WHERE table_schema = current_schema()
|
|
160
|
+
AND table_name = 'lattice_schema_migrations'
|
|
149
161
|
)
|
|
150
162
|
`);
|
|
151
163
|
if (!tableExists.rows[0].exists) {
|
|
@@ -5142,6 +5154,7 @@ var PostgreSQLEvalStore = class {
|
|
|
5142
5154
|
// src/stores/ThreadMessageQueueStore.ts
|
|
5143
5155
|
var import_pg13 = require("pg");
|
|
5144
5156
|
var import_crypto = __toESM(require("crypto"));
|
|
5157
|
+
var import_protocols2 = require("@axiom-lattice/protocols");
|
|
5145
5158
|
|
|
5146
5159
|
// src/migrations/thread_message_queue_migrations.ts
|
|
5147
5160
|
var createThreadMessageQueueTable = {
|
|
@@ -5278,6 +5291,23 @@ var addWorkspaceProjectToQueue = {
|
|
|
5278
5291
|
}
|
|
5279
5292
|
};
|
|
5280
5293
|
|
|
5294
|
+
// src/migrations/add_trusted_run_context_column.ts
|
|
5295
|
+
var addTrustedRunContextColumn = {
|
|
5296
|
+
version: 173,
|
|
5297
|
+
name: "add_thread_queue_trusted_run_context",
|
|
5298
|
+
up: async (client) => {
|
|
5299
|
+
await client.query(`ALTER TABLE lattice_thread_message_queue
|
|
5300
|
+
ADD COLUMN IF NOT EXISTS trusted_run_context JSONB,
|
|
5301
|
+
ADD COLUMN IF NOT EXISTS execution_mode VARCHAR(20)
|
|
5302
|
+
CHECK (execution_mode IS NULL OR execution_mode = 'followup')`);
|
|
5303
|
+
},
|
|
5304
|
+
down: async (client) => {
|
|
5305
|
+
await client.query(`ALTER TABLE lattice_thread_message_queue
|
|
5306
|
+
DROP COLUMN IF EXISTS execution_mode,
|
|
5307
|
+
DROP COLUMN IF EXISTS trusted_run_context`);
|
|
5308
|
+
}
|
|
5309
|
+
};
|
|
5310
|
+
|
|
5281
5311
|
// src/stores/ThreadMessageQueueStore.ts
|
|
5282
5312
|
var ThreadMessageQueueStore = class {
|
|
5283
5313
|
constructor(options) {
|
|
@@ -5303,6 +5333,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5303
5333
|
this.migrationManager.register(addCustomRunConfigColumn);
|
|
5304
5334
|
this.migrationManager.register(alterMessageQueueIdColumn);
|
|
5305
5335
|
this.migrationManager.register(addWorkspaceProjectToQueue);
|
|
5336
|
+
this.migrationManager.register(addTrustedRunContextColumn);
|
|
5306
5337
|
if (options.autoMigrate !== false) {
|
|
5307
5338
|
this.initialize().catch((error) => {
|
|
5308
5339
|
console.error("Failed to initialize ThreadMessageQueueStore:", error);
|
|
@@ -5332,6 +5363,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5332
5363
|
* Add message to queue
|
|
5333
5364
|
*/
|
|
5334
5365
|
async addMessage(params) {
|
|
5366
|
+
const trusted = validateQueueTrust(params);
|
|
5335
5367
|
const { threadId, tenantId, assistantId, workspaceId, projectId, content, type = "human", priority = 0, command, custom_run_config, id } = params;
|
|
5336
5368
|
const seqResult = await this.pool.query(
|
|
5337
5369
|
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq
|
|
@@ -5342,14 +5374,15 @@ var ThreadMessageQueueStore = class {
|
|
|
5342
5374
|
const nextSeq = seqResult.rows[0].next_seq;
|
|
5343
5375
|
const result = await this.pool.query(
|
|
5344
5376
|
`INSERT INTO lattice_thread_message_queue
|
|
5345
|
-
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5346
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
5377
|
+
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode)
|
|
5378
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
|
5347
5379
|
RETURNING *`,
|
|
5348
|
-
[id || import_crypto.default.randomUUID(), threadId, tenantId, assistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, priority, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null]
|
|
5380
|
+
[id || import_crypto.default.randomUUID(), threadId, tenantId, assistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, priority, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null, trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null, trusted.executionMode ?? null]
|
|
5349
5381
|
);
|
|
5350
5382
|
return this.rowToMessage(result.rows[0]);
|
|
5351
5383
|
}
|
|
5352
5384
|
async addMessageIfCapacity(params, maxSize) {
|
|
5385
|
+
const trusted = validateQueueTrust(params);
|
|
5353
5386
|
if (maxSize === Infinity) {
|
|
5354
5387
|
await this.addMessage(params);
|
|
5355
5388
|
return true;
|
|
@@ -5374,9 +5407,9 @@ var ThreadMessageQueueStore = class {
|
|
|
5374
5407
|
[params.threadId]
|
|
5375
5408
|
);
|
|
5376
5409
|
const result = await client.query(
|
|
5377
|
-
`INSERT INTO lattice_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5378
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`,
|
|
5379
|
-
[params.id || import_crypto.default.randomUUID(), params.threadId, params.tenantId, params.assistantId, params.workspaceId ?? null, params.projectId ?? null, JSON.stringify(params.content), params.type || "human", seq.rows[0].next_seq, params.priority ?? 0, params.command ? JSON.stringify(params.command) : null, params.custom_run_config ? JSON.stringify(params.custom_run_config) : null]
|
|
5410
|
+
`INSERT INTO lattice_thread_message_queue (id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode)
|
|
5411
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14) RETURNING *`,
|
|
5412
|
+
[params.id || import_crypto.default.randomUUID(), params.threadId, params.tenantId, params.assistantId, params.workspaceId ?? null, params.projectId ?? null, JSON.stringify(params.content), params.type || "human", seq.rows[0].next_seq, params.priority ?? 0, params.command ? JSON.stringify(params.command) : null, params.custom_run_config ? JSON.stringify(params.custom_run_config) : null, trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null, trusted.executionMode ?? null]
|
|
5380
5413
|
);
|
|
5381
5414
|
await client.query("COMMIT");
|
|
5382
5415
|
return Boolean(result.rows[0]);
|
|
@@ -5392,6 +5425,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5392
5425
|
* Uses priority=100 to ensure message is processed first
|
|
5393
5426
|
*/
|
|
5394
5427
|
async addMessageAtHead(params) {
|
|
5428
|
+
const trusted = validateQueueTrust(params);
|
|
5395
5429
|
const { threadId, tenantId, assistantId, workspaceId, projectId, content, type = "human", command, custom_run_config, id } = params;
|
|
5396
5430
|
const resolvedTenantId = tenantId;
|
|
5397
5431
|
const resolvedAssistantId = assistantId;
|
|
@@ -5404,10 +5438,10 @@ var ThreadMessageQueueStore = class {
|
|
|
5404
5438
|
const nextSeq = seqResult.rows[0].next_seq;
|
|
5405
5439
|
const result = await this.pool.query(
|
|
5406
5440
|
`INSERT INTO lattice_thread_message_queue
|
|
5407
|
-
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5408
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 100, $10, $11)
|
|
5441
|
+
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config, trusted_run_context, execution_mode)
|
|
5442
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, 100, $10, $11, $12, $13)
|
|
5409
5443
|
RETURNING *`,
|
|
5410
|
-
[id || import_crypto.default.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null]
|
|
5444
|
+
[id || import_crypto.default.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId ?? null, projectId ?? null, JSON.stringify(content), type, nextSeq, command ? JSON.stringify(command) : null, custom_run_config ? JSON.stringify(custom_run_config) : null, trusted.trustedRunContext ? JSON.stringify(trusted.trustedRunContext) : null, trusted.executionMode ?? null]
|
|
5411
5445
|
);
|
|
5412
5446
|
return this.rowToMessage(result.rows[0]);
|
|
5413
5447
|
}
|
|
@@ -5530,10 +5564,18 @@ var ThreadMessageQueueStore = class {
|
|
|
5530
5564
|
createdAt: new Date(row.created_at),
|
|
5531
5565
|
priority: row.priority || 0,
|
|
5532
5566
|
command: row.command ? typeof row.command === "string" ? JSON.parse(row.command) : row.command : void 0,
|
|
5533
|
-
custom_run_config: row.custom_run_config ? typeof row.custom_run_config === "string" ? JSON.parse(row.custom_run_config) : row.custom_run_config : void 0
|
|
5567
|
+
custom_run_config: row.custom_run_config ? typeof row.custom_run_config === "string" ? JSON.parse(row.custom_run_config) : row.custom_run_config : void 0,
|
|
5568
|
+
trusted_run_context: row.trusted_run_context ? (0, import_protocols2.parseTrustedRunContext)(typeof row.trusted_run_context === "string" ? JSON.parse(row.trusted_run_context) : row.trusted_run_context) : void 0,
|
|
5569
|
+
execution_mode: row.execution_mode == null ? void 0 : (0, import_protocols2.parseQueuedExecutionMode)(row.execution_mode)
|
|
5534
5570
|
};
|
|
5535
5571
|
}
|
|
5536
5572
|
};
|
|
5573
|
+
function validateQueueTrust(params) {
|
|
5574
|
+
return {
|
|
5575
|
+
trustedRunContext: params.trusted_run_context === void 0 ? void 0 : (0, import_protocols2.parseTrustedRunContext)(params.trusted_run_context),
|
|
5576
|
+
executionMode: params.execution_mode === void 0 ? void 0 : (0, import_protocols2.parseQueuedExecutionMode)(params.execution_mode)
|
|
5577
|
+
};
|
|
5578
|
+
}
|
|
5537
5579
|
function scopeClause(scope, start) {
|
|
5538
5580
|
if (!scope) return { sql: "", params: [] };
|
|
5539
5581
|
const params = [];
|
|
@@ -5549,6 +5591,7 @@ function scopeClause(scope, start) {
|
|
|
5549
5591
|
|
|
5550
5592
|
// src/stores/ChannelBindingStore.ts
|
|
5551
5593
|
var import_pg14 = require("pg");
|
|
5594
|
+
var import_protocols3 = require("@axiom-lattice/protocols");
|
|
5552
5595
|
|
|
5553
5596
|
// src/migrations/channel_bindings_migration.ts
|
|
5554
5597
|
var createChannelBindingsTable = {
|
|
@@ -5596,6 +5639,7 @@ var createChannelBindingsTable = {
|
|
|
5596
5639
|
};
|
|
5597
5640
|
|
|
5598
5641
|
// src/stores/ChannelBindingStore.ts
|
|
5642
|
+
var BINDING_SUBJECT_CONSTRAINT = "lattice_channel_bindings_channel_channel_installation_id_te_key";
|
|
5599
5643
|
var ChannelBindingStore = class {
|
|
5600
5644
|
constructor(options) {
|
|
5601
5645
|
this.initialized = false;
|
|
@@ -5652,80 +5696,91 @@ var ChannelBindingStore = class {
|
|
|
5652
5696
|
if (result.rows.length === 0) return null;
|
|
5653
5697
|
return this.mapRowToBinding(result.rows[0]);
|
|
5654
5698
|
}
|
|
5655
|
-
async
|
|
5699
|
+
async findById(tenantId, id) {
|
|
5656
5700
|
await this.ensureInitialized();
|
|
5657
5701
|
const result = await this.pool.query(
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
thread_mode, sender_display_name, sender_metadata, workspace_id, project_id)
|
|
5661
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
5662
|
-
RETURNING *`,
|
|
5663
|
-
[
|
|
5664
|
-
input.channel,
|
|
5665
|
-
input.channelInstallationId,
|
|
5666
|
-
input.tenantId,
|
|
5667
|
-
input.senderId,
|
|
5668
|
-
input.agentId,
|
|
5669
|
-
input.threadMode || "fixed",
|
|
5670
|
-
input.senderDisplayName || null,
|
|
5671
|
-
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
5672
|
-
input.workspaceId || null,
|
|
5673
|
-
input.projectId || null
|
|
5674
|
-
]
|
|
5702
|
+
"SELECT * FROM lattice_channel_bindings WHERE id = $1 AND tenant_id = $2",
|
|
5703
|
+
[id, tenantId]
|
|
5675
5704
|
);
|
|
5676
|
-
return this.mapRowToBinding(result.rows[0]);
|
|
5705
|
+
return result.rows[0] ? this.mapRowToBinding(result.rows[0]) : null;
|
|
5677
5706
|
}
|
|
5678
|
-
async
|
|
5707
|
+
async findBySubject(params) {
|
|
5679
5708
|
await this.ensureInitialized();
|
|
5680
|
-
const
|
|
5681
|
-
`SELECT * FROM lattice_channel_bindings
|
|
5682
|
-
|
|
5709
|
+
const result = await this.pool.query(
|
|
5710
|
+
`SELECT * FROM lattice_channel_bindings
|
|
5711
|
+
WHERE tenant_id = $1 AND channel = $2 AND channel_installation_id = $3 AND sender_id = $4 LIMIT 1`,
|
|
5712
|
+
[params.tenantId, params.channel, params.channelInstallationId, params.senderId]
|
|
5683
5713
|
);
|
|
5684
|
-
|
|
5685
|
-
|
|
5714
|
+
return result.rows[0] ? this.mapRowToBinding(result.rows[0]) : null;
|
|
5715
|
+
}
|
|
5716
|
+
async create(input) {
|
|
5717
|
+
await this.ensureInitialized();
|
|
5718
|
+
try {
|
|
5719
|
+
const result = await this.pool.query(
|
|
5720
|
+
`INSERT INTO lattice_channel_bindings
|
|
5721
|
+
(channel, channel_installation_id, tenant_id, sender_id, agent_id,
|
|
5722
|
+
thread_id, thread_mode, sender_display_name, sender_metadata, workspace_id, project_id, enabled)
|
|
5723
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
5724
|
+
RETURNING *`,
|
|
5725
|
+
[
|
|
5726
|
+
input.channel,
|
|
5727
|
+
input.channelInstallationId,
|
|
5728
|
+
input.tenantId,
|
|
5729
|
+
input.senderId,
|
|
5730
|
+
input.agentId,
|
|
5731
|
+
input.threadId || null,
|
|
5732
|
+
input.threadMode || "fixed",
|
|
5733
|
+
input.senderDisplayName || null,
|
|
5734
|
+
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
5735
|
+
input.workspaceId || null,
|
|
5736
|
+
input.projectId || null,
|
|
5737
|
+
input.enabled ?? true
|
|
5738
|
+
]
|
|
5739
|
+
);
|
|
5740
|
+
return this.mapRowToBinding(result.rows[0]);
|
|
5741
|
+
} catch (error) {
|
|
5742
|
+
if (isBindingSubjectConflict(error)) {
|
|
5743
|
+
throw new import_protocols3.DuplicateChannelBindingSubjectError();
|
|
5744
|
+
}
|
|
5745
|
+
throw error;
|
|
5686
5746
|
}
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
channel_installation_id: patch.channelInstallationId ?? row.channel_installation_id,
|
|
5691
|
-
sender_id: patch.senderId ?? row.sender_id,
|
|
5692
|
-
agent_id: patch.agentId ?? row.agent_id,
|
|
5693
|
-
thread_id: patch.threadId !== void 0 ? patch.threadId : row.thread_id,
|
|
5694
|
-
workspace_id: patch.workspaceId !== void 0 ? patch.workspaceId : row.workspace_id,
|
|
5695
|
-
project_id: patch.projectId !== void 0 ? patch.projectId : row.project_id,
|
|
5696
|
-
thread_mode: patch.threadMode ?? row.thread_mode,
|
|
5697
|
-
sender_display_name: patch.senderDisplayName !== void 0 ? patch.senderDisplayName : row.sender_display_name,
|
|
5698
|
-
sender_metadata: patch.senderMetadata !== void 0 ? patch.senderMetadata : row.sender_metadata,
|
|
5699
|
-
enabled: patch.enabled ?? row.enabled
|
|
5700
|
-
};
|
|
5747
|
+
}
|
|
5748
|
+
async update(tenantId, id, patch) {
|
|
5749
|
+
await this.ensureInitialized();
|
|
5701
5750
|
const result = await this.pool.query(
|
|
5702
5751
|
`UPDATE lattice_channel_bindings SET
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5707
|
-
|
|
5752
|
+
agent_id = COALESCE($1, agent_id),
|
|
5753
|
+
thread_id = COALESCE($2, thread_id),
|
|
5754
|
+
workspace_id = COALESCE($3, workspace_id),
|
|
5755
|
+
project_id = COALESCE($4, project_id),
|
|
5756
|
+
thread_mode = COALESCE($5, thread_mode),
|
|
5757
|
+
sender_display_name = COALESCE($6, sender_display_name),
|
|
5758
|
+
sender_metadata = COALESCE($7, sender_metadata),
|
|
5759
|
+
enabled = COALESCE($8, enabled), updated_at = NOW()
|
|
5760
|
+
WHERE id = $9 AND tenant_id = $10
|
|
5708
5761
|
RETURNING *`,
|
|
5709
5762
|
[
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
updated.enabled,
|
|
5721
|
-
id
|
|
5763
|
+
patch.agentId ?? null,
|
|
5764
|
+
patch.threadId ?? null,
|
|
5765
|
+
patch.workspaceId ?? null,
|
|
5766
|
+
patch.projectId ?? null,
|
|
5767
|
+
patch.threadMode ?? null,
|
|
5768
|
+
patch.senderDisplayName ?? null,
|
|
5769
|
+
patch.senderMetadata ? JSON.stringify(patch.senderMetadata) : null,
|
|
5770
|
+
patch.enabled ?? null,
|
|
5771
|
+
id,
|
|
5772
|
+
tenantId
|
|
5722
5773
|
]
|
|
5723
5774
|
);
|
|
5775
|
+
if (!result.rows[0]) throw new Error(`Binding ${id} not found`);
|
|
5724
5776
|
return this.mapRowToBinding(result.rows[0]);
|
|
5725
5777
|
}
|
|
5726
|
-
async delete(id) {
|
|
5778
|
+
async delete(tenantId, id) {
|
|
5727
5779
|
await this.ensureInitialized();
|
|
5728
|
-
await this.pool.query(
|
|
5780
|
+
await this.pool.query(
|
|
5781
|
+
`DELETE FROM lattice_channel_bindings WHERE id = $1 AND tenant_id = $2`,
|
|
5782
|
+
[id, tenantId]
|
|
5783
|
+
);
|
|
5729
5784
|
}
|
|
5730
5785
|
async list(params) {
|
|
5731
5786
|
await this.ensureInitialized();
|
|
@@ -5744,6 +5799,14 @@ var ChannelBindingStore = class {
|
|
|
5744
5799
|
conditions.push(`channel_installation_id = $${idx++}`);
|
|
5745
5800
|
values.push(params.channelInstallationId);
|
|
5746
5801
|
}
|
|
5802
|
+
if (params.excludeChannels?.length) {
|
|
5803
|
+
conditions.push(`channel <> ALL($${idx++}::text[])`);
|
|
5804
|
+
values.push(params.excludeChannels);
|
|
5805
|
+
}
|
|
5806
|
+
for (const prefix of params.excludeInstallationIdPrefixes ?? []) {
|
|
5807
|
+
conditions.push(`channel_installation_id NOT LIKE $${idx++} ESCAPE '\\'`);
|
|
5808
|
+
values.push(`${escapeLikePattern(prefix)}%`);
|
|
5809
|
+
}
|
|
5747
5810
|
const limit = params.limit ?? 50;
|
|
5748
5811
|
const offset = params.offset ?? 0;
|
|
5749
5812
|
values.push(limit, offset);
|
|
@@ -5756,7 +5819,16 @@ var ChannelBindingStore = class {
|
|
|
5756
5819
|
);
|
|
5757
5820
|
return result.rows.map((r) => this.mapRowToBinding(r));
|
|
5758
5821
|
}
|
|
5759
|
-
async import(bindings) {
|
|
5822
|
+
async import(tenantId, bindings) {
|
|
5823
|
+
if (bindings.some((binding) => binding.channel === "room")) {
|
|
5824
|
+
throw new Error("Room bindings cannot be imported through the public store API");
|
|
5825
|
+
}
|
|
5826
|
+
if (bindings.some((binding) => binding.channelInstallationId.startsWith("room-internal:"))) {
|
|
5827
|
+
throw new Error("Internal bindings cannot be imported through the public store API");
|
|
5828
|
+
}
|
|
5829
|
+
if (bindings.some((binding) => binding.tenantId !== tenantId)) {
|
|
5830
|
+
throw new Error("Binding import tenant mismatch");
|
|
5831
|
+
}
|
|
5760
5832
|
const result = [];
|
|
5761
5833
|
for (const input of bindings) {
|
|
5762
5834
|
result.push(await this.create(input));
|
|
@@ -5764,7 +5836,13 @@ var ChannelBindingStore = class {
|
|
|
5764
5836
|
return result;
|
|
5765
5837
|
}
|
|
5766
5838
|
async export(params) {
|
|
5767
|
-
return this.list({
|
|
5839
|
+
return this.list({
|
|
5840
|
+
tenantId: params.tenantId,
|
|
5841
|
+
excludeChannels: ["room"],
|
|
5842
|
+
excludeInstallationIdPrefixes: ["room-internal:"],
|
|
5843
|
+
limit: 1e4,
|
|
5844
|
+
offset: 0
|
|
5845
|
+
});
|
|
5768
5846
|
}
|
|
5769
5847
|
async ensureInitialized() {
|
|
5770
5848
|
if (!this.initialized) {
|
|
@@ -5791,6 +5869,14 @@ var ChannelBindingStore = class {
|
|
|
5791
5869
|
};
|
|
5792
5870
|
}
|
|
5793
5871
|
};
|
|
5872
|
+
function isBindingSubjectConflict(error) {
|
|
5873
|
+
if (typeof error !== "object" || error === null) return false;
|
|
5874
|
+
const pgError = error;
|
|
5875
|
+
return pgError.code === "23505" && pgError.constraint === BINDING_SUBJECT_CONSTRAINT;
|
|
5876
|
+
}
|
|
5877
|
+
function escapeLikePattern(value) {
|
|
5878
|
+
return value.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
|
|
5879
|
+
}
|
|
5794
5880
|
|
|
5795
5881
|
// src/stores/PostgreSQLChannelInstallationStore.ts
|
|
5796
5882
|
var import_pg15 = require("pg");
|
|
@@ -5965,7 +6051,8 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5965
6051
|
async createInstallation(tenantId, installationId, data) {
|
|
5966
6052
|
await this.ensureInitialized();
|
|
5967
6053
|
const now = /* @__PURE__ */ new Date();
|
|
5968
|
-
const
|
|
6054
|
+
const config = { ...data.config };
|
|
6055
|
+
const encryptedConfig = this.encryptSecrets(config);
|
|
5969
6056
|
await this.pool.query(
|
|
5970
6057
|
`
|
|
5971
6058
|
INSERT INTO lattice_channel_installations (
|
|
@@ -5990,7 +6077,7 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5990
6077
|
tenantId,
|
|
5991
6078
|
channel: data.channel,
|
|
5992
6079
|
name: data.name,
|
|
5993
|
-
config
|
|
6080
|
+
config,
|
|
5994
6081
|
enabled: data.enabled ?? true,
|
|
5995
6082
|
fallbackAgentId: data.fallbackAgentId,
|
|
5996
6083
|
rejectWhenNoBinding: data.rejectWhenNoBinding ?? true,
|
|
@@ -6077,17 +6164,17 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
6077
6164
|
encryptSecrets(config) {
|
|
6078
6165
|
return {
|
|
6079
6166
|
...config,
|
|
6080
|
-
|
|
6081
|
-
|
|
6082
|
-
|
|
6167
|
+
...typeof config.appSecret === "string" ? { appSecret: (0, import_core4.encrypt)(config.appSecret) } : {},
|
|
6168
|
+
...typeof config.verificationToken === "string" ? { verificationToken: (0, import_core4.encrypt)(config.verificationToken) } : {},
|
|
6169
|
+
...typeof config.encryptKey === "string" ? { encryptKey: (0, import_core4.encrypt)(config.encryptKey) } : {}
|
|
6083
6170
|
};
|
|
6084
6171
|
}
|
|
6085
6172
|
decryptSecrets(config) {
|
|
6086
6173
|
return {
|
|
6087
6174
|
...config,
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6175
|
+
...typeof config.appSecret === "string" ? { appSecret: (0, import_core4.decrypt)(config.appSecret) } : {},
|
|
6176
|
+
...typeof config.verificationToken === "string" ? { verificationToken: (0, import_core4.decrypt)(config.verificationToken) } : {},
|
|
6177
|
+
...typeof config.encryptKey === "string" ? { encryptKey: (0, import_core4.decrypt)(config.encryptKey) } : {}
|
|
6091
6178
|
};
|
|
6092
6179
|
}
|
|
6093
6180
|
};
|
|
@@ -6536,7 +6623,7 @@ var PostgreSQLAgentWebAppStore = class {
|
|
|
6536
6623
|
|
|
6537
6624
|
// src/stores/PostgreSQLScheduleStorage.ts
|
|
6538
6625
|
var import_pg18 = require("pg");
|
|
6539
|
-
var
|
|
6626
|
+
var import_protocols4 = require("@axiom-lattice/protocols");
|
|
6540
6627
|
|
|
6541
6628
|
// src/migrations/schedule_migrations.ts
|
|
6542
6629
|
var createScheduledTasksTable = {
|
|
@@ -6950,7 +7037,7 @@ var PostgreSQLScheduleStorage = class {
|
|
|
6950
7037
|
await this.ensureInitialized();
|
|
6951
7038
|
const result = await this.pool.query(
|
|
6952
7039
|
`SELECT * FROM lattice_scheduled_tasks WHERE status IN ($1, $2) ORDER BY created_at ASC`,
|
|
6953
|
-
[
|
|
7040
|
+
[import_protocols4.ScheduledTaskStatus.PENDING, import_protocols4.ScheduledTaskStatus.PAUSED]
|
|
6954
7041
|
);
|
|
6955
7042
|
return result.rows.map((row) => this.mapRowToTask(row));
|
|
6956
7043
|
}
|
|
@@ -7109,9 +7196,9 @@ var PostgreSQLScheduleStorage = class {
|
|
|
7109
7196
|
AND updated_at < $4
|
|
7110
7197
|
`,
|
|
7111
7198
|
[
|
|
7112
|
-
|
|
7113
|
-
|
|
7114
|
-
|
|
7199
|
+
import_protocols4.ScheduledTaskStatus.COMPLETED,
|
|
7200
|
+
import_protocols4.ScheduledTaskStatus.CANCELLED,
|
|
7201
|
+
import_protocols4.ScheduledTaskStatus.FAILED,
|
|
7115
7202
|
cutoff
|
|
7116
7203
|
]
|
|
7117
7204
|
);
|
|
@@ -7240,15 +7327,37 @@ var addFilesToTasks = {
|
|
|
7240
7327
|
`);
|
|
7241
7328
|
}
|
|
7242
7329
|
};
|
|
7330
|
+
var addTaskDependenciesGinIndex = {
|
|
7331
|
+
version: 174,
|
|
7332
|
+
name: "add_task_dependencies_gin_index",
|
|
7333
|
+
up: async (client) => {
|
|
7334
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_lattice_tasks_dependencies_gin
|
|
7335
|
+
ON lattice_tasks USING GIN (dependencies jsonb_path_ops)
|
|
7336
|
+
WHERE dependencies IS NOT NULL`);
|
|
7337
|
+
},
|
|
7338
|
+
down: async (client) => {
|
|
7339
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_tasks_dependencies_gin");
|
|
7340
|
+
}
|
|
7341
|
+
};
|
|
7243
7342
|
var taskMigrations = [
|
|
7244
7343
|
createTasksTable,
|
|
7245
7344
|
addTaskFieldsMigration,
|
|
7246
7345
|
addTaskProjectFieldsMigration,
|
|
7247
|
-
addFilesToTasks
|
|
7346
|
+
addFilesToTasks,
|
|
7347
|
+
addTaskDependenciesGinIndex
|
|
7248
7348
|
];
|
|
7249
7349
|
|
|
7250
7350
|
// src/stores/PostgreSQLTaskStore.ts
|
|
7251
7351
|
var import_uuid3 = require("uuid");
|
|
7352
|
+
var TASK_STATUSES = /* @__PURE__ */ new Set([
|
|
7353
|
+
"pending",
|
|
7354
|
+
"in_progress",
|
|
7355
|
+
"review",
|
|
7356
|
+
"failed",
|
|
7357
|
+
"interrupted",
|
|
7358
|
+
"completed",
|
|
7359
|
+
"cancelled"
|
|
7360
|
+
]);
|
|
7252
7361
|
function nextUpdatedAtSql(column = "updated_at") {
|
|
7253
7362
|
return `to_char(
|
|
7254
7363
|
date_trunc('milliseconds', GREATEST(
|
|
@@ -7328,6 +7437,9 @@ function mapRowToTask(row) {
|
|
|
7328
7437
|
updatedAt: new Date(row.updated_at)
|
|
7329
7438
|
};
|
|
7330
7439
|
}
|
|
7440
|
+
function assertPage(limit, offset = 0) {
|
|
7441
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100 || !Number.isSafeInteger(offset) || offset < 0) throw new RangeError("Invalid task page");
|
|
7442
|
+
}
|
|
7331
7443
|
var PostgreSQLTaskStore = class {
|
|
7332
7444
|
constructor(options) {
|
|
7333
7445
|
this.initialized = false;
|
|
@@ -7351,6 +7463,7 @@ var PostgreSQLTaskStore = class {
|
|
|
7351
7463
|
this.migrationManager.register(addTaskFieldsMigration);
|
|
7352
7464
|
this.migrationManager.register(addTaskProjectFieldsMigration);
|
|
7353
7465
|
this.migrationManager.register(addFilesToTasks);
|
|
7466
|
+
this.migrationManager.register(addTaskDependenciesGinIndex);
|
|
7354
7467
|
if (options.autoMigrate !== false) {
|
|
7355
7468
|
this.initialize().catch((error) => {
|
|
7356
7469
|
console.error("Failed to initialize PostgreSQLTaskStore:", error);
|
|
@@ -7446,7 +7559,9 @@ var PostgreSQLTaskStore = class {
|
|
|
7446
7559
|
conditions.push(`workspace_id = $${paramIndex++}`);
|
|
7447
7560
|
params.push(filter.workspaceId);
|
|
7448
7561
|
}
|
|
7449
|
-
if (filter.projectId) {
|
|
7562
|
+
if (filter.projectId === null) {
|
|
7563
|
+
conditions.push("(project_id IS NULL OR project_id = '' OR project_id = 'default')");
|
|
7564
|
+
} else if (filter.projectId !== void 0) {
|
|
7450
7565
|
conditions.push(`project_id = $${paramIndex++}`);
|
|
7451
7566
|
params.push(filter.projectId);
|
|
7452
7567
|
}
|
|
@@ -7471,11 +7586,35 @@ var PostgreSQLTaskStore = class {
|
|
|
7471
7586
|
const limit = filter.limit || 100;
|
|
7472
7587
|
const offset = filter.offset || 0;
|
|
7473
7588
|
const result = await this.pool.query(
|
|
7474
|
-
`SELECT * FROM lattice_tasks WHERE ${where} ORDER BY created_at DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
|
|
7589
|
+
`SELECT * FROM lattice_tasks WHERE ${where} ORDER BY created_at DESC, id DESC LIMIT $${paramIndex++} OFFSET $${paramIndex++}`,
|
|
7475
7590
|
[...params, limit, offset]
|
|
7476
7591
|
);
|
|
7477
7592
|
return result.rows.map((r) => mapRowToTask(r));
|
|
7478
7593
|
}
|
|
7594
|
+
/** Lists exact project tasks containing a JSON string dependency. */
|
|
7595
|
+
async listDependents(query) {
|
|
7596
|
+
assertPage(query.limit, query.offset);
|
|
7597
|
+
if (query.statuses.length === 0 || query.statuses.some((status) => !TASK_STATUSES.has(status))) {
|
|
7598
|
+
throw new RangeError("Invalid task statuses");
|
|
7599
|
+
}
|
|
7600
|
+
await this.ensureInitialized();
|
|
7601
|
+
const result = await this.pool.query(
|
|
7602
|
+
`SELECT * FROM lattice_tasks
|
|
7603
|
+
WHERE tenant_id=$1 AND workspace_id=$2 AND project_id=$3
|
|
7604
|
+
AND dependencies @> $4::jsonb AND status = ANY($5::text[])
|
|
7605
|
+
ORDER BY created_at DESC, id DESC LIMIT $6 OFFSET $7`,
|
|
7606
|
+
[
|
|
7607
|
+
query.tenantId,
|
|
7608
|
+
query.workspaceId,
|
|
7609
|
+
query.projectId,
|
|
7610
|
+
JSON.stringify([query.dependencyTaskId]),
|
|
7611
|
+
query.statuses,
|
|
7612
|
+
query.limit,
|
|
7613
|
+
query.offset
|
|
7614
|
+
]
|
|
7615
|
+
);
|
|
7616
|
+
return result.rows.map(mapRowToTask);
|
|
7617
|
+
}
|
|
7479
7618
|
async update(tenantId, id, updates) {
|
|
7480
7619
|
await this.ensureInitialized();
|
|
7481
7620
|
const existing = await this.getById(tenantId, id);
|
|
@@ -7846,6 +7985,63 @@ var PostgreSQLTaskStore = class {
|
|
|
7846
7985
|
);
|
|
7847
7986
|
return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
|
|
7848
7987
|
}
|
|
7988
|
+
/** Atomically update only while status, timestamp, owner, and Project scope match. */
|
|
7989
|
+
async updateIfSnapshot(tenantId, id, updates, snapshot) {
|
|
7990
|
+
await this.ensureInitialized();
|
|
7991
|
+
const setClauses = [];
|
|
7992
|
+
const params = [];
|
|
7993
|
+
let index = 1;
|
|
7994
|
+
const fields = [
|
|
7995
|
+
["title", "title"],
|
|
7996
|
+
["description", "description"],
|
|
7997
|
+
["status", "status"],
|
|
7998
|
+
["priority", "priority"],
|
|
7999
|
+
["dueDate", "due_date"],
|
|
8000
|
+
["metadata", "metadata", true],
|
|
8001
|
+
["files", "files", true],
|
|
8002
|
+
["parentId", "parent_id"],
|
|
8003
|
+
["sourceId", "source_id"],
|
|
8004
|
+
["context", "context", true],
|
|
8005
|
+
["ownerType", "owner_type"],
|
|
8006
|
+
["ownerId", "owner_id"],
|
|
8007
|
+
["requireReview", "require_review"],
|
|
8008
|
+
["dependencies", "dependencies", true],
|
|
8009
|
+
["result", "result"],
|
|
8010
|
+
["failureReason", "failure_reason"],
|
|
8011
|
+
["workspaceId", "workspace_id"],
|
|
8012
|
+
["projectId", "project_id"]
|
|
8013
|
+
];
|
|
8014
|
+
for (const [field, column, json] of fields) {
|
|
8015
|
+
const value = updates[field];
|
|
8016
|
+
if (value === void 0) continue;
|
|
8017
|
+
setClauses.push(`${column} = $${index++}`);
|
|
8018
|
+
params.push(json && value !== null ? JSON.stringify(value) : value);
|
|
8019
|
+
}
|
|
8020
|
+
if (setClauses.length === 0) return this.getById(tenantId, id);
|
|
8021
|
+
setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
|
|
8022
|
+
const predicates = [
|
|
8023
|
+
tenantId,
|
|
8024
|
+
id,
|
|
8025
|
+
snapshot.status,
|
|
8026
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
8027
|
+
snapshot.ownerType,
|
|
8028
|
+
snapshot.ownerId,
|
|
8029
|
+
snapshot.workspaceId,
|
|
8030
|
+
snapshot.projectId
|
|
8031
|
+
];
|
|
8032
|
+
const placeholders = predicates.map(() => `$${index++}`);
|
|
8033
|
+
params.push(...predicates);
|
|
8034
|
+
const result = await this.pool.query(
|
|
8035
|
+
`UPDATE lattice_tasks SET ${setClauses.join(", ")}
|
|
8036
|
+
WHERE tenant_id = ${placeholders[0]} AND id = ${placeholders[1]} AND status = ${placeholders[2]}
|
|
8037
|
+
AND ${canonicalTimestampSnapshotSql("updated_at", placeholders[3])}
|
|
8038
|
+
AND owner_type = ${placeholders[4]} AND owner_id = ${placeholders[5]}
|
|
8039
|
+
AND workspace_id IS NOT DISTINCT FROM ${placeholders[6]}
|
|
8040
|
+
AND project_id IS NOT DISTINCT FROM ${placeholders[7]} RETURNING *`,
|
|
8041
|
+
params
|
|
8042
|
+
);
|
|
8043
|
+
return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
|
|
8044
|
+
}
|
|
7849
8045
|
/** Atomically update a child only when both child and parent snapshots match. */
|
|
7850
8046
|
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
7851
8047
|
await this.ensureInitialized();
|
|
@@ -7964,6 +8160,27 @@ var PostgreSQLTaskStore = class {
|
|
|
7964
8160
|
);
|
|
7965
8161
|
return (result.rowCount ?? 0) > 0;
|
|
7966
8162
|
}
|
|
8163
|
+
/** Atomically delete only while status, timestamp, owner, and Project scope match. */
|
|
8164
|
+
async deleteIfSnapshot(tenantId, id, snapshot) {
|
|
8165
|
+
await this.ensureInitialized();
|
|
8166
|
+
const result = await this.pool.query(
|
|
8167
|
+
`DELETE FROM lattice_tasks WHERE tenant_id = $1 AND id = $2 AND status = $3
|
|
8168
|
+
AND ${canonicalTimestampSnapshotSql("updated_at", "$4")}
|
|
8169
|
+
AND owner_type = $5 AND owner_id = $6
|
|
8170
|
+
AND workspace_id IS NOT DISTINCT FROM $7 AND project_id IS NOT DISTINCT FROM $8`,
|
|
8171
|
+
[
|
|
8172
|
+
tenantId,
|
|
8173
|
+
id,
|
|
8174
|
+
snapshot.status,
|
|
8175
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
8176
|
+
snapshot.ownerType,
|
|
8177
|
+
snapshot.ownerId,
|
|
8178
|
+
snapshot.workspaceId,
|
|
8179
|
+
snapshot.projectId
|
|
8180
|
+
]
|
|
8181
|
+
);
|
|
8182
|
+
return (result.rowCount ?? 0) > 0;
|
|
8183
|
+
}
|
|
7967
8184
|
async dispose() {
|
|
7968
8185
|
if (this.ownsPool && this.pool) {
|
|
7969
8186
|
await this.pool.end();
|
|
@@ -7977,18 +8194,175 @@ var PostgreSQLTaskStore = class {
|
|
|
7977
8194
|
};
|
|
7978
8195
|
|
|
7979
8196
|
// src/stores/PostgreSQLTaskWorkItemStore.ts
|
|
7980
|
-
var
|
|
8197
|
+
var import_pg20 = require("pg");
|
|
8198
|
+
var import_protocols5 = require("@axiom-lattice/protocols");
|
|
7981
8199
|
var import_uuid4 = require("uuid");
|
|
8200
|
+
|
|
8201
|
+
// src/migrations/task_work_items_migration.ts
|
|
8202
|
+
var createTaskWorkItemsMigration = {
|
|
8203
|
+
version: 138,
|
|
8204
|
+
name: "create_task_work_items_table",
|
|
8205
|
+
up: async (client) => {
|
|
8206
|
+
await client.query(`
|
|
8207
|
+
CREATE TABLE IF NOT EXISTS lattice_task_work_items (
|
|
8208
|
+
id TEXT NOT NULL,
|
|
8209
|
+
tenant_id TEXT NOT NULL,
|
|
8210
|
+
task_id TEXT NOT NULL,
|
|
8211
|
+
action TEXT NOT NULL,
|
|
8212
|
+
actor TEXT NOT NULL,
|
|
8213
|
+
thread_id TEXT,
|
|
8214
|
+
summary TEXT,
|
|
8215
|
+
detail JSONB,
|
|
8216
|
+
attempt INTEGER,
|
|
8217
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT date_trunc('milliseconds', clock_timestamp()),
|
|
8218
|
+
PRIMARY KEY (tenant_id, id)
|
|
8219
|
+
)
|
|
8220
|
+
`);
|
|
8221
|
+
await client.query(`
|
|
8222
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_task_id
|
|
8223
|
+
ON lattice_task_work_items (tenant_id, task_id)
|
|
8224
|
+
`);
|
|
8225
|
+
await client.query(`
|
|
8226
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_action
|
|
8227
|
+
ON lattice_task_work_items (tenant_id, task_id, action)
|
|
8228
|
+
`);
|
|
8229
|
+
}
|
|
8230
|
+
};
|
|
8231
|
+
var addWorkItemProjectFieldsMigration = {
|
|
8232
|
+
version: 140,
|
|
8233
|
+
name: "add_task_work_item_project_fields",
|
|
8234
|
+
up: async (client) => {
|
|
8235
|
+
await client.query(`
|
|
8236
|
+
ALTER TABLE lattice_task_work_items
|
|
8237
|
+
ADD COLUMN IF NOT EXISTS workspace_id TEXT,
|
|
8238
|
+
ADD COLUMN IF NOT EXISTS project_id TEXT
|
|
8239
|
+
`);
|
|
8240
|
+
await client.query(`
|
|
8241
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_project
|
|
8242
|
+
ON lattice_task_work_items (tenant_id, project_id)
|
|
8243
|
+
`);
|
|
8244
|
+
}
|
|
8245
|
+
};
|
|
8246
|
+
var addTaskWorkItemEventKeyMigration = {
|
|
8247
|
+
version: 168,
|
|
8248
|
+
name: "add_task_work_item_event_key",
|
|
8249
|
+
up: async (client) => {
|
|
8250
|
+
await client.query(`
|
|
8251
|
+
ALTER TABLE lattice_task_work_items
|
|
8252
|
+
ADD COLUMN IF NOT EXISTS event_key TEXT
|
|
8253
|
+
`);
|
|
8254
|
+
await client.query(`
|
|
8255
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
|
|
8256
|
+
ON lattice_task_work_items (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
|
|
8257
|
+
`);
|
|
8258
|
+
}
|
|
8259
|
+
};
|
|
8260
|
+
var addTaskWorkItemPendingIndexesMigration = {
|
|
8261
|
+
version: 171,
|
|
8262
|
+
name: "add_task_work_item_pending_indexes",
|
|
8263
|
+
up: async (client) => {
|
|
8264
|
+
await client.query(`
|
|
8265
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_pending_order
|
|
8266
|
+
ON lattice_task_work_items (tenant_id, task_id, action, created_at DESC, id DESC)
|
|
8267
|
+
`);
|
|
8268
|
+
await client.query(`
|
|
8269
|
+
CREATE INDEX IF NOT EXISTS idx_task_work_items_reconciled_result
|
|
8270
|
+
ON lattice_task_work_items (tenant_id, task_id, (detail ->> 'executionResultId'))
|
|
8271
|
+
WHERE action = 'execution_reconciled'
|
|
8272
|
+
`);
|
|
8273
|
+
}
|
|
8274
|
+
};
|
|
8275
|
+
var addProjectLifecycleEventIndex = {
|
|
8276
|
+
version: 175,
|
|
8277
|
+
name: "add_project_lifecycle_event_index",
|
|
8278
|
+
up: async (client) => {
|
|
8279
|
+
await client.query(`UPDATE lattice_task_work_items
|
|
8280
|
+
SET created_at = date_trunc('milliseconds', created_at)
|
|
8281
|
+
WHERE created_at <> date_trunc('milliseconds', created_at)`);
|
|
8282
|
+
await client.query(`ALTER TABLE lattice_task_work_items
|
|
8283
|
+
ALTER COLUMN created_at SET DEFAULT date_trunc('milliseconds', clock_timestamp())`);
|
|
8284
|
+
await client.query(`CREATE INDEX IF NOT EXISTS idx_task_work_items_project_lifecycle
|
|
8285
|
+
ON lattice_task_work_items (tenant_id, workspace_id, project_id, created_at DESC, id DESC)
|
|
8286
|
+
WHERE event_key IS NOT NULL`);
|
|
8287
|
+
},
|
|
8288
|
+
down: async (client) => {
|
|
8289
|
+
await client.query("DROP INDEX IF EXISTS idx_task_work_items_project_lifecycle");
|
|
8290
|
+
}
|
|
8291
|
+
};
|
|
8292
|
+
var taskWorkItemMigrations = [
|
|
8293
|
+
createTaskWorkItemsMigration,
|
|
8294
|
+
addWorkItemProjectFieldsMigration,
|
|
8295
|
+
addTaskWorkItemEventKeyMigration,
|
|
8296
|
+
addTaskWorkItemPendingIndexesMigration,
|
|
8297
|
+
addProjectLifecycleEventIndex
|
|
8298
|
+
];
|
|
8299
|
+
|
|
8300
|
+
// src/stores/PostgreSQLTaskWorkItemStore.ts
|
|
8301
|
+
var PROJECT_LIFECYCLE_ACTIONS = /* @__PURE__ */ new Set([
|
|
8302
|
+
"in_progress",
|
|
8303
|
+
"interrupted",
|
|
8304
|
+
"failed",
|
|
8305
|
+
"completed",
|
|
8306
|
+
"cancelled",
|
|
8307
|
+
"reassigned"
|
|
8308
|
+
]);
|
|
8309
|
+
function isRecord2(value) {
|
|
8310
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
8311
|
+
}
|
|
8312
|
+
function isPool(value) {
|
|
8313
|
+
return typeof value.query === "function";
|
|
8314
|
+
}
|
|
7982
8315
|
var PostgreSQLTaskWorkItemStore = class {
|
|
7983
|
-
constructor(
|
|
7984
|
-
this.
|
|
8316
|
+
constructor(poolOrOptions) {
|
|
8317
|
+
this.initialized = false;
|
|
8318
|
+
this.ownsPool = false;
|
|
8319
|
+
this.initPromise = null;
|
|
8320
|
+
if (isPool(poolOrOptions)) {
|
|
8321
|
+
this.pool = poolOrOptions;
|
|
8322
|
+
this.initialized = true;
|
|
8323
|
+
return;
|
|
8324
|
+
}
|
|
8325
|
+
const options = poolOrOptions;
|
|
8326
|
+
if (options.pool) {
|
|
8327
|
+
this.pool = options.pool;
|
|
8328
|
+
this.initialized = true;
|
|
8329
|
+
return;
|
|
8330
|
+
}
|
|
8331
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg20.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg20.Pool(options.poolConfig) : (() => {
|
|
8332
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
8333
|
+
})();
|
|
8334
|
+
this.ownsPool = true;
|
|
8335
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
8336
|
+
for (const migration of taskWorkItemMigrations) this.migrationManager.register(migration);
|
|
8337
|
+
if (options.autoMigrate !== false) this.startInitialization();
|
|
8338
|
+
}
|
|
8339
|
+
/** Applies the complete standalone TaskWorkItem migration chain once. */
|
|
8340
|
+
async initialize() {
|
|
8341
|
+
if (this.initialized) return;
|
|
8342
|
+
if (this.initPromise) return this.initPromise;
|
|
8343
|
+
return this.startInitialization();
|
|
8344
|
+
}
|
|
8345
|
+
/** Closes the pool only when this store created it from connection configuration. */
|
|
8346
|
+
async dispose() {
|
|
8347
|
+
if (this.ownsPool) await this.pool.end();
|
|
8348
|
+
}
|
|
8349
|
+
startInitialization() {
|
|
8350
|
+
this.initPromise = this.migrationManager.migrate().then(() => {
|
|
8351
|
+
this.initialized = true;
|
|
8352
|
+
});
|
|
8353
|
+
void this.initPromise.catch(() => void 0);
|
|
8354
|
+
return this.initPromise;
|
|
8355
|
+
}
|
|
8356
|
+
async ensureInitialized() {
|
|
8357
|
+
if (!this.initialized) await this.initialize();
|
|
7985
8358
|
}
|
|
7986
8359
|
async create(params) {
|
|
8360
|
+
await this.ensureInitialized();
|
|
7987
8361
|
const id = (0, import_uuid4.v4)();
|
|
7988
8362
|
const result = await this.pool.query(
|
|
7989
8363
|
`INSERT INTO lattice_task_work_items
|
|
7990
|
-
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id)
|
|
7991
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
8364
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, created_at)
|
|
8365
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, date_trunc('milliseconds', clock_timestamp()))
|
|
7992
8366
|
RETURNING *`,
|
|
7993
8367
|
[
|
|
7994
8368
|
id,
|
|
@@ -8006,8 +8380,46 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
8006
8380
|
);
|
|
8007
8381
|
return this.rowToItem(result.rows[0]);
|
|
8008
8382
|
}
|
|
8383
|
+
/** Atomically inserts a work item by selecting one exact task snapshot. */
|
|
8384
|
+
async createIfTaskSnapshot(params, snapshot) {
|
|
8385
|
+
await this.ensureInitialized();
|
|
8386
|
+
const result = await this.pool.query(
|
|
8387
|
+
`INSERT INTO lattice_task_work_items
|
|
8388
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, created_at)
|
|
8389
|
+
SELECT $1, task.tenant_id, task.id, $2, $3, $4, $5, $6, $7, task.workspace_id, task.project_id,
|
|
8390
|
+
date_trunc('milliseconds', clock_timestamp())
|
|
8391
|
+
FROM lattice_tasks AS task
|
|
8392
|
+
WHERE task.tenant_id = $8 AND task.id = $9 AND task.status = $10
|
|
8393
|
+
AND date_trunc('milliseconds', task.updated_at::timestamptz) = $11::timestamptz
|
|
8394
|
+
AND task.owner_type = $12 AND task.owner_id = $13
|
|
8395
|
+
AND task.workspace_id IS NOT DISTINCT FROM $14 AND task.project_id IS NOT DISTINCT FROM $15
|
|
8396
|
+
AND $14 IS NOT DISTINCT FROM $16 AND $15 IS NOT DISTINCT FROM $17
|
|
8397
|
+
RETURNING *`,
|
|
8398
|
+
[
|
|
8399
|
+
(0, import_uuid4.v4)(),
|
|
8400
|
+
params.action,
|
|
8401
|
+
params.actor,
|
|
8402
|
+
params.threadId || null,
|
|
8403
|
+
params.summary || null,
|
|
8404
|
+
params.detail ? JSON.stringify(params.detail) : null,
|
|
8405
|
+
params.attempt ?? null,
|
|
8406
|
+
params.tenantId,
|
|
8407
|
+
params.taskId,
|
|
8408
|
+
snapshot.status,
|
|
8409
|
+
new Date(snapshot.updatedAt).toISOString(),
|
|
8410
|
+
snapshot.ownerType,
|
|
8411
|
+
snapshot.ownerId,
|
|
8412
|
+
snapshot.workspaceId,
|
|
8413
|
+
snapshot.projectId,
|
|
8414
|
+
params.workspaceId,
|
|
8415
|
+
params.projectId
|
|
8416
|
+
]
|
|
8417
|
+
);
|
|
8418
|
+
return result.rows[0] ? this.rowToItem(result.rows[0]) : null;
|
|
8419
|
+
}
|
|
8009
8420
|
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
8010
8421
|
async findByEventKey(tenantId, taskId, eventKey) {
|
|
8422
|
+
await this.ensureInitialized();
|
|
8011
8423
|
const result = await this.pool.query(
|
|
8012
8424
|
`SELECT * FROM lattice_task_work_items
|
|
8013
8425
|
WHERE tenant_id = $1 AND task_id = $2 AND event_key = $3`,
|
|
@@ -8017,10 +8429,11 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
8017
8429
|
}
|
|
8018
8430
|
/** Atomically return an existing event or create it once. */
|
|
8019
8431
|
async createIfAbsentByEventKey(params) {
|
|
8432
|
+
await this.ensureInitialized();
|
|
8020
8433
|
const result = await this.pool.query(
|
|
8021
8434
|
`INSERT INTO lattice_task_work_items
|
|
8022
|
-
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key)
|
|
8023
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
8435
|
+
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key, created_at)
|
|
8436
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, date_trunc('milliseconds', clock_timestamp()))
|
|
8024
8437
|
ON CONFLICT (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
|
|
8025
8438
|
DO UPDATE SET event_key = EXCLUDED.event_key
|
|
8026
8439
|
RETURNING *`,
|
|
@@ -8042,6 +8455,7 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
8042
8455
|
return this.rowToItem(result.rows[0]);
|
|
8043
8456
|
}
|
|
8044
8457
|
async list(filter) {
|
|
8458
|
+
await this.ensureInitialized();
|
|
8045
8459
|
let query = `SELECT * FROM lattice_task_work_items WHERE tenant_id = $1 AND task_id = $2`;
|
|
8046
8460
|
const params = [filter.tenantId, filter.taskId];
|
|
8047
8461
|
if (filter.action) {
|
|
@@ -8071,8 +8485,8 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
8071
8485
|
}
|
|
8072
8486
|
/** List pending execution results using one bounded PostgreSQL anti-join query. */
|
|
8073
8487
|
async listPendingExecutionResults(params) {
|
|
8074
|
-
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit >
|
|
8075
|
-
const error = new RangeError(`limit must be a safe integer between 0 and ${
|
|
8488
|
+
if (!Number.isSafeInteger(params.limit) || params.limit < 0 || params.limit > import_protocols5.MAX_PENDING_EXECUTION_RESULTS_LIMIT) {
|
|
8489
|
+
const error = new RangeError(`limit must be a safe integer between 0 and ${import_protocols5.MAX_PENDING_EXECUTION_RESULTS_LIMIT}`);
|
|
8076
8490
|
error.code = "INVALID_LIMIT";
|
|
8077
8491
|
throw error;
|
|
8078
8492
|
}
|
|
@@ -8098,27 +8512,76 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
8098
8512
|
);
|
|
8099
8513
|
return result.rows.map((row) => this.rowToItem(row));
|
|
8100
8514
|
}
|
|
8101
|
-
|
|
8102
|
-
|
|
8103
|
-
|
|
8104
|
-
|
|
8105
|
-
|
|
8106
|
-
|
|
8107
|
-
|
|
8108
|
-
|
|
8109
|
-
|
|
8110
|
-
|
|
8111
|
-
|
|
8112
|
-
|
|
8113
|
-
|
|
8114
|
-
|
|
8115
|
-
|
|
8515
|
+
/** Lists canonical project lifecycle events with an exclusive cursor. */
|
|
8516
|
+
async listProjectLifecycleEvents(query) {
|
|
8517
|
+
let cursorMilliseconds = null;
|
|
8518
|
+
if (!Number.isSafeInteger(query.limit) || query.limit < 1 || query.limit > 100 || query.actions.length === 0 || query.actions.some((action) => !PROJECT_LIFECYCLE_ACTIONS.has(action))) {
|
|
8519
|
+
throw new RangeError("Invalid project lifecycle event page");
|
|
8520
|
+
}
|
|
8521
|
+
if (query.before) {
|
|
8522
|
+
try {
|
|
8523
|
+
cursorMilliseconds = Date.prototype.getTime.call(query.before.createdAt);
|
|
8524
|
+
} catch {
|
|
8525
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
8526
|
+
}
|
|
8527
|
+
if (!Number.isFinite(cursorMilliseconds) || typeof query.before.id !== "string" || query.before.id.length === 0) {
|
|
8528
|
+
throw new RangeError("Invalid project lifecycle event cursor");
|
|
8529
|
+
}
|
|
8530
|
+
}
|
|
8531
|
+
const cursor = cursorMilliseconds === null ? null : new Date(cursorMilliseconds);
|
|
8532
|
+
await this.ensureInitialized();
|
|
8533
|
+
const result = await this.pool.query(
|
|
8534
|
+
`SELECT * FROM lattice_task_work_items
|
|
8535
|
+
WHERE tenant_id=$1 AND workspace_id=$2 AND project_id=$3
|
|
8536
|
+
AND action = ANY($4::text[]) AND event_key IS NOT NULL AND event_key <> ''
|
|
8537
|
+
AND ($5::timestamptz IS NULL OR created_at < $5::timestamptz
|
|
8538
|
+
OR (created_at = $5::timestamptz AND id < $6))
|
|
8539
|
+
ORDER BY created_at DESC, id DESC LIMIT $7`,
|
|
8540
|
+
[
|
|
8541
|
+
query.tenantId,
|
|
8542
|
+
query.workspaceId,
|
|
8543
|
+
query.projectId,
|
|
8544
|
+
query.actions,
|
|
8545
|
+
cursor,
|
|
8546
|
+
query.before?.id ?? null,
|
|
8547
|
+
query.limit
|
|
8548
|
+
]
|
|
8549
|
+
);
|
|
8550
|
+
return result.rows.map((row) => this.assertProjectLifecycleEvent(this.rowToItem(row)));
|
|
8551
|
+
}
|
|
8552
|
+
assertProjectLifecycleEvent(item) {
|
|
8553
|
+
let milliseconds;
|
|
8554
|
+
try {
|
|
8555
|
+
milliseconds = Date.prototype.getTime.call(item.createdAt);
|
|
8556
|
+
} catch {
|
|
8557
|
+
throw new Error("Invalid project lifecycle event row");
|
|
8558
|
+
}
|
|
8559
|
+
if (!Number.isFinite(milliseconds) || typeof item.id !== "string" || item.id.length === 0 || typeof item.eventKey !== "string" || item.eventKey.length === 0) {
|
|
8560
|
+
throw new Error("Invalid project lifecycle event row");
|
|
8561
|
+
}
|
|
8562
|
+
return { ...item, createdAt: new Date(milliseconds) };
|
|
8563
|
+
}
|
|
8564
|
+
rowToItem(row) {
|
|
8565
|
+
return {
|
|
8566
|
+
id: row.id,
|
|
8567
|
+
taskId: row.task_id,
|
|
8568
|
+
tenantId: row.tenant_id,
|
|
8569
|
+
action: row.action,
|
|
8570
|
+
actor: row.actor,
|
|
8571
|
+
threadId: row.thread_id,
|
|
8572
|
+
summary: row.summary,
|
|
8573
|
+
detail: isRecord2(row.detail) ? row.detail : void 0,
|
|
8574
|
+
attempt: row.attempt,
|
|
8575
|
+
workspaceId: row.workspace_id,
|
|
8576
|
+
projectId: row.project_id,
|
|
8577
|
+
eventKey: row.event_key == null ? void 0 : row.event_key,
|
|
8578
|
+
createdAt: new Date(row.created_at)
|
|
8116
8579
|
};
|
|
8117
8580
|
}
|
|
8118
8581
|
};
|
|
8119
8582
|
|
|
8120
8583
|
// src/stores/MenuStore.ts
|
|
8121
|
-
var
|
|
8584
|
+
var import_pg21 = require("pg");
|
|
8122
8585
|
|
|
8123
8586
|
// src/migrations/menu_items_migration.ts
|
|
8124
8587
|
var createMenuItemsTable = {
|
|
@@ -8195,7 +8658,7 @@ var MenuStore = class {
|
|
|
8195
8658
|
this.initialized = true;
|
|
8196
8659
|
return;
|
|
8197
8660
|
}
|
|
8198
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
8661
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg21.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg21.Pool(options.poolConfig) : (() => {
|
|
8199
8662
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8200
8663
|
})();
|
|
8201
8664
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -8350,7 +8813,7 @@ var MenuStore = class {
|
|
|
8350
8813
|
};
|
|
8351
8814
|
|
|
8352
8815
|
// src/stores/PostgresSharedResourceStore.ts
|
|
8353
|
-
var
|
|
8816
|
+
var import_pg22 = require("pg");
|
|
8354
8817
|
|
|
8355
8818
|
// src/migrations/shared_resources_migration.ts
|
|
8356
8819
|
var createSharedResourcesTable = {
|
|
@@ -8404,7 +8867,7 @@ var PostgresSharedResourceStore = class {
|
|
|
8404
8867
|
this.initialized = true;
|
|
8405
8868
|
return;
|
|
8406
8869
|
}
|
|
8407
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
8870
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg22.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg22.Pool(options.poolConfig) : (() => {
|
|
8408
8871
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8409
8872
|
})();
|
|
8410
8873
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -8573,7 +9036,7 @@ var PostgresSharedResourceStore = class {
|
|
|
8573
9036
|
};
|
|
8574
9037
|
|
|
8575
9038
|
// src/stores/PostgreSQLCollectionStore.ts
|
|
8576
|
-
var
|
|
9039
|
+
var import_pg23 = require("pg");
|
|
8577
9040
|
var PostgreSQLCollectionStore = class {
|
|
8578
9041
|
constructor(options) {
|
|
8579
9042
|
this.initialized = false;
|
|
@@ -8586,9 +9049,9 @@ var PostgreSQLCollectionStore = class {
|
|
|
8586
9049
|
return;
|
|
8587
9050
|
}
|
|
8588
9051
|
if (typeof options.poolConfig === "string") {
|
|
8589
|
-
this.pool = new
|
|
9052
|
+
this.pool = new import_pg23.Pool({ connectionString: options.poolConfig });
|
|
8590
9053
|
} else if (options.poolConfig) {
|
|
8591
|
-
this.pool = new
|
|
9054
|
+
this.pool = new import_pg23.Pool(options.poolConfig);
|
|
8592
9055
|
} else {
|
|
8593
9056
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8594
9057
|
}
|
|
@@ -8703,7 +9166,7 @@ var PostgreSQLCollectionStore = class {
|
|
|
8703
9166
|
var import_langgraph_checkpoint_postgres = require("@langchain/langgraph-checkpoint-postgres");
|
|
8704
9167
|
|
|
8705
9168
|
// src/PGVectorStoreProvider.ts
|
|
8706
|
-
var
|
|
9169
|
+
var import_pg24 = require("pg");
|
|
8707
9170
|
var import_pgvector = require("@langchain/community/vectorstores/pgvector");
|
|
8708
9171
|
var import_documents = require("@langchain/core/documents");
|
|
8709
9172
|
var import_core6 = require("@axiom-lattice/core");
|
|
@@ -8777,7 +9240,7 @@ var PGVectorStoreProvider = class {
|
|
|
8777
9240
|
}
|
|
8778
9241
|
};
|
|
8779
9242
|
function createPGVectorStoreProvider(connectionString) {
|
|
8780
|
-
const pool = new
|
|
9243
|
+
const pool = new import_pg24.Pool({ connectionString });
|
|
8781
9244
|
return {
|
|
8782
9245
|
provider: new PGVectorStoreProvider(pool, connectionString),
|
|
8783
9246
|
pool
|
|
@@ -9066,103 +9529,152 @@ var createCollectionsTable = {
|
|
|
9066
9529
|
}
|
|
9067
9530
|
};
|
|
9068
9531
|
|
|
9069
|
-
// src/migrations/
|
|
9070
|
-
var
|
|
9071
|
-
version:
|
|
9072
|
-
name: "
|
|
9532
|
+
// src/migrations/capability_bundle_migration.ts
|
|
9533
|
+
var createCapabilityBundlesTable = {
|
|
9534
|
+
version: 170,
|
|
9535
|
+
name: "create_capability_bundles_table",
|
|
9536
|
+
up: async (client) => {
|
|
9537
|
+
await client.query(`CREATE TABLE IF NOT EXISTS lattice_capability_bundles (
|
|
9538
|
+
id UUID PRIMARY KEY, tenant_id VARCHAR(255) NOT NULL, bundle_key VARCHAR(255) NOT NULL,
|
|
9539
|
+
name VARCHAR(255) NOT NULL, description TEXT, capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9540
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9541
|
+
UNIQUE (tenant_id, bundle_key)
|
|
9542
|
+
)`);
|
|
9543
|
+
await client.query("CREATE INDEX IF NOT EXISTS idx_lattice_capability_bundles_tenant ON lattice_capability_bundles(tenant_id)");
|
|
9544
|
+
},
|
|
9545
|
+
down: async (client) => {
|
|
9546
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_capability_bundles_tenant");
|
|
9547
|
+
await client.query("DROP TABLE IF EXISTS lattice_capability_bundles");
|
|
9548
|
+
}
|
|
9549
|
+
};
|
|
9550
|
+
|
|
9551
|
+
// src/migrations/project_room_migration.ts
|
|
9552
|
+
var createProjectRoomTables = {
|
|
9553
|
+
version: 172,
|
|
9554
|
+
name: "create_project_room_tables",
|
|
9073
9555
|
up: async (client) => {
|
|
9074
9556
|
await client.query(`
|
|
9075
|
-
CREATE TABLE IF NOT EXISTS
|
|
9076
|
-
id
|
|
9077
|
-
tenant_id
|
|
9078
|
-
|
|
9079
|
-
|
|
9080
|
-
|
|
9081
|
-
|
|
9082
|
-
|
|
9083
|
-
detail JSONB,
|
|
9084
|
-
attempt INTEGER,
|
|
9557
|
+
CREATE TABLE IF NOT EXISTS lattice_project_rooms (
|
|
9558
|
+
id VARCHAR(255) NOT NULL,
|
|
9559
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
9560
|
+
workspace_id VARCHAR(255) NOT NULL,
|
|
9561
|
+
project_id VARCHAR(255) NOT NULL,
|
|
9562
|
+
name VARCHAR(255) NOT NULL,
|
|
9563
|
+
type VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_rooms_type
|
|
9564
|
+
CHECK (type IN ('main')),
|
|
9085
9565
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9086
|
-
|
|
9566
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9567
|
+
PRIMARY KEY (tenant_id, id),
|
|
9568
|
+
UNIQUE (tenant_id, project_id, type)
|
|
9087
9569
|
)
|
|
9088
9570
|
`);
|
|
9089
9571
|
await client.query(`
|
|
9090
|
-
CREATE
|
|
9091
|
-
|
|
9572
|
+
CREATE TABLE IF NOT EXISTS lattice_project_memberships (
|
|
9573
|
+
id VARCHAR(255) NOT NULL,
|
|
9574
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
9575
|
+
project_id VARCHAR(255) NOT NULL,
|
|
9576
|
+
user_id VARCHAR(255) NOT NULL,
|
|
9577
|
+
role VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_memberships_role
|
|
9578
|
+
CHECK (role IN ('owner', 'admin', 'member', 'viewer')),
|
|
9579
|
+
status VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_memberships_status
|
|
9580
|
+
CHECK (status IN ('active', 'removed')),
|
|
9581
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9582
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9583
|
+
PRIMARY KEY (tenant_id, id),
|
|
9584
|
+
UNIQUE (tenant_id, project_id, user_id)
|
|
9585
|
+
)
|
|
9092
9586
|
`);
|
|
9093
9587
|
await client.query(`
|
|
9094
|
-
CREATE INDEX IF NOT EXISTS
|
|
9095
|
-
|
|
9588
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_memberships_project
|
|
9589
|
+
ON lattice_project_memberships (tenant_id, project_id)
|
|
9096
9590
|
`);
|
|
9097
|
-
}
|
|
9098
|
-
};
|
|
9099
|
-
var addWorkItemProjectFieldsMigration = {
|
|
9100
|
-
version: 140,
|
|
9101
|
-
name: "add_task_work_item_project_fields",
|
|
9102
|
-
up: async (client) => {
|
|
9103
9591
|
await client.query(`
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9592
|
+
CREATE TABLE IF NOT EXISTS lattice_project_bot_memberships (
|
|
9593
|
+
id VARCHAR(255) NOT NULL,
|
|
9594
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
9595
|
+
workspace_id VARCHAR(255) NOT NULL,
|
|
9596
|
+
project_id VARCHAR(255) NOT NULL,
|
|
9597
|
+
room_id VARCHAR(255) NOT NULL,
|
|
9598
|
+
assistant_id VARCHAR(255) NOT NULL,
|
|
9599
|
+
role VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_bot_memberships_role
|
|
9600
|
+
CHECK (role IN ('coordinator', 'specialist')),
|
|
9601
|
+
title VARCHAR(255) NOT NULL,
|
|
9602
|
+
responsibility TEXT,
|
|
9603
|
+
mention_name VARCHAR(255) NOT NULL,
|
|
9604
|
+
status VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_bot_memberships_status
|
|
9605
|
+
CHECK (status IN ('active', 'paused', 'removed')),
|
|
9606
|
+
room_thread_id VARCHAR(255) NOT NULL,
|
|
9607
|
+
joined_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9608
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9609
|
+
PRIMARY KEY (tenant_id, id),
|
|
9610
|
+
UNIQUE (tenant_id, project_id, assistant_id)
|
|
9611
|
+
)
|
|
9107
9612
|
`);
|
|
9108
9613
|
await client.query(`
|
|
9109
|
-
CREATE INDEX IF NOT EXISTS
|
|
9110
|
-
|
|
9614
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lattice_project_bot_memberships_coordinator
|
|
9615
|
+
ON lattice_project_bot_memberships (tenant_id, project_id)
|
|
9616
|
+
WHERE role = 'coordinator' AND status IN ('active', 'paused')
|
|
9111
9617
|
`);
|
|
9112
|
-
}
|
|
9113
|
-
};
|
|
9114
|
-
var addTaskWorkItemEventKeyMigration = {
|
|
9115
|
-
version: 168,
|
|
9116
|
-
name: "add_task_work_item_event_key",
|
|
9117
|
-
up: async (client) => {
|
|
9118
9618
|
await client.query(`
|
|
9119
|
-
|
|
9120
|
-
|
|
9619
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lattice_project_bot_memberships_mention
|
|
9620
|
+
ON lattice_project_bot_memberships (tenant_id, room_id, mention_name)
|
|
9621
|
+
WHERE status IN ('active', 'paused')
|
|
9121
9622
|
`);
|
|
9122
9623
|
await client.query(`
|
|
9123
|
-
CREATE
|
|
9124
|
-
|
|
9624
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_bot_memberships_project
|
|
9625
|
+
ON lattice_project_bot_memberships (tenant_id, project_id)
|
|
9125
9626
|
`);
|
|
9126
|
-
}
|
|
9127
|
-
};
|
|
9128
|
-
var addTaskWorkItemPendingIndexesMigration = {
|
|
9129
|
-
version: 171,
|
|
9130
|
-
name: "add_task_work_item_pending_indexes",
|
|
9131
|
-
up: async (client) => {
|
|
9132
9627
|
await client.query(`
|
|
9133
|
-
CREATE INDEX IF NOT EXISTS
|
|
9134
|
-
|
|
9628
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_bot_memberships_room
|
|
9629
|
+
ON lattice_project_bot_memberships (tenant_id, room_id)
|
|
9135
9630
|
`);
|
|
9136
9631
|
await client.query(`
|
|
9137
|
-
CREATE
|
|
9138
|
-
|
|
9139
|
-
|
|
9632
|
+
CREATE TABLE IF NOT EXISTS lattice_project_room_messages (
|
|
9633
|
+
id VARCHAR(255) NOT NULL,
|
|
9634
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
9635
|
+
workspace_id VARCHAR(255) NOT NULL,
|
|
9636
|
+
project_id VARCHAR(255) NOT NULL,
|
|
9637
|
+
room_id VARCHAR(255) NOT NULL,
|
|
9638
|
+
author JSONB NOT NULL,
|
|
9639
|
+
content JSONB NOT NULL,
|
|
9640
|
+
mentions JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9641
|
+
reply_to_message_id VARCHAR(255),
|
|
9642
|
+
source VARCHAR(32) NOT NULL CONSTRAINT chk_lattice_project_room_messages_source
|
|
9643
|
+
CHECK (source IN ('user', 'agent', 'task', 'routine', 'system')),
|
|
9644
|
+
source_id VARCHAR(255),
|
|
9645
|
+
idempotency_key VARCHAR(255),
|
|
9646
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9647
|
+
PRIMARY KEY (tenant_id, id)
|
|
9648
|
+
)
|
|
9649
|
+
`);
|
|
9650
|
+
await client.query(`
|
|
9651
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uq_lattice_project_room_messages_idempotency
|
|
9652
|
+
ON lattice_project_room_messages (tenant_id, room_id, idempotency_key)
|
|
9653
|
+
WHERE idempotency_key IS NOT NULL
|
|
9654
|
+
`);
|
|
9655
|
+
await client.query(`
|
|
9656
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_room_messages_room_created
|
|
9657
|
+
ON lattice_project_room_messages (tenant_id, room_id, created_at DESC, id DESC)
|
|
9140
9658
|
`);
|
|
9141
|
-
}
|
|
9142
|
-
};
|
|
9143
|
-
|
|
9144
|
-
// src/migrations/capability_bundle_migration.ts
|
|
9145
|
-
var createCapabilityBundlesTable = {
|
|
9146
|
-
version: 170,
|
|
9147
|
-
name: "create_capability_bundles_table",
|
|
9148
|
-
up: async (client) => {
|
|
9149
|
-
await client.query(`CREATE TABLE IF NOT EXISTS lattice_capability_bundles (
|
|
9150
|
-
id UUID PRIMARY KEY, tenant_id VARCHAR(255) NOT NULL, bundle_key VARCHAR(255) NOT NULL,
|
|
9151
|
-
name VARCHAR(255) NOT NULL, description TEXT, capabilities JSONB NOT NULL DEFAULT '[]'::jsonb,
|
|
9152
|
-
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9153
|
-
UNIQUE (tenant_id, bundle_key)
|
|
9154
|
-
)`);
|
|
9155
|
-
await client.query("CREATE INDEX IF NOT EXISTS idx_lattice_capability_bundles_tenant ON lattice_capability_bundles(tenant_id)");
|
|
9156
9659
|
},
|
|
9157
9660
|
down: async (client) => {
|
|
9158
|
-
await client.query("DROP INDEX IF EXISTS
|
|
9159
|
-
await client.query("DROP
|
|
9661
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_project_room_messages_room_created");
|
|
9662
|
+
await client.query("DROP INDEX IF EXISTS uq_lattice_project_room_messages_idempotency");
|
|
9663
|
+
await client.query("DROP TABLE IF EXISTS lattice_project_room_messages");
|
|
9664
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_project_bot_memberships_room");
|
|
9665
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_project_bot_memberships_project");
|
|
9666
|
+
await client.query("DROP INDEX IF EXISTS uq_lattice_project_bot_memberships_mention");
|
|
9667
|
+
await client.query("DROP INDEX IF EXISTS uq_lattice_project_bot_memberships_coordinator");
|
|
9668
|
+
await client.query("DROP TABLE IF EXISTS lattice_project_bot_memberships");
|
|
9669
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_project_memberships_project");
|
|
9670
|
+
await client.query("DROP TABLE IF EXISTS lattice_project_memberships");
|
|
9671
|
+
await client.query("DROP TABLE IF EXISTS lattice_project_rooms");
|
|
9160
9672
|
}
|
|
9161
9673
|
};
|
|
9162
9674
|
|
|
9163
9675
|
// src/stores/PostgreSQLCapabilityBundleStore.ts
|
|
9164
9676
|
var import_crypto4 = require("crypto");
|
|
9165
|
-
var
|
|
9677
|
+
var import_pg25 = require("pg");
|
|
9166
9678
|
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
9167
9679
|
function map(row) {
|
|
9168
9680
|
return {
|
|
@@ -9195,7 +9707,7 @@ var PostgreSQLCapabilityBundleStore = class {
|
|
|
9195
9707
|
this.initialized = true;
|
|
9196
9708
|
return;
|
|
9197
9709
|
}
|
|
9198
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
9710
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg25.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg25.Pool(options.poolConfig) : (() => {
|
|
9199
9711
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9200
9712
|
})();
|
|
9201
9713
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -9384,9 +9896,589 @@ var PostgreSQLCapabilityBundleStore = class {
|
|
|
9384
9896
|
}
|
|
9385
9897
|
};
|
|
9386
9898
|
|
|
9899
|
+
// src/stores/PostgreSQLProjectRoomStore.ts
|
|
9900
|
+
function isValidDate(value) {
|
|
9901
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
9902
|
+
}
|
|
9903
|
+
function mapRow2(row) {
|
|
9904
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || row.type !== "main" || typeof row.name !== "string" || !isValidDate(row.created_at) || !isValidDate(row.updated_at)) {
|
|
9905
|
+
throw new Error("Invalid project room row");
|
|
9906
|
+
}
|
|
9907
|
+
return {
|
|
9908
|
+
id: row.id,
|
|
9909
|
+
tenantId: row.tenant_id,
|
|
9910
|
+
workspaceId: row.workspace_id,
|
|
9911
|
+
projectId: row.project_id,
|
|
9912
|
+
type: "main",
|
|
9913
|
+
name: row.name,
|
|
9914
|
+
createdAt: row.created_at,
|
|
9915
|
+
updatedAt: row.updated_at
|
|
9916
|
+
};
|
|
9917
|
+
}
|
|
9918
|
+
var PostgreSQLProjectRoomStore = class {
|
|
9919
|
+
/** Creates a store using an externally managed pool; the pool is not migrated or closed. */
|
|
9920
|
+
constructor(options) {
|
|
9921
|
+
this.pool = options.pool;
|
|
9922
|
+
}
|
|
9923
|
+
/** Creates or returns the single persisted main room for a project. */
|
|
9924
|
+
async ensureMainRoom(input) {
|
|
9925
|
+
const result = await this.pool.query(
|
|
9926
|
+
`INSERT INTO lattice_project_rooms
|
|
9927
|
+
(id, tenant_id, workspace_id, project_id, type, name)
|
|
9928
|
+
VALUES ($1, $2, $3, $4, $5, $6)
|
|
9929
|
+
ON CONFLICT (tenant_id, project_id, type)
|
|
9930
|
+
DO UPDATE SET updated_at = lattice_project_rooms.updated_at
|
|
9931
|
+
RETURNING id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at`,
|
|
9932
|
+
[input.id, input.tenantId, input.workspaceId, input.projectId, "main", input.name]
|
|
9933
|
+
);
|
|
9934
|
+
return mapRow2(result.rows[0]);
|
|
9935
|
+
}
|
|
9936
|
+
/** Finds a tenant-scoped project's main room, or returns null. */
|
|
9937
|
+
async getMainRoom(tenantId, projectId) {
|
|
9938
|
+
const result = await this.pool.query(
|
|
9939
|
+
"SELECT id, tenant_id, workspace_id, project_id, type, name, created_at, updated_at FROM lattice_project_rooms WHERE tenant_id = $1 AND project_id = $2 AND type = 'main'",
|
|
9940
|
+
[tenantId, projectId]
|
|
9941
|
+
);
|
|
9942
|
+
return result.rows[0] ? mapRow2(result.rows[0]) : null;
|
|
9943
|
+
}
|
|
9944
|
+
};
|
|
9945
|
+
|
|
9946
|
+
// src/stores/PostgreSQLProjectMembershipStore.ts
|
|
9947
|
+
var DuplicateProjectMembershipError = class extends Error {
|
|
9948
|
+
constructor() {
|
|
9949
|
+
super("Project membership already exists for tenant, project, and user");
|
|
9950
|
+
this.name = "DuplicateProjectMembershipError";
|
|
9951
|
+
}
|
|
9952
|
+
};
|
|
9953
|
+
var ProjectMembershipIdConflictError = class extends Error {
|
|
9954
|
+
constructor() {
|
|
9955
|
+
super("Project membership ID already exists for tenant");
|
|
9956
|
+
this.name = "ProjectMembershipIdConflictError";
|
|
9957
|
+
}
|
|
9958
|
+
};
|
|
9959
|
+
function isDate(value) {
|
|
9960
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
9961
|
+
}
|
|
9962
|
+
function isRole(value) {
|
|
9963
|
+
return value === "owner" || value === "admin" || value === "member" || value === "viewer";
|
|
9964
|
+
}
|
|
9965
|
+
function isStatus2(value) {
|
|
9966
|
+
return value === "active" || value === "removed";
|
|
9967
|
+
}
|
|
9968
|
+
function mapRow3(row) {
|
|
9969
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.project_id !== "string" || typeof row.user_id !== "string" || !isRole(row.role) || !isStatus2(row.status) || !isDate(row.joined_at) || !isDate(row.updated_at)) throw new Error("Invalid project membership row");
|
|
9970
|
+
return {
|
|
9971
|
+
id: row.id,
|
|
9972
|
+
tenantId: row.tenant_id,
|
|
9973
|
+
projectId: row.project_id,
|
|
9974
|
+
userId: row.user_id,
|
|
9975
|
+
role: row.role,
|
|
9976
|
+
status: row.status,
|
|
9977
|
+
joinedAt: row.joined_at,
|
|
9978
|
+
updatedAt: row.updated_at
|
|
9979
|
+
};
|
|
9980
|
+
}
|
|
9981
|
+
function isDuplicate2(error) {
|
|
9982
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
9983
|
+
}
|
|
9984
|
+
function mapDuplicate(error) {
|
|
9985
|
+
if (isDuplicate2(error) && typeof error.constraint === "string" && error.constraint.includes("project_id_user_id")) throw new DuplicateProjectMembershipError();
|
|
9986
|
+
if (isDuplicate2(error)) throw new ProjectMembershipIdConflictError();
|
|
9987
|
+
throw error;
|
|
9988
|
+
}
|
|
9989
|
+
var columns = "id, tenant_id, project_id, user_id, role, status, joined_at, updated_at";
|
|
9990
|
+
var PostgreSQLProjectMembershipStore = class {
|
|
9991
|
+
/** Creates a store using an externally managed pool; the pool is not migrated or closed. */
|
|
9992
|
+
constructor(options) {
|
|
9993
|
+
this.pool = options.pool;
|
|
9994
|
+
}
|
|
9995
|
+
/** Lists memberships in stable joined-time and ID order. */
|
|
9996
|
+
async list(tenantId, projectId) {
|
|
9997
|
+
const result = await this.pool.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND project_id = $2 ORDER BY joined_at ASC, id ASC`, [tenantId, projectId]);
|
|
9998
|
+
return result.rows.map(mapRow3);
|
|
9999
|
+
}
|
|
10000
|
+
/** Finds a membership by exact tenant, project, and user identity. */
|
|
10001
|
+
async findByUser(tenantId, projectId, userId) {
|
|
10002
|
+
const result = await this.pool.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND project_id = $2 AND user_id = $3`, [tenantId, projectId, userId]);
|
|
10003
|
+
return result.rows[0] ? mapRow3(result.rows[0]) : null;
|
|
10004
|
+
}
|
|
10005
|
+
/** Inserts a membership and maps database uniqueness errors to stable typed errors. */
|
|
10006
|
+
async create(input) {
|
|
10007
|
+
const client = await this.pool.connect();
|
|
10008
|
+
try {
|
|
10009
|
+
await client.query("BEGIN");
|
|
10010
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${input.tenantId}:${input.projectId}`]);
|
|
10011
|
+
const result = await client.query(`INSERT INTO lattice_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, date_trunc('milliseconds', clock_timestamp()), date_trunc('milliseconds', clock_timestamp())) RETURNING ${columns}`, [input.id, input.tenantId, input.projectId, input.userId, input.role, input.status]);
|
|
10012
|
+
const membership = mapRow3(result.rows[0]);
|
|
10013
|
+
await client.query("COMMIT");
|
|
10014
|
+
return membership;
|
|
10015
|
+
} catch (error) {
|
|
10016
|
+
try {
|
|
10017
|
+
await client.query("ROLLBACK");
|
|
10018
|
+
} catch {
|
|
10019
|
+
}
|
|
10020
|
+
return mapDuplicate(error);
|
|
10021
|
+
} finally {
|
|
10022
|
+
client.release();
|
|
10023
|
+
}
|
|
10024
|
+
}
|
|
10025
|
+
/** Atomically creates the first active owner while serializing the project scope. */
|
|
10026
|
+
async createInitialOwner(input) {
|
|
10027
|
+
const client = await this.pool.connect();
|
|
10028
|
+
try {
|
|
10029
|
+
await client.query("BEGIN");
|
|
10030
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${input.tenantId}:${input.projectId}`]);
|
|
10031
|
+
const existingResult = await client.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND project_id = $2 FOR UPDATE`, [input.tenantId, input.projectId]);
|
|
10032
|
+
if (existingResult.rows.length > 0) {
|
|
10033
|
+
const existing = existingResult.rows.map(mapRow3).find((item) => item.userId === input.userId && item.role === "owner" && item.status === "active");
|
|
10034
|
+
await client.query("COMMIT");
|
|
10035
|
+
return existing ? { kind: "existing", membership: existing } : { kind: "already_initialized" };
|
|
10036
|
+
}
|
|
10037
|
+
const idResult = await client.query("SELECT id FROM lattice_project_memberships WHERE tenant_id = $1 AND id = $2", [input.tenantId, input.id]);
|
|
10038
|
+
if (idResult.rows.length > 0) throw new ProjectMembershipIdConflictError();
|
|
10039
|
+
const inserted = await client.query(`INSERT INTO lattice_project_memberships (id, tenant_id, project_id, user_id, role, status, joined_at, updated_at) VALUES ($1, $2, $3, $4, 'owner', 'active', date_trunc('milliseconds', clock_timestamp()), date_trunc('milliseconds', clock_timestamp())) RETURNING ${columns}`, [input.id, input.tenantId, input.projectId, input.userId]);
|
|
10040
|
+
const membership = mapRow3(inserted.rows[0]);
|
|
10041
|
+
await client.query("COMMIT");
|
|
10042
|
+
return { kind: "created", membership };
|
|
10043
|
+
} catch (error) {
|
|
10044
|
+
try {
|
|
10045
|
+
await client.query("ROLLBACK");
|
|
10046
|
+
} catch {
|
|
10047
|
+
}
|
|
10048
|
+
if (isDuplicate2(error)) return mapDuplicate(error);
|
|
10049
|
+
throw error;
|
|
10050
|
+
} finally {
|
|
10051
|
+
client.release();
|
|
10052
|
+
}
|
|
10053
|
+
}
|
|
10054
|
+
/** Updates a role under a project lock with exact timestamp and owner safeguards. */
|
|
10055
|
+
async updateRole(input) {
|
|
10056
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, input.role, false);
|
|
10057
|
+
}
|
|
10058
|
+
/** Marks a membership removed under a project lock with exact timestamp and owner safeguards. */
|
|
10059
|
+
async remove(input) {
|
|
10060
|
+
return this.mutate(input.tenantId, input.id, input.expectedUpdatedAt, void 0, true);
|
|
10061
|
+
}
|
|
10062
|
+
async mutate(tenantId, id, expected, role, remove) {
|
|
10063
|
+
const client = await this.pool.connect();
|
|
10064
|
+
try {
|
|
10065
|
+
await client.query("BEGIN");
|
|
10066
|
+
const identity = await client.query("SELECT project_id FROM lattice_project_memberships WHERE tenant_id = $1 AND id = $2", [tenantId, id]);
|
|
10067
|
+
if (!identity.rows[0] || typeof identity.rows[0].project_id !== "string") {
|
|
10068
|
+
await client.query("COMMIT");
|
|
10069
|
+
return { kind: "not_found" };
|
|
10070
|
+
}
|
|
10071
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${tenantId}:${identity.rows[0].project_id}`]);
|
|
10072
|
+
const activeRows = await client.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND project_id = $2 AND status = 'active' FOR UPDATE`, [tenantId, identity.rows[0].project_id]);
|
|
10073
|
+
const lockedMemberships = activeRows.rows.map(mapRow3);
|
|
10074
|
+
let current = lockedMemberships.find((membership) => membership.id === id);
|
|
10075
|
+
if (!current) {
|
|
10076
|
+
const target = await client.query(`SELECT ${columns} FROM lattice_project_memberships WHERE tenant_id = $1 AND id = $2 FOR UPDATE`, [tenantId, id]);
|
|
10077
|
+
if (!target.rows[0]) {
|
|
10078
|
+
await client.query("COMMIT");
|
|
10079
|
+
return { kind: "not_found" };
|
|
10080
|
+
}
|
|
10081
|
+
current = mapRow3(target.rows[0]);
|
|
10082
|
+
}
|
|
10083
|
+
if (current.updatedAt.getTime() !== expected.getTime()) {
|
|
10084
|
+
await client.query("COMMIT");
|
|
10085
|
+
return { kind: "conflict" };
|
|
10086
|
+
}
|
|
10087
|
+
if (current.role === "owner" && current.status === "active" && (remove || role !== "owner")) {
|
|
10088
|
+
const owners = lockedMemberships.filter((membership) => membership.id !== id && membership.role === "owner");
|
|
10089
|
+
if (owners.length === 0) {
|
|
10090
|
+
await client.query("COMMIT");
|
|
10091
|
+
return { kind: "last_owner" };
|
|
10092
|
+
}
|
|
10093
|
+
}
|
|
10094
|
+
const result = await client.query(remove ? `UPDATE lattice_project_memberships SET status = 'removed', updated_at = GREATEST(date_trunc('milliseconds', updated_at) + interval '1 millisecond', date_trunc('milliseconds', clock_timestamp())) WHERE tenant_id = $1 AND id = $2 AND date_trunc('milliseconds', updated_at) = $3 RETURNING ${columns}` : `UPDATE lattice_project_memberships SET role = $4, updated_at = GREATEST(date_trunc('milliseconds', updated_at) + interval '1 millisecond', date_trunc('milliseconds', clock_timestamp())) WHERE tenant_id = $1 AND id = $2 AND date_trunc('milliseconds', updated_at) = $3 RETURNING ${columns}`, remove ? [tenantId, id, expected] : [tenantId, id, expected, role]);
|
|
10095
|
+
if (!result.rows[0]) {
|
|
10096
|
+
await client.query("COMMIT");
|
|
10097
|
+
return { kind: "conflict" };
|
|
10098
|
+
}
|
|
10099
|
+
await client.query("COMMIT");
|
|
10100
|
+
return { kind: remove ? "removed" : "updated", membership: mapRow3(result.rows[0]) };
|
|
10101
|
+
} catch (error) {
|
|
10102
|
+
try {
|
|
10103
|
+
await client.query("ROLLBACK");
|
|
10104
|
+
} catch {
|
|
10105
|
+
}
|
|
10106
|
+
throw error;
|
|
10107
|
+
} finally {
|
|
10108
|
+
client.release();
|
|
10109
|
+
}
|
|
10110
|
+
}
|
|
10111
|
+
};
|
|
10112
|
+
|
|
10113
|
+
// src/stores/PostgreSQLProjectBotMembershipStore.ts
|
|
10114
|
+
var columns2 = "id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at";
|
|
10115
|
+
var ProjectBotMembershipIdConflictError = class extends Error {
|
|
10116
|
+
/** Creates an accurate tenant-scoped membership ID collision error. */
|
|
10117
|
+
constructor(tenantId, id) {
|
|
10118
|
+
super(`Project bot membership ID '${id}' already exists in tenant '${tenantId}'`);
|
|
10119
|
+
this.name = "ProjectBotMembershipIdConflictError";
|
|
10120
|
+
}
|
|
10121
|
+
};
|
|
10122
|
+
function isDate2(value) {
|
|
10123
|
+
return value instanceof Date && !Number.isNaN(value.getTime());
|
|
10124
|
+
}
|
|
10125
|
+
function isRole2(value) {
|
|
10126
|
+
return value === "coordinator" || value === "specialist";
|
|
10127
|
+
}
|
|
10128
|
+
function isStatus3(value) {
|
|
10129
|
+
return value === "active" || value === "paused" || value === "removed";
|
|
10130
|
+
}
|
|
10131
|
+
function mapRow4(row) {
|
|
10132
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || typeof row.assistant_id !== "string" || !isRole2(row.role) || typeof row.title !== "string" || row.responsibility !== null && typeof row.responsibility !== "string" || typeof row.mention_name !== "string" || !isStatus3(row.status) || typeof row.room_thread_id !== "string" || !isDate2(row.joined_at) || !isDate2(row.updated_at)) {
|
|
10133
|
+
throw new Error("Invalid project bot membership row");
|
|
10134
|
+
}
|
|
10135
|
+
return {
|
|
10136
|
+
id: row.id,
|
|
10137
|
+
tenantId: row.tenant_id,
|
|
10138
|
+
workspaceId: row.workspace_id,
|
|
10139
|
+
projectId: row.project_id,
|
|
10140
|
+
roomId: row.room_id,
|
|
10141
|
+
assistantId: row.assistant_id,
|
|
10142
|
+
role: row.role,
|
|
10143
|
+
title: row.title,
|
|
10144
|
+
...row.responsibility === null ? {} : { responsibility: row.responsibility },
|
|
10145
|
+
mentionName: row.mention_name,
|
|
10146
|
+
status: row.status,
|
|
10147
|
+
roomThreadId: row.room_thread_id,
|
|
10148
|
+
joinedAt: row.joined_at,
|
|
10149
|
+
updatedAt: row.updated_at
|
|
10150
|
+
};
|
|
10151
|
+
}
|
|
10152
|
+
function isUniqueViolation(error) {
|
|
10153
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
10154
|
+
}
|
|
10155
|
+
function constraintConflict(error) {
|
|
10156
|
+
if (!isUniqueViolation(error) || typeof error.constraint !== "string") return void 0;
|
|
10157
|
+
if (error.constraint === "uq_lattice_project_bot_memberships_coordinator") return "coordinator_conflict";
|
|
10158
|
+
if (error.constraint === "uq_lattice_project_bot_memberships_mention") return "mention_conflict";
|
|
10159
|
+
return void 0;
|
|
10160
|
+
}
|
|
10161
|
+
async function rollback(client) {
|
|
10162
|
+
try {
|
|
10163
|
+
await client.query("ROLLBACK");
|
|
10164
|
+
} catch {
|
|
10165
|
+
}
|
|
10166
|
+
}
|
|
10167
|
+
var PostgreSQLProjectBotMembershipStore = class {
|
|
10168
|
+
/** Creates a store using an externally managed shared pool. */
|
|
10169
|
+
constructor(options) {
|
|
10170
|
+
this.pool = options.pool;
|
|
10171
|
+
}
|
|
10172
|
+
/** Lists retained memberships in stable join order. */
|
|
10173
|
+
async list(tenantId, projectId) {
|
|
10174
|
+
const result = await this.pool.query(
|
|
10175
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND project_id = $2 ORDER BY joined_at ASC, id ASC`,
|
|
10176
|
+
[tenantId, projectId]
|
|
10177
|
+
);
|
|
10178
|
+
return result.rows.map(mapRow4);
|
|
10179
|
+
}
|
|
10180
|
+
/** Finds a membership by tenant-scoped ID. */
|
|
10181
|
+
async findById(tenantId, id) {
|
|
10182
|
+
const result = await this.pool.query(
|
|
10183
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND id = $2`,
|
|
10184
|
+
[tenantId, id]
|
|
10185
|
+
);
|
|
10186
|
+
return result.rows[0] ? mapRow4(result.rows[0]) : null;
|
|
10187
|
+
}
|
|
10188
|
+
/** Finds an assistant's durable membership in a tenant-scoped project. */
|
|
10189
|
+
async findByAssistant(tenantId, projectId, assistantId) {
|
|
10190
|
+
const result = await this.pool.query(
|
|
10191
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND project_id = $2 AND assistant_id = $3`,
|
|
10192
|
+
[tenantId, projectId, assistantId]
|
|
10193
|
+
);
|
|
10194
|
+
return result.rows[0] ? mapRow4(result.rows[0]) : null;
|
|
10195
|
+
}
|
|
10196
|
+
/** Inserts a new membership or reactivates the assistant's durable membership atomically. */
|
|
10197
|
+
async save(input) {
|
|
10198
|
+
const client = await this.pool.connect();
|
|
10199
|
+
try {
|
|
10200
|
+
await client.query("BEGIN");
|
|
10201
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${input.tenantId}:${input.projectId}`]);
|
|
10202
|
+
const existingResult = await client.query(
|
|
10203
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND project_id = $2 AND assistant_id = $3 FOR UPDATE`,
|
|
10204
|
+
[input.tenantId, input.projectId, input.assistantId]
|
|
10205
|
+
);
|
|
10206
|
+
const existing = existingResult.rows[0] ? mapRow4(existingResult.rows[0]) : void 0;
|
|
10207
|
+
let result;
|
|
10208
|
+
if (existing) {
|
|
10209
|
+
result = await client.query(
|
|
10210
|
+
`UPDATE lattice_project_bot_memberships
|
|
10211
|
+
SET role = $3, title = $4, responsibility = $5, mention_name = $6, status = 'active',
|
|
10212
|
+
updated_at = GREATEST(date_trunc('milliseconds', updated_at) + interval '1 millisecond', date_trunc('milliseconds', clock_timestamp()))
|
|
10213
|
+
WHERE tenant_id = $1 AND id = $2 RETURNING ${columns2}`,
|
|
10214
|
+
[input.tenantId, existing.id, input.role, input.title, input.responsibility ?? null, input.mentionName]
|
|
10215
|
+
);
|
|
10216
|
+
} else {
|
|
10217
|
+
const idResult = await client.query(
|
|
10218
|
+
"SELECT id FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND id = $2",
|
|
10219
|
+
[input.tenantId, input.id]
|
|
10220
|
+
);
|
|
10221
|
+
if (idResult.rows.length > 0) throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
10222
|
+
result = await client.query(
|
|
10223
|
+
`INSERT INTO lattice_project_bot_memberships
|
|
10224
|
+
(id, tenant_id, workspace_id, project_id, room_id, assistant_id, role, title, responsibility, mention_name, status, room_thread_id, joined_at, updated_at)
|
|
10225
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'active', $11, date_trunc('milliseconds', clock_timestamp()), date_trunc('milliseconds', clock_timestamp()))
|
|
10226
|
+
RETURNING ${columns2}`,
|
|
10227
|
+
[
|
|
10228
|
+
input.id,
|
|
10229
|
+
input.tenantId,
|
|
10230
|
+
input.workspaceId,
|
|
10231
|
+
input.projectId,
|
|
10232
|
+
input.roomId,
|
|
10233
|
+
input.assistantId,
|
|
10234
|
+
input.role,
|
|
10235
|
+
input.title,
|
|
10236
|
+
input.responsibility ?? null,
|
|
10237
|
+
input.mentionName,
|
|
10238
|
+
input.roomThreadId
|
|
10239
|
+
]
|
|
10240
|
+
);
|
|
10241
|
+
}
|
|
10242
|
+
const membership = mapRow4(result.rows[0]);
|
|
10243
|
+
await client.query("COMMIT");
|
|
10244
|
+
return { kind: existing === void 0 ? "created" : existing.status === "removed" ? "reactivated" : "updated", membership };
|
|
10245
|
+
} catch (error) {
|
|
10246
|
+
await rollback(client);
|
|
10247
|
+
const conflict = constraintConflict(error);
|
|
10248
|
+
if (conflict) return { kind: conflict };
|
|
10249
|
+
if (isUniqueViolation(error) && error.constraint === "lattice_project_bot_memberships_pkey") {
|
|
10250
|
+
throw new ProjectBotMembershipIdConflictError(input.tenantId, input.id);
|
|
10251
|
+
}
|
|
10252
|
+
throw error;
|
|
10253
|
+
} finally {
|
|
10254
|
+
client.release();
|
|
10255
|
+
}
|
|
10256
|
+
}
|
|
10257
|
+
/** Applies a mutable-field patch with project serialization and millisecond-safe optimistic concurrency. */
|
|
10258
|
+
async update(input) {
|
|
10259
|
+
const client = await this.pool.connect();
|
|
10260
|
+
try {
|
|
10261
|
+
await client.query("BEGIN");
|
|
10262
|
+
const identity = await client.query(
|
|
10263
|
+
"SELECT project_id FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND id = $2",
|
|
10264
|
+
[input.tenantId, input.id]
|
|
10265
|
+
);
|
|
10266
|
+
if (!identity.rows[0]) {
|
|
10267
|
+
await client.query("COMMIT");
|
|
10268
|
+
return { kind: "not_found" };
|
|
10269
|
+
}
|
|
10270
|
+
if (typeof identity.rows[0].project_id !== "string") throw new Error("Invalid project bot membership row");
|
|
10271
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1, 0))", [`${input.tenantId}:${identity.rows[0].project_id}`]);
|
|
10272
|
+
const locked = await client.query(
|
|
10273
|
+
`SELECT ${columns2} FROM lattice_project_bot_memberships WHERE tenant_id = $1 AND id = $2 FOR UPDATE`,
|
|
10274
|
+
[input.tenantId, input.id]
|
|
10275
|
+
);
|
|
10276
|
+
if (!locked.rows[0]) {
|
|
10277
|
+
await client.query("COMMIT");
|
|
10278
|
+
return { kind: "not_found" };
|
|
10279
|
+
}
|
|
10280
|
+
const existing = mapRow4(locked.rows[0]);
|
|
10281
|
+
if (existing.updatedAt.getTime() !== input.expectedUpdatedAt.getTime()) {
|
|
10282
|
+
await client.query("COMMIT");
|
|
10283
|
+
return { kind: "conflict" };
|
|
10284
|
+
}
|
|
10285
|
+
const candidate = { ...existing, ...input.patch };
|
|
10286
|
+
const updated = await client.query(
|
|
10287
|
+
`UPDATE lattice_project_bot_memberships
|
|
10288
|
+
SET role = $4, title = $5, responsibility = $6, mention_name = $7, status = $8,
|
|
10289
|
+
updated_at = GREATEST(date_trunc('milliseconds', updated_at) + interval '1 millisecond', date_trunc('milliseconds', clock_timestamp()))
|
|
10290
|
+
WHERE tenant_id = $1 AND id = $2 AND date_trunc('milliseconds', updated_at) = $3
|
|
10291
|
+
RETURNING ${columns2}`,
|
|
10292
|
+
[
|
|
10293
|
+
input.tenantId,
|
|
10294
|
+
input.id,
|
|
10295
|
+
input.expectedUpdatedAt,
|
|
10296
|
+
candidate.role,
|
|
10297
|
+
candidate.title,
|
|
10298
|
+
candidate.responsibility ?? null,
|
|
10299
|
+
candidate.mentionName,
|
|
10300
|
+
candidate.status
|
|
10301
|
+
]
|
|
10302
|
+
);
|
|
10303
|
+
if (!updated.rows[0]) {
|
|
10304
|
+
await client.query("COMMIT");
|
|
10305
|
+
return { kind: "conflict" };
|
|
10306
|
+
}
|
|
10307
|
+
const membership = mapRow4(updated.rows[0]);
|
|
10308
|
+
await client.query("COMMIT");
|
|
10309
|
+
return { kind: "updated", membership };
|
|
10310
|
+
} catch (error) {
|
|
10311
|
+
await rollback(client);
|
|
10312
|
+
const conflict = constraintConflict(error);
|
|
10313
|
+
if (conflict) return { kind: conflict };
|
|
10314
|
+
throw error;
|
|
10315
|
+
} finally {
|
|
10316
|
+
client.release();
|
|
10317
|
+
}
|
|
10318
|
+
}
|
|
10319
|
+
};
|
|
10320
|
+
|
|
10321
|
+
// src/stores/PostgreSQLProjectRoomMessageStore.ts
|
|
10322
|
+
var columns3 = "id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at";
|
|
10323
|
+
var ProjectRoomMessageIdConflictError = class extends Error {
|
|
10324
|
+
/** Creates an accurate tenant-scoped message ID collision error. */
|
|
10325
|
+
constructor(tenantId, id) {
|
|
10326
|
+
super(`Project room message ID '${id}' already exists in tenant '${tenantId}'`);
|
|
10327
|
+
this.name = "ProjectRoomMessageIdConflictError";
|
|
10328
|
+
}
|
|
10329
|
+
};
|
|
10330
|
+
var DuplicateProjectRoomMessageIdempotencyKeyError = class extends Error {
|
|
10331
|
+
/** Creates an accurate tenant and room-scoped idempotency collision error. */
|
|
10332
|
+
constructor(tenantId, roomId, idempotencyKey) {
|
|
10333
|
+
super(`Project room message idempotency key '${idempotencyKey}' already exists in tenant '${tenantId}' room '${roomId}'`);
|
|
10334
|
+
this.name = "DuplicateProjectRoomMessageIdempotencyKeyError";
|
|
10335
|
+
}
|
|
10336
|
+
};
|
|
10337
|
+
function isRecord3(value) {
|
|
10338
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10339
|
+
}
|
|
10340
|
+
function hasOnlyKeys(value, keys) {
|
|
10341
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
10342
|
+
}
|
|
10343
|
+
function mapAuthor(value) {
|
|
10344
|
+
if (!isRecord3(value) || typeof value.type !== "string") return void 0;
|
|
10345
|
+
if (value.type === "human" && hasOnlyKeys(value, ["type", "userId"]) && typeof value.userId === "string") return { type: "human", userId: value.userId };
|
|
10346
|
+
if (value.type === "bot" && hasOnlyKeys(value, ["type", "membershipId", "assistantId"]) && typeof value.membershipId === "string" && typeof value.assistantId === "string") {
|
|
10347
|
+
return { type: "bot", membershipId: value.membershipId, assistantId: value.assistantId };
|
|
10348
|
+
}
|
|
10349
|
+
if (value.type === "system" && hasOnlyKeys(value, ["type"])) return { type: "system" };
|
|
10350
|
+
return void 0;
|
|
10351
|
+
}
|
|
10352
|
+
function mapMention(value) {
|
|
10353
|
+
if (!isRecord3(value) || typeof value.type !== "string") return void 0;
|
|
10354
|
+
if (value.type === "bot" && hasOnlyKeys(value, ["type", "membershipId"]) && typeof value.membershipId === "string") {
|
|
10355
|
+
return { type: "bot", membershipId: value.membershipId };
|
|
10356
|
+
}
|
|
10357
|
+
if (value.type === "team" && hasOnlyKeys(value, ["type"])) return { type: "team" };
|
|
10358
|
+
return void 0;
|
|
10359
|
+
}
|
|
10360
|
+
function isSource(value) {
|
|
10361
|
+
return value === "user" || value === "agent" || value === "task" || value === "routine" || value === "system";
|
|
10362
|
+
}
|
|
10363
|
+
function mapRow5(row) {
|
|
10364
|
+
const author = mapAuthor(row.author);
|
|
10365
|
+
const content = isRecord3(row.content) && row.content.type === "text" && typeof row.content.text === "string" && hasOnlyKeys(row.content, ["type", "text"]) ? { type: "text", text: row.content.text } : void 0;
|
|
10366
|
+
const mentions = Array.isArray(row.mentions) ? row.mentions.map(mapMention) : void 0;
|
|
10367
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.workspace_id !== "string" || typeof row.project_id !== "string" || typeof row.room_id !== "string" || !author || !content || !mentions || mentions.some((mention) => mention === void 0) || row.reply_to_message_id !== null && typeof row.reply_to_message_id !== "string" || !isSource(row.source) || row.source_id !== null && typeof row.source_id !== "string" || row.idempotency_key !== null && typeof row.idempotency_key !== "string" || !(row.created_at instanceof Date) || Number.isNaN(row.created_at.getTime())) {
|
|
10368
|
+
throw new Error("Invalid project room message row");
|
|
10369
|
+
}
|
|
10370
|
+
return {
|
|
10371
|
+
id: row.id,
|
|
10372
|
+
tenantId: row.tenant_id,
|
|
10373
|
+
workspaceId: row.workspace_id,
|
|
10374
|
+
projectId: row.project_id,
|
|
10375
|
+
roomId: row.room_id,
|
|
10376
|
+
author,
|
|
10377
|
+
content,
|
|
10378
|
+
mentions,
|
|
10379
|
+
...row.reply_to_message_id === null ? {} : { replyToMessageId: row.reply_to_message_id },
|
|
10380
|
+
source: row.source,
|
|
10381
|
+
...row.source_id === null ? {} : { sourceId: row.source_id },
|
|
10382
|
+
...row.idempotency_key === null ? {} : { idempotencyKey: row.idempotency_key },
|
|
10383
|
+
createdAt: row.created_at
|
|
10384
|
+
};
|
|
10385
|
+
}
|
|
10386
|
+
function isUniqueViolation2(error) {
|
|
10387
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
10388
|
+
}
|
|
10389
|
+
var PostgreSQLProjectRoomMessageStore = class {
|
|
10390
|
+
/** Creates a store using an externally managed shared pool. */
|
|
10391
|
+
constructor(options) {
|
|
10392
|
+
this.pool = options.pool;
|
|
10393
|
+
}
|
|
10394
|
+
/** Creates a room message and maps known uniqueness failures to typed errors. */
|
|
10395
|
+
async create(input) {
|
|
10396
|
+
try {
|
|
10397
|
+
const result = await this.pool.query(
|
|
10398
|
+
`INSERT INTO lattice_project_room_messages
|
|
10399
|
+
(id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at)
|
|
10400
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, date_trunc('milliseconds', clock_timestamp()))
|
|
10401
|
+
RETURNING ${columns3}`,
|
|
10402
|
+
this.parameters(input)
|
|
10403
|
+
);
|
|
10404
|
+
return mapRow5(result.rows[0]);
|
|
10405
|
+
} catch (error) {
|
|
10406
|
+
if (isUniqueViolation2(error) && error.constraint === "lattice_project_room_messages_pkey") {
|
|
10407
|
+
throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
10408
|
+
}
|
|
10409
|
+
if (isUniqueViolation2(error) && error.constraint === "uq_lattice_project_room_messages_idempotency") {
|
|
10410
|
+
throw new DuplicateProjectRoomMessageIdempotencyKeyError(input.tenantId, input.roomId, input.idempotencyKey ?? "");
|
|
10411
|
+
}
|
|
10412
|
+
throw error;
|
|
10413
|
+
}
|
|
10414
|
+
}
|
|
10415
|
+
/** Atomically creates or returns the canonical message for a room-scoped idempotency key. */
|
|
10416
|
+
async createIdempotent(input) {
|
|
10417
|
+
try {
|
|
10418
|
+
const result = await this.pool.query(
|
|
10419
|
+
`INSERT INTO lattice_project_room_messages
|
|
10420
|
+
(id, tenant_id, workspace_id, project_id, room_id, author, content, mentions, reply_to_message_id, source, source_id, idempotency_key, created_at)
|
|
10421
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, date_trunc('milliseconds', clock_timestamp()))
|
|
10422
|
+
ON CONFLICT (tenant_id, room_id, idempotency_key) WHERE idempotency_key IS NOT NULL
|
|
10423
|
+
DO UPDATE SET idempotency_key = lattice_project_room_messages.idempotency_key
|
|
10424
|
+
RETURNING ${columns3}`,
|
|
10425
|
+
this.parameters(input)
|
|
10426
|
+
);
|
|
10427
|
+
return mapRow5(result.rows[0]);
|
|
10428
|
+
} catch (error) {
|
|
10429
|
+
if (isUniqueViolation2(error) && error.constraint === "lattice_project_room_messages_pkey") {
|
|
10430
|
+
throw new ProjectRoomMessageIdConflictError(input.tenantId, input.id);
|
|
10431
|
+
}
|
|
10432
|
+
throw error;
|
|
10433
|
+
}
|
|
10434
|
+
}
|
|
10435
|
+
/** Lists messages newest first using an exclusive stable cursor and a clamped limit. */
|
|
10436
|
+
async list(input) {
|
|
10437
|
+
if (input.before && (!(input.before.createdAt instanceof Date) || Number.isNaN(input.before.createdAt.getTime()))) {
|
|
10438
|
+
throw new RangeError("Project room message cursor date is invalid");
|
|
10439
|
+
}
|
|
10440
|
+
const limit = Math.min(100, Math.max(1, Math.trunc(Number.isFinite(input.limit) ? input.limit : 1)));
|
|
10441
|
+
const result = input.before ? await this.pool.query(
|
|
10442
|
+
`SELECT ${columns3} FROM lattice_project_room_messages
|
|
10443
|
+
WHERE tenant_id = $1 AND room_id = $2 AND (created_at < $3 OR (created_at = $3 AND id < $4))
|
|
10444
|
+
ORDER BY created_at DESC, id DESC LIMIT $5`,
|
|
10445
|
+
[input.tenantId, input.roomId, input.before.createdAt, input.before.id, limit]
|
|
10446
|
+
) : await this.pool.query(
|
|
10447
|
+
`SELECT ${columns3} FROM lattice_project_room_messages
|
|
10448
|
+
WHERE tenant_id = $1 AND room_id = $2 ORDER BY created_at DESC, id DESC LIMIT $3`,
|
|
10449
|
+
[input.tenantId, input.roomId, limit]
|
|
10450
|
+
);
|
|
10451
|
+
return result.rows.map(mapRow5);
|
|
10452
|
+
}
|
|
10453
|
+
/** Finds a message by tenant-scoped ID. */
|
|
10454
|
+
async findById(tenantId, id) {
|
|
10455
|
+
const result = await this.pool.query(
|
|
10456
|
+
`SELECT ${columns3} FROM lattice_project_room_messages WHERE tenant_id = $1 AND id = $2`,
|
|
10457
|
+
[tenantId, id]
|
|
10458
|
+
);
|
|
10459
|
+
return result.rows[0] ? mapRow5(result.rows[0]) : null;
|
|
10460
|
+
}
|
|
10461
|
+
parameters(input) {
|
|
10462
|
+
return [
|
|
10463
|
+
input.id,
|
|
10464
|
+
input.tenantId,
|
|
10465
|
+
input.workspaceId,
|
|
10466
|
+
input.projectId,
|
|
10467
|
+
input.roomId,
|
|
10468
|
+
JSON.stringify(input.author),
|
|
10469
|
+
JSON.stringify(input.content),
|
|
10470
|
+
JSON.stringify(input.mentions),
|
|
10471
|
+
input.replyToMessageId ?? null,
|
|
10472
|
+
input.source,
|
|
10473
|
+
input.sourceId ?? null,
|
|
10474
|
+
input.idempotencyKey ?? null
|
|
10475
|
+
];
|
|
10476
|
+
}
|
|
10477
|
+
};
|
|
10478
|
+
|
|
9387
10479
|
// src/createPgStoreConfig.ts
|
|
9388
10480
|
async function createPgStoreConfig(connectionString) {
|
|
9389
|
-
const pool = new
|
|
10481
|
+
const pool = new import_pg26.Pool({ connectionString });
|
|
9390
10482
|
const mm = new MigrationManager(pool);
|
|
9391
10483
|
mm.register(createThreadsTable);
|
|
9392
10484
|
mm.register(createScheduledTasksTable);
|
|
@@ -9443,6 +10535,9 @@ async function createPgStoreConfig(connectionString) {
|
|
|
9443
10535
|
mm.register(createAgentWebAppsTable);
|
|
9444
10536
|
mm.register(createCapabilityBundlesTable);
|
|
9445
10537
|
mm.register(addTaskWorkItemPendingIndexesMigration);
|
|
10538
|
+
mm.register(createProjectRoomTables);
|
|
10539
|
+
mm.register(addTrustedRunContextColumn);
|
|
10540
|
+
mm.register(addProjectLifecycleEventIndex);
|
|
9446
10541
|
await mm.migrate();
|
|
9447
10542
|
const checkpoint = import_langgraph_checkpoint_postgres.PostgresSaver.fromConnString(connectionString);
|
|
9448
10543
|
checkpoint.setup().catch((err) => {
|
|
@@ -9476,13 +10571,17 @@ async function createPgStoreConfig(connectionString) {
|
|
|
9476
10571
|
menu: new MenuStore(opts),
|
|
9477
10572
|
sharedResource: new PostgresSharedResourceStore(opts),
|
|
9478
10573
|
collection: new PostgreSQLCollectionStore(opts),
|
|
10574
|
+
projectRoom: new PostgreSQLProjectRoomStore(opts),
|
|
10575
|
+
projectMembership: new PostgreSQLProjectMembershipStore(opts),
|
|
10576
|
+
projectBotMembership: new PostgreSQLProjectBotMembershipStore(opts),
|
|
10577
|
+
projectRoomMessage: new PostgreSQLProjectRoomMessageStore(opts),
|
|
9479
10578
|
vectorStoreProvider: new PGVectorStoreProvider(pool, connectionString),
|
|
9480
10579
|
checkpoint
|
|
9481
10580
|
};
|
|
9482
10581
|
}
|
|
9483
10582
|
|
|
9484
10583
|
// src/stores/PostgreSQLSkillStore.ts
|
|
9485
|
-
var
|
|
10584
|
+
var import_pg27 = require("pg");
|
|
9486
10585
|
var PostgreSQLSkillStore = class {
|
|
9487
10586
|
constructor(options) {
|
|
9488
10587
|
this.initialized = false;
|
|
@@ -9495,9 +10594,9 @@ var PostgreSQLSkillStore = class {
|
|
|
9495
10594
|
return;
|
|
9496
10595
|
}
|
|
9497
10596
|
if (typeof options.poolConfig === "string") {
|
|
9498
|
-
this.pool = new
|
|
10597
|
+
this.pool = new import_pg27.Pool({ connectionString: options.poolConfig });
|
|
9499
10598
|
} else if (options.poolConfig) {
|
|
9500
|
-
this.pool = new
|
|
10599
|
+
this.pool = new import_pg27.Pool(options.poolConfig);
|
|
9501
10600
|
} else {
|
|
9502
10601
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9503
10602
|
}
|
|
@@ -9798,7 +10897,7 @@ var PostgreSQLSkillStore = class {
|
|
|
9798
10897
|
};
|
|
9799
10898
|
|
|
9800
10899
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
9801
|
-
var
|
|
10900
|
+
var import_pg28 = require("pg");
|
|
9802
10901
|
var ChannelIdentityMappingStore = class {
|
|
9803
10902
|
constructor(options) {
|
|
9804
10903
|
this.initialized = false;
|
|
@@ -9810,7 +10909,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
9810
10909
|
this.initialized = true;
|
|
9811
10910
|
return;
|
|
9812
10911
|
}
|
|
9813
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
10912
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg28.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg28.Pool(options.poolConfig) : (() => {
|
|
9814
10913
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9815
10914
|
})();
|
|
9816
10915
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -10032,6 +11131,8 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
10032
11131
|
0 && (module.exports = {
|
|
10033
11132
|
ChannelBindingStore,
|
|
10034
11133
|
ChannelIdentityMappingStore,
|
|
11134
|
+
DuplicateProjectMembershipError,
|
|
11135
|
+
DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
10035
11136
|
MenuStore,
|
|
10036
11137
|
MigrationManager,
|
|
10037
11138
|
PGVectorStoreProvider,
|
|
@@ -10047,9 +11148,14 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
10047
11148
|
PostgreSQLEvalStore,
|
|
10048
11149
|
PostgreSQLMcpServerConfigStore,
|
|
10049
11150
|
PostgreSQLMetricsServerConfigStore,
|
|
11151
|
+
PostgreSQLProjectBotMembershipStore,
|
|
11152
|
+
PostgreSQLProjectMembershipStore,
|
|
11153
|
+
PostgreSQLProjectRoomMessageStore,
|
|
11154
|
+
PostgreSQLProjectRoomStore,
|
|
10050
11155
|
PostgreSQLProjectStore,
|
|
10051
11156
|
PostgreSQLScheduleStorage,
|
|
10052
11157
|
PostgreSQLSkillStore,
|
|
11158
|
+
PostgreSQLTaskWorkItemStore,
|
|
10053
11159
|
PostgreSQLTenantStore,
|
|
10054
11160
|
PostgreSQLThreadStore,
|
|
10055
11161
|
PostgreSQLUserStore,
|
|
@@ -10057,6 +11163,9 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
10057
11163
|
PostgreSQLWorkflowTrackingStore,
|
|
10058
11164
|
PostgreSQLWorkspaceStore,
|
|
10059
11165
|
PostgresSharedResourceStore,
|
|
11166
|
+
ProjectBotMembershipIdConflictError,
|
|
11167
|
+
ProjectMembershipIdConflictError,
|
|
11168
|
+
ProjectRoomMessageIdConflictError,
|
|
10060
11169
|
ThreadMessageQueueStore,
|
|
10061
11170
|
addAssistantTenantId,
|
|
10062
11171
|
addEnvToEvalRuns,
|
|
@@ -10094,6 +11203,7 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
10094
11203
|
createMetricsConfigsTable,
|
|
10095
11204
|
createPGVectorStoreProvider,
|
|
10096
11205
|
createPgStoreConfig,
|
|
11206
|
+
createProjectRoomTables,
|
|
10097
11207
|
createProjectsTable,
|
|
10098
11208
|
createScheduledTasksTable,
|
|
10099
11209
|
createSharedResourcesTable,
|