@axiom-lattice/pg-stores 2.0.10 → 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.js CHANGED
@@ -2460,6 +2460,24 @@ var dropProjectFolderPathColumn = {
2460
2460
  }
2461
2461
  };
2462
2462
 
2463
+ // src/migrations/add_project_kind_column.ts
2464
+ var addProjectKindColumn = {
2465
+ version: 165,
2466
+ name: "add_project_kind_column",
2467
+ up: async (client) => {
2468
+ await client.query(`
2469
+ ALTER TABLE lattice_projects
2470
+ ADD COLUMN IF NOT EXISTS kind TEXT NOT NULL DEFAULT 'business'
2471
+ `);
2472
+ },
2473
+ down: async (client) => {
2474
+ await client.query(`
2475
+ ALTER TABLE lattice_projects
2476
+ DROP COLUMN IF EXISTS kind
2477
+ `);
2478
+ }
2479
+ };
2480
+
2463
2481
  // src/stores/PostgreSQLProjectStore.ts
2464
2482
  var PostgreSQLProjectStore = class {
2465
2483
  constructor(options) {
@@ -2482,6 +2500,7 @@ var PostgreSQLProjectStore = class {
2482
2500
  this.migrationManager.register(createProjectsTable);
2483
2501
  this.migrationManager.register(addProjectConfigColumn);
2484
2502
  this.migrationManager.register(dropProjectFolderPathColumn);
2503
+ this.migrationManager.register(addProjectKindColumn);
2485
2504
  if (options.autoMigrate !== false) {
2486
2505
  this.initialize().catch((error) => {
2487
2506
  console.error("Failed to initialize PostgreSQLProjectStore:", error);
@@ -2527,6 +2546,7 @@ var PostgreSQLProjectStore = class {
2527
2546
  name: row.name,
2528
2547
  description: row.description || void 0,
2529
2548
  config: row.config || void 0,
2549
+ kind: row.kind || "business",
2530
2550
  createdAt: row.created_at,
2531
2551
  updatedAt: row.updated_at
2532
2552
  };
@@ -2534,16 +2554,22 @@ var PostgreSQLProjectStore = class {
2534
2554
  /**
2535
2555
  * Get all projects for a specific workspace
2536
2556
  */
2537
- async getProjectsByWorkspace(tenantId, workspaceId) {
2557
+ async getProjectsByWorkspace(tenantId, workspaceId, filter) {
2538
2558
  await this.ensureInitialized();
2559
+ const conditions = ["tenant_id = $1", "workspace_id = $2"];
2560
+ const values = [tenantId, workspaceId];
2561
+ if (filter?.kind !== void 0) {
2562
+ values.push(filter.kind);
2563
+ conditions.push(`kind = $${values.length}`);
2564
+ }
2539
2565
  const result = await this.pool.query(
2540
2566
  `
2541
- SELECT id, tenant_id, workspace_id, name, description, config, created_at, updated_at
2567
+ SELECT id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at
2542
2568
  FROM lattice_projects
2543
- WHERE tenant_id = $1 AND workspace_id = $2
2569
+ WHERE ${conditions.join(" AND ")}
2544
2570
  ORDER BY created_at DESC
2545
2571
  `,
2546
- [tenantId, workspaceId]
2572
+ values
2547
2573
  );
2548
2574
  return result.rows.map(this.mapRowToProject);
2549
2575
  }
@@ -2554,7 +2580,7 @@ var PostgreSQLProjectStore = class {
2554
2580
  await this.ensureInitialized();
2555
2581
  const result = await this.pool.query(
2556
2582
  `
2557
- SELECT id, tenant_id, workspace_id, name, description, config, created_at, updated_at
2583
+ SELECT id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at
2558
2584
  FROM lattice_projects
2559
2585
  WHERE id = $1 AND tenant_id = $2
2560
2586
  `,
@@ -2571,18 +2597,20 @@ var PostgreSQLProjectStore = class {
2571
2597
  async createProject(tenantId, workspaceId, id, data) {
2572
2598
  await this.ensureInitialized();
2573
2599
  const now = /* @__PURE__ */ new Date();
2600
+ const kind = data.kind || "business";
2574
2601
  await this.pool.query(
2575
2602
  `
2576
- INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, config, created_at, updated_at)
2577
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
2603
+ INSERT INTO lattice_projects (id, tenant_id, workspace_id, name, description, config, kind, created_at, updated_at)
2604
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
2578
2605
  ON CONFLICT (id, tenant_id) DO UPDATE SET
2579
2606
  workspace_id = EXCLUDED.workspace_id,
2580
2607
  name = EXCLUDED.name,
2581
2608
  description = EXCLUDED.description,
2582
2609
  config = EXCLUDED.config,
2610
+ kind = EXCLUDED.kind,
2583
2611
  updated_at = EXCLUDED.updated_at
2584
2612
  `,
2585
- [id, tenantId, workspaceId, data.name, data.description || null, data.config || null, now, now]
2613
+ [id, tenantId, workspaceId, data.name, data.description || null, data.config || null, kind, now, now]
2586
2614
  );
2587
2615
  return {
2588
2616
  id,
@@ -2591,6 +2619,7 @@ var PostgreSQLProjectStore = class {
2591
2619
  name: data.name,
2592
2620
  description: data.description,
2593
2621
  config: data.config,
2622
+ kind,
2594
2623
  createdAt: now,
2595
2624
  updatedAt: now
2596
2625
  };
@@ -2623,6 +2652,10 @@ var PostgreSQLProjectStore = class {
2623
2652
  updateFields.push(`config = $${paramIndex++}`);
2624
2653
  updateValues.push(updates.config || null);
2625
2654
  }
2655
+ if (updates.kind !== void 0) {
2656
+ updateFields.push(`kind = $${paramIndex++}`);
2657
+ updateValues.push(updates.kind);
2658
+ }
2626
2659
  if (updateFields.length === 0) {
2627
2660
  return existing;
2628
2661
  }
@@ -5853,6 +5886,24 @@ var createA2AApiKeysTable = {
5853
5886
  }
5854
5887
  };
5855
5888
 
5889
+ // src/migrations/add_a2a_key_assistant_ids.ts
5890
+ var addA2AKeyAssistantIds = {
5891
+ version: 167,
5892
+ name: "add_a2a_key_assistant_ids",
5893
+ up: async (client) => {
5894
+ await client.query(`
5895
+ ALTER TABLE lattice_a2a_api_keys
5896
+ ADD COLUMN IF NOT EXISTS assistant_ids JSONB
5897
+ `);
5898
+ },
5899
+ down: async (client) => {
5900
+ await client.query(`
5901
+ ALTER TABLE lattice_a2a_api_keys
5902
+ DROP COLUMN IF EXISTS assistant_ids
5903
+ `);
5904
+ }
5905
+ };
5906
+
5856
5907
  // src/stores/PostgreSQLA2AApiKeyStore.ts
5857
5908
  var import_core5 = require("@axiom-lattice/core");
5858
5909
  var import_crypto2 = require("crypto");
@@ -5864,8 +5915,8 @@ function mapRowToRecord(row) {
5864
5915
  id: row.id,
5865
5916
  key: (0, import_core5.decrypt)(row.key_value),
5866
5917
  tenantId: row.tenant_id,
5867
- projectId: row.project_id || void 0,
5868
- workspaceId: row.workspace_id || void 0,
5918
+ projectId: row.project_id,
5919
+ assistantIds: row.assistant_ids ?? void 0,
5869
5920
  label: row.label || void 0,
5870
5921
  enabled: row.enabled,
5871
5922
  createdAt: row.created_at,
@@ -5888,6 +5939,7 @@ var PostgreSQLA2AApiKeyStore = class {
5888
5939
  })();
5889
5940
  this.migrationManager = new MigrationManager(this.pool);
5890
5941
  this.migrationManager.register(createA2AApiKeysTable);
5942
+ this.migrationManager.register(addA2AKeyAssistantIds);
5891
5943
  if (options.autoMigrate !== false) {
5892
5944
  this.initialize().catch((error) => {
5893
5945
  console.error("Failed to initialize PostgreSQLA2AApiKeyStore:", error);
@@ -5926,6 +5978,14 @@ var PostgreSQLA2AApiKeyStore = class {
5926
5978
  }
5927
5979
  return null;
5928
5980
  }
5981
+ async findById(id) {
5982
+ await this.ensureInitialized();
5983
+ const result = await this.pool.query(
5984
+ `SELECT * FROM lattice_a2a_api_keys WHERE id = $1`,
5985
+ [id]
5986
+ );
5987
+ return result.rows[0] ? mapRowToRecord(result.rows[0]) : null;
5988
+ }
5929
5989
  async list(params) {
5930
5990
  await this.ensureInitialized();
5931
5991
  const limit = params.limit || 100;
@@ -5947,9 +6007,9 @@ var PostgreSQLA2AApiKeyStore = class {
5947
6007
  await this.ensureInitialized();
5948
6008
  const key = generateApiKey();
5949
6009
  const result = await this.pool.query(
5950
- `INSERT INTO lattice_a2a_api_keys (key_value, tenant_id, project_id, workspace_id, label)
6010
+ `INSERT INTO lattice_a2a_api_keys (key_value, tenant_id, project_id, assistant_ids, label)
5951
6011
  VALUES ($1, $2, $3, $4, $5) RETURNING *`,
5952
- [(0, import_core5.encrypt)(key), input.tenantId, input.projectId || null, input.workspaceId || null, input.label || null]
6012
+ [(0, import_core5.encrypt)(key), input.tenantId, input.projectId, input.assistantIds ? JSON.stringify(input.assistantIds) : null, input.label || null]
5953
6013
  );
5954
6014
  const record = mapRowToRecord(result.rows[0]);
5955
6015
  record.key = key;
@@ -6000,8 +6060,8 @@ var PostgreSQLA2AApiKeyStore = class {
6000
6060
  map.set(key, {
6001
6061
  key,
6002
6062
  tenantId: row.tenant_id,
6003
- projectId: row.project_id || void 0,
6004
- workspaceId: row.workspace_id || void 0
6063
+ projectId: row.project_id,
6064
+ assistantIds: row.assistant_ids ?? void 0
6005
6065
  });
6006
6066
  }
6007
6067
  return map;
@@ -6723,6 +6783,49 @@ var taskMigrations = [
6723
6783
 
6724
6784
  // src/stores/PostgreSQLTaskStore.ts
6725
6785
  var import_uuid3 = require("uuid");
6786
+ function nextUpdatedAtSql(column = "updated_at") {
6787
+ return `to_char(
6788
+ date_trunc('milliseconds', GREATEST(
6789
+ clock_timestamp(),
6790
+ CASE
6791
+ WHEN ${canonicalTimestampValidationSql(column)}
6792
+ THEN ${column}::timestamptz + interval '1 millisecond'
6793
+ ELSE clock_timestamp()
6794
+ END
6795
+ ))
6796
+ AT TIME ZONE 'UTC',
6797
+ 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'
6798
+ )`;
6799
+ }
6800
+ function canonicalTimestampValidationSql(column, allowMaximum = false) {
6801
+ const year = `substring(${column} FROM 1 FOR 4)::integer`;
6802
+ const month = `substring(${column} FROM 6 FOR 2)::integer`;
6803
+ const day = `substring(${column} FROM 9 FOR 2)::integer`;
6804
+ return `CASE
6805
+ 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
6806
+ ${allowMaximum ? "" : `${column} <> '9999-12-31T23:59:59.999Z' AND`}
6807
+ ${year} BETWEEN 1 AND 9999
6808
+ AND ${month} BETWEEN 1 AND 12
6809
+ AND ${day} BETWEEN 1 AND CASE
6810
+ WHEN ${month} = 2 THEN CASE
6811
+ WHEN (${year} % 400 = 0) OR (${year} % 4 = 0 AND ${year} % 100 <> 0) THEN 29
6812
+ ELSE 28
6813
+ END
6814
+ WHEN ${month} IN (4, 6, 9, 11) THEN 30
6815
+ ELSE 31
6816
+ END
6817
+ AND substring(${column} FROM 12 FOR 2)::integer BETWEEN 0 AND 23
6818
+ AND substring(${column} FROM 15 FOR 2)::integer BETWEEN 0 AND 59
6819
+ AND substring(${column} FROM 18 FOR 2)::integer BETWEEN 0 AND 59
6820
+ ELSE FALSE
6821
+ END`;
6822
+ }
6823
+ function canonicalTimestampSnapshotSql(column, parameter, allowMaximum = false) {
6824
+ return `CASE WHEN ${canonicalTimestampValidationSql(column, allowMaximum)}
6825
+ THEN ${column}::timestamptz = ${parameter}::timestamptz
6826
+ ELSE FALSE
6827
+ END`;
6828
+ }
6726
6829
  function parseTaskFiles(raw) {
6727
6830
  if (!raw) return void 0;
6728
6831
  try {
@@ -6808,7 +6911,7 @@ var PostgreSQLTaskStore = class {
6808
6911
  }
6809
6912
  async create(params) {
6810
6913
  await this.ensureInitialized();
6811
- const id = (0, import_uuid3.v4)();
6914
+ const id = params.id ?? (0, import_uuid3.v4)();
6812
6915
  const now = (/* @__PURE__ */ new Date()).toISOString();
6813
6916
  await this.pool.query(
6814
6917
  `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)
@@ -6952,7 +7055,7 @@ var PostgreSQLTaskStore = class {
6952
7055
  }
6953
7056
  if (updates.context !== void 0) {
6954
7057
  setClauses.push(`context = $${paramIndex++}`);
6955
- params.push(JSON.stringify(updates.context));
7058
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
6956
7059
  }
6957
7060
  if (updates.ownerType !== void 0) {
6958
7061
  setClauses.push(`owner_type = $${paramIndex++}`);
@@ -6989,14 +7092,403 @@ var PostgreSQLTaskStore = class {
6989
7092
  if (setClauses.length === 0) {
6990
7093
  return existing;
6991
7094
  }
6992
- setClauses.push(`updated_at = $${paramIndex++}`);
6993
- params.push((/* @__PURE__ */ new Date()).toISOString());
7095
+ setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
6994
7096
  params.push(tenantId, id);
6995
- await this.pool.query(
6996
- `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${paramIndex++} AND id = $${paramIndex++}`,
7097
+ const result = await this.pool.query(
7098
+ `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${paramIndex++} AND id = $${paramIndex++} AND updated_at <> '9999-12-31T23:59:59.999Z' RETURNING *`,
7099
+ params
7100
+ );
7101
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
7102
+ }
7103
+ /**
7104
+ * Atomically update a task unless its current status is blocked.
7105
+ *
7106
+ * @param tenantId Tenant identifier.
7107
+ * @param id Task identifier.
7108
+ * @param updates Partial task data to update.
7109
+ * @param blockedStatuses Current statuses that prevent the update.
7110
+ * @returns The updated task, or `null` when missing or blocked.
7111
+ */
7112
+ async updateIfStatusNotIn(tenantId, id, updates, blockedStatuses) {
7113
+ await this.ensureInitialized();
7114
+ const setClauses = [];
7115
+ const params = [];
7116
+ let paramIndex = 1;
7117
+ if (updates.title !== void 0) {
7118
+ setClauses.push(`title = $${paramIndex++}`);
7119
+ params.push(updates.title);
7120
+ }
7121
+ if (updates.description !== void 0) {
7122
+ setClauses.push(`description = $${paramIndex++}`);
7123
+ params.push(updates.description);
7124
+ }
7125
+ if (updates.status !== void 0) {
7126
+ setClauses.push(`status = $${paramIndex++}`);
7127
+ params.push(updates.status);
7128
+ }
7129
+ if (updates.priority !== void 0) {
7130
+ setClauses.push(`priority = $${paramIndex++}`);
7131
+ params.push(updates.priority);
7132
+ }
7133
+ if (updates.dueDate !== void 0) {
7134
+ setClauses.push(`due_date = $${paramIndex++}`);
7135
+ params.push(updates.dueDate);
7136
+ }
7137
+ if (updates.metadata !== void 0) {
7138
+ setClauses.push(`metadata = $${paramIndex++}`);
7139
+ params.push(JSON.stringify(updates.metadata));
7140
+ }
7141
+ if (updates.files !== void 0) {
7142
+ setClauses.push(`files = $${paramIndex++}`);
7143
+ params.push(JSON.stringify(updates.files));
7144
+ }
7145
+ if (updates.parentId !== void 0) {
7146
+ setClauses.push(`parent_id = $${paramIndex++}`);
7147
+ params.push(updates.parentId);
7148
+ }
7149
+ if (updates.sourceId !== void 0) {
7150
+ setClauses.push(`source_id = $${paramIndex++}`);
7151
+ params.push(updates.sourceId);
7152
+ }
7153
+ if (updates.context !== void 0) {
7154
+ setClauses.push(`context = $${paramIndex++}`);
7155
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
7156
+ }
7157
+ if (updates.ownerType !== void 0) {
7158
+ setClauses.push(`owner_type = $${paramIndex++}`);
7159
+ params.push(updates.ownerType);
7160
+ }
7161
+ if (updates.ownerId !== void 0) {
7162
+ setClauses.push(`owner_id = $${paramIndex++}`);
7163
+ params.push(updates.ownerId);
7164
+ }
7165
+ if (updates.requireReview !== void 0) {
7166
+ setClauses.push(`require_review = $${paramIndex++}`);
7167
+ params.push(updates.requireReview);
7168
+ }
7169
+ if (updates.dependencies !== void 0) {
7170
+ setClauses.push(`dependencies = $${paramIndex++}`);
7171
+ params.push(JSON.stringify(updates.dependencies));
7172
+ }
7173
+ if (updates.result !== void 0) {
7174
+ setClauses.push(`result = $${paramIndex++}`);
7175
+ params.push(updates.result);
7176
+ }
7177
+ if (updates.failureReason !== void 0) {
7178
+ setClauses.push(`failure_reason = $${paramIndex++}`);
7179
+ params.push(updates.failureReason);
7180
+ }
7181
+ if (updates.workspaceId !== void 0) {
7182
+ setClauses.push(`workspace_id = $${paramIndex++}`);
7183
+ params.push(updates.workspaceId);
7184
+ }
7185
+ if (updates.projectId !== void 0) {
7186
+ setClauses.push(`project_id = $${paramIndex++}`);
7187
+ params.push(updates.projectId);
7188
+ }
7189
+ setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
7190
+ const tenantParam = paramIndex++;
7191
+ const idParam = paramIndex++;
7192
+ params.push(tenantId, id);
7193
+ const blockedClause = blockedStatuses.length > 0 ? ` AND status <> ALL($${paramIndex}::text[])` : "";
7194
+ if (blockedStatuses.length > 0) params.push(blockedStatuses);
7195
+ const result = await this.pool.query(
7196
+ `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${tenantParam} AND id = $${idParam} AND updated_at <> '9999-12-31T23:59:59.999Z'${blockedClause} RETURNING *`,
6997
7197
  params
6998
7198
  );
6999
- return this.getById(tenantId, id);
7199
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
7200
+ }
7201
+ /** Atomically update a task only when its current status is expected. */
7202
+ async updateIfStatusIn(tenantId, id, updates, expectedStatuses) {
7203
+ await this.ensureInitialized();
7204
+ if (expectedStatuses.length === 0) return null;
7205
+ const setClauses = [];
7206
+ const params = [];
7207
+ let paramIndex = 1;
7208
+ if (updates.title !== void 0) {
7209
+ setClauses.push(`title = $${paramIndex++}`);
7210
+ params.push(updates.title);
7211
+ }
7212
+ if (updates.description !== void 0) {
7213
+ setClauses.push(`description = $${paramIndex++}`);
7214
+ params.push(updates.description);
7215
+ }
7216
+ if (updates.status !== void 0) {
7217
+ setClauses.push(`status = $${paramIndex++}`);
7218
+ params.push(updates.status);
7219
+ }
7220
+ if (updates.priority !== void 0) {
7221
+ setClauses.push(`priority = $${paramIndex++}`);
7222
+ params.push(updates.priority);
7223
+ }
7224
+ if (updates.dueDate !== void 0) {
7225
+ setClauses.push(`due_date = $${paramIndex++}`);
7226
+ params.push(updates.dueDate);
7227
+ }
7228
+ if (updates.metadata !== void 0) {
7229
+ setClauses.push(`metadata = $${paramIndex++}`);
7230
+ params.push(JSON.stringify(updates.metadata));
7231
+ }
7232
+ if (updates.files !== void 0) {
7233
+ setClauses.push(`files = $${paramIndex++}`);
7234
+ params.push(JSON.stringify(updates.files));
7235
+ }
7236
+ if (updates.parentId !== void 0) {
7237
+ setClauses.push(`parent_id = $${paramIndex++}`);
7238
+ params.push(updates.parentId);
7239
+ }
7240
+ if (updates.sourceId !== void 0) {
7241
+ setClauses.push(`source_id = $${paramIndex++}`);
7242
+ params.push(updates.sourceId);
7243
+ }
7244
+ if (updates.context !== void 0) {
7245
+ setClauses.push(`context = $${paramIndex++}`);
7246
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
7247
+ }
7248
+ if (updates.ownerType !== void 0) {
7249
+ setClauses.push(`owner_type = $${paramIndex++}`);
7250
+ params.push(updates.ownerType);
7251
+ }
7252
+ if (updates.ownerId !== void 0) {
7253
+ setClauses.push(`owner_id = $${paramIndex++}`);
7254
+ params.push(updates.ownerId);
7255
+ }
7256
+ if (updates.requireReview !== void 0) {
7257
+ setClauses.push(`require_review = $${paramIndex++}`);
7258
+ params.push(updates.requireReview);
7259
+ }
7260
+ if (updates.dependencies !== void 0) {
7261
+ setClauses.push(`dependencies = $${paramIndex++}`);
7262
+ params.push(JSON.stringify(updates.dependencies));
7263
+ }
7264
+ if (updates.result !== void 0) {
7265
+ setClauses.push(`result = $${paramIndex++}`);
7266
+ params.push(updates.result);
7267
+ }
7268
+ if (updates.failureReason !== void 0) {
7269
+ setClauses.push(`failure_reason = $${paramIndex++}`);
7270
+ params.push(updates.failureReason);
7271
+ }
7272
+ if (updates.workspaceId !== void 0) {
7273
+ setClauses.push(`workspace_id = $${paramIndex++}`);
7274
+ params.push(updates.workspaceId);
7275
+ }
7276
+ if (updates.projectId !== void 0) {
7277
+ setClauses.push(`project_id = $${paramIndex++}`);
7278
+ params.push(updates.projectId);
7279
+ }
7280
+ setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
7281
+ const tenantParam = paramIndex++;
7282
+ const idParam = paramIndex++;
7283
+ const statusesParam = paramIndex++;
7284
+ params.push(tenantId, id, expectedStatuses);
7285
+ const result = await this.pool.query(
7286
+ `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 *`,
7287
+ params
7288
+ );
7289
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
7290
+ }
7291
+ /** Atomically update a task only when status and updatedAt match a read snapshot. */
7292
+ async updateIfStatusAndUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt) {
7293
+ await this.ensureInitialized();
7294
+ if (expectedStatuses.length === 0) return null;
7295
+ const expectedIso = new Date(expectedUpdatedAt).toISOString();
7296
+ const setClauses = [];
7297
+ const params = [];
7298
+ let paramIndex = 1;
7299
+ if (updates.title !== void 0) {
7300
+ setClauses.push(`title = $${paramIndex++}`);
7301
+ params.push(updates.title);
7302
+ }
7303
+ if (updates.description !== void 0) {
7304
+ setClauses.push(`description = $${paramIndex++}`);
7305
+ params.push(updates.description);
7306
+ }
7307
+ if (updates.status !== void 0) {
7308
+ setClauses.push(`status = $${paramIndex++}`);
7309
+ params.push(updates.status);
7310
+ }
7311
+ if (updates.priority !== void 0) {
7312
+ setClauses.push(`priority = $${paramIndex++}`);
7313
+ params.push(updates.priority);
7314
+ }
7315
+ if (updates.dueDate !== void 0) {
7316
+ setClauses.push(`due_date = $${paramIndex++}`);
7317
+ params.push(updates.dueDate);
7318
+ }
7319
+ if (updates.metadata !== void 0) {
7320
+ setClauses.push(`metadata = $${paramIndex++}`);
7321
+ params.push(JSON.stringify(updates.metadata));
7322
+ }
7323
+ if (updates.files !== void 0) {
7324
+ setClauses.push(`files = $${paramIndex++}`);
7325
+ params.push(JSON.stringify(updates.files));
7326
+ }
7327
+ if (updates.parentId !== void 0) {
7328
+ setClauses.push(`parent_id = $${paramIndex++}`);
7329
+ params.push(updates.parentId);
7330
+ }
7331
+ if (updates.sourceId !== void 0) {
7332
+ setClauses.push(`source_id = $${paramIndex++}`);
7333
+ params.push(updates.sourceId);
7334
+ }
7335
+ if (updates.context !== void 0) {
7336
+ setClauses.push(`context = $${paramIndex++}`);
7337
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
7338
+ }
7339
+ if (updates.ownerType !== void 0) {
7340
+ setClauses.push(`owner_type = $${paramIndex++}`);
7341
+ params.push(updates.ownerType);
7342
+ }
7343
+ if (updates.ownerId !== void 0) {
7344
+ setClauses.push(`owner_id = $${paramIndex++}`);
7345
+ params.push(updates.ownerId);
7346
+ }
7347
+ if (updates.requireReview !== void 0) {
7348
+ setClauses.push(`require_review = $${paramIndex++}`);
7349
+ params.push(updates.requireReview);
7350
+ }
7351
+ if (updates.dependencies !== void 0) {
7352
+ setClauses.push(`dependencies = $${paramIndex++}`);
7353
+ params.push(JSON.stringify(updates.dependencies));
7354
+ }
7355
+ if (updates.result !== void 0) {
7356
+ setClauses.push(`result = $${paramIndex++}`);
7357
+ params.push(updates.result);
7358
+ }
7359
+ if (updates.failureReason !== void 0) {
7360
+ setClauses.push(`failure_reason = $${paramIndex++}`);
7361
+ params.push(updates.failureReason);
7362
+ }
7363
+ if (updates.workspaceId !== void 0) {
7364
+ setClauses.push(`workspace_id = $${paramIndex++}`);
7365
+ params.push(updates.workspaceId);
7366
+ }
7367
+ if (updates.projectId !== void 0) {
7368
+ setClauses.push(`project_id = $${paramIndex++}`);
7369
+ params.push(updates.projectId);
7370
+ }
7371
+ setClauses.push(`updated_at = ${nextUpdatedAtSql()}`);
7372
+ const tenantParam = paramIndex++;
7373
+ const idParam = paramIndex++;
7374
+ const statusesParam = paramIndex++;
7375
+ const updatedAtParam = paramIndex++;
7376
+ params.push(tenantId, id, expectedStatuses, expectedIso);
7377
+ const result = await this.pool.query(
7378
+ `UPDATE lattice_tasks SET ${setClauses.join(", ")} WHERE tenant_id = $${tenantParam} AND id = $${idParam} AND status = ANY($${statusesParam}::text[]) AND ${canonicalTimestampSnapshotSql("updated_at", `$${updatedAtParam}`)} RETURNING *`,
7379
+ params
7380
+ );
7381
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
7382
+ }
7383
+ /** Atomically update a child only when both child and parent snapshots match. */
7384
+ async updateIfStatusUpdatedAtAndParentUpdatedAt(tenantId, id, updates, expectedStatuses, expectedUpdatedAt, parentId, expectedParentUpdatedAt) {
7385
+ await this.ensureInitialized();
7386
+ if (expectedStatuses.length === 0) return null;
7387
+ const setClauses = [];
7388
+ const params = [];
7389
+ let paramIndex = 1;
7390
+ if (updates.title !== void 0) {
7391
+ setClauses.push(`title = $${paramIndex++}`);
7392
+ params.push(updates.title);
7393
+ }
7394
+ if (updates.description !== void 0) {
7395
+ setClauses.push(`description = $${paramIndex++}`);
7396
+ params.push(updates.description);
7397
+ }
7398
+ if (updates.status !== void 0) {
7399
+ setClauses.push(`status = $${paramIndex++}`);
7400
+ params.push(updates.status);
7401
+ }
7402
+ if (updates.priority !== void 0) {
7403
+ setClauses.push(`priority = $${paramIndex++}`);
7404
+ params.push(updates.priority);
7405
+ }
7406
+ if (updates.dueDate !== void 0) {
7407
+ setClauses.push(`due_date = $${paramIndex++}`);
7408
+ params.push(updates.dueDate);
7409
+ }
7410
+ if (updates.metadata !== void 0) {
7411
+ setClauses.push(`metadata = $${paramIndex++}`);
7412
+ params.push(JSON.stringify(updates.metadata));
7413
+ }
7414
+ if (updates.files !== void 0) {
7415
+ setClauses.push(`files = $${paramIndex++}`);
7416
+ params.push(JSON.stringify(updates.files));
7417
+ }
7418
+ if (updates.parentId !== void 0) {
7419
+ setClauses.push(`parent_id = $${paramIndex++}`);
7420
+ params.push(updates.parentId);
7421
+ }
7422
+ if (updates.sourceId !== void 0) {
7423
+ setClauses.push(`source_id = $${paramIndex++}`);
7424
+ params.push(updates.sourceId);
7425
+ }
7426
+ if (updates.context !== void 0) {
7427
+ setClauses.push(`context = $${paramIndex++}`);
7428
+ params.push(updates.context === null ? null : JSON.stringify(updates.context));
7429
+ }
7430
+ if (updates.ownerType !== void 0) {
7431
+ setClauses.push(`owner_type = $${paramIndex++}`);
7432
+ params.push(updates.ownerType);
7433
+ }
7434
+ if (updates.ownerId !== void 0) {
7435
+ setClauses.push(`owner_id = $${paramIndex++}`);
7436
+ params.push(updates.ownerId);
7437
+ }
7438
+ if (updates.requireReview !== void 0) {
7439
+ setClauses.push(`require_review = $${paramIndex++}`);
7440
+ params.push(updates.requireReview);
7441
+ }
7442
+ if (updates.dependencies !== void 0) {
7443
+ setClauses.push(`dependencies = $${paramIndex++}`);
7444
+ params.push(JSON.stringify(updates.dependencies));
7445
+ }
7446
+ if (updates.result !== void 0) {
7447
+ setClauses.push(`result = $${paramIndex++}`);
7448
+ params.push(updates.result);
7449
+ }
7450
+ if (updates.failureReason !== void 0) {
7451
+ setClauses.push(`failure_reason = $${paramIndex++}`);
7452
+ params.push(updates.failureReason);
7453
+ }
7454
+ if (updates.workspaceId !== void 0) {
7455
+ setClauses.push(`workspace_id = $${paramIndex++}`);
7456
+ params.push(updates.workspaceId);
7457
+ }
7458
+ if (updates.projectId !== void 0) {
7459
+ setClauses.push(`project_id = $${paramIndex++}`);
7460
+ params.push(updates.projectId);
7461
+ }
7462
+ setClauses.push(`updated_at = ${nextUpdatedAtSql("child.updated_at")}`);
7463
+ const tenantParam = paramIndex++;
7464
+ const idParam = paramIndex++;
7465
+ const statusesParam = paramIndex++;
7466
+ const updatedAtParam = paramIndex++;
7467
+ const parentIdParam = paramIndex++;
7468
+ const parentUpdatedAtParam = paramIndex++;
7469
+ params.push(
7470
+ tenantId,
7471
+ id,
7472
+ expectedStatuses,
7473
+ new Date(expectedUpdatedAt).toISOString(),
7474
+ parentId,
7475
+ new Date(expectedParentUpdatedAt).toISOString()
7476
+ );
7477
+ const result = await this.pool.query(
7478
+ `UPDATE lattice_tasks AS child SET ${setClauses.join(", ")}
7479
+ WHERE child.tenant_id = $${tenantParam} AND child.id = $${idParam}
7480
+ AND child.status = ANY($${statusesParam}::text[])
7481
+ AND ${canonicalTimestampSnapshotSql("child.updated_at", `$${updatedAtParam}`)}
7482
+ AND EXISTS (
7483
+ SELECT 1 FROM lattice_tasks AS parent
7484
+ WHERE parent.tenant_id = child.tenant_id
7485
+ AND parent.id = $${parentIdParam}
7486
+ AND ${canonicalTimestampSnapshotSql("parent.updated_at", `$${parentUpdatedAtParam}`, true)}
7487
+ )
7488
+ RETURNING child.*`,
7489
+ params
7490
+ );
7491
+ return result.rows[0] ? mapRowToTask(result.rows[0]) : null;
7000
7492
  }
7001
7493
  async delete(tenantId, id) {
7002
7494
  await this.ensureInitialized();
@@ -7047,6 +7539,41 @@ var PostgreSQLTaskWorkItemStore = class {
7047
7539
  );
7048
7540
  return this.rowToItem(result.rows[0]);
7049
7541
  }
7542
+ /** Find an event by its tenant- and task-scoped key without pagination. */
7543
+ async findByEventKey(tenantId, taskId, eventKey) {
7544
+ const result = await this.pool.query(
7545
+ `SELECT * FROM lattice_task_work_items
7546
+ WHERE tenant_id = $1 AND task_id = $2 AND event_key = $3`,
7547
+ [tenantId, taskId, eventKey]
7548
+ );
7549
+ return result.rows[0] ? this.rowToItem(result.rows[0]) : null;
7550
+ }
7551
+ /** Atomically return an existing event or create it once. */
7552
+ async createIfAbsentByEventKey(params) {
7553
+ const result = await this.pool.query(
7554
+ `INSERT INTO lattice_task_work_items
7555
+ (id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id, event_key)
7556
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
7557
+ ON CONFLICT (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
7558
+ DO UPDATE SET event_key = EXCLUDED.event_key
7559
+ RETURNING *`,
7560
+ [
7561
+ (0, import_uuid4.v4)(),
7562
+ params.tenantId,
7563
+ params.taskId,
7564
+ params.action,
7565
+ params.actor,
7566
+ params.threadId || null,
7567
+ params.summary || null,
7568
+ params.detail ? JSON.stringify(params.detail) : null,
7569
+ params.attempt ?? null,
7570
+ params.workspaceId || null,
7571
+ params.projectId || null,
7572
+ params.eventKey
7573
+ ]
7574
+ );
7575
+ return this.rowToItem(result.rows[0]);
7576
+ }
7050
7577
  async list(filter) {
7051
7578
  let query = `SELECT * FROM lattice_task_work_items WHERE tenant_id = $1 AND task_id = $2`;
7052
7579
  const params = [filter.tenantId, filter.taskId];
@@ -7054,7 +7581,16 @@ var PostgreSQLTaskWorkItemStore = class {
7054
7581
  query += ` AND action = $${params.length + 1}`;
7055
7582
  params.push(filter.action);
7056
7583
  }
7057
- query += ` ORDER BY created_at ASC`;
7584
+ if (filter.workspaceId) {
7585
+ query += ` AND workspace_id = $${params.length + 1}`;
7586
+ params.push(filter.workspaceId);
7587
+ }
7588
+ if (filter.projectId) {
7589
+ query += ` AND project_id = $${params.length + 1}`;
7590
+ params.push(filter.projectId);
7591
+ }
7592
+ const order = filter.order === "desc" ? "DESC" : "ASC";
7593
+ query += ` ORDER BY created_at ${order}, id ${order}`;
7058
7594
  if (filter.limit) {
7059
7595
  query += ` LIMIT $${params.length + 1}`;
7060
7596
  params.push(filter.limit);
@@ -7079,6 +7615,7 @@ var PostgreSQLTaskWorkItemStore = class {
7079
7615
  attempt: row.attempt,
7080
7616
  workspaceId: row.workspace_id,
7081
7617
  projectId: row.project_id,
7618
+ eventKey: row.event_key == null ? void 0 : row.event_key,
7082
7619
  createdAt: new Date(row.created_at)
7083
7620
  };
7084
7621
  }
@@ -8078,6 +8615,20 @@ var addWorkItemProjectFieldsMigration = {
8078
8615
  `);
8079
8616
  }
8080
8617
  };
8618
+ var addTaskWorkItemEventKeyMigration = {
8619
+ version: 168,
8620
+ name: "add_task_work_item_event_key",
8621
+ up: async (client) => {
8622
+ await client.query(`
8623
+ ALTER TABLE lattice_task_work_items
8624
+ ADD COLUMN IF NOT EXISTS event_key TEXT
8625
+ `);
8626
+ await client.query(`
8627
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_task_work_items_event_key
8628
+ ON lattice_task_work_items (tenant_id, task_id, event_key) WHERE event_key IS NOT NULL
8629
+ `);
8630
+ }
8631
+ };
8081
8632
 
8082
8633
  // src/createPgStoreConfig.ts
8083
8634
  async function createPgStoreConfig(connectionString) {
@@ -8132,6 +8683,9 @@ async function createPgStoreConfig(connectionString) {
8132
8683
  mm.register(addWorkItemProjectFieldsMigration);
8133
8684
  mm.register(createConnectionConfigsTable);
8134
8685
  mm.register(addWorkflowRunsTenantStatusUpdatedIndex);
8686
+ mm.register(addProjectKindColumn);
8687
+ mm.register(addA2AKeyAssistantIds);
8688
+ mm.register(addTaskWorkItemEventKeyMigration);
8135
8689
  await mm.migrate();
8136
8690
  const checkpoint = import_langgraph_checkpoint_postgres.PostgresSaver.fromConnString(connectionString);
8137
8691
  checkpoint.setup().catch((err) => {