@axiom-lattice/pg-stores 3.1.0 → 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 +18 -0
- package/dist/index.d.mts +283 -23
- package/dist/index.d.ts +283 -23
- package/dist/index.js +1867 -224
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1854 -217
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/ChannelBindingStore.test.ts +122 -0
- package/src/__tests__/PostgreSQLCapabilityBundleStore.migrations.test.ts +67 -0
- package/src/__tests__/PostgreSQLCapabilityBundleStore.test.ts +383 -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 +107 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.migrations.test.ts +62 -0
- package/src/__tests__/PostgreSQLTaskWorkItemStore.test.ts +162 -1
- package/src/__tests__/ThreadMessageQueueStore.migrations.test.ts +63 -0
- package/src/__tests__/ThreadMessageQueueStore.test.ts +209 -4
- package/src/__tests__/add_workspace_project_to_queue.test.ts +15 -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 +25 -1
- package/src/index.ts +13 -0
- package/src/migrations/add_trusted_run_context_column.ts +18 -0
- package/src/migrations/capability_bundle_migration.ts +20 -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 +45 -1
- package/src/stores/ChannelBindingStore.ts +99 -59
- package/src/stores/PostgreSQLCapabilityBundleStore.ts +306 -0
- 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/PostgreSQLProjectStore.ts +230 -50
- package/src/stores/PostgreSQLTaskStore.ts +89 -3
- package/src/stores/PostgreSQLTaskWorkItemStore.ts +198 -8
- package/src/stores/ThreadMessageQueueStore.ts +130 -32
package/dist/index.js
CHANGED
|
@@ -32,13 +32,16 @@ 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,
|
|
44
|
+
PostgreSQLCapabilityBundleStore: () => PostgreSQLCapabilityBundleStore,
|
|
42
45
|
PostgreSQLChannelInstallationStore: () => PostgreSQLChannelInstallationStore,
|
|
43
46
|
PostgreSQLCollectionStore: () => PostgreSQLCollectionStore,
|
|
44
47
|
PostgreSQLConnectionStore: () => PostgreSQLConnectionStore,
|
|
@@ -46,9 +49,14 @@ __export(index_exports, {
|
|
|
46
49
|
PostgreSQLEvalStore: () => PostgreSQLEvalStore,
|
|
47
50
|
PostgreSQLMcpServerConfigStore: () => PostgreSQLMcpServerConfigStore,
|
|
48
51
|
PostgreSQLMetricsServerConfigStore: () => PostgreSQLMetricsServerConfigStore,
|
|
52
|
+
PostgreSQLProjectBotMembershipStore: () => PostgreSQLProjectBotMembershipStore,
|
|
53
|
+
PostgreSQLProjectMembershipStore: () => PostgreSQLProjectMembershipStore,
|
|
54
|
+
PostgreSQLProjectRoomMessageStore: () => PostgreSQLProjectRoomMessageStore,
|
|
55
|
+
PostgreSQLProjectRoomStore: () => PostgreSQLProjectRoomStore,
|
|
49
56
|
PostgreSQLProjectStore: () => PostgreSQLProjectStore,
|
|
50
57
|
PostgreSQLScheduleStorage: () => PostgreSQLScheduleStorage,
|
|
51
58
|
PostgreSQLSkillStore: () => PostgreSQLSkillStore,
|
|
59
|
+
PostgreSQLTaskWorkItemStore: () => PostgreSQLTaskWorkItemStore,
|
|
52
60
|
PostgreSQLTenantStore: () => PostgreSQLTenantStore,
|
|
53
61
|
PostgreSQLThreadStore: () => PostgreSQLThreadStore,
|
|
54
62
|
PostgreSQLUserStore: () => PostgreSQLUserStore,
|
|
@@ -56,6 +64,9 @@ __export(index_exports, {
|
|
|
56
64
|
PostgreSQLWorkflowTrackingStore: () => PostgreSQLWorkflowTrackingStore,
|
|
57
65
|
PostgreSQLWorkspaceStore: () => PostgreSQLWorkspaceStore,
|
|
58
66
|
PostgresSharedResourceStore: () => PostgresSharedResourceStore,
|
|
67
|
+
ProjectBotMembershipIdConflictError: () => ProjectBotMembershipIdConflictError,
|
|
68
|
+
ProjectMembershipIdConflictError: () => ProjectMembershipIdConflictError,
|
|
69
|
+
ProjectRoomMessageIdConflictError: () => ProjectRoomMessageIdConflictError,
|
|
59
70
|
ThreadMessageQueueStore: () => ThreadMessageQueueStore,
|
|
60
71
|
addAssistantTenantId: () => addAssistantTenantId,
|
|
61
72
|
addEnvToEvalRuns: () => addEnvToEvalRuns,
|
|
@@ -77,6 +88,7 @@ __export(index_exports, {
|
|
|
77
88
|
createA2AApiKeysTable: () => createA2AApiKeysTable,
|
|
78
89
|
createAgentWebAppsTable: () => createAgentWebAppsTable,
|
|
79
90
|
createAssistantsTable: () => createAssistantsTable,
|
|
91
|
+
createCapabilityBundlesTable: () => createCapabilityBundlesTable,
|
|
80
92
|
createChannelBindingsTable: () => createChannelBindingsTable,
|
|
81
93
|
createChannelIdentityMappingTables: () => createChannelIdentityMappingTables,
|
|
82
94
|
createChannelInstallationsTable: () => createChannelInstallationsTable,
|
|
@@ -92,6 +104,7 @@ __export(index_exports, {
|
|
|
92
104
|
createMetricsConfigsTable: () => createMetricsConfigsTable,
|
|
93
105
|
createPGVectorStoreProvider: () => createPGVectorStoreProvider,
|
|
94
106
|
createPgStoreConfig: () => createPgStoreConfig,
|
|
107
|
+
createProjectRoomTables: () => createProjectRoomTables,
|
|
95
108
|
createProjectsTable: () => createProjectsTable,
|
|
96
109
|
createScheduledTasksTable: () => createScheduledTasksTable,
|
|
97
110
|
createSharedResourcesTable: () => createSharedResourcesTable,
|
|
@@ -109,10 +122,10 @@ __export(index_exports, {
|
|
|
109
122
|
safeParse: () => safeParse
|
|
110
123
|
});
|
|
111
124
|
module.exports = __toCommonJS(index_exports);
|
|
112
|
-
var
|
|
125
|
+
var import_pg29 = require("pg");
|
|
113
126
|
|
|
114
127
|
// src/createPgStoreConfig.ts
|
|
115
|
-
var
|
|
128
|
+
var import_pg26 = require("pg");
|
|
116
129
|
|
|
117
130
|
// src/migrations/migration.ts
|
|
118
131
|
var MigrationManager = class {
|
|
@@ -143,7 +156,8 @@ var MigrationManager = class {
|
|
|
143
156
|
const tableExists = await client.query(`
|
|
144
157
|
SELECT EXISTS (
|
|
145
158
|
SELECT FROM information_schema.tables
|
|
146
|
-
WHERE
|
|
159
|
+
WHERE table_schema = current_schema()
|
|
160
|
+
AND table_name = 'lattice_schema_migrations'
|
|
147
161
|
)
|
|
148
162
|
`);
|
|
149
163
|
if (!tableExists.rows[0].exists) {
|
|
@@ -2382,6 +2396,7 @@ var PostgreSQLWorkspaceStore = class {
|
|
|
2382
2396
|
|
|
2383
2397
|
// src/stores/PostgreSQLProjectStore.ts
|
|
2384
2398
|
var import_pg7 = require("pg");
|
|
2399
|
+
var import_protocols = require("@axiom-lattice/protocols");
|
|
2385
2400
|
|
|
2386
2401
|
// src/migrations/project_migrations.ts
|
|
2387
2402
|
var createProjectsTable = {
|
|
@@ -2597,34 +2612,62 @@ var PostgreSQLProjectStore = class {
|
|
|
2597
2612
|
* Create a new project
|
|
2598
2613
|
*/
|
|
2599
2614
|
async createProject(tenantId, workspaceId, id, data) {
|
|
2615
|
+
(0, import_protocols.assertGenericProjectConfig)(data.config);
|
|
2600
2616
|
await this.ensureInitialized();
|
|
2601
2617
|
const now = /* @__PURE__ */ new Date();
|
|
2602
2618
|
const kind = data.kind || "business";
|
|
2603
|
-
await this.pool.
|
|
2604
|
-
|
|
2605
|
-
|
|
2619
|
+
const client = await this.pool.connect();
|
|
2620
|
+
try {
|
|
2621
|
+
await client.query("BEGIN");
|
|
2622
|
+
await client.query(
|
|
2623
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2624
|
+
[tenantId]
|
|
2625
|
+
);
|
|
2626
|
+
await client.query(
|
|
2627
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2628
|
+
[id, tenantId]
|
|
2629
|
+
);
|
|
2630
|
+
const existing = await client.query(
|
|
2631
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2632
|
+
[id, tenantId]
|
|
2633
|
+
);
|
|
2634
|
+
const ids = existing.rows[0]?.config?.capabilityBundleIds;
|
|
2635
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2636
|
+
if (bundleIds.length > 0) {
|
|
2637
|
+
await client.query(
|
|
2638
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2639
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2640
|
+
ORDER BY bundle_id`,
|
|
2641
|
+
[tenantId, bundleIds]
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
const result = await client.query(
|
|
2645
|
+
`INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at)
|
|
2606
2646
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
2607
2647
|
ON CONFLICT (id, tenant_id) DO UPDATE SET
|
|
2608
2648
|
workspace_id = EXCLUDED.workspace_id,
|
|
2609
2649
|
name = EXCLUDED.name,
|
|
2610
2650
|
description = EXCLUDED.description,
|
|
2611
|
-
config =
|
|
2651
|
+
config = CASE
|
|
2652
|
+
WHEN lattice_projects.config ? 'capabilityBundleIds'
|
|
2653
|
+
THEN COALESCE(EXCLUDED.config, '{}'::jsonb)
|
|
2654
|
+
|| jsonb_build_object('capabilityBundleIds', lattice_projects.config->'capabilityBundleIds')
|
|
2655
|
+
ELSE EXCLUDED.config
|
|
2656
|
+
END,
|
|
2612
2657
|
kind = EXCLUDED.kind,
|
|
2613
2658
|
updated_at = EXCLUDED.updated_at
|
|
2659
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at
|
|
2614
2660
|
`,
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
createdAt: now,
|
|
2626
|
-
updatedAt: now
|
|
2627
|
-
};
|
|
2661
|
+
[id, tenantId, workspaceId, data.name, data.description || null, data.config || null, kind, now, now]
|
|
2662
|
+
);
|
|
2663
|
+
await client.query("COMMIT");
|
|
2664
|
+
return this.mapRowToProject(result.rows[0]);
|
|
2665
|
+
} catch (error) {
|
|
2666
|
+
await client.query("ROLLBACK");
|
|
2667
|
+
throw error;
|
|
2668
|
+
} finally {
|
|
2669
|
+
client.release();
|
|
2670
|
+
}
|
|
2628
2671
|
}
|
|
2629
2672
|
/**
|
|
2630
2673
|
* Update an existing project
|
|
@@ -2634,11 +2677,8 @@ var PostgreSQLProjectStore = class {
|
|
|
2634
2677
|
* overwrites the existing config. To preserve the current config, omit this field.
|
|
2635
2678
|
*/
|
|
2636
2679
|
async updateProject(tenantId, id, updates) {
|
|
2680
|
+
(0, import_protocols.assertGenericProjectConfig)(updates.config);
|
|
2637
2681
|
await this.ensureInitialized();
|
|
2638
|
-
const existing = await this.getProjectById(tenantId, id);
|
|
2639
|
-
if (!existing) {
|
|
2640
|
-
return null;
|
|
2641
|
-
}
|
|
2642
2682
|
const updateFields = [];
|
|
2643
2683
|
const updateValues = [];
|
|
2644
2684
|
let paramIndex = 1;
|
|
@@ -2651,43 +2691,190 @@ var PostgreSQLProjectStore = class {
|
|
|
2651
2691
|
updateValues.push(updates.description || null);
|
|
2652
2692
|
}
|
|
2653
2693
|
if (updates.config !== void 0) {
|
|
2654
|
-
|
|
2655
|
-
|
|
2694
|
+
const configParam = `$${paramIndex++}`;
|
|
2695
|
+
updateFields.push(`config = CASE
|
|
2696
|
+
WHEN config ? 'capabilityBundleIds'
|
|
2697
|
+
THEN COALESCE(${configParam}::jsonb, '{}'::jsonb)
|
|
2698
|
+
|| jsonb_build_object('capabilityBundleIds', config->'capabilityBundleIds')
|
|
2699
|
+
ELSE ${configParam}::jsonb
|
|
2700
|
+
END`);
|
|
2701
|
+
updateValues.push(updates.config);
|
|
2656
2702
|
}
|
|
2657
2703
|
if (updates.kind !== void 0) {
|
|
2658
2704
|
updateFields.push(`kind = $${paramIndex++}`);
|
|
2659
2705
|
updateValues.push(updates.kind);
|
|
2660
2706
|
}
|
|
2661
2707
|
if (updateFields.length === 0) {
|
|
2662
|
-
return
|
|
2708
|
+
return await this.getProjectById(tenantId, id);
|
|
2663
2709
|
}
|
|
2664
2710
|
updateFields.push(`updated_at = $${paramIndex++}`);
|
|
2665
2711
|
updateValues.push(/* @__PURE__ */ new Date());
|
|
2666
|
-
updateValues.push(id);
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2712
|
+
updateValues.push(id, tenantId);
|
|
2713
|
+
const client = await this.pool.connect();
|
|
2714
|
+
try {
|
|
2715
|
+
await client.query("BEGIN");
|
|
2716
|
+
await client.query(
|
|
2717
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2718
|
+
[tenantId]
|
|
2719
|
+
);
|
|
2720
|
+
await client.query(
|
|
2721
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2722
|
+
[id, tenantId]
|
|
2723
|
+
);
|
|
2724
|
+
const existing = await client.query(
|
|
2725
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2726
|
+
[id, tenantId]
|
|
2727
|
+
);
|
|
2728
|
+
if (!existing.rows[0]) {
|
|
2729
|
+
await client.query("COMMIT");
|
|
2730
|
+
return null;
|
|
2731
|
+
}
|
|
2732
|
+
const ids = existing.rows[0].config?.capabilityBundleIds;
|
|
2733
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2734
|
+
if (bundleIds.length > 0) {
|
|
2735
|
+
await client.query(
|
|
2736
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2737
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2738
|
+
ORDER BY bundle_id`,
|
|
2739
|
+
[tenantId, bundleIds]
|
|
2740
|
+
);
|
|
2741
|
+
}
|
|
2742
|
+
const result = await client.query(
|
|
2743
|
+
`UPDATE lattice_projects SET ${updateFields.join(", ")}
|
|
2744
|
+
WHERE id = $${paramIndex} AND tenant_id = $${paramIndex + 1}
|
|
2745
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at`,
|
|
2746
|
+
updateValues
|
|
2747
|
+
);
|
|
2748
|
+
await client.query("COMMIT");
|
|
2749
|
+
return this.mapRowToProject(result.rows[0]);
|
|
2750
|
+
} catch (error) {
|
|
2751
|
+
await client.query("ROLLBACK");
|
|
2752
|
+
throw error;
|
|
2753
|
+
} finally {
|
|
2754
|
+
client.release();
|
|
2755
|
+
}
|
|
2677
2756
|
}
|
|
2678
2757
|
/**
|
|
2679
2758
|
* Delete a project by ID
|
|
2680
2759
|
*/
|
|
2681
2760
|
async deleteProject(tenantId, id) {
|
|
2761
|
+
await this.ensureInitialized();
|
|
2762
|
+
const client = await this.pool.connect();
|
|
2763
|
+
try {
|
|
2764
|
+
await client.query("BEGIN");
|
|
2765
|
+
await client.query(
|
|
2766
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2767
|
+
[tenantId]
|
|
2768
|
+
);
|
|
2769
|
+
await client.query(
|
|
2770
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2771
|
+
[id, tenantId]
|
|
2772
|
+
);
|
|
2773
|
+
const selected = await client.query(
|
|
2774
|
+
"SELECT config FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2775
|
+
[id, tenantId]
|
|
2776
|
+
);
|
|
2777
|
+
if (!selected.rows[0]) {
|
|
2778
|
+
await client.query("COMMIT");
|
|
2779
|
+
return false;
|
|
2780
|
+
}
|
|
2781
|
+
const ids = selected.rows[0].config?.capabilityBundleIds;
|
|
2782
|
+
const bundleIds = Array.isArray(ids) && ids.every((bundleId) => typeof bundleId === "string") ? [...ids].sort() : [];
|
|
2783
|
+
if (bundleIds.length > 0) {
|
|
2784
|
+
await client.query(
|
|
2785
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2786
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2787
|
+
ORDER BY bundle_id`,
|
|
2788
|
+
[tenantId, bundleIds]
|
|
2789
|
+
);
|
|
2790
|
+
}
|
|
2791
|
+
const result = await client.query(
|
|
2792
|
+
"DELETE FROM lattice_projects WHERE id = $1 AND tenant_id = $2",
|
|
2793
|
+
[id, tenantId]
|
|
2794
|
+
);
|
|
2795
|
+
await client.query("COMMIT");
|
|
2796
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
2797
|
+
} catch (error) {
|
|
2798
|
+
await client.query("ROLLBACK");
|
|
2799
|
+
throw error;
|
|
2800
|
+
} finally {
|
|
2801
|
+
client.release();
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
async updateCapabilityBundleIds(tenantId, projectId, bundleIds, expectedRevisions = {}) {
|
|
2805
|
+
await this.ensureInitialized();
|
|
2806
|
+
const client = await this.pool.connect();
|
|
2807
|
+
try {
|
|
2808
|
+
await client.query("BEGIN");
|
|
2809
|
+
await client.query(
|
|
2810
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
2811
|
+
[tenantId]
|
|
2812
|
+
);
|
|
2813
|
+
await client.query(
|
|
2814
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($2 || ':project:' || $1, 0))",
|
|
2815
|
+
[projectId, tenantId]
|
|
2816
|
+
);
|
|
2817
|
+
await client.query(
|
|
2818
|
+
"SELECT id FROM lattice_projects WHERE id = $1 AND tenant_id = $2 FOR UPDATE",
|
|
2819
|
+
[projectId, tenantId]
|
|
2820
|
+
);
|
|
2821
|
+
if (bundleIds.length > 0) {
|
|
2822
|
+
await client.query(
|
|
2823
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || bundle_id, 0))
|
|
2824
|
+
FROM unnest($2::text[]) AS bundle_id
|
|
2825
|
+
ORDER BY bundle_id`,
|
|
2826
|
+
[tenantId, [...bundleIds].sort()]
|
|
2827
|
+
);
|
|
2828
|
+
if (Object.keys(expectedRevisions).length > 0) {
|
|
2829
|
+
const revisions = await client.query("SELECT id, updated_at::text AS updated_at FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = ANY($2::uuid[]) FOR UPDATE", [tenantId, bundleIds]);
|
|
2830
|
+
if (revisions.rows.some((bundle) => expectedRevisions[bundle.id] !== void 0 && expectedRevisions[bundle.id] !== bundle.updated_at)) {
|
|
2831
|
+
await client.query("ROLLBACK");
|
|
2832
|
+
return { status: "bundle_conflict" };
|
|
2833
|
+
}
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
const result = await client.query(
|
|
2837
|
+
`UPDATE lattice_projects
|
|
2838
|
+
SET config = COALESCE(config, '{}'::jsonb) || jsonb_build_object('capabilityBundleIds', $3::jsonb), updated_at = NOW()
|
|
2839
|
+
WHERE id = $1 AND tenant_id = $2
|
|
2840
|
+
AND NOT EXISTS (
|
|
2841
|
+
SELECT 1 FROM unnest($4::uuid[]) AS bundle_id
|
|
2842
|
+
WHERE NOT EXISTS (
|
|
2843
|
+
SELECT 1 FROM lattice_capability_bundles
|
|
2844
|
+
WHERE tenant_id = $2 AND id = bundle_id
|
|
2845
|
+
)
|
|
2846
|
+
)
|
|
2847
|
+
RETURNING id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at`,
|
|
2848
|
+
[projectId, tenantId, JSON.stringify(bundleIds), bundleIds]
|
|
2849
|
+
);
|
|
2850
|
+
if (result.rows[0]) {
|
|
2851
|
+
await client.query("COMMIT");
|
|
2852
|
+
return { status: "updated", project: this.mapRowToProject(result.rows[0]) };
|
|
2853
|
+
}
|
|
2854
|
+
const project = await client.query(
|
|
2855
|
+
"SELECT id FROM lattice_projects WHERE id = $1 AND tenant_id = $2",
|
|
2856
|
+
[projectId, tenantId]
|
|
2857
|
+
);
|
|
2858
|
+
await client.query("COMMIT");
|
|
2859
|
+
return project.rows.length > 0 ? { status: "bundle_not_found" } : { status: "project_not_found" };
|
|
2860
|
+
} catch (error) {
|
|
2861
|
+
await client.query("ROLLBACK");
|
|
2862
|
+
throw error;
|
|
2863
|
+
} finally {
|
|
2864
|
+
client.release();
|
|
2865
|
+
}
|
|
2866
|
+
}
|
|
2867
|
+
async isCapabilityBundleReferenced(tenantId, bundleId) {
|
|
2682
2868
|
await this.ensureInitialized();
|
|
2683
2869
|
const result = await this.pool.query(
|
|
2684
|
-
`
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
2870
|
+
`SELECT 1 AS exists FROM lattice_projects
|
|
2871
|
+
WHERE tenant_id = $1
|
|
2872
|
+
AND jsonb_typeof(config->'capabilityBundleIds') = 'array'
|
|
2873
|
+
AND COALESCE(config->'capabilityBundleIds', '[]'::jsonb) ? $2
|
|
2874
|
+
LIMIT 1`,
|
|
2875
|
+
[tenantId, bundleId]
|
|
2689
2876
|
);
|
|
2690
|
-
return result.
|
|
2877
|
+
return result.rows.length > 0;
|
|
2691
2878
|
}
|
|
2692
2879
|
};
|
|
2693
2880
|
|
|
@@ -4967,6 +5154,7 @@ var PostgreSQLEvalStore = class {
|
|
|
4967
5154
|
// src/stores/ThreadMessageQueueStore.ts
|
|
4968
5155
|
var import_pg13 = require("pg");
|
|
4969
5156
|
var import_crypto = __toESM(require("crypto"));
|
|
5157
|
+
var import_protocols2 = require("@axiom-lattice/protocols");
|
|
4970
5158
|
|
|
4971
5159
|
// src/migrations/thread_message_queue_migrations.ts
|
|
4972
5160
|
var createThreadMessageQueueTable = {
|
|
@@ -5103,6 +5291,23 @@ var addWorkspaceProjectToQueue = {
|
|
|
5103
5291
|
}
|
|
5104
5292
|
};
|
|
5105
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
|
+
|
|
5106
5311
|
// src/stores/ThreadMessageQueueStore.ts
|
|
5107
5312
|
var ThreadMessageQueueStore = class {
|
|
5108
5313
|
constructor(options) {
|
|
@@ -5128,6 +5333,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5128
5333
|
this.migrationManager.register(addCustomRunConfigColumn);
|
|
5129
5334
|
this.migrationManager.register(alterMessageQueueIdColumn);
|
|
5130
5335
|
this.migrationManager.register(addWorkspaceProjectToQueue);
|
|
5336
|
+
this.migrationManager.register(addTrustedRunContextColumn);
|
|
5131
5337
|
if (options.autoMigrate !== false) {
|
|
5132
5338
|
this.initialize().catch((error) => {
|
|
5133
5339
|
console.error("Failed to initialize ThreadMessageQueueStore:", error);
|
|
@@ -5157,6 +5363,7 @@ var ThreadMessageQueueStore = class {
|
|
|
5157
5363
|
* Add message to queue
|
|
5158
5364
|
*/
|
|
5159
5365
|
async addMessage(params) {
|
|
5366
|
+
const trusted = validateQueueTrust(params);
|
|
5160
5367
|
const { threadId, tenantId, assistantId, workspaceId, projectId, content, type = "human", priority = 0, command, custom_run_config, id } = params;
|
|
5161
5368
|
const seqResult = await this.pool.query(
|
|
5162
5369
|
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq
|
|
@@ -5167,18 +5374,58 @@ var ThreadMessageQueueStore = class {
|
|
|
5167
5374
|
const nextSeq = seqResult.rows[0].next_seq;
|
|
5168
5375
|
const result = await this.pool.query(
|
|
5169
5376
|
`INSERT INTO lattice_thread_message_queue
|
|
5170
|
-
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5171
|
-
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)
|
|
5172
5379
|
RETURNING *`,
|
|
5173
|
-
[id || import_crypto.default.randomUUID(), threadId, tenantId, assistantId, workspaceId
|
|
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]
|
|
5174
5381
|
);
|
|
5175
5382
|
return this.rowToMessage(result.rows[0]);
|
|
5176
5383
|
}
|
|
5384
|
+
async addMessageIfCapacity(params, maxSize) {
|
|
5385
|
+
const trusted = validateQueueTrust(params);
|
|
5386
|
+
if (maxSize === Infinity) {
|
|
5387
|
+
await this.addMessage(params);
|
|
5388
|
+
return true;
|
|
5389
|
+
}
|
|
5390
|
+
const client = await this.pool.connect();
|
|
5391
|
+
try {
|
|
5392
|
+
await client.query("BEGIN");
|
|
5393
|
+
const scope = { tenantId: params.tenantId, assistantId: params.assistantId, workspaceId: params.workspaceId, projectId: params.projectId };
|
|
5394
|
+
const lockKey = `${params.tenantId}:${params.assistantId}:${params.threadId}:${params.workspaceId ?? ""}:${params.projectId ?? ""}`;
|
|
5395
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtext($1))", [lockKey]);
|
|
5396
|
+
const filter = scopeClause(scope, 2);
|
|
5397
|
+
const count = await client.query(
|
|
5398
|
+
`SELECT COUNT(*) as count FROM lattice_thread_message_queue WHERE thread_id = $1 AND status = 'pending'${filter.sql}`,
|
|
5399
|
+
[params.threadId, ...filter.params]
|
|
5400
|
+
);
|
|
5401
|
+
if (parseInt(count.rows[0].count, 10) >= maxSize) {
|
|
5402
|
+
await client.query("ROLLBACK");
|
|
5403
|
+
return false;
|
|
5404
|
+
}
|
|
5405
|
+
const seq = await client.query(
|
|
5406
|
+
`SELECT COALESCE(MAX(sequence_order), 0) + 1 as next_seq FROM lattice_thread_message_queue WHERE thread_id = $1`,
|
|
5407
|
+
[params.threadId]
|
|
5408
|
+
);
|
|
5409
|
+
const result = await client.query(
|
|
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]
|
|
5413
|
+
);
|
|
5414
|
+
await client.query("COMMIT");
|
|
5415
|
+
return Boolean(result.rows[0]);
|
|
5416
|
+
} catch (error) {
|
|
5417
|
+
await client.query("ROLLBACK");
|
|
5418
|
+
throw error;
|
|
5419
|
+
} finally {
|
|
5420
|
+
client.release();
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5177
5423
|
/**
|
|
5178
5424
|
* Add message at head of queue (high priority, e.g., STEER/Command messages)
|
|
5179
5425
|
* Uses priority=100 to ensure message is processed first
|
|
5180
5426
|
*/
|
|
5181
5427
|
async addMessageAtHead(params) {
|
|
5428
|
+
const trusted = validateQueueTrust(params);
|
|
5182
5429
|
const { threadId, tenantId, assistantId, workspaceId, projectId, content, type = "human", command, custom_run_config, id } = params;
|
|
5183
5430
|
const resolvedTenantId = tenantId;
|
|
5184
5431
|
const resolvedAssistantId = assistantId;
|
|
@@ -5191,45 +5438,48 @@ var ThreadMessageQueueStore = class {
|
|
|
5191
5438
|
const nextSeq = seqResult.rows[0].next_seq;
|
|
5192
5439
|
const result = await this.pool.query(
|
|
5193
5440
|
`INSERT INTO lattice_thread_message_queue
|
|
5194
|
-
(id, thread_id, tenant_id, assistant_id, workspace_id, project_id, message_content, message_type, sequence_order, priority, command, custom_run_config)
|
|
5195
|
-
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)
|
|
5196
5443
|
RETURNING *`,
|
|
5197
|
-
[id || import_crypto.default.randomUUID(), threadId, resolvedTenantId, resolvedAssistantId, workspaceId
|
|
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]
|
|
5198
5445
|
);
|
|
5199
5446
|
return this.rowToMessage(result.rows[0]);
|
|
5200
5447
|
}
|
|
5201
5448
|
/**
|
|
5202
5449
|
* Get pending messages for thread
|
|
5203
5450
|
*/
|
|
5204
|
-
async getPendingMessages(threadId) {
|
|
5451
|
+
async getPendingMessages(threadId, scope) {
|
|
5452
|
+
const filter = scopeClause(scope, 2);
|
|
5205
5453
|
const result = await this.pool.query(
|
|
5206
|
-
`SELECT * FROM lattice_thread_message_queue
|
|
5207
|
-
|
|
5454
|
+
`SELECT * FROM lattice_thread_message_queue
|
|
5455
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}
|
|
5208
5456
|
ORDER BY priority DESC, sequence_order ASC`,
|
|
5209
|
-
[threadId]
|
|
5457
|
+
[threadId, ...filter.params]
|
|
5210
5458
|
);
|
|
5211
5459
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5212
5460
|
}
|
|
5213
5461
|
/**
|
|
5214
5462
|
* Get processing messages for a thread
|
|
5215
5463
|
*/
|
|
5216
|
-
async getProcessingMessages(threadId) {
|
|
5464
|
+
async getProcessingMessages(threadId, scope) {
|
|
5465
|
+
const filter = scopeClause(scope, 2);
|
|
5217
5466
|
const result = await this.pool.query(
|
|
5218
5467
|
`SELECT * FROM lattice_thread_message_queue
|
|
5219
|
-
|
|
5468
|
+
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5220
5469
|
ORDER BY priority DESC, sequence_order ASC`,
|
|
5221
|
-
[threadId]
|
|
5470
|
+
[threadId, ...filter.params]
|
|
5222
5471
|
);
|
|
5223
5472
|
return result.rows.map((row) => this.rowToMessage(row));
|
|
5224
5473
|
}
|
|
5225
5474
|
/**
|
|
5226
5475
|
* Get queue size
|
|
5227
5476
|
*/
|
|
5228
|
-
async getQueueSize(threadId) {
|
|
5477
|
+
async getQueueSize(threadId, scope) {
|
|
5478
|
+
const filter = scopeClause(scope, 2);
|
|
5229
5479
|
const result = await this.pool.query(
|
|
5230
5480
|
`SELECT COUNT(*) as count FROM lattice_thread_message_queue
|
|
5231
|
-
|
|
5232
|
-
[threadId]
|
|
5481
|
+
WHERE thread_id = $1 AND status = 'pending'${filter.sql}`,
|
|
5482
|
+
[threadId, ...filter.params]
|
|
5233
5483
|
);
|
|
5234
5484
|
return parseInt(result.rows[0].count, 10);
|
|
5235
5485
|
}
|
|
@@ -5238,58 +5488,70 @@ var ThreadMessageQueueStore = class {
|
|
|
5238
5488
|
*/
|
|
5239
5489
|
async getThreadsWithPendingMessages() {
|
|
5240
5490
|
const result = await this.pool.query(
|
|
5241
|
-
`SELECT DISTINCT ON (thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id
|
|
5491
|
+
`SELECT DISTINCT ON (tenant_id, assistant_id, workspace_id, project_id, thread_id) tenant_id, assistant_id, thread_id, workspace_id, project_id
|
|
5242
5492
|
FROM lattice_thread_message_queue
|
|
5243
5493
|
WHERE status IN ('pending', 'processing')
|
|
5244
|
-
|
|
5494
|
+
ORDER BY tenant_id, assistant_id, workspace_id, project_id, thread_id`
|
|
5245
5495
|
);
|
|
5246
5496
|
return result.rows.map((row) => ({
|
|
5247
5497
|
tenantId: row.tenant_id,
|
|
5248
5498
|
assistantId: row.assistant_id,
|
|
5249
5499
|
threadId: row.thread_id,
|
|
5250
|
-
workspaceId: row.workspace_id
|
|
5251
|
-
projectId: row.project_id
|
|
5500
|
+
workspaceId: row.workspace_id,
|
|
5501
|
+
projectId: row.project_id
|
|
5252
5502
|
}));
|
|
5253
5503
|
}
|
|
5254
5504
|
/**
|
|
5255
5505
|
* Remove message
|
|
5256
5506
|
*/
|
|
5257
|
-
async removeMessage(messageId) {
|
|
5507
|
+
async removeMessage(messageId, scope) {
|
|
5508
|
+
const filter = scopeClause(scope, 2);
|
|
5258
5509
|
const result = await this.pool.query(
|
|
5259
|
-
`DELETE FROM lattice_thread_message_queue WHERE id = $1 RETURNING id`,
|
|
5260
|
-
[messageId]
|
|
5510
|
+
`DELETE FROM lattice_thread_message_queue WHERE id = $1${filter.sql} RETURNING id`,
|
|
5511
|
+
[messageId, ...filter.params]
|
|
5261
5512
|
);
|
|
5262
5513
|
return (result.rowCount ?? 0) > 0;
|
|
5263
5514
|
}
|
|
5264
5515
|
/**
|
|
5265
5516
|
* Clear all messages for thread
|
|
5266
5517
|
*/
|
|
5267
|
-
async clearMessages(threadId) {
|
|
5518
|
+
async clearMessages(threadId, scope) {
|
|
5519
|
+
const filter = scopeClause(scope, 2);
|
|
5268
5520
|
await this.pool.query(
|
|
5269
|
-
`DELETE FROM lattice_thread_message_queue WHERE thread_id = $1`,
|
|
5270
|
-
[threadId]
|
|
5521
|
+
`DELETE FROM lattice_thread_message_queue WHERE thread_id = $1${filter.sql}`,
|
|
5522
|
+
[threadId, ...filter.params]
|
|
5271
5523
|
);
|
|
5272
5524
|
}
|
|
5273
5525
|
/**
|
|
5274
5526
|
* Mark message as processing
|
|
5275
5527
|
*/
|
|
5276
|
-
async markProcessing(messageId) {
|
|
5528
|
+
async markProcessing(messageId, customRunConfig, scope) {
|
|
5529
|
+
if (customRunConfig !== void 0) {
|
|
5530
|
+
const filter2 = scopeClause(scope, 3);
|
|
5531
|
+
await this.pool.query(
|
|
5532
|
+
`UPDATE lattice_thread_message_queue SET status = 'processing', custom_run_config = $2 WHERE id = $1${filter2.sql}`,
|
|
5533
|
+
[messageId, JSON.stringify(customRunConfig), ...filter2.params]
|
|
5534
|
+
);
|
|
5535
|
+
return;
|
|
5536
|
+
}
|
|
5537
|
+
const filter = scopeClause(scope, 2);
|
|
5277
5538
|
await this.pool.query(
|
|
5278
|
-
`UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1`,
|
|
5279
|
-
[messageId]
|
|
5539
|
+
`UPDATE lattice_thread_message_queue SET status = 'processing' WHERE id = $1${filter.sql}`,
|
|
5540
|
+
[messageId, ...filter.params]
|
|
5280
5541
|
);
|
|
5281
5542
|
}
|
|
5282
5543
|
/**
|
|
5283
5544
|
* Reset all processing messages to pending state for a thread
|
|
5284
5545
|
* Returns the number of messages reset
|
|
5285
5546
|
*/
|
|
5286
|
-
async resetProcessingToPending(threadId) {
|
|
5547
|
+
async resetProcessingToPending(threadId, scope) {
|
|
5548
|
+
const filter = scopeClause(scope, 2);
|
|
5287
5549
|
const result = await this.pool.query(
|
|
5288
5550
|
`UPDATE lattice_thread_message_queue
|
|
5289
5551
|
SET status = 'pending'
|
|
5290
|
-
|
|
5552
|
+
WHERE thread_id = $1 AND status = 'processing'${filter.sql}
|
|
5291
5553
|
RETURNING id`,
|
|
5292
|
-
[threadId]
|
|
5554
|
+
[threadId, ...filter.params]
|
|
5293
5555
|
);
|
|
5294
5556
|
return result.rowCount ?? 0;
|
|
5295
5557
|
}
|
|
@@ -5302,13 +5564,34 @@ var ThreadMessageQueueStore = class {
|
|
|
5302
5564
|
createdAt: new Date(row.created_at),
|
|
5303
5565
|
priority: row.priority || 0,
|
|
5304
5566
|
command: row.command ? typeof row.command === "string" ? JSON.parse(row.command) : row.command : void 0,
|
|
5305
|
-
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)
|
|
5306
5570
|
};
|
|
5307
5571
|
}
|
|
5308
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
|
+
}
|
|
5579
|
+
function scopeClause(scope, start) {
|
|
5580
|
+
if (!scope) return { sql: "", params: [] };
|
|
5581
|
+
const params = [];
|
|
5582
|
+
const entries = ["tenantId", "assistantId", "workspaceId", "projectId"].map((key) => [key, scope[key]]);
|
|
5583
|
+
const sql = entries.map(([key, value]) => {
|
|
5584
|
+
const column = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
|
|
5585
|
+
if (value == null) return ` AND ${column} IS NULL`;
|
|
5586
|
+
params.push(value);
|
|
5587
|
+
return ` AND ${column} = $${start + params.length - 1}`;
|
|
5588
|
+
}).join("");
|
|
5589
|
+
return { sql, params };
|
|
5590
|
+
}
|
|
5309
5591
|
|
|
5310
5592
|
// src/stores/ChannelBindingStore.ts
|
|
5311
5593
|
var import_pg14 = require("pg");
|
|
5594
|
+
var import_protocols3 = require("@axiom-lattice/protocols");
|
|
5312
5595
|
|
|
5313
5596
|
// src/migrations/channel_bindings_migration.ts
|
|
5314
5597
|
var createChannelBindingsTable = {
|
|
@@ -5356,6 +5639,7 @@ var createChannelBindingsTable = {
|
|
|
5356
5639
|
};
|
|
5357
5640
|
|
|
5358
5641
|
// src/stores/ChannelBindingStore.ts
|
|
5642
|
+
var BINDING_SUBJECT_CONSTRAINT = "lattice_channel_bindings_channel_channel_installation_id_te_key";
|
|
5359
5643
|
var ChannelBindingStore = class {
|
|
5360
5644
|
constructor(options) {
|
|
5361
5645
|
this.initialized = false;
|
|
@@ -5412,80 +5696,91 @@ var ChannelBindingStore = class {
|
|
|
5412
5696
|
if (result.rows.length === 0) return null;
|
|
5413
5697
|
return this.mapRowToBinding(result.rows[0]);
|
|
5414
5698
|
}
|
|
5415
|
-
async
|
|
5699
|
+
async findById(tenantId, id) {
|
|
5416
5700
|
await this.ensureInitialized();
|
|
5417
5701
|
const result = await this.pool.query(
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
thread_mode, sender_display_name, sender_metadata, workspace_id, project_id)
|
|
5421
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
|
|
5422
|
-
RETURNING *`,
|
|
5423
|
-
[
|
|
5424
|
-
input.channel,
|
|
5425
|
-
input.channelInstallationId,
|
|
5426
|
-
input.tenantId,
|
|
5427
|
-
input.senderId,
|
|
5428
|
-
input.agentId,
|
|
5429
|
-
input.threadMode || "fixed",
|
|
5430
|
-
input.senderDisplayName || null,
|
|
5431
|
-
input.senderMetadata ? JSON.stringify(input.senderMetadata) : null,
|
|
5432
|
-
input.workspaceId || null,
|
|
5433
|
-
input.projectId || null
|
|
5434
|
-
]
|
|
5702
|
+
"SELECT * FROM lattice_channel_bindings WHERE id = $1 AND tenant_id = $2",
|
|
5703
|
+
[id, tenantId]
|
|
5435
5704
|
);
|
|
5436
|
-
return this.mapRowToBinding(result.rows[0]);
|
|
5705
|
+
return result.rows[0] ? this.mapRowToBinding(result.rows[0]) : null;
|
|
5437
5706
|
}
|
|
5438
|
-
async
|
|
5707
|
+
async findBySubject(params) {
|
|
5439
5708
|
await this.ensureInitialized();
|
|
5440
|
-
const
|
|
5441
|
-
`SELECT * FROM lattice_channel_bindings
|
|
5442
|
-
|
|
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]
|
|
5443
5713
|
);
|
|
5444
|
-
|
|
5445
|
-
|
|
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;
|
|
5446
5746
|
}
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
channel_installation_id: patch.channelInstallationId ?? row.channel_installation_id,
|
|
5451
|
-
sender_id: patch.senderId ?? row.sender_id,
|
|
5452
|
-
agent_id: patch.agentId ?? row.agent_id,
|
|
5453
|
-
thread_id: patch.threadId !== void 0 ? patch.threadId : row.thread_id,
|
|
5454
|
-
workspace_id: patch.workspaceId !== void 0 ? patch.workspaceId : row.workspace_id,
|
|
5455
|
-
project_id: patch.projectId !== void 0 ? patch.projectId : row.project_id,
|
|
5456
|
-
thread_mode: patch.threadMode ?? row.thread_mode,
|
|
5457
|
-
sender_display_name: patch.senderDisplayName !== void 0 ? patch.senderDisplayName : row.sender_display_name,
|
|
5458
|
-
sender_metadata: patch.senderMetadata !== void 0 ? patch.senderMetadata : row.sender_metadata,
|
|
5459
|
-
enabled: patch.enabled ?? row.enabled
|
|
5460
|
-
};
|
|
5747
|
+
}
|
|
5748
|
+
async update(tenantId, id, patch) {
|
|
5749
|
+
await this.ensureInitialized();
|
|
5461
5750
|
const result = await this.pool.query(
|
|
5462
5751
|
`UPDATE lattice_channel_bindings SET
|
|
5463
|
-
|
|
5464
|
-
|
|
5465
|
-
|
|
5466
|
-
|
|
5467
|
-
|
|
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
|
|
5468
5761
|
RETURNING *`,
|
|
5469
5762
|
[
|
|
5470
|
-
|
|
5471
|
-
|
|
5472
|
-
|
|
5473
|
-
|
|
5474
|
-
|
|
5475
|
-
|
|
5476
|
-
|
|
5477
|
-
|
|
5478
|
-
|
|
5479
|
-
|
|
5480
|
-
updated.enabled,
|
|
5481
|
-
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
|
|
5482
5773
|
]
|
|
5483
5774
|
);
|
|
5775
|
+
if (!result.rows[0]) throw new Error(`Binding ${id} not found`);
|
|
5484
5776
|
return this.mapRowToBinding(result.rows[0]);
|
|
5485
5777
|
}
|
|
5486
|
-
async delete(id) {
|
|
5778
|
+
async delete(tenantId, id) {
|
|
5487
5779
|
await this.ensureInitialized();
|
|
5488
|
-
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
|
+
);
|
|
5489
5784
|
}
|
|
5490
5785
|
async list(params) {
|
|
5491
5786
|
await this.ensureInitialized();
|
|
@@ -5504,6 +5799,14 @@ var ChannelBindingStore = class {
|
|
|
5504
5799
|
conditions.push(`channel_installation_id = $${idx++}`);
|
|
5505
5800
|
values.push(params.channelInstallationId);
|
|
5506
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
|
+
}
|
|
5507
5810
|
const limit = params.limit ?? 50;
|
|
5508
5811
|
const offset = params.offset ?? 0;
|
|
5509
5812
|
values.push(limit, offset);
|
|
@@ -5516,7 +5819,16 @@ var ChannelBindingStore = class {
|
|
|
5516
5819
|
);
|
|
5517
5820
|
return result.rows.map((r) => this.mapRowToBinding(r));
|
|
5518
5821
|
}
|
|
5519
|
-
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
|
+
}
|
|
5520
5832
|
const result = [];
|
|
5521
5833
|
for (const input of bindings) {
|
|
5522
5834
|
result.push(await this.create(input));
|
|
@@ -5524,7 +5836,13 @@ var ChannelBindingStore = class {
|
|
|
5524
5836
|
return result;
|
|
5525
5837
|
}
|
|
5526
5838
|
async export(params) {
|
|
5527
|
-
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
|
+
});
|
|
5528
5846
|
}
|
|
5529
5847
|
async ensureInitialized() {
|
|
5530
5848
|
if (!this.initialized) {
|
|
@@ -5551,6 +5869,14 @@ var ChannelBindingStore = class {
|
|
|
5551
5869
|
};
|
|
5552
5870
|
}
|
|
5553
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
|
+
}
|
|
5554
5880
|
|
|
5555
5881
|
// src/stores/PostgreSQLChannelInstallationStore.ts
|
|
5556
5882
|
var import_pg15 = require("pg");
|
|
@@ -5725,7 +6051,8 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5725
6051
|
async createInstallation(tenantId, installationId, data) {
|
|
5726
6052
|
await this.ensureInitialized();
|
|
5727
6053
|
const now = /* @__PURE__ */ new Date();
|
|
5728
|
-
const
|
|
6054
|
+
const config = { ...data.config };
|
|
6055
|
+
const encryptedConfig = this.encryptSecrets(config);
|
|
5729
6056
|
await this.pool.query(
|
|
5730
6057
|
`
|
|
5731
6058
|
INSERT INTO lattice_channel_installations (
|
|
@@ -5750,7 +6077,7 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5750
6077
|
tenantId,
|
|
5751
6078
|
channel: data.channel,
|
|
5752
6079
|
name: data.name,
|
|
5753
|
-
config
|
|
6080
|
+
config,
|
|
5754
6081
|
enabled: data.enabled ?? true,
|
|
5755
6082
|
fallbackAgentId: data.fallbackAgentId,
|
|
5756
6083
|
rejectWhenNoBinding: data.rejectWhenNoBinding ?? true,
|
|
@@ -5837,17 +6164,17 @@ var PostgreSQLChannelInstallationStore = class {
|
|
|
5837
6164
|
encryptSecrets(config) {
|
|
5838
6165
|
return {
|
|
5839
6166
|
...config,
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
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) } : {}
|
|
5843
6170
|
};
|
|
5844
6171
|
}
|
|
5845
6172
|
decryptSecrets(config) {
|
|
5846
6173
|
return {
|
|
5847
6174
|
...config,
|
|
5848
|
-
|
|
5849
|
-
|
|
5850
|
-
|
|
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) } : {}
|
|
5851
6178
|
};
|
|
5852
6179
|
}
|
|
5853
6180
|
};
|
|
@@ -6056,17 +6383,17 @@ var PostgreSQLA2AApiKeyStore = class {
|
|
|
6056
6383
|
const result = await this.pool.query(
|
|
6057
6384
|
`SELECT * FROM lattice_a2a_api_keys WHERE enabled = true`
|
|
6058
6385
|
);
|
|
6059
|
-
const
|
|
6386
|
+
const map2 = /* @__PURE__ */ new Map();
|
|
6060
6387
|
for (const row of result.rows) {
|
|
6061
6388
|
const key = (0, import_core5.decrypt)(row.key_value);
|
|
6062
|
-
|
|
6389
|
+
map2.set(key, {
|
|
6063
6390
|
key,
|
|
6064
6391
|
tenantId: row.tenant_id,
|
|
6065
6392
|
projectId: row.project_id,
|
|
6066
6393
|
assistantIds: row.assistant_ids ?? void 0
|
|
6067
6394
|
});
|
|
6068
6395
|
}
|
|
6069
|
-
return
|
|
6396
|
+
return map2;
|
|
6070
6397
|
}
|
|
6071
6398
|
};
|
|
6072
6399
|
|
|
@@ -6296,7 +6623,7 @@ var PostgreSQLAgentWebAppStore = class {
|
|
|
6296
6623
|
|
|
6297
6624
|
// src/stores/PostgreSQLScheduleStorage.ts
|
|
6298
6625
|
var import_pg18 = require("pg");
|
|
6299
|
-
var
|
|
6626
|
+
var import_protocols4 = require("@axiom-lattice/protocols");
|
|
6300
6627
|
|
|
6301
6628
|
// src/migrations/schedule_migrations.ts
|
|
6302
6629
|
var createScheduledTasksTable = {
|
|
@@ -6710,7 +7037,7 @@ var PostgreSQLScheduleStorage = class {
|
|
|
6710
7037
|
await this.ensureInitialized();
|
|
6711
7038
|
const result = await this.pool.query(
|
|
6712
7039
|
`SELECT * FROM lattice_scheduled_tasks WHERE status IN ($1, $2) ORDER BY created_at ASC`,
|
|
6713
|
-
[
|
|
7040
|
+
[import_protocols4.ScheduledTaskStatus.PENDING, import_protocols4.ScheduledTaskStatus.PAUSED]
|
|
6714
7041
|
);
|
|
6715
7042
|
return result.rows.map((row) => this.mapRowToTask(row));
|
|
6716
7043
|
}
|
|
@@ -6869,9 +7196,9 @@ var PostgreSQLScheduleStorage = class {
|
|
|
6869
7196
|
AND updated_at < $4
|
|
6870
7197
|
`,
|
|
6871
7198
|
[
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
|
|
7199
|
+
import_protocols4.ScheduledTaskStatus.COMPLETED,
|
|
7200
|
+
import_protocols4.ScheduledTaskStatus.CANCELLED,
|
|
7201
|
+
import_protocols4.ScheduledTaskStatus.FAILED,
|
|
6875
7202
|
cutoff
|
|
6876
7203
|
]
|
|
6877
7204
|
);
|
|
@@ -7000,15 +7327,37 @@ var addFilesToTasks = {
|
|
|
7000
7327
|
`);
|
|
7001
7328
|
}
|
|
7002
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
|
+
};
|
|
7003
7342
|
var taskMigrations = [
|
|
7004
7343
|
createTasksTable,
|
|
7005
7344
|
addTaskFieldsMigration,
|
|
7006
7345
|
addTaskProjectFieldsMigration,
|
|
7007
|
-
addFilesToTasks
|
|
7346
|
+
addFilesToTasks,
|
|
7347
|
+
addTaskDependenciesGinIndex
|
|
7008
7348
|
];
|
|
7009
7349
|
|
|
7010
7350
|
// src/stores/PostgreSQLTaskStore.ts
|
|
7011
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
|
+
]);
|
|
7012
7361
|
function nextUpdatedAtSql(column = "updated_at") {
|
|
7013
7362
|
return `to_char(
|
|
7014
7363
|
date_trunc('milliseconds', GREATEST(
|
|
@@ -7088,6 +7437,9 @@ function mapRowToTask(row) {
|
|
|
7088
7437
|
updatedAt: new Date(row.updated_at)
|
|
7089
7438
|
};
|
|
7090
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
|
+
}
|
|
7091
7443
|
var PostgreSQLTaskStore = class {
|
|
7092
7444
|
constructor(options) {
|
|
7093
7445
|
this.initialized = false;
|
|
@@ -7111,6 +7463,7 @@ var PostgreSQLTaskStore = class {
|
|
|
7111
7463
|
this.migrationManager.register(addTaskFieldsMigration);
|
|
7112
7464
|
this.migrationManager.register(addTaskProjectFieldsMigration);
|
|
7113
7465
|
this.migrationManager.register(addFilesToTasks);
|
|
7466
|
+
this.migrationManager.register(addTaskDependenciesGinIndex);
|
|
7114
7467
|
if (options.autoMigrate !== false) {
|
|
7115
7468
|
this.initialize().catch((error) => {
|
|
7116
7469
|
console.error("Failed to initialize PostgreSQLTaskStore:", error);
|
|
@@ -7206,7 +7559,9 @@ var PostgreSQLTaskStore = class {
|
|
|
7206
7559
|
conditions.push(`workspace_id = $${paramIndex++}`);
|
|
7207
7560
|
params.push(filter.workspaceId);
|
|
7208
7561
|
}
|
|
7209
|
-
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) {
|
|
7210
7565
|
conditions.push(`project_id = $${paramIndex++}`);
|
|
7211
7566
|
params.push(filter.projectId);
|
|
7212
7567
|
}
|
|
@@ -7231,11 +7586,35 @@ var PostgreSQLTaskStore = class {
|
|
|
7231
7586
|
const limit = filter.limit || 100;
|
|
7232
7587
|
const offset = filter.offset || 0;
|
|
7233
7588
|
const result = await this.pool.query(
|
|
7234
|
-
`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++}`,
|
|
7235
7590
|
[...params, limit, offset]
|
|
7236
7591
|
);
|
|
7237
7592
|
return result.rows.map((r) => mapRowToTask(r));
|
|
7238
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
|
+
}
|
|
7239
7618
|
async update(tenantId, id, updates) {
|
|
7240
7619
|
await this.ensureInitialized();
|
|
7241
7620
|
const existing = await this.getById(tenantId, id);
|
|
@@ -7606,6 +7985,63 @@ var PostgreSQLTaskStore = class {
|
|
|
7606
7985
|
);
|
|
7607
7986
|
return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
|
|
7608
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
|
+
}
|
|
7609
8045
|
/** Atomically update a child only when both child and parent snapshots match. */
|
|
7610
8046
|
async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
|
|
7611
8047
|
await this.ensureInitialized();
|
|
@@ -7724,6 +8160,27 @@ var PostgreSQLTaskStore = class {
|
|
|
7724
8160
|
);
|
|
7725
8161
|
return (result.rowCount ?? 0) > 0;
|
|
7726
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
|
+
}
|
|
7727
8184
|
async dispose() {
|
|
7728
8185
|
if (this.ownsPool && this.pool) {
|
|
7729
8186
|
await this.pool.end();
|
|
@@ -7737,17 +8194,175 @@ var PostgreSQLTaskStore = class {
|
|
|
7737
8194
|
};
|
|
7738
8195
|
|
|
7739
8196
|
// src/stores/PostgreSQLTaskWorkItemStore.ts
|
|
8197
|
+
var import_pg20 = require("pg");
|
|
8198
|
+
var import_protocols5 = require("@axiom-lattice/protocols");
|
|
7740
8199
|
var import_uuid4 = require("uuid");
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
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
|
+
}
|
|
8315
|
+
var PostgreSQLTaskWorkItemStore = class {
|
|
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();
|
|
8358
|
+
}
|
|
8359
|
+
async create(params) {
|
|
8360
|
+
await this.ensureInitialized();
|
|
7746
8361
|
const id = (0, import_uuid4.v4)();
|
|
7747
8362
|
const result = await this.pool.query(
|
|
7748
8363
|
`INSERT INTO lattice_task_work_items
|
|
7749
|
-
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id)
|
|
7750
|
-
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()))
|
|
7751
8366
|
RETURNING *`,
|
|
7752
8367
|
[
|
|
7753
8368
|
id,
|
|
@@ -7765,8 +8380,46 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7765
8380
|
);
|
|
7766
8381
|
return this.rowToItem(result.rows[0]);
|
|
7767
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
|
+
}
|
|
7768
8420
|
/** Find an event by its tenant- and task-scoped key without pagination. */
|
|
7769
8421
|
async findByEventKey(tenantId, taskId, eventKey) {
|
|
8422
|
+
await this.ensureInitialized();
|
|
7770
8423
|
const result = await this.pool.query(
|
|
7771
8424
|
`SELECT * FROM lattice_task_work_items
|
|
7772
8425
|
WHERE tenant_id = $1 AND task_id = $2 AND event_key = $3`,
|
|
@@ -7776,10 +8429,11 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7776
8429
|
}
|
|
7777
8430
|
/** Atomically return an existing event or create it once. */
|
|
7778
8431
|
async createIfAbsentByEventKey(params) {
|
|
8432
|
+
await this.ensureInitialized();
|
|
7779
8433
|
const result = await this.pool.query(
|
|
7780
8434
|
`INSERT INTO lattice_task_work_items
|
|
7781
|
-
(id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key)
|
|
7782
|
-
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()))
|
|
7783
8437
|
ON CONFLICT (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
|
|
7784
8438
|
DO UPDATE SET event_key = EXCLUDED.event_key
|
|
7785
8439
|
RETURNING *`,
|
|
@@ -7801,6 +8455,7 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7801
8455
|
return this.rowToItem(result.rows[0]);
|
|
7802
8456
|
}
|
|
7803
8457
|
async list(filter) {
|
|
8458
|
+
await this.ensureInitialized();
|
|
7804
8459
|
let query = `SELECT * FROM lattice_task_work_items WHERE tenant_id = $1 AND task_id = $2`;
|
|
7805
8460
|
const params = [filter.tenantId, filter.taskId];
|
|
7806
8461
|
if (filter.action) {
|
|
@@ -7828,6 +8483,84 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7828
8483
|
const result = await this.pool.query(query, params);
|
|
7829
8484
|
return result.rows.map((row) => this.rowToItem(row));
|
|
7830
8485
|
}
|
|
8486
|
+
/** List pending execution results using one bounded PostgreSQL anti-join query. */
|
|
8487
|
+
async listPendingExecutionResults(params) {
|
|
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}`);
|
|
8490
|
+
error.code = "INVALID_LIMIT";
|
|
8491
|
+
throw error;
|
|
8492
|
+
}
|
|
8493
|
+
if (params.limit === 0) return [];
|
|
8494
|
+
const result = await this.pool.query(
|
|
8495
|
+
`SELECT result.*
|
|
8496
|
+
FROM lattice_task_work_items AS result
|
|
8497
|
+
WHERE result.tenant_id = $1
|
|
8498
|
+
AND result.task_id = $2
|
|
8499
|
+
AND result.action = 'execution_result'
|
|
8500
|
+
AND result.event_key COLLATE "C" ~ '^execution-result:[A-Za-z0-9._:-]+$'
|
|
8501
|
+
AND NOT EXISTS (
|
|
8502
|
+
SELECT 1
|
|
8503
|
+
FROM lattice_task_work_items AS reconciled
|
|
8504
|
+
WHERE reconciled.tenant_id = result.tenant_id
|
|
8505
|
+
AND reconciled.task_id = result.task_id
|
|
8506
|
+
AND reconciled.action = 'execution_reconciled'
|
|
8507
|
+
AND reconciled.detail ->> 'executionResultId' = result.event_key
|
|
8508
|
+
)
|
|
8509
|
+
ORDER BY result.created_at DESC, result.id DESC
|
|
8510
|
+
LIMIT $3`,
|
|
8511
|
+
[params.tenantId, params.taskId, params.limit]
|
|
8512
|
+
);
|
|
8513
|
+
return result.rows.map((row) => this.rowToItem(row));
|
|
8514
|
+
}
|
|
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
|
+
}
|
|
7831
8564
|
rowToItem(row) {
|
|
7832
8565
|
return {
|
|
7833
8566
|
id: row.id,
|
|
@@ -7837,7 +8570,7 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7837
8570
|
actor: row.actor,
|
|
7838
8571
|
threadId: row.thread_id,
|
|
7839
8572
|
summary: row.summary,
|
|
7840
|
-
detail: row.detail,
|
|
8573
|
+
detail: isRecord2(row.detail) ? row.detail : void 0,
|
|
7841
8574
|
attempt: row.attempt,
|
|
7842
8575
|
workspaceId: row.workspace_id,
|
|
7843
8576
|
projectId: row.project_id,
|
|
@@ -7848,7 +8581,7 @@ var PostgreSQLTaskWorkItemStore = class {
|
|
|
7848
8581
|
};
|
|
7849
8582
|
|
|
7850
8583
|
// src/stores/MenuStore.ts
|
|
7851
|
-
var
|
|
8584
|
+
var import_pg21 = require("pg");
|
|
7852
8585
|
|
|
7853
8586
|
// src/migrations/menu_items_migration.ts
|
|
7854
8587
|
var createMenuItemsTable = {
|
|
@@ -7925,7 +8658,7 @@ var MenuStore = class {
|
|
|
7925
8658
|
this.initialized = true;
|
|
7926
8659
|
return;
|
|
7927
8660
|
}
|
|
7928
|
-
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) : (() => {
|
|
7929
8662
|
throw new Error("Either pool or poolConfig must be provided");
|
|
7930
8663
|
})();
|
|
7931
8664
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -8080,7 +8813,7 @@ var MenuStore = class {
|
|
|
8080
8813
|
};
|
|
8081
8814
|
|
|
8082
8815
|
// src/stores/PostgresSharedResourceStore.ts
|
|
8083
|
-
var
|
|
8816
|
+
var import_pg22 = require("pg");
|
|
8084
8817
|
|
|
8085
8818
|
// src/migrations/shared_resources_migration.ts
|
|
8086
8819
|
var createSharedResourcesTable = {
|
|
@@ -8134,7 +8867,7 @@ var PostgresSharedResourceStore = class {
|
|
|
8134
8867
|
this.initialized = true;
|
|
8135
8868
|
return;
|
|
8136
8869
|
}
|
|
8137
|
-
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) : (() => {
|
|
8138
8871
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8139
8872
|
})();
|
|
8140
8873
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -8303,7 +9036,7 @@ var PostgresSharedResourceStore = class {
|
|
|
8303
9036
|
};
|
|
8304
9037
|
|
|
8305
9038
|
// src/stores/PostgreSQLCollectionStore.ts
|
|
8306
|
-
var
|
|
9039
|
+
var import_pg23 = require("pg");
|
|
8307
9040
|
var PostgreSQLCollectionStore = class {
|
|
8308
9041
|
constructor(options) {
|
|
8309
9042
|
this.initialized = false;
|
|
@@ -8316,9 +9049,9 @@ var PostgreSQLCollectionStore = class {
|
|
|
8316
9049
|
return;
|
|
8317
9050
|
}
|
|
8318
9051
|
if (typeof options.poolConfig === "string") {
|
|
8319
|
-
this.pool = new
|
|
9052
|
+
this.pool = new import_pg23.Pool({ connectionString: options.poolConfig });
|
|
8320
9053
|
} else if (options.poolConfig) {
|
|
8321
|
-
this.pool = new
|
|
9054
|
+
this.pool = new import_pg23.Pool(options.poolConfig);
|
|
8322
9055
|
} else {
|
|
8323
9056
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8324
9057
|
}
|
|
@@ -8433,7 +9166,7 @@ var PostgreSQLCollectionStore = class {
|
|
|
8433
9166
|
var import_langgraph_checkpoint_postgres = require("@langchain/langgraph-checkpoint-postgres");
|
|
8434
9167
|
|
|
8435
9168
|
// src/PGVectorStoreProvider.ts
|
|
8436
|
-
var
|
|
9169
|
+
var import_pg24 = require("pg");
|
|
8437
9170
|
var import_pgvector = require("@langchain/community/vectorstores/pgvector");
|
|
8438
9171
|
var import_documents = require("@langchain/core/documents");
|
|
8439
9172
|
var import_core6 = require("@axiom-lattice/core");
|
|
@@ -8507,7 +9240,7 @@ var PGVectorStoreProvider = class {
|
|
|
8507
9240
|
}
|
|
8508
9241
|
};
|
|
8509
9242
|
function createPGVectorStoreProvider(connectionString) {
|
|
8510
|
-
const pool = new
|
|
9243
|
+
const pool = new import_pg24.Pool({ connectionString });
|
|
8511
9244
|
return {
|
|
8512
9245
|
provider: new PGVectorStoreProvider(pool, connectionString),
|
|
8513
9246
|
pool
|
|
@@ -8796,69 +9529,956 @@ var createCollectionsTable = {
|
|
|
8796
9529
|
}
|
|
8797
9530
|
};
|
|
8798
9531
|
|
|
8799
|
-
// src/migrations/
|
|
8800
|
-
var
|
|
8801
|
-
version:
|
|
8802
|
-
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",
|
|
8803
9555
|
up: async (client) => {
|
|
8804
9556
|
await client.query(`
|
|
8805
|
-
CREATE TABLE IF NOT EXISTS
|
|
8806
|
-
id
|
|
8807
|
-
tenant_id
|
|
8808
|
-
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
detail JSONB,
|
|
8814
|
-
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')),
|
|
8815
9565
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
8816
|
-
|
|
9566
|
+
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
9567
|
+
PRIMARY KEY (tenant_id, id),
|
|
9568
|
+
UNIQUE (tenant_id, project_id, type)
|
|
8817
9569
|
)
|
|
8818
9570
|
`);
|
|
8819
9571
|
await client.query(`
|
|
8820
|
-
CREATE
|
|
8821
|
-
|
|
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
|
+
)
|
|
8822
9586
|
`);
|
|
8823
9587
|
await client.query(`
|
|
8824
|
-
CREATE INDEX IF NOT EXISTS
|
|
8825
|
-
|
|
9588
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_memberships_project
|
|
9589
|
+
ON lattice_project_memberships (tenant_id, project_id)
|
|
8826
9590
|
`);
|
|
8827
|
-
}
|
|
8828
|
-
};
|
|
8829
|
-
var addWorkItemProjectFieldsMigration = {
|
|
8830
|
-
version: 140,
|
|
8831
|
-
name: "add_task_work_item_project_fields",
|
|
8832
|
-
up: async (client) => {
|
|
8833
9591
|
await client.query(`
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
|
|
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
|
+
)
|
|
8837
9612
|
`);
|
|
8838
9613
|
await client.query(`
|
|
8839
|
-
CREATE INDEX IF NOT EXISTS
|
|
8840
|
-
|
|
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')
|
|
8841
9617
|
`);
|
|
8842
|
-
}
|
|
8843
|
-
};
|
|
8844
|
-
var addTaskWorkItemEventKeyMigration = {
|
|
8845
|
-
version: 168,
|
|
8846
|
-
name: "add_task_work_item_event_key",
|
|
8847
|
-
up: async (client) => {
|
|
8848
9618
|
await client.query(`
|
|
8849
|
-
|
|
8850
|
-
|
|
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')
|
|
8851
9622
|
`);
|
|
8852
9623
|
await client.query(`
|
|
8853
|
-
CREATE
|
|
8854
|
-
|
|
9624
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_bot_memberships_project
|
|
9625
|
+
ON lattice_project_bot_memberships (tenant_id, project_id)
|
|
9626
|
+
`);
|
|
9627
|
+
await client.query(`
|
|
9628
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_project_bot_memberships_room
|
|
9629
|
+
ON lattice_project_bot_memberships (tenant_id, room_id)
|
|
9630
|
+
`);
|
|
9631
|
+
await client.query(`
|
|
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)
|
|
8855
9658
|
`);
|
|
9659
|
+
},
|
|
9660
|
+
down: async (client) => {
|
|
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");
|
|
8856
9672
|
}
|
|
8857
9673
|
};
|
|
8858
9674
|
|
|
8859
|
-
// src/
|
|
8860
|
-
|
|
8861
|
-
|
|
9675
|
+
// src/stores/PostgreSQLCapabilityBundleStore.ts
|
|
9676
|
+
var import_crypto4 = require("crypto");
|
|
9677
|
+
var import_pg25 = require("pg");
|
|
9678
|
+
var duplicateMessage = "Capability bundle key already exists for tenant";
|
|
9679
|
+
function map(row) {
|
|
9680
|
+
return {
|
|
9681
|
+
id: row.id,
|
|
9682
|
+
tenantId: row.tenant_id,
|
|
9683
|
+
key: row.bundle_key,
|
|
9684
|
+
name: row.name,
|
|
9685
|
+
description: row.description ?? void 0,
|
|
9686
|
+
capabilities: row.capabilities,
|
|
9687
|
+
createdAt: row.created_at.toISOString(),
|
|
9688
|
+
updatedAt: typeof row.updated_at === "string" ? row.updated_at : row.updated_at.toISOString()
|
|
9689
|
+
};
|
|
9690
|
+
}
|
|
9691
|
+
function isDuplicate(error) {
|
|
9692
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "23505";
|
|
9693
|
+
}
|
|
9694
|
+
var PostgreSQLCapabilityBundleStore = class {
|
|
9695
|
+
/**
|
|
9696
|
+
* Creates a PostgreSQL capability bundle store.
|
|
9697
|
+
*
|
|
9698
|
+
* @param options - Pool ownership, connection, and migration options.
|
|
9699
|
+
*/
|
|
9700
|
+
constructor(options) {
|
|
9701
|
+
this.initialized = false;
|
|
9702
|
+
this.ownsPool = true;
|
|
9703
|
+
this.initPromise = null;
|
|
9704
|
+
if (options.pool) {
|
|
9705
|
+
this.pool = options.pool;
|
|
9706
|
+
this.ownsPool = false;
|
|
9707
|
+
this.initialized = true;
|
|
9708
|
+
return;
|
|
9709
|
+
}
|
|
9710
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg25.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg25.Pool(options.poolConfig) : (() => {
|
|
9711
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
9712
|
+
})();
|
|
9713
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
9714
|
+
this.migrationManager.register(createCapabilityBundlesTable);
|
|
9715
|
+
if (options.autoMigrate !== false) {
|
|
9716
|
+
this.startInitialization();
|
|
9717
|
+
}
|
|
9718
|
+
}
|
|
9719
|
+
/**
|
|
9720
|
+
* Applies pending migrations for an internally managed pool.
|
|
9721
|
+
*
|
|
9722
|
+
* @returns A shared promise that resolves when initialization completes.
|
|
9723
|
+
*/
|
|
9724
|
+
async initialize() {
|
|
9725
|
+
if (this.initialized) return;
|
|
9726
|
+
if (this.initPromise) return this.initPromise;
|
|
9727
|
+
return this.startInitialization();
|
|
9728
|
+
}
|
|
9729
|
+
startInitialization() {
|
|
9730
|
+
this.initPromise = this.migrationManager.migrate().then(() => {
|
|
9731
|
+
this.initialized = true;
|
|
9732
|
+
});
|
|
9733
|
+
void this.initPromise.catch(() => void 0);
|
|
9734
|
+
return this.initPromise;
|
|
9735
|
+
}
|
|
9736
|
+
/**
|
|
9737
|
+
* Closes the pool when it was created by this store.
|
|
9738
|
+
*
|
|
9739
|
+
* @returns A promise that resolves after owned resources are released.
|
|
9740
|
+
*/
|
|
9741
|
+
async dispose() {
|
|
9742
|
+
if (this.ownsPool) await this.pool.end();
|
|
9743
|
+
}
|
|
9744
|
+
async ready() {
|
|
9745
|
+
if (!this.initialized) await this.initialize();
|
|
9746
|
+
}
|
|
9747
|
+
async listByTenant(tenantId) {
|
|
9748
|
+
await this.ready();
|
|
9749
|
+
const result = await this.pool.query(
|
|
9750
|
+
"SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at FROM lattice_capability_bundles WHERE tenant_id = $1 ORDER BY created_at",
|
|
9751
|
+
[tenantId]
|
|
9752
|
+
);
|
|
9753
|
+
return result.rows.map(map);
|
|
9754
|
+
}
|
|
9755
|
+
async getById(tenantId, id) {
|
|
9756
|
+
await this.ready();
|
|
9757
|
+
const result = await this.pool.query(
|
|
9758
|
+
"SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = $2",
|
|
9759
|
+
[tenantId, id]
|
|
9760
|
+
);
|
|
9761
|
+
return result.rows[0] ? map(result.rows[0]) : null;
|
|
9762
|
+
}
|
|
9763
|
+
async getManyByIds(tenantId, ids) {
|
|
9764
|
+
await this.ready();
|
|
9765
|
+
if (ids.length === 0) return [];
|
|
9766
|
+
const result = await this.pool.query(
|
|
9767
|
+
"SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = ANY($2::uuid[])",
|
|
9768
|
+
[tenantId, ids]
|
|
9769
|
+
);
|
|
9770
|
+
return result.rows.map(map);
|
|
9771
|
+
}
|
|
9772
|
+
async create(tenantId, input) {
|
|
9773
|
+
await this.ready();
|
|
9774
|
+
try {
|
|
9775
|
+
const result = await this.pool.query(
|
|
9776
|
+
`INSERT INTO lattice_capability_bundles
|
|
9777
|
+
(id, tenant_id, bundle_key, name, description, capabilities)
|
|
9778
|
+
VALUES ($1, $2, $3, $4, $5, $6::jsonb)
|
|
9779
|
+
RETURNING id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at`,
|
|
9780
|
+
[
|
|
9781
|
+
(0, import_crypto4.randomUUID)(),
|
|
9782
|
+
tenantId,
|
|
9783
|
+
input.key,
|
|
9784
|
+
input.name,
|
|
9785
|
+
input.description ?? null,
|
|
9786
|
+
JSON.stringify(input.capabilities)
|
|
9787
|
+
]
|
|
9788
|
+
);
|
|
9789
|
+
return map(result.rows[0]);
|
|
9790
|
+
} catch (error) {
|
|
9791
|
+
if (isDuplicate(error)) throw new Error(duplicateMessage);
|
|
9792
|
+
throw error;
|
|
9793
|
+
}
|
|
9794
|
+
}
|
|
9795
|
+
async update(tenantId, id, input) {
|
|
9796
|
+
await this.ready();
|
|
9797
|
+
const fields = [];
|
|
9798
|
+
const values = [];
|
|
9799
|
+
const add = (field, value) => {
|
|
9800
|
+
values.push(value);
|
|
9801
|
+
fields.push(`${field} = $${values.length}`);
|
|
9802
|
+
};
|
|
9803
|
+
if (input.name !== void 0) add("name", input.name);
|
|
9804
|
+
if (Object.prototype.hasOwnProperty.call(input, "description")) {
|
|
9805
|
+
add("description", input.description ?? null);
|
|
9806
|
+
}
|
|
9807
|
+
if (input.capabilities !== void 0) {
|
|
9808
|
+
add("capabilities", JSON.stringify(input.capabilities));
|
|
9809
|
+
}
|
|
9810
|
+
if (fields.length === 0) {
|
|
9811
|
+
const expectedUpdatedAt2 = input.expectedUpdatedAt;
|
|
9812
|
+
const result = await this.pool.query(
|
|
9813
|
+
`SELECT id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at
|
|
9814
|
+
FROM lattice_capability_bundles
|
|
9815
|
+
WHERE tenant_id = $1 AND id = $2${expectedUpdatedAt2 === void 0 ? "" : " AND updated_at::text = $3"}`,
|
|
9816
|
+
expectedUpdatedAt2 === void 0 ? [tenantId, id] : [tenantId, id, expectedUpdatedAt2]
|
|
9817
|
+
);
|
|
9818
|
+
if (result.rows[0]) return map(result.rows[0]);
|
|
9819
|
+
return expectedUpdatedAt2 === void 0 ? null : { status: "conflict" };
|
|
9820
|
+
}
|
|
9821
|
+
const expectedUpdatedAt = input.expectedUpdatedAt;
|
|
9822
|
+
values.push(tenantId, id);
|
|
9823
|
+
if (expectedUpdatedAt !== void 0) values.push(expectedUpdatedAt);
|
|
9824
|
+
const tenantParam = values.length - (expectedUpdatedAt === void 0 ? 1 : 2);
|
|
9825
|
+
const idParam = tenantParam + 1;
|
|
9826
|
+
const revisionPredicate = expectedUpdatedAt === void 0 ? "" : ` AND updated_at::text = $${values.length}`;
|
|
9827
|
+
try {
|
|
9828
|
+
const result = await this.pool.query(
|
|
9829
|
+
`UPDATE lattice_capability_bundles
|
|
9830
|
+
SET ${fields.join(", ")}, updated_at = GREATEST(updated_at + interval '1 microsecond', clock_timestamp())
|
|
9831
|
+
WHERE tenant_id = $${tenantParam} AND id = $${idParam}${revisionPredicate}
|
|
9832
|
+
RETURNING id, tenant_id, bundle_key, name, description, capabilities, created_at, updated_at::text AS updated_at`,
|
|
9833
|
+
values
|
|
9834
|
+
);
|
|
9835
|
+
if (result.rows[0]) return map(result.rows[0]);
|
|
9836
|
+
return expectedUpdatedAt === void 0 ? null : { status: "conflict" };
|
|
9837
|
+
} catch (error) {
|
|
9838
|
+
if (isDuplicate(error)) throw new Error(duplicateMessage);
|
|
9839
|
+
throw error;
|
|
9840
|
+
}
|
|
9841
|
+
}
|
|
9842
|
+
async deleteIfUnreferenced(tenantId, id) {
|
|
9843
|
+
await this.ready();
|
|
9844
|
+
const client = await this.pool.connect();
|
|
9845
|
+
try {
|
|
9846
|
+
await client.query("BEGIN");
|
|
9847
|
+
await client.query(
|
|
9848
|
+
"SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project-mutations', 0))",
|
|
9849
|
+
[tenantId]
|
|
9850
|
+
);
|
|
9851
|
+
const projectIds = await client.query(
|
|
9852
|
+
`SELECT id FROM lattice_projects
|
|
9853
|
+
WHERE tenant_id = $1
|
|
9854
|
+
ORDER BY id`,
|
|
9855
|
+
[tenantId]
|
|
9856
|
+
);
|
|
9857
|
+
if (projectIds.rows.length > 0) {
|
|
9858
|
+
await client.query(
|
|
9859
|
+
`SELECT pg_advisory_xact_lock(hashtextextended($1 || ':project:' || project_id, 0))
|
|
9860
|
+
FROM unnest($2::text[]) AS project_id
|
|
9861
|
+
ORDER BY project_id`,
|
|
9862
|
+
[tenantId, projectIds.rows.map((project) => project.id)]
|
|
9863
|
+
);
|
|
9864
|
+
}
|
|
9865
|
+
await client.query(
|
|
9866
|
+
`SELECT id FROM lattice_projects
|
|
9867
|
+
WHERE tenant_id = $1
|
|
9868
|
+
FOR UPDATE`,
|
|
9869
|
+
[tenantId]
|
|
9870
|
+
);
|
|
9871
|
+
await client.query("SELECT pg_advisory_xact_lock(hashtextextended($1 || ':' || $2, 0))", [tenantId, id]);
|
|
9872
|
+
const referenced = await client.query(
|
|
9873
|
+
`SELECT 1 FROM lattice_projects
|
|
9874
|
+
WHERE tenant_id = $1
|
|
9875
|
+
AND jsonb_typeof(config->'capabilityBundleIds') = 'array'
|
|
9876
|
+
AND COALESCE(config->'capabilityBundleIds', '[]'::jsonb) ? $2
|
|
9877
|
+
LIMIT 1`,
|
|
9878
|
+
[tenantId, id]
|
|
9879
|
+
);
|
|
9880
|
+
if (referenced.rows.length > 0) {
|
|
9881
|
+
await client.query("COMMIT");
|
|
9882
|
+
return "in_use";
|
|
9883
|
+
}
|
|
9884
|
+
const deleted = await client.query(
|
|
9885
|
+
"DELETE FROM lattice_capability_bundles WHERE tenant_id = $1 AND id = $2 RETURNING id",
|
|
9886
|
+
[tenantId, id]
|
|
9887
|
+
);
|
|
9888
|
+
await client.query("COMMIT");
|
|
9889
|
+
return deleted.rows.length > 0 ? "deleted" : "not_found";
|
|
9890
|
+
} catch (error) {
|
|
9891
|
+
await client.query("ROLLBACK");
|
|
9892
|
+
throw error;
|
|
9893
|
+
} finally {
|
|
9894
|
+
client.release();
|
|
9895
|
+
}
|
|
9896
|
+
}
|
|
9897
|
+
};
|
|
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
|
+
|
|
10479
|
+
// src/createPgStoreConfig.ts
|
|
10480
|
+
async function createPgStoreConfig(connectionString) {
|
|
10481
|
+
const pool = new import_pg26.Pool({ connectionString });
|
|
8862
10482
|
const mm = new MigrationManager(pool);
|
|
8863
10483
|
mm.register(createThreadsTable);
|
|
8864
10484
|
mm.register(createScheduledTasksTable);
|
|
@@ -8913,6 +10533,11 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8913
10533
|
mm.register(addA2AKeyAssistantIds);
|
|
8914
10534
|
mm.register(addTaskWorkItemEventKeyMigration);
|
|
8915
10535
|
mm.register(createAgentWebAppsTable);
|
|
10536
|
+
mm.register(createCapabilityBundlesTable);
|
|
10537
|
+
mm.register(addTaskWorkItemPendingIndexesMigration);
|
|
10538
|
+
mm.register(createProjectRoomTables);
|
|
10539
|
+
mm.register(addTrustedRunContextColumn);
|
|
10540
|
+
mm.register(addProjectLifecycleEventIndex);
|
|
8916
10541
|
await mm.migrate();
|
|
8917
10542
|
const checkpoint = import_langgraph_checkpoint_postgres.PostgresSaver.fromConnString(connectionString);
|
|
8918
10543
|
checkpoint.setup().catch((err) => {
|
|
@@ -8941,17 +10566,22 @@ async function createPgStoreConfig(connectionString) {
|
|
|
8941
10566
|
taskWorkItem: taskWorkItemStore,
|
|
8942
10567
|
a2aApiKey: new PostgreSQLA2AApiKeyStore(opts),
|
|
8943
10568
|
agentWebApp: new PostgreSQLAgentWebAppStore(opts),
|
|
10569
|
+
capabilityBundle: new PostgreSQLCapabilityBundleStore(opts),
|
|
8944
10570
|
schedule: new PostgreSQLScheduleStorage(opts),
|
|
8945
10571
|
menu: new MenuStore(opts),
|
|
8946
10572
|
sharedResource: new PostgresSharedResourceStore(opts),
|
|
8947
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),
|
|
8948
10578
|
vectorStoreProvider: new PGVectorStoreProvider(pool, connectionString),
|
|
8949
10579
|
checkpoint
|
|
8950
10580
|
};
|
|
8951
10581
|
}
|
|
8952
10582
|
|
|
8953
10583
|
// src/stores/PostgreSQLSkillStore.ts
|
|
8954
|
-
var
|
|
10584
|
+
var import_pg27 = require("pg");
|
|
8955
10585
|
var PostgreSQLSkillStore = class {
|
|
8956
10586
|
constructor(options) {
|
|
8957
10587
|
this.initialized = false;
|
|
@@ -8964,9 +10594,9 @@ var PostgreSQLSkillStore = class {
|
|
|
8964
10594
|
return;
|
|
8965
10595
|
}
|
|
8966
10596
|
if (typeof options.poolConfig === "string") {
|
|
8967
|
-
this.pool = new
|
|
10597
|
+
this.pool = new import_pg27.Pool({ connectionString: options.poolConfig });
|
|
8968
10598
|
} else if (options.poolConfig) {
|
|
8969
|
-
this.pool = new
|
|
10599
|
+
this.pool = new import_pg27.Pool(options.poolConfig);
|
|
8970
10600
|
} else {
|
|
8971
10601
|
throw new Error("Either pool or poolConfig must be provided");
|
|
8972
10602
|
}
|
|
@@ -9267,7 +10897,7 @@ var PostgreSQLSkillStore = class {
|
|
|
9267
10897
|
};
|
|
9268
10898
|
|
|
9269
10899
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
9270
|
-
var
|
|
10900
|
+
var import_pg28 = require("pg");
|
|
9271
10901
|
var ChannelIdentityMappingStore = class {
|
|
9272
10902
|
constructor(options) {
|
|
9273
10903
|
this.initialized = false;
|
|
@@ -9279,7 +10909,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
9279
10909
|
this.initialized = true;
|
|
9280
10910
|
return;
|
|
9281
10911
|
}
|
|
9282
|
-
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) : (() => {
|
|
9283
10913
|
throw new Error("Either pool or poolConfig must be provided");
|
|
9284
10914
|
})();
|
|
9285
10915
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -9501,6 +11131,8 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9501
11131
|
0 && (module.exports = {
|
|
9502
11132
|
ChannelBindingStore,
|
|
9503
11133
|
ChannelIdentityMappingStore,
|
|
11134
|
+
DuplicateProjectMembershipError,
|
|
11135
|
+
DuplicateProjectRoomMessageIdempotencyKeyError,
|
|
9504
11136
|
MenuStore,
|
|
9505
11137
|
MigrationManager,
|
|
9506
11138
|
PGVectorStoreProvider,
|
|
@@ -9508,6 +11140,7 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9508
11140
|
PostgreSQLA2AApiKeyStore,
|
|
9509
11141
|
PostgreSQLAgentWebAppStore,
|
|
9510
11142
|
PostgreSQLAssistantStore,
|
|
11143
|
+
PostgreSQLCapabilityBundleStore,
|
|
9511
11144
|
PostgreSQLChannelInstallationStore,
|
|
9512
11145
|
PostgreSQLCollectionStore,
|
|
9513
11146
|
PostgreSQLConnectionStore,
|
|
@@ -9515,9 +11148,14 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9515
11148
|
PostgreSQLEvalStore,
|
|
9516
11149
|
PostgreSQLMcpServerConfigStore,
|
|
9517
11150
|
PostgreSQLMetricsServerConfigStore,
|
|
11151
|
+
PostgreSQLProjectBotMembershipStore,
|
|
11152
|
+
PostgreSQLProjectMembershipStore,
|
|
11153
|
+
PostgreSQLProjectRoomMessageStore,
|
|
11154
|
+
PostgreSQLProjectRoomStore,
|
|
9518
11155
|
PostgreSQLProjectStore,
|
|
9519
11156
|
PostgreSQLScheduleStorage,
|
|
9520
11157
|
PostgreSQLSkillStore,
|
|
11158
|
+
PostgreSQLTaskWorkItemStore,
|
|
9521
11159
|
PostgreSQLTenantStore,
|
|
9522
11160
|
PostgreSQLThreadStore,
|
|
9523
11161
|
PostgreSQLUserStore,
|
|
@@ -9525,6 +11163,9 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9525
11163
|
PostgreSQLWorkflowTrackingStore,
|
|
9526
11164
|
PostgreSQLWorkspaceStore,
|
|
9527
11165
|
PostgresSharedResourceStore,
|
|
11166
|
+
ProjectBotMembershipIdConflictError,
|
|
11167
|
+
ProjectMembershipIdConflictError,
|
|
11168
|
+
ProjectRoomMessageIdConflictError,
|
|
9528
11169
|
ThreadMessageQueueStore,
|
|
9529
11170
|
addAssistantTenantId,
|
|
9530
11171
|
addEnvToEvalRuns,
|
|
@@ -9546,6 +11187,7 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9546
11187
|
createA2AApiKeysTable,
|
|
9547
11188
|
createAgentWebAppsTable,
|
|
9548
11189
|
createAssistantsTable,
|
|
11190
|
+
createCapabilityBundlesTable,
|
|
9549
11191
|
createChannelBindingsTable,
|
|
9550
11192
|
createChannelIdentityMappingTables,
|
|
9551
11193
|
createChannelInstallationsTable,
|
|
@@ -9561,6 +11203,7 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
9561
11203
|
createMetricsConfigsTable,
|
|
9562
11204
|
createPGVectorStoreProvider,
|
|
9563
11205
|
createPgStoreConfig,
|
|
11206
|
+
createProjectRoomTables,
|
|
9564
11207
|
createProjectsTable,
|
|
9565
11208
|
createScheduledTasksTable,
|
|
9566
11209
|
createSharedResourcesTable,
|