@axiom-lattice/pg-stores 2.0.9 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -2352,6 +2352,24 @@ var dropProjectFolderPathColumn = {
2352
2352
  }
2353
2353
  };
2354
2354
 
2355
+ // src/migrations/add_project_kind_column.ts
2356
+ var addProjectKindColumn = {
2357
+ version: 165,
2358
+ name: "add_project_kind_column",
2359
+ up: async (client) => {
2360
+ await client.query(`
2361
+ ALTER TABLE lattice_projects
2362
+ ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'business'
2363
+ `);
2364
+ },
2365
+ down: async (client) => {
2366
+ await client.query(`
2367
+ ALTER TABLE lattice_projects
2368
+ DROP COLUMN IF EXISTS kind
2369
+ `);
2370
+ }
2371
+ };
2372
+
2355
2373
  // src/stores/PostgreSQLProjectStore.ts
2356
2374
  var PostgreSQLProjectStore = class {
2357
2375
  constructor(options) {
@@ -2374,6 +2392,7 @@ var PostgreSQLProjectStore = class {
2374
2392
  this.migrationManager.register(createProjectsTable);
2375
2393
  this.migrationManager.register(addProjectConfigColumn);
2376
2394
  this.migrationManager.register(dropProjectFolderPathColumn);
2395
+ this.migrationManager.register(addProjectKindColumn);
2377
2396
  if (options.autoMigrate !== false) {
2378
2397
  this.initialize().catch((error) => {
2379
2398
  console.error("Failed to initialize PostgreSQLProjectStore:", error);
@@ -2419,6 +2438,7 @@ var PostgreSQLProjectStore = class {
2419
2438
  name: row.name,
2420
2439
  description: row.description || void 0,
2421
2440
  config: row.config || void 0,
2441
+ kind: row.kind || "business",
2422
2442
  createdAt: row.created_at,
2423
2443
  updatedAt: row.updated_at
2424
2444
  };
@@ -2426,16 +2446,22 @@ var PostgreSQLProjectStore = class {
2426
2446
  /**
2427
2447
  * Get all projects for a specific workspace
2428
2448
  */
2429
- async getProjectsByWorkspace(tenantId, workspaceId) {
2449
+ async getProjectsByWorkspace(tenantId, workspaceId, filter) {
2430
2450
  await this.ensureInitialized();
2451
+ const conditions = ["tenant_id = $1", "workspace_id = $2"];
2452
+ const values = [tenantId, workspaceId];
2453
+ if (filter?.kind !== void 0) {
2454
+ values.push(filter.kind);
2455
+ conditions.push(`kind = $${values.length}`);
2456
+ }
2431
2457
  const result = await this.pool.query(
2432
2458
  `
2433
- SELECT id, tenant_id, workspace_id, name, description, config, created_at, updated_at
2459
+ SELECT id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at
2434
2460
  FROM lattice_projects
2435
- WHERE tenant_id = $1 AND workspace_id = $2
2461
+ WHERE ${conditions.join(" AND ")}
2436
2462
  ORDER BY created_at DESC
2437
2463
  `,
2438
- [tenantId, workspaceId]
2464
+ values
2439
2465
  );
2440
2466
  return result.rows.map(this.mapRowToProject);
2441
2467
  }
@@ -2446,7 +2472,7 @@ var PostgreSQLProjectStore = class {
2446
2472
  await this.ensureInitialized();
2447
2473
  const result = await this.pool.query(
2448
2474
  `
2449
- SELECT id, tenant_id, workspace_id, name, description, config, created_at, updated_at
2475
+ SELECT id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at
2450
2476
  FROM lattice_projects
2451
2477
  WHERE id = $1 AND tenant_id = $2
2452
2478
  `,
@@ -2463,18 +2489,20 @@ var PostgreSQLProjectStore = class {
2463
2489
  async createProject(tenantId, workspaceId, id, data) {
2464
2490
  await this.ensureInitialized();
2465
2491
  const now = /* @__PURE__ */ new Date();
2492
+ const kind = data.kind || "business";
2466
2493
  await this.pool.query(
2467
2494
  `
2468
- INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, config, created_at, updated_at)
2469
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
2495
+ INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at)
2496
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
2470
2497
  ON CONFLICT (id, tenant_id) DO UPDATE SET
2471
2498
  workspace_id = EXCLUDED.workspace_id,
2472
2499
  name = EXCLUDED.name,
2473
2500
  description = EXCLUDED.description,
2474
2501
  config = EXCLUDED.config,
2502
+ kind = EXCLUDED.kind,
2475
2503
  updated_at = EXCLUDED.updated_at
2476
2504
  `,
2477
- [id, tenantId, workspaceId, data.name, data.description || null, data.config || null, now, now]
2505
+ [id, tenantId, workspaceId, data.name, data.description || null, data.config || null, kind, now, now]
2478
2506
  );
2479
2507
  return {
2480
2508
  id,
@@ -2483,6 +2511,7 @@ var PostgreSQLProjectStore = class {
2483
2511
  name: data.name,
2484
2512
  description: data.description,
2485
2513
  config: data.config,
2514
+ kind,
2486
2515
  createdAt: now,
2487
2516
  updatedAt: now
2488
2517
  };
@@ -2515,6 +2544,10 @@ var PostgreSQLProjectStore = class {
2515
2544
  updateFields.push(`config = $${paramIndex++}`);
2516
2545
  updateValues.push(updates.config || null);
2517
2546
  }
2547
+ if (updates.kind !== void 0) {
2548
+ updateFields.push(`kind = $${paramIndex++}`);
2549
+ updateValues.push(updates.kind);
2550
+ }
2518
2551
  if (updateFields.length === 0) {
2519
2552
  return existing;
2520
2553
  }
@@ -5745,6 +5778,24 @@ var createA2AApiKeysTable = {
5745
5778
  }
5746
5779
  };
5747
5780
 
5781
+ // src/migrations/add_a2a_key_assistant_ids.ts
5782
+ var addA2AKeyAssistantIds = {
5783
+ version: 167,
5784
+ name: "add_a2a_key_assistant_ids",
5785
+ up: async (client) => {
5786
+ await client.query(`
5787
+ ALTER TABLE lattice_a2a_api_keys
5788
+ ADD COLUMN IF NOT EXISTS assistant_ids JSONB
5789
+ `);
5790
+ },
5791
+ down: async (client) => {
5792
+ await client.query(`
5793
+ ALTER TABLE lattice_a2a_api_keys
5794
+ DROP COLUMN IF EXISTS assistant_ids
5795
+ `);
5796
+ }
5797
+ };
5798
+
5748
5799
  // src/stores/PostgreSQLA2AApiKeyStore.ts
5749
5800
  import { encrypt as encrypt5, decrypt as decrypt5 } from "@axiom-lattice/core";
5750
5801
  import { randomUUID } from "crypto";
@@ -5756,8 +5807,8 @@ function mapRowToRecord(row) {
5756
5807
  id: row.id,
5757
5808
  key: decrypt5(row.key_value),
5758
5809
  tenantId: row.tenant_id,
5759
- projectId: row.project_id || void 0,
5760
- workspaceId: row.workspace_id || void 0,
5810
+ projectId: row.project_id,
5811
+ assistantIds: row.assistant_ids ?? void 0,
5761
5812
  label: row.label || void 0,
5762
5813
  enabled: row.enabled,
5763
5814
  createdAt: row.created_at,
@@ -5780,6 +5831,7 @@ var PostgreSQLA2AApiKeyStore = class {
5780
5831
  })();
5781
5832
  this.migrationManager = new MigrationManager(this.pool);
5782
5833
  this.migrationManager.register(createA2AApiKeysTable);
5834
+ this.migrationManager.register(addA2AKeyAssistantIds);
5783
5835
  if (options.autoMigrate !== false) {
5784
5836
  this.initialize().catch((error) => {
5785
5837
  console.error("Failed to initialize PostgreSQLA2AApiKeyStore:", error);
@@ -5818,6 +5870,14 @@ var PostgreSQLA2AApiKeyStore = class {
5818
5870
  }
5819
5871
  return null;
5820
5872
  }
5873
+ async findById(id) {
5874
+ await this.ensureInitialized();
5875
+ const result = await this.pool.query(
5876
+ `SELECT * FROM lattice_a2a_api_keys WHERE id = $1`,
5877
+ [id]
5878
+ );
5879
+ return result.rows[0] ? mapRowToRecord(result.rows[0]) : null;
5880
+ }
5821
5881
  async list(params) {
5822
5882
  await this.ensureInitialized();
5823
5883
  const limit = params.limit || 100;
@@ -5839,9 +5899,9 @@ var PostgreSQLA2AApiKeyStore = class {
5839
5899
  await this.ensureInitialized();
5840
5900
  const key = generateApiKey();
5841
5901
  const result = await this.pool.query(
5842
- `INSERT INTO lattice_a2a_api_keys (key_value, tenant_id, project_id, workspace_id, label)
5902
+ `INSERT INTO lattice_a2a_api_keys (key_value, tenant_id, project_id, assistant_ids, label)
5843
5903
  VALUES ($1, $2, $3, $4, $5) RETURNING *`,
5844
- [encrypt5(key), input.tenantId, input.projectId || null, input.workspaceId || null, input.label || null]
5904
+ [encrypt5(key), input.tenantId, input.projectId, input.assistantIds ? JSON.stringify(input.assistantIds) : null, input.label || null]
5845
5905
  );
5846
5906
  const record = mapRowToRecord(result.rows[0]);
5847
5907
  record.key = key;
@@ -5892,8 +5952,8 @@ var PostgreSQLA2AApiKeyStore = class {
5892
5952
  map.set(key, {
5893
5953
  key,
5894
5954
  tenantId: row.tenant_id,
5895
- projectId: row.project_id || void 0,
5896
- workspaceId: row.workspace_id || void 0
5955
+ projectId: row.project_id,
5956
+ assistantIds: row.assistant_ids ?? void 0
5897
5957
  });
5898
5958
  }
5899
5959
  return map;
@@ -6617,6 +6677,49 @@ var taskMigrations = [
6617
6677
 
6618
6678
  // src/stores/PostgreSQLTaskStore.ts
6619
6679
  import { v4 as uuidv42 } from "uuid";
6680
+ function nextUpdatedAtSql(column = "updated_at") {
6681
+ return `to_char(
6682
+ date_trunc('milliseconds', GREATEST(
6683
+ clock_timestamp(),
6684
+ CASE
6685
+ WHEN ${canonicalTimestampValidationSql(column)}
6686
+ THEN ${column}::timestamptz + interval '1 millisecond'
6687
+ ELSE clock_timestamp()
6688
+ END
6689
+ ))
6690
+ AT TIME ZONE 'UTC',
6691
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
6692
+ )`;
6693
+ }
6694
+ function canonicalTimestampValidationSql(column, allowMaximum = false) {
6695
+ const year = `substring(${column} FROM 1 FOR 4)::integer`;
6696
+ const month = `substring(${column} FROM 6 FOR 2)::integer`;
6697
+ const day = `substring(${column} FROM 9 FOR 2)::integer`;
6698
+ return `CASE
6699
+ WHEN ${column} ~ '^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}[.][0-9]{3}Z$' THEN
6700
+ ${allowMaximum ? "" : `${column} <> '9999-12-31T23:59:59.999Z' AND`}
6701
+ ${year} BETWEEN 1 AND 9999
6702
+ AND ${month} BETWEEN 1 AND 12
6703
+ AND ${day} BETWEEN 1 AND CASE
6704
+ WHEN ${month} = 2 THEN CASE
6705
+ WHEN (${year} % 400 = 0) OR (${year} % 4 = 0 AND ${year} % 100 <> 0) THEN 29
6706
+ ELSE 28
6707
+ END
6708
+ WHEN ${month} IN (4, 6, 9, 11) THEN 30
6709
+ ELSE 31
6710
+ END
6711
+ AND substring(${column} FROM 12 FOR 2)::integer BETWEEN 0 AND 23
6712
+ AND substring(${column} FROM 15 FOR 2)::integer BETWEEN 0 AND 59
6713
+ AND substring(${column} FROM 18 FOR 2)::integer BETWEEN 0 AND 59
6714
+ ELSE FALSE
6715
+ END`;
6716
+ }
6717
+ function canonicalTimestampSnapshotSql(column, parameter, allowMaximum = false) {
6718
+ return `CASE WHEN ${canonicalTimestampValidationSql(column, allowMaximum)}
6719
+ THEN ${column}::timestamptz = ${parameter}::timestamptz
6720
+ ELSE FALSE
6721
+ END`;
6722
+ }
6620
6723
  function parseTaskFiles(raw) {
6621
6724
  if (!raw) return void 0;
6622
6725
  try {
@@ -6702,7 +6805,7 @@ var PostgreSQLTaskStore = class {
6702
6805
  }
6703
6806
  async create(params) {
6704
6807
  await this.ensureInitialized();
6705
- const id = uuidv42();
6808
+ const id = params.id ?? uuidv42();
6706
6809
  const now = (/* @__PURE__ */ new Date()).toISOString();
6707
6810
  await this.pool.query(
6708
6811
  `INSERT INTO lattice_tasks (id, tenant_id, owner_type, owner_id, title, description, status, priority, due_date, metadata, parent_id, source_id, context, require_review, dependencies, result, failure_reason, workspace_id, project_id, files, created_at, updated_at)
@@ -6846,7 +6949,7 @@ var PostgreSQLTaskStore = class {
6846
6949
  }
6847
6950
  if (updates.context !== void 0) {
6848
6951
  setClauses.push(`context = $${paramIndex++}`);
6849
- params.push(JSON.stringify(updates.context));
6952
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
6850
6953
  }
6851
6954
  if (updates.ownerType !== void 0) {
6852
6955
  setClauses.push(`owner_type = $${paramIndex++}`);
@@ -6883,14 +6986,403 @@ var PostgreSQLTaskStore = class {
6883
6986
  if (setClauses.length === 0) {
6884
6987
  return existing;
6885
6988
  }
6886
- setClauses.push(`updated_at = $${paramIndex++}`);
6887
- params.push((/* @__PURE__ */ new Date()).toISOString());
6989
+ setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
6888
6990
  params.push(tenantId, id);
6889
- await this.pool.query(
6890
- `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${paramIndex++} AND id = $${paramIndex++}`,
6991
+ const result = await this.pool.query(
6992
+ `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${paramIndex++} AND id = $${paramIndex++} AND updated_at <> '9999-12-31T23:59:59.999Z' RETURNING *`,
6993
+ params
6994
+ );
6995
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
6996
+ }
6997
+ /**
6998
+ * Atomically update a task unless its current status is blocked.
6999
+ *
7000
+ * @param tenantId Tenant identifier.
7001
+ * @param id Task identifier.
7002
+ * @param updates Partial task data to update.
7003
+ * @param blockedStatuses Current statuses that prevent the update.
7004
+ * @returns The updated task, or `null` when missing or blocked.
7005
+ */
7006
+ async updateIfStatusNotIn(tenantId, id, updates, blockedStatuses) {
7007
+ await this.ensureInitialized();
7008
+ const setClauses = [];
7009
+ const params = [];
7010
+ let paramIndex = 1;
7011
+ if (updates.title !== void 0) {
7012
+ setClauses.push(`title = $${paramIndex++}`);
7013
+ params.push(updates.title);
7014
+ }
7015
+ if (updates.description !== void 0) {
7016
+ setClauses.push(`description = $${paramIndex++}`);
7017
+ params.push(updates.description);
7018
+ }
7019
+ if (updates.status !== void 0) {
7020
+ setClauses.push(`status = $${paramIndex++}`);
7021
+ params.push(updates.status);
7022
+ }
7023
+ if (updates.priority !== void 0) {
7024
+ setClauses.push(`priority = $${paramIndex++}`);
7025
+ params.push(updates.priority);
7026
+ }
7027
+ if (updates.dueDate !== void 0) {
7028
+ setClauses.push(`due_date = $${paramIndex++}`);
7029
+ params.push(updates.dueDate);
7030
+ }
7031
+ if (updates.metadata !== void 0) {
7032
+ setClauses.push(`metadata = $${paramIndex++}`);
7033
+ params.push(JSON.stringify(updates.metadata));
7034
+ }
7035
+ if (updates.files !== void 0) {
7036
+ setClauses.push(`files = $${paramIndex++}`);
7037
+ params.push(JSON.stringify(updates.files));
7038
+ }
7039
+ if (updates.parentId !== void 0) {
7040
+ setClauses.push(`parent_id = $${paramIndex++}`);
7041
+ params.push(updates.parentId);
7042
+ }
7043
+ if (updates.sourceId !== void 0) {
7044
+ setClauses.push(`source_id = $${paramIndex++}`);
7045
+ params.push(updates.sourceId);
7046
+ }
7047
+ if (updates.context !== void 0) {
7048
+ setClauses.push(`context = $${paramIndex++}`);
7049
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
7050
+ }
7051
+ if (updates.ownerType !== void 0) {
7052
+ setClauses.push(`owner_type = $${paramIndex++}`);
7053
+ params.push(updates.ownerType);
7054
+ }
7055
+ if (updates.ownerId !== void 0) {
7056
+ setClauses.push(`owner_id = $${paramIndex++}`);
7057
+ params.push(updates.ownerId);
7058
+ }
7059
+ if (updates.requireReview !== void 0) {
7060
+ setClauses.push(`require_review = $${paramIndex++}`);
7061
+ params.push(updates.requireReview);
7062
+ }
7063
+ if (updates.dependencies !== void 0) {
7064
+ setClauses.push(`dependencies = $${paramIndex++}`);
7065
+ params.push(JSON.stringify(updates.dependencies));
7066
+ }
7067
+ if (updates.result !== void 0) {
7068
+ setClauses.push(`result = $${paramIndex++}`);
7069
+ params.push(updates.result);
7070
+ }
7071
+ if (updates.failureReason !== void 0) {
7072
+ setClauses.push(`failure_reason = $${paramIndex++}`);
7073
+ params.push(updates.failureReason);
7074
+ }
7075
+ if (updates.workspaceId !== void 0) {
7076
+ setClauses.push(`workspace_id = $${paramIndex++}`);
7077
+ params.push(updates.workspaceId);
7078
+ }
7079
+ if (updates.projectId !== void 0) {
7080
+ setClauses.push(`project_id = $${paramIndex++}`);
7081
+ params.push(updates.projectId);
7082
+ }
7083
+ setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
7084
+ const tenantParam = paramIndex++;
7085
+ const idParam = paramIndex++;
7086
+ params.push(tenantId, id);
7087
+ const blockedClause = blockedStatuses.length > 0 ? ` AND status <> ALL($${paramIndex}::text[])` : "";
7088
+ if (blockedStatuses.length > 0) params.push(blockedStatuses);
7089
+ const result = await this.pool.query(
7090
+ `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${tenantParam} AND id = $${idParam} AND updated_at <> '9999-12-31T23:59:59.999Z'${blockedClause} RETURNING *`,
6891
7091
  params
6892
7092
  );
6893
- return this.getById(tenantId, id);
7093
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
7094
+ }
7095
+ /** Atomically update a task only when its current status is expected. */
7096
+ async updateIfStatusIn(tenantId, id, updates, expectedStatuses) {
7097
+ await this.ensureInitialized();
7098
+ if (expectedStatuses.length === 0) return null;
7099
+ const setClauses = [];
7100
+ const params = [];
7101
+ let paramIndex = 1;
7102
+ if (updates.title !== void 0) {
7103
+ setClauses.push(`title = $${paramIndex++}`);
7104
+ params.push(updates.title);
7105
+ }
7106
+ if (updates.description !== void 0) {
7107
+ setClauses.push(`description = $${paramIndex++}`);
7108
+ params.push(updates.description);
7109
+ }
7110
+ if (updates.status !== void 0) {
7111
+ setClauses.push(`status = $${paramIndex++}`);
7112
+ params.push(updates.status);
7113
+ }
7114
+ if (updates.priority !== void 0) {
7115
+ setClauses.push(`priority = $${paramIndex++}`);
7116
+ params.push(updates.priority);
7117
+ }
7118
+ if (updates.dueDate !== void 0) {
7119
+ setClauses.push(`due_date = $${paramIndex++}`);
7120
+ params.push(updates.dueDate);
7121
+ }
7122
+ if (updates.metadata !== void 0) {
7123
+ setClauses.push(`metadata = $${paramIndex++}`);
7124
+ params.push(JSON.stringify(updates.metadata));
7125
+ }
7126
+ if (updates.files !== void 0) {
7127
+ setClauses.push(`files = $${paramIndex++}`);
7128
+ params.push(JSON.stringify(updates.files));
7129
+ }
7130
+ if (updates.parentId !== void 0) {
7131
+ setClauses.push(`parent_id = $${paramIndex++}`);
7132
+ params.push(updates.parentId);
7133
+ }
7134
+ if (updates.sourceId !== void 0) {
7135
+ setClauses.push(`source_id = $${paramIndex++}`);
7136
+ params.push(updates.sourceId);
7137
+ }
7138
+ if (updates.context !== void 0) {
7139
+ setClauses.push(`context = $${paramIndex++}`);
7140
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
7141
+ }
7142
+ if (updates.ownerType !== void 0) {
7143
+ setClauses.push(`owner_type = $${paramIndex++}`);
7144
+ params.push(updates.ownerType);
7145
+ }
7146
+ if (updates.ownerId !== void 0) {
7147
+ setClauses.push(`owner_id = $${paramIndex++}`);
7148
+ params.push(updates.ownerId);
7149
+ }
7150
+ if (updates.requireReview !== void 0) {
7151
+ setClauses.push(`require_review = $${paramIndex++}`);
7152
+ params.push(updates.requireReview);
7153
+ }
7154
+ if (updates.dependencies !== void 0) {
7155
+ setClauses.push(`dependencies = $${paramIndex++}`);
7156
+ params.push(JSON.stringify(updates.dependencies));
7157
+ }
7158
+ if (updates.result !== void 0) {
7159
+ setClauses.push(`result = $${paramIndex++}`);
7160
+ params.push(updates.result);
7161
+ }
7162
+ if (updates.failureReason !== void 0) {
7163
+ setClauses.push(`failure_reason = $${paramIndex++}`);
7164
+ params.push(updates.failureReason);
7165
+ }
7166
+ if (updates.workspaceId !== void 0) {
7167
+ setClauses.push(`workspace_id = $${paramIndex++}`);
7168
+ params.push(updates.workspaceId);
7169
+ }
7170
+ if (updates.projectId !== void 0) {
7171
+ setClauses.push(`project_id = $${paramIndex++}`);
7172
+ params.push(updates.projectId);
7173
+ }
7174
+ setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
7175
+ const tenantParam = paramIndex++;
7176
+ const idParam = paramIndex++;
7177
+ const statusesParam = paramIndex++;
7178
+ params.push(tenantId, id, expectedStatuses);
7179
+ const result = await this.pool.query(
7180
+ `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${tenantParam} AND id = $${idParam} AND updated_at <> '9999-12-31T23:59:59.999Z' AND status = ANY($${statusesParam}::text[]) RETURNING *`,
7181
+ params
7182
+ );
7183
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
7184
+ }
7185
+ /** Atomically update a task only when status and updatedAt match a read snapshot. */
7186
+ async updateIfStatusAndUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt) {
7187
+ await this.ensureInitialized();
7188
+ if (expectedStatuses.length === 0) return null;
7189
+ const expectedIso = new Date(expectedUpdatedAt).toISOString();
7190
+ const setClauses = [];
7191
+ const params = [];
7192
+ let paramIndex = 1;
7193
+ if (updates.title !== void 0) {
7194
+ setClauses.push(`title = $${paramIndex++}`);
7195
+ params.push(updates.title);
7196
+ }
7197
+ if (updates.description !== void 0) {
7198
+ setClauses.push(`description = $${paramIndex++}`);
7199
+ params.push(updates.description);
7200
+ }
7201
+ if (updates.status !== void 0) {
7202
+ setClauses.push(`status = $${paramIndex++}`);
7203
+ params.push(updates.status);
7204
+ }
7205
+ if (updates.priority !== void 0) {
7206
+ setClauses.push(`priority = $${paramIndex++}`);
7207
+ params.push(updates.priority);
7208
+ }
7209
+ if (updates.dueDate !== void 0) {
7210
+ setClauses.push(`due_date = $${paramIndex++}`);
7211
+ params.push(updates.dueDate);
7212
+ }
7213
+ if (updates.metadata !== void 0) {
7214
+ setClauses.push(`metadata = $${paramIndex++}`);
7215
+ params.push(JSON.stringify(updates.metadata));
7216
+ }
7217
+ if (updates.files !== void 0) {
7218
+ setClauses.push(`files = $${paramIndex++}`);
7219
+ params.push(JSON.stringify(updates.files));
7220
+ }
7221
+ if (updates.parentId !== void 0) {
7222
+ setClauses.push(`parent_id = $${paramIndex++}`);
7223
+ params.push(updates.parentId);
7224
+ }
7225
+ if (updates.sourceId !== void 0) {
7226
+ setClauses.push(`source_id = $${paramIndex++}`);
7227
+ params.push(updates.sourceId);
7228
+ }
7229
+ if (updates.context !== void 0) {
7230
+ setClauses.push(`context = $${paramIndex++}`);
7231
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
7232
+ }
7233
+ if (updates.ownerType !== void 0) {
7234
+ setClauses.push(`owner_type = $${paramIndex++}`);
7235
+ params.push(updates.ownerType);
7236
+ }
7237
+ if (updates.ownerId !== void 0) {
7238
+ setClauses.push(`owner_id = $${paramIndex++}`);
7239
+ params.push(updates.ownerId);
7240
+ }
7241
+ if (updates.requireReview !== void 0) {
7242
+ setClauses.push(`require_review = $${paramIndex++}`);
7243
+ params.push(updates.requireReview);
7244
+ }
7245
+ if (updates.dependencies !== void 0) {
7246
+ setClauses.push(`dependencies = $${paramIndex++}`);
7247
+ params.push(JSON.stringify(updates.dependencies));
7248
+ }
7249
+ if (updates.result !== void 0) {
7250
+ setClauses.push(`result = $${paramIndex++}`);
7251
+ params.push(updates.result);
7252
+ }
7253
+ if (updates.failureReason !== void 0) {
7254
+ setClauses.push(`failure_reason = $${paramIndex++}`);
7255
+ params.push(updates.failureReason);
7256
+ }
7257
+ if (updates.workspaceId !== void 0) {
7258
+ setClauses.push(`workspace_id = $${paramIndex++}`);
7259
+ params.push(updates.workspaceId);
7260
+ }
7261
+ if (updates.projectId !== void 0) {
7262
+ setClauses.push(`project_id = $${paramIndex++}`);
7263
+ params.push(updates.projectId);
7264
+ }
7265
+ setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
7266
+ const tenantParam = paramIndex++;
7267
+ const idParam = paramIndex++;
7268
+ const statusesParam = paramIndex++;
7269
+ const updatedAtParam = paramIndex++;
7270
+ params.push(tenantId, id, expectedStatuses, expectedIso);
7271
+ const result = await this.pool.query(
7272
+ `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${tenantParam} AND id = $${idParam} AND status = ANY($${statusesParam}::text[]) AND ${canonicalTimestampSnapshotSql("updated_at", `$${updatedAtParam}`)} RETURNING *`,
7273
+ params
7274
+ );
7275
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
7276
+ }
7277
+ /** Atomically update a child only when both child and parent snapshots match. */
7278
+ async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
7279
+ await this.ensureInitialized();
7280
+ if (expectedStatuses.length === 0) return null;
7281
+ const setClauses = [];
7282
+ const params = [];
7283
+ let paramIndex = 1;
7284
+ if (updates.title !== void 0) {
7285
+ setClauses.push(`title = $${paramIndex++}`);
7286
+ params.push(updates.title);
7287
+ }
7288
+ if (updates.description !== void 0) {
7289
+ setClauses.push(`description = $${paramIndex++}`);
7290
+ params.push(updates.description);
7291
+ }
7292
+ if (updates.status !== void 0) {
7293
+ setClauses.push(`status = $${paramIndex++}`);
7294
+ params.push(updates.status);
7295
+ }
7296
+ if (updates.priority !== void 0) {
7297
+ setClauses.push(`priority = $${paramIndex++}`);
7298
+ params.push(updates.priority);
7299
+ }
7300
+ if (updates.dueDate !== void 0) {
7301
+ setClauses.push(`due_date = $${paramIndex++}`);
7302
+ params.push(updates.dueDate);
7303
+ }
7304
+ if (updates.metadata !== void 0) {
7305
+ setClauses.push(`metadata = $${paramIndex++}`);
7306
+ params.push(JSON.stringify(updates.metadata));
7307
+ }
7308
+ if (updates.files !== void 0) {
7309
+ setClauses.push(`files = $${paramIndex++}`);
7310
+ params.push(JSON.stringify(updates.files));
7311
+ }
7312
+ if (updates.parentId !== void 0) {
7313
+ setClauses.push(`parent_id = $${paramIndex++}`);
7314
+ params.push(updates.parentId);
7315
+ }
7316
+ if (updates.sourceId !== void 0) {
7317
+ setClauses.push(`source_id = $${paramIndex++}`);
7318
+ params.push(updates.sourceId);
7319
+ }
7320
+ if (updates.context !== void 0) {
7321
+ setClauses.push(`context = $${paramIndex++}`);
7322
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
7323
+ }
7324
+ if (updates.ownerType !== void 0) {
7325
+ setClauses.push(`owner_type = $${paramIndex++}`);
7326
+ params.push(updates.ownerType);
7327
+ }
7328
+ if (updates.ownerId !== void 0) {
7329
+ setClauses.push(`owner_id = $${paramIndex++}`);
7330
+ params.push(updates.ownerId);
7331
+ }
7332
+ if (updates.requireReview !== void 0) {
7333
+ setClauses.push(`require_review = $${paramIndex++}`);
7334
+ params.push(updates.requireReview);
7335
+ }
7336
+ if (updates.dependencies !== void 0) {
7337
+ setClauses.push(`dependencies = $${paramIndex++}`);
7338
+ params.push(JSON.stringify(updates.dependencies));
7339
+ }
7340
+ if (updates.result !== void 0) {
7341
+ setClauses.push(`result = $${paramIndex++}`);
7342
+ params.push(updates.result);
7343
+ }
7344
+ if (updates.failureReason !== void 0) {
7345
+ setClauses.push(`failure_reason = $${paramIndex++}`);
7346
+ params.push(updates.failureReason);
7347
+ }
7348
+ if (updates.workspaceId !== void 0) {
7349
+ setClauses.push(`workspace_id = $${paramIndex++}`);
7350
+ params.push(updates.workspaceId);
7351
+ }
7352
+ if (updates.projectId !== void 0) {
7353
+ setClauses.push(`project_id = $${paramIndex++}`);
7354
+ params.push(updates.projectId);
7355
+ }
7356
+ setClauses.push(`updated_at = ${nextUpdatedAtSql("child.updated_at")}`);
7357
+ const tenantParam = paramIndex++;
7358
+ const idParam = paramIndex++;
7359
+ const statusesParam = paramIndex++;
7360
+ const updatedAtParam = paramIndex++;
7361
+ const parentIdParam = paramIndex++;
7362
+ const parentUpdatedAtParam = paramIndex++;
7363
+ params.push(
7364
+ tenantId,
7365
+ id,
7366
+ expectedStatuses,
7367
+ new Date(expectedUpdatedAt).toISOString(),
7368
+ parentId,
7369
+ new Date(expectedParentUpdatedAt).toISOString()
7370
+ );
7371
+ const result = await this.pool.query(
7372
+ `UPDATE lattice_tasks AS child SET ${setClauses.join(", ")}
7373
+ WHERE child.tenant_id = $${tenantParam} AND child.id = $${idParam}
7374
+ AND child.status = ANY($${statusesParam}::text[])
7375
+ AND ${canonicalTimestampSnapshotSql("child.updated_at", `$${updatedAtParam}`)}
7376
+ AND EXISTS (
7377
+ SELECT 1 FROM lattice_tasks AS parent
7378
+ WHERE parent.tenant_id = child.tenant_id
7379
+ AND parent.id = $${parentIdParam}
7380
+ AND ${canonicalTimestampSnapshotSql("parent.updated_at", `$${parentUpdatedAtParam}`, true)}
7381
+ )
7382
+ RETURNING child.*`,
7383
+ params
7384
+ );
7385
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
6894
7386
  }
6895
7387
  async delete(tenantId, id) {
6896
7388
  await this.ensureInitialized();
@@ -6941,6 +7433,41 @@ var PostgreSQLTaskWorkItemStore = class {
6941
7433
  );
6942
7434
  return this.rowToItem(result.rows[0]);
6943
7435
  }
7436
+ /** Find an event by its tenant- and task-scoped key without pagination. */
7437
+ async findByEventKey(tenantId, taskId, eventKey) {
7438
+ const result = await this.pool.query(
7439
+ `SELECT * FROM lattice_task_work_items
7440
+ WHERE tenant_id = $1 AND task_id = $2 AND event_key = $3`,
7441
+ [tenantId, taskId, eventKey]
7442
+ );
7443
+ return result.rows[0] ? this.rowToItem(result.rows[0]) : null;
7444
+ }
7445
+ /** Atomically return an existing event or create it once. */
7446
+ async createIfAbsentByEventKey(params) {
7447
+ const result = await this.pool.query(
7448
+ `INSERT INTO lattice_task_work_items
7449
+ (id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key)
7450
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
7451
+ ON CONFLICT (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
7452
+ DO UPDATE SET event_key = EXCLUDED.event_key
7453
+ RETURNING *`,
7454
+ [
7455
+ v4(),
7456
+ params.tenantId,
7457
+ params.taskId,
7458
+ params.action,
7459
+ params.actor,
7460
+ params.threadId || null,
7461
+ params.summary || null,
7462
+ params.detail ? JSON.stringify(params.detail) : null,
7463
+ params.attempt ?? null,
7464
+ params.workspaceId || null,
7465
+ params.projectId || null,
7466
+ params.eventKey
7467
+ ]
7468
+ );
7469
+ return this.rowToItem(result.rows[0]);
7470
+ }
6944
7471
  async list(filter) {
6945
7472
  let query = `SELECT * FROM lattice_task_work_items WHERE tenant_id = $1 AND task_id = $2`;
6946
7473
  const params = [filter.tenantId, filter.taskId];
@@ -6948,7 +7475,16 @@ var PostgreSQLTaskWorkItemStore = class {
6948
7475
  query += ` AND action = $${params.length + 1}`;
6949
7476
  params.push(filter.action);
6950
7477
  }
6951
- query += ` ORDER BY created_at ASC`;
7478
+ if (filter.workspaceId) {
7479
+ query += ` AND workspace_id = $${params.length + 1}`;
7480
+ params.push(filter.workspaceId);
7481
+ }
7482
+ if (filter.projectId) {
7483
+ query += ` AND project_id = $${params.length + 1}`;
7484
+ params.push(filter.projectId);
7485
+ }
7486
+ const order = filter.order === "desc" ? "DESC" : "ASC";
7487
+ query += ` ORDER BY created_at ${order}, id ${order}`;
6952
7488
  if (filter.limit) {
6953
7489
  query += ` LIMIT $${params.length + 1}`;
6954
7490
  params.push(filter.limit);
@@ -6973,6 +7509,7 @@ var PostgreSQLTaskWorkItemStore = class {
6973
7509
  attempt: row.attempt,
6974
7510
  workspaceId: row.workspace_id,
6975
7511
  projectId: row.project_id,
7512
+ eventKey: row.event_key == null ? void 0 : row.event_key,
6976
7513
  createdAt: new Date(row.created_at)
6977
7514
  };
6978
7515
  }
@@ -7972,6 +8509,20 @@ var addWorkItemProjectFieldsMigration = {
7972
8509
  `);
7973
8510
  }
7974
8511
  };
8512
+ var addTaskWorkItemEventKeyMigration = {
8513
+ version: 168,
8514
+ name: "add_task_work_item_event_key",
8515
+ up: async (client) => {
8516
+ await client.query(`
8517
+ ALTER TABLE lattice_task_work_items
8518
+ ADD COLUMN IF NOT EXISTS event_key TEXT
8519
+ `);
8520
+ await client.query(`
8521
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
8522
+ ON lattice_task_work_items (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
8523
+ `);
8524
+ }
8525
+ };
7975
8526
 
7976
8527
  // src/createPgStoreConfig.ts
7977
8528
  async function createPgStoreConfig(connectionString) {
@@ -8026,6 +8577,9 @@ async function createPgStoreConfig(connectionString) {
8026
8577
  mm.register(addWorkItemProjectFieldsMigration);
8027
8578
  mm.register(createConnectionConfigsTable);
8028
8579
  mm.register(addWorkflowRunsTenantStatusUpdatedIndex);
8580
+ mm.register(addProjectKindColumn);
8581
+ mm.register(addA2AKeyAssistantIds);
8582
+ mm.register(addTaskWorkItemEventKeyMigration);
8029
8583
  await mm.migrate();
8030
8584
  const checkpoint = PostgresSaver.fromConnString(connectionString);
8031
8585
  checkpoint.setup().catch((err) => {