@tomflow/proflow-task-store-sqlite 0.1.12 → 0.1.14

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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @tomflow/proflow-task-store-sqlite
2
2
 
3
+ ## 0.1.14
4
+
5
+ ### Patch Changes
6
+
7
+ - fix(task-store): make task evidence non-destructive
8
+
9
+ ## 0.1.13
10
+
11
+ ### Patch Changes
12
+
13
+ - fix(task-store): preserve durable campaign membership
14
+
3
15
  ## 0.1.12
4
16
 
5
17
  ### Patch Changes
@@ -7,7 +7,7 @@ export declare const behaviorAdapter: {
7
7
  ok: true;
8
8
  status: "SUCCEEDED";
9
9
  moduleRef: "task-store-sqlite";
10
- moduleVersion: "0.1.12";
10
+ moduleVersion: "0.1.14";
11
11
  data: {
12
12
  databasePath: string;
13
13
  };
@@ -20,7 +20,7 @@ export declare const behaviorAdapter: {
20
20
  readonly ok: true;
21
21
  readonly status: "SUCCEEDED";
22
22
  readonly moduleRef: "task-store-sqlite";
23
- readonly moduleVersion: "0.1.12";
23
+ readonly moduleVersion: "0.1.14";
24
24
  };
25
25
  observedEffects: never[];
26
26
  }>;
@@ -30,7 +30,7 @@ export declare const behaviorAdapter: {
30
30
  ok: true;
31
31
  status: "SUCCEEDED";
32
32
  moduleRef: "task-store-sqlite";
33
- moduleVersion: "0.1.12";
33
+ moduleVersion: "0.1.14";
34
34
  data: {
35
35
  setupStatus: "FAILED" | "READY";
36
36
  runtimeStatus: "NOT_APPLICABLE";
@@ -51,11 +51,11 @@ export declare const behaviorAdapter: {
51
51
  readonly ok: true;
52
52
  readonly status: "SUCCEEDED";
53
53
  readonly moduleRef: "task-store-sqlite";
54
- readonly moduleVersion: "0.1.12";
54
+ readonly moduleVersion: "0.1.14";
55
55
  } | {
56
56
  contract: "deployment.result.v1";
57
57
  moduleRef: "task-store-sqlite";
58
- moduleVersion: "0.1.12";
58
+ moduleVersion: "0.1.14";
59
59
  ok: false;
60
60
  status: "FAILED";
61
61
  error: {
@@ -72,7 +72,7 @@ export declare const behaviorAdapter: {
72
72
  ok: true;
73
73
  status: "SUCCEEDED";
74
74
  moduleRef: "task-store-sqlite";
75
- moduleVersion: "0.1.12";
75
+ moduleVersion: "0.1.14";
76
76
  data: {
77
77
  docs: string;
78
78
  };
@@ -85,7 +85,7 @@ export declare const behaviorAdapter: {
85
85
  readonly ok: true;
86
86
  readonly status: "SUCCEEDED";
87
87
  readonly moduleRef: "task-store-sqlite";
88
- readonly moduleVersion: "0.1.12";
88
+ readonly moduleVersion: "0.1.14";
89
89
  };
90
90
  observedEffects: never[];
91
91
  }>;
@@ -95,7 +95,7 @@ export declare const behaviorAdapter: {
95
95
  readonly ok: true;
96
96
  readonly status: "SUCCEEDED";
97
97
  readonly moduleRef: "task-store-sqlite";
98
- readonly moduleVersion: "0.1.12";
98
+ readonly moduleVersion: "0.1.14";
99
99
  };
100
100
  observedEffects: never[];
101
101
  }>;
@@ -3,7 +3,7 @@ export declare const descriptor: {
3
3
  readonly contractVersion: "1.0.0";
4
4
  readonly moduleRef: "task-store-sqlite";
5
5
  readonly packageName: "@tomflow/proflow-task-store-sqlite";
6
- readonly moduleVersion: "0.1.12";
6
+ readonly moduleVersion: "0.1.14";
7
7
  readonly kind: "library";
8
8
  readonly templateVersion: "1.0.0";
9
9
  readonly platformCompatibility: ">=1.0.0 <2.0.0";
@@ -3,7 +3,7 @@ export const descriptor = {
3
3
  contractVersion: "1.0.0",
4
4
  moduleRef: "task-store-sqlite",
5
5
  packageName: "@tomflow/proflow-task-store-sqlite",
6
- moduleVersion: "0.1.12",
6
+ moduleVersion: "0.1.14",
7
7
  kind: "library",
8
8
  templateVersion: "1.0.0",
9
9
  platformCompatibility: ">=1.0.0 <2.0.0",
@@ -1,6 +1,7 @@
1
- import type { Task, TaskRepositories, TaskStore } from "@tomflow/proflow-task-orchestration";
2
- export { type TaskMigration, type TaskMigrationContext, taskMigrations, } from "./migrations.ts";
3
- export declare class SqliteTaskStore implements TaskStore {
1
+ import type { Phase4TaskRepositories, Phase4TaskStore, Task, TaskRepositories } from "@tomflow/proflow-task-orchestration";
2
+ export { type TaskMigration, type TaskMigrationContext } from "./migrations.ts";
3
+ export { taskMigrations } from "./phase4-migrations.ts";
4
+ export declare class SqliteTaskStore implements Phase4TaskStore {
4
5
  #private;
5
6
  constructor(input: {
6
7
  databasePath: string;
@@ -8,6 +9,8 @@ export declare class SqliteTaskStore implements TaskStore {
8
9
  });
9
10
  transaction<T>(work: (repositories: TaskRepositories) => T): T;
10
11
  read<T>(work: (repositories: TaskRepositories) => T): T;
12
+ phase4Transaction<T>(work: (repositories: TaskRepositories & Phase4TaskRepositories) => T): T;
13
+ phase4Read<T>(work: (repositories: TaskRepositories & Phase4TaskRepositories) => T): T;
11
14
  listReconciliationTaskIds(input: {
12
15
  statuses: readonly Task["status"][];
13
16
  afterTaskId?: string;
package/dist/src/index.js CHANGED
@@ -1,5 +1,8 @@
1
1
  import { DatabaseSync } from "node:sqlite";
2
- export { taskMigrations, } from "./migrations.js";
2
+ import { taskDocumentRef } from "@tomflow/proflow-task-orchestration";
3
+ import { createPhase4Repositories } from "./phase4-repositories.js";
4
+ export {} from "./migrations.js";
5
+ export { taskMigrations } from "./phase4-migrations.js";
3
6
  const json = (value) => JSON.stringify(value);
4
7
  const text = (value) => value === null || value === undefined ? null : String(value);
5
8
  const number = (value) => Number(value);
@@ -90,10 +93,19 @@ function history(row) {
90
93
  };
91
94
  }
92
95
  function document(row) {
96
+ const taskId = String(row.task_id);
97
+ const documentType = String(row.document_type);
93
98
  return {
94
- taskId: String(row.task_id),
95
- documentType: String(row.document_type),
99
+ documentRef: taskDocumentRef(taskId, documentType),
100
+ taskId,
101
+ documentType,
96
102
  sourceNodeId: text(row.source_node_id),
103
+ sourceNodeRunNo: row.source_node_run_no === null ? null : number(row.source_node_run_no),
104
+ revision: number(row.revision),
105
+ sourceDocumentRef: text(row.source_document_ref),
106
+ sourceDocumentRevision: row.source_document_revision === null
107
+ ? null
108
+ : number(row.source_document_revision),
97
109
  filePath: String(row.file_path),
98
110
  contentHash: String(row.content_hash),
99
111
  updatedByRef: String(row.updated_by_ref),
@@ -228,7 +240,17 @@ export class SqliteTaskStore {
228
240
  return row ? document(row) : undefined;
229
241
  },
230
242
  upsert: (v) => {
231
- db.prepare("INSERT INTO task_documents VALUES (?,?,?,?,?,?,?) ON CONFLICT(task_id,document_type) DO UPDATE SET source_node_id=excluded.source_node_id,file_path=excluded.file_path,content_hash=excluded.content_hash,updated_by_ref=excluded.updated_by_ref,updated_at=excluded.updated_at").run(v.taskId, v.documentType, v.sourceNodeId, v.filePath, v.contentHash, v.updatedByRef, v.updatedAt);
243
+ if (v.documentRef !== taskDocumentRef(v.taskId, v.documentType))
244
+ throw new Error("TASK_DOCUMENT_IDENTITY_CONFLICT");
245
+ if (!Number.isSafeInteger(v.revision) || v.revision < 1)
246
+ throw new Error("TASK_DOCUMENT_REVISION_CONFLICT");
247
+ if (v.revision === 1) {
248
+ db.prepare("INSERT INTO task_documents(task_id,document_type,source_node_id,file_path,content_hash,updated_by_ref,updated_at,source_node_run_no,revision,source_document_ref,source_document_revision) VALUES (?,?,?,?,?,?,?,?,?,?,?)").run(v.taskId, v.documentType, v.sourceNodeId, v.filePath, v.contentHash, v.updatedByRef, v.updatedAt, v.sourceNodeRunNo, v.revision, v.sourceDocumentRef, v.sourceDocumentRevision);
249
+ return;
250
+ }
251
+ const result = db.prepare("UPDATE task_documents SET source_node_id=?,file_path=?,content_hash=?,updated_by_ref=?,updated_at=?,source_node_run_no=?,revision=?,source_document_ref=?,source_document_revision=? WHERE task_id=? AND document_type=? AND revision=?").run(v.sourceNodeId, v.filePath, v.contentHash, v.updatedByRef, v.updatedAt, v.sourceNodeRunNo, v.revision, v.sourceDocumentRef, v.sourceDocumentRevision, v.taskId, v.documentType, v.revision - 1);
252
+ if (Number(result.changes) !== 1)
253
+ throw new Error("TASK_DOCUMENT_REVISION_CONFLICT");
232
254
  },
233
255
  listByTask: (taskId) => all("SELECT * FROM task_documents WHERE task_id=? ORDER BY document_type", taskId).map(document),
234
256
  },
@@ -262,6 +284,9 @@ export class SqliteTaskStore {
262
284
  },
263
285
  };
264
286
  }
287
+ #phase4Repositories() {
288
+ return createPhase4Repositories(this.#database);
289
+ }
265
290
  transaction(work) {
266
291
  this.#database.exec("BEGIN IMMEDIATE");
267
292
  try {
@@ -277,6 +302,21 @@ export class SqliteTaskStore {
277
302
  read(work) {
278
303
  return work(this.#repositories());
279
304
  }
305
+ phase4Transaction(work) {
306
+ this.#database.exec("BEGIN IMMEDIATE");
307
+ try {
308
+ const result = work({ ...this.#repositories(), ...this.#phase4Repositories() });
309
+ this.#database.exec("COMMIT");
310
+ return result;
311
+ }
312
+ catch (error) {
313
+ this.#database.exec("ROLLBACK");
314
+ throw error;
315
+ }
316
+ }
317
+ phase4Read(work) {
318
+ return work({ ...this.#repositories(), ...this.#phase4Repositories() });
319
+ }
280
320
  listReconciliationTaskIds(input) {
281
321
  const statuses = [...new Set(input.statuses.filter((value) => value.length > 0))];
282
322
  if (statuses.length === 0)
@@ -322,6 +362,10 @@ export class SqliteTaskStore {
322
362
  "task_messages",
323
363
  "task_events",
324
364
  "idempotency_records",
365
+ "product_campaigns",
366
+ "product_intents",
367
+ "product_documents",
368
+ "campaign_authorizations",
325
369
  ];
326
370
  return Object.fromEntries(tables.map((table) => [
327
371
  table,
@@ -0,0 +1,3 @@
1
+ import { type TaskMigration } from "./migrations.ts";
2
+ export type { TaskMigration, TaskMigrationContext } from "./migrations.ts";
3
+ export declare const taskMigrations: readonly TaskMigration[];
@@ -0,0 +1,357 @@
1
+ import { taskMigrations as legacyTaskMigrations, } from "./migrations.js";
2
+ const legacyTaskColumns = [
3
+ "task_id",
4
+ "task_group_id",
5
+ "sequence_no",
6
+ "title",
7
+ "objective",
8
+ "status",
9
+ "version",
10
+ "plan_version",
11
+ "current_node_id",
12
+ "created_by_ref",
13
+ "created_at",
14
+ "started_at",
15
+ "completed_at",
16
+ "updated_at",
17
+ ];
18
+ const phase4TaskColumns = [
19
+ "source_product_intent_ref",
20
+ "campaign_ref",
21
+ ];
22
+ const legacyTaskDocumentColumns = [
23
+ "task_id",
24
+ "document_type",
25
+ "source_node_id",
26
+ "file_path",
27
+ "content_hash",
28
+ "updated_by_ref",
29
+ "updated_at",
30
+ ];
31
+ const evidenceTaskDocumentColumns = [
32
+ "source_node_run_no",
33
+ "revision",
34
+ "source_document_ref",
35
+ "source_document_revision",
36
+ ];
37
+ const phase4Tables = [
38
+ "product_campaigns",
39
+ "product_intents",
40
+ "product_documents",
41
+ "campaign_authorizations",
42
+ ];
43
+ function tableExists(database, table) {
44
+ return Boolean(database
45
+ .prepare("SELECT 1 AS present FROM sqlite_master WHERE type='table' AND name=?")
46
+ .get(table));
47
+ }
48
+ function tableColumns(database, table) {
49
+ return new Set(database.prepare(`PRAGMA table_info(${table})`).all().map((row) => row.name));
50
+ }
51
+ const legacyV3 = legacyTaskMigrations.find((migration) => migration.version === 3);
52
+ if (!legacyV3)
53
+ throw new Error("TASK_SCHEMA_V3_REQUIRED");
54
+ const v3Compatible = {
55
+ ...legacyV3,
56
+ verify: (database) => {
57
+ let issues = [...(legacyV3.verify?.(database) ?? [])];
58
+ if (tableExists(database, "tasks")) {
59
+ const columns = tableColumns(database, "tasks");
60
+ const phase4ShapePresent = columns.size === legacyTaskColumns.length + phase4TaskColumns.length &&
61
+ legacyTaskColumns.every((column) => columns.has(column)) &&
62
+ phase4TaskColumns.every((column) => columns.has(column));
63
+ if (phase4ShapePresent)
64
+ issues = issues.filter((issue) => issue !== "tasks:column_shape_mismatch");
65
+ }
66
+ if (tableExists(database, "task_documents")) {
67
+ const columns = tableColumns(database, "task_documents");
68
+ const evidenceShapePresent = columns.size ===
69
+ legacyTaskDocumentColumns.length + evidenceTaskDocumentColumns.length &&
70
+ legacyTaskDocumentColumns.every((column) => columns.has(column)) &&
71
+ evidenceTaskDocumentColumns.every((column) => columns.has(column));
72
+ if (evidenceShapePresent)
73
+ issues = issues.filter((issue) => issue !== "task_documents:column_shape_mismatch");
74
+ }
75
+ return issues;
76
+ },
77
+ };
78
+ const phase4Migration = {
79
+ version: 4,
80
+ name: "product_campaign_owner_facts",
81
+ identity: "product_campaign_owner_facts.20260914",
82
+ sql: `
83
+ CREATE TABLE product_campaigns (
84
+ campaign_ref TEXT PRIMARY KEY,
85
+ discussion_ref TEXT NOT NULL,
86
+ goal_revision INTEGER NOT NULL CHECK (goal_revision >= 1),
87
+ goal_summary TEXT NOT NULL,
88
+ scope_json TEXT NOT NULL,
89
+ constraints_json TEXT NOT NULL,
90
+ status TEXT NOT NULL CHECK (status IN ('OPEN','CLOSED')),
91
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version >= 1),
92
+ created_by_ref TEXT NOT NULL,
93
+ created_at TEXT NOT NULL,
94
+ closed_at TEXT,
95
+ closure_summary TEXT
96
+ );
97
+ CREATE TABLE product_intents (
98
+ intent_ref TEXT PRIMARY KEY,
99
+ campaign_ref TEXT,
100
+ discussion_ref TEXT NOT NULL,
101
+ goal_revision INTEGER NOT NULL CHECK (goal_revision >= 1),
102
+ intent_revision INTEGER NOT NULL CHECK (intent_revision >= 1),
103
+ predecessor_task_id TEXT,
104
+ title TEXT NOT NULL,
105
+ objective TEXT NOT NULL,
106
+ scope_json TEXT NOT NULL,
107
+ constraints_json TEXT NOT NULL,
108
+ plan_json TEXT NOT NULL,
109
+ materialized_task_id TEXT,
110
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version >= 1),
111
+ created_by_ref TEXT NOT NULL,
112
+ created_at TEXT NOT NULL,
113
+ materialized_at TEXT,
114
+ FOREIGN KEY (campaign_ref) REFERENCES product_campaigns(campaign_ref),
115
+ FOREIGN KEY (predecessor_task_id) REFERENCES tasks(task_id),
116
+ FOREIGN KEY (materialized_task_id) REFERENCES tasks(task_id)
117
+ );
118
+ CREATE TABLE product_documents (
119
+ document_ref TEXT PRIMARY KEY,
120
+ campaign_ref TEXT NOT NULL,
121
+ intent_ref TEXT,
122
+ document_type TEXT NOT NULL,
123
+ file_path TEXT NOT NULL,
124
+ content_hash TEXT NOT NULL,
125
+ revision INTEGER NOT NULL CHECK (revision >= 1),
126
+ updated_by_ref TEXT NOT NULL,
127
+ updated_at TEXT NOT NULL,
128
+ FOREIGN KEY (campaign_ref) REFERENCES product_campaigns(campaign_ref) ON DELETE CASCADE,
129
+ FOREIGN KEY (intent_ref) REFERENCES product_intents(intent_ref) ON DELETE CASCADE
130
+ );
131
+ CREATE TABLE campaign_authorizations (
132
+ authorization_ref TEXT PRIMARY KEY,
133
+ campaign_ref TEXT NOT NULL,
134
+ discussion_ref TEXT NOT NULL,
135
+ goal_revision INTEGER NOT NULL CHECK (goal_revision >= 1),
136
+ goal_summary TEXT NOT NULL,
137
+ scope_json TEXT NOT NULL,
138
+ constraints_json TEXT NOT NULL,
139
+ version INTEGER NOT NULL DEFAULT 1 CHECK (version >= 1),
140
+ created_by_ref TEXT NOT NULL,
141
+ created_at TEXT NOT NULL,
142
+ revoked_at TEXT,
143
+ revoked_by_ref TEXT,
144
+ revoke_reason TEXT,
145
+ FOREIGN KEY (campaign_ref) REFERENCES product_campaigns(campaign_ref) ON DELETE CASCADE
146
+ );
147
+ ALTER TABLE tasks ADD COLUMN source_product_intent_ref TEXT REFERENCES product_intents(intent_ref);
148
+ ALTER TABLE tasks ADD COLUMN campaign_ref TEXT REFERENCES product_campaigns(campaign_ref);
149
+ CREATE UNIQUE INDEX idx_product_intents_materialized_task ON product_intents(materialized_task_id) WHERE materialized_task_id IS NOT NULL;
150
+ CREATE INDEX idx_product_intents_campaign ON product_intents(campaign_ref, created_at);
151
+ CREATE INDEX idx_product_documents_campaign ON product_documents(campaign_ref, intent_ref);
152
+ CREATE INDEX idx_campaign_authorizations_campaign ON campaign_authorizations(campaign_ref, revoked_at);
153
+ CREATE INDEX idx_tasks_campaign ON tasks(campaign_ref, task_id);
154
+ `,
155
+ verify: (database) => {
156
+ const issues = [];
157
+ for (const table of phase4Tables)
158
+ if (!tableExists(database, table))
159
+ issues.push(`${table}:missing`);
160
+ if (!tableExists(database, "tasks"))
161
+ issues.push("tasks:missing");
162
+ else {
163
+ const columns = tableColumns(database, "tasks");
164
+ for (const column of [...legacyTaskColumns, ...phase4TaskColumns])
165
+ if (!columns.has(column))
166
+ issues.push(`tasks.${column}:missing`);
167
+ }
168
+ const foreignKeyViolations = database
169
+ .prepare("PRAGMA foreign_key_check")
170
+ .all();
171
+ if (foreignKeyViolations.length > 0)
172
+ issues.push(`foreign_key_check:${foreignKeyViolations.length}`);
173
+ return issues;
174
+ },
175
+ };
176
+ function productLinkMismatchCount(database) {
177
+ const row = database
178
+ .prepare(`SELECT COUNT(*) AS count
179
+ FROM tasks AS task
180
+ LEFT JOIN product_intents AS intent
181
+ ON intent.intent_ref = task.source_product_intent_ref
182
+ WHERE (task.campaign_ref IS NOT NULL AND task.source_product_intent_ref IS NULL)
183
+ OR (
184
+ task.source_product_intent_ref IS NOT NULL
185
+ AND (
186
+ intent.intent_ref IS NULL
187
+ OR intent.materialized_task_id IS NOT task.task_id
188
+ OR intent.campaign_ref IS NOT task.campaign_ref
189
+ )
190
+ )
191
+ OR EXISTS (
192
+ SELECT 1
193
+ FROM product_intents AS materialized
194
+ WHERE materialized.materialized_task_id = task.task_id
195
+ AND (
196
+ task.source_product_intent_ref IS NOT materialized.intent_ref
197
+ OR task.campaign_ref IS NOT materialized.campaign_ref
198
+ )
199
+ )`)
200
+ .get();
201
+ return Number(row.count);
202
+ }
203
+ function triggerExists(database, name) {
204
+ return Boolean(database
205
+ .prepare("SELECT 1 AS present FROM sqlite_master WHERE type='trigger' AND name=?")
206
+ .get(name));
207
+ }
208
+ const gapReviewMigration = {
209
+ version: 5,
210
+ name: "product_gap_review_owner_facts",
211
+ identity: "product_gap_review_owner_facts.20260914",
212
+ sql: `
213
+ CREATE TABLE product_gap_reviews (
214
+ review_ref TEXT PRIMARY KEY,
215
+ campaign_ref TEXT NOT NULL,
216
+ intent_ref TEXT NOT NULL UNIQUE,
217
+ task_id TEXT NOT NULL UNIQUE,
218
+ discussion_ref TEXT NOT NULL,
219
+ gap_identity TEXT NOT NULL,
220
+ progress_evidence_refs_json TEXT NOT NULL,
221
+ terminal_task_version INTEGER NOT NULL CHECK (terminal_task_version >= 1),
222
+ created_by_ref TEXT NOT NULL,
223
+ created_at TEXT NOT NULL,
224
+ FOREIGN KEY (campaign_ref) REFERENCES product_campaigns(campaign_ref) ON DELETE CASCADE,
225
+ FOREIGN KEY (intent_ref) REFERENCES product_intents(intent_ref) ON DELETE CASCADE,
226
+ FOREIGN KEY (task_id) REFERENCES tasks(task_id) ON DELETE CASCADE
227
+ );
228
+ CREATE INDEX idx_product_gap_reviews_campaign
229
+ ON product_gap_reviews(campaign_ref, gap_identity, created_at, review_ref);
230
+ `,
231
+ verify: (database) => {
232
+ const issues = [];
233
+ if (!tableExists(database, "product_gap_reviews"))
234
+ issues.push("product_gap_reviews:missing");
235
+ const foreignKeyViolations = database
236
+ .prepare("PRAGMA foreign_key_check")
237
+ .all();
238
+ if (foreignKeyViolations.length > 0)
239
+ issues.push(`foreign_key_check:${foreignKeyViolations.length}`);
240
+ return issues;
241
+ },
242
+ };
243
+ const taskProductLinkIntegrityMigration = {
244
+ version: 6,
245
+ name: "task_product_link_integrity",
246
+ identity: "task_product_link_integrity.20260928",
247
+ sql: `
248
+ CREATE TRIGGER trg_tasks_product_link_consistency
249
+ BEFORE UPDATE OF source_product_intent_ref, campaign_ref ON tasks
250
+ BEGIN
251
+ SELECT CASE
252
+ WHEN NEW.campaign_ref IS NOT NULL AND NEW.source_product_intent_ref IS NULL
253
+ THEN RAISE(ABORT, 'TASK_PRODUCT_LINK_MISMATCH')
254
+ END;
255
+ SELECT CASE
256
+ WHEN NEW.source_product_intent_ref IS NOT NULL
257
+ AND NOT EXISTS (
258
+ SELECT 1
259
+ FROM product_intents AS intent
260
+ WHERE intent.intent_ref = NEW.source_product_intent_ref
261
+ AND intent.materialized_task_id IS NEW.task_id
262
+ AND intent.campaign_ref IS NEW.campaign_ref
263
+ )
264
+ THEN RAISE(ABORT, 'TASK_PRODUCT_LINK_MISMATCH')
265
+ END;
266
+ END;
267
+ CREATE TRIGGER trg_product_intent_task_link_consistency
268
+ BEFORE UPDATE OF campaign_ref, materialized_task_id ON product_intents
269
+ WHEN EXISTS (
270
+ SELECT 1 FROM tasks AS task
271
+ WHERE task.source_product_intent_ref = OLD.intent_ref
272
+ )
273
+ BEGIN
274
+ SELECT CASE
275
+ WHEN NEW.campaign_ref IS NOT OLD.campaign_ref
276
+ OR NEW.materialized_task_id IS NOT OLD.materialized_task_id
277
+ THEN RAISE(ABORT, 'TASK_PRODUCT_LINK_MISMATCH')
278
+ END;
279
+ END;
280
+ `,
281
+ apply: (database) => {
282
+ const mismatchCount = productLinkMismatchCount(database);
283
+ if (mismatchCount > 0)
284
+ throw new Error(`TASK_PRODUCT_LINK_MISMATCH:${mismatchCount}`);
285
+ },
286
+ verify: (database) => {
287
+ const issues = [];
288
+ for (const trigger of [
289
+ "trg_tasks_product_link_consistency",
290
+ "trg_product_intent_task_link_consistency",
291
+ ])
292
+ if (!triggerExists(database, trigger))
293
+ issues.push(`${trigger}:missing`);
294
+ const mismatchCount = productLinkMismatchCount(database);
295
+ if (mismatchCount > 0)
296
+ issues.push(`task_product_link_mismatch:${mismatchCount}`);
297
+ return issues;
298
+ },
299
+ };
300
+ const taskDocumentEvidenceIntegrityMigration = {
301
+ version: 7,
302
+ name: "task_document_evidence_integrity",
303
+ identity: "task_document_evidence_integrity.20260928",
304
+ sql: [
305
+ "ALTER TABLE task_documents ADD COLUMN source_node_run_no INTEGER CHECK (source_node_run_no IS NULL OR source_node_run_no >= 1);",
306
+ "ALTER TABLE task_documents ADD COLUMN revision INTEGER NOT NULL DEFAULT 1 CHECK (revision >= 1);",
307
+ "ALTER TABLE task_documents ADD COLUMN source_document_ref TEXT;",
308
+ "ALTER TABLE task_documents ADD COLUMN source_document_revision INTEGER CHECK (source_document_revision IS NULL OR source_document_revision >= 1);",
309
+ "CREATE TRIGGER trg_task_documents_insert_evidence BEFORE INSERT ON task_documents BEGIN "
310
+ + "SELECT CASE WHEN NEW.revision <> 1 THEN RAISE(ABORT, 'TASK_DOCUMENT_REVISION_CONFLICT') END; "
311
+ + "SELECT CASE WHEN (NEW.source_node_id IS NULL) <> (NEW.source_node_run_no IS NULL) OR (NEW.source_document_ref IS NULL) <> (NEW.source_document_revision IS NULL) OR (NEW.source_node_id IS NOT NULL AND NEW.source_document_ref IS NOT NULL) THEN RAISE(ABORT, 'TASK_DOCUMENT_EVIDENCE_INVALID') END; "
312
+ + "SELECT CASE WHEN NEW.source_node_id IS NULL AND NOT EXISTS (SELECT 1 FROM tasks AS task WHERE task.task_id=NEW.task_id AND task.status='PENDING') THEN RAISE(ABORT, 'TASK_DOCUMENT_EVIDENCE_INVALID') END; "
313
+ + "SELECT CASE WHEN NEW.source_node_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM nodes AS node JOIN tasks AS task ON task.task_id=node.task_id WHERE node.node_id=NEW.source_node_id AND node.task_id=NEW.task_id AND node.run_no=NEW.source_node_run_no AND node.status='IN_PROGRESS' AND node.worker_ref IS NOT NULL AND task.status='ACTIVE' AND task.current_node_id=node.node_id AND NEW.updated_by_ref='worker:' || node.worker_ref AND EXISTS (SELECT 1 FROM json_each(node.output_documents_json) WHERE value=NEW.document_type)) THEN RAISE(ABORT, 'TASK_DOCUMENT_EVIDENCE_INVALID') END; "
314
+ + "SELECT CASE WHEN NEW.source_document_ref IS NOT NULL AND NOT EXISTS (SELECT 1 FROM product_documents AS source JOIN product_intents AS intent ON intent.intent_ref=source.intent_ref WHERE source.document_ref=NEW.source_document_ref AND source.revision=NEW.source_document_revision AND source.file_path=NEW.file_path AND source.content_hash=NEW.content_hash AND source.updated_by_ref=NEW.updated_by_ref AND intent.materialized_task_id=NEW.task_id) THEN RAISE(ABORT, 'TASK_DOCUMENT_SOURCE_MISMATCH') END; END;",
315
+ "CREATE TRIGGER trg_task_documents_update_evidence BEFORE UPDATE ON task_documents BEGIN "
316
+ + "SELECT CASE WHEN NEW.task_id IS NOT OLD.task_id OR NEW.document_type IS NOT OLD.document_type THEN RAISE(ABORT, 'TASK_DOCUMENT_IDENTITY_CONFLICT') END; "
317
+ + "SELECT CASE WHEN NEW.revision <> OLD.revision + 1 THEN RAISE(ABORT, 'TASK_DOCUMENT_REVISION_CONFLICT') END; "
318
+ + "SELECT CASE WHEN (NEW.source_node_id IS NULL) <> (NEW.source_node_run_no IS NULL) OR (NEW.source_document_ref IS NULL) <> (NEW.source_document_revision IS NULL) OR (NEW.source_node_id IS NOT NULL AND NEW.source_document_ref IS NOT NULL) THEN RAISE(ABORT, 'TASK_DOCUMENT_EVIDENCE_INVALID') END; "
319
+ + "SELECT CASE WHEN NEW.source_node_id IS NULL AND NOT EXISTS (SELECT 1 FROM tasks AS task WHERE task.task_id=NEW.task_id AND task.status='PENDING') THEN RAISE(ABORT, 'TASK_DOCUMENT_EVIDENCE_INVALID') END; "
320
+ + "SELECT CASE WHEN NEW.source_node_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM nodes AS node JOIN tasks AS task ON task.task_id=node.task_id WHERE node.node_id=NEW.source_node_id AND node.task_id=NEW.task_id AND node.run_no=NEW.source_node_run_no AND node.status='IN_PROGRESS' AND node.worker_ref IS NOT NULL AND task.status='ACTIVE' AND task.current_node_id=node.node_id AND NEW.updated_by_ref='worker:' || node.worker_ref AND EXISTS (SELECT 1 FROM json_each(node.output_documents_json) WHERE value=NEW.document_type)) THEN RAISE(ABORT, 'TASK_DOCUMENT_EVIDENCE_INVALID') END; "
321
+ + "SELECT CASE WHEN NEW.source_document_ref IS NOT NULL AND NOT EXISTS (SELECT 1 FROM product_documents AS source JOIN product_intents AS intent ON intent.intent_ref=source.intent_ref WHERE source.document_ref=NEW.source_document_ref AND source.revision=NEW.source_document_revision AND source.file_path=NEW.file_path AND source.content_hash=NEW.content_hash AND source.updated_by_ref=NEW.updated_by_ref AND intent.materialized_task_id=NEW.task_id) THEN RAISE(ABORT, 'TASK_DOCUMENT_SOURCE_MISMATCH') END; END;",
322
+ "CREATE TRIGGER trg_product_document_transferred_immutable BEFORE UPDATE OF revision,file_path,content_hash,updated_by_ref ON product_documents WHEN EXISTS (SELECT 1 FROM task_documents WHERE source_document_ref=OLD.document_ref) BEGIN SELECT RAISE(ABORT, 'PRODUCT_DOCUMENT_ALREADY_TRANSFERRED'); END;",
323
+ "CREATE TRIGGER trg_task_documents_delete_evidence BEFORE DELETE ON task_documents BEGIN SELECT RAISE(ABORT, 'TASK_DOCUMENT_DELETE_FORBIDDEN'); END;",
324
+ "CREATE TRIGGER trg_product_document_transferred_delete_forbidden BEFORE DELETE ON product_documents WHEN EXISTS (SELECT 1 FROM task_documents WHERE source_document_ref=OLD.document_ref) BEGIN SELECT RAISE(ABORT, 'PRODUCT_DOCUMENT_DELETE_FORBIDDEN'); END;",
325
+ ].join("\n"),
326
+ verify: (database) => {
327
+ const issues = [];
328
+ if (!tableExists(database, "task_documents"))
329
+ return ["task_documents:missing"];
330
+ const columns = tableColumns(database, "task_documents");
331
+ for (const column of evidenceTaskDocumentColumns)
332
+ if (!columns.has(column))
333
+ issues.push("task_documents." + column + ":missing");
334
+ for (const trigger of [
335
+ "trg_task_documents_insert_evidence",
336
+ "trg_task_documents_update_evidence",
337
+ "trg_product_document_transferred_immutable",
338
+ "trg_task_documents_delete_evidence",
339
+ "trg_product_document_transferred_delete_forbidden",
340
+ ])
341
+ if (!triggerExists(database, trigger))
342
+ issues.push(trigger + ":missing");
343
+ const invalidRevision = database
344
+ .prepare("SELECT COUNT(*) AS count FROM task_documents WHERE revision < 1")
345
+ .get();
346
+ if (Number(invalidRevision.count) !== 0)
347
+ issues.push("task_documents.revision:invalid");
348
+ return issues;
349
+ },
350
+ };
351
+ export const taskMigrations = [
352
+ ...legacyTaskMigrations.map((migration) => migration.version === 3 ? v3Compatible : migration),
353
+ phase4Migration,
354
+ gapReviewMigration,
355
+ taskProductLinkIntegrityMigration,
356
+ taskDocumentEvidenceIntegrityMigration,
357
+ ];
@@ -0,0 +1,3 @@
1
+ import type { DatabaseSync } from "node:sqlite";
2
+ import type { Phase4TaskRepositories } from "@tomflow/proflow-task-orchestration";
3
+ export declare function createPhase4Repositories(db: DatabaseSync): Phase4TaskRepositories;
@@ -0,0 +1,195 @@
1
+ const text = (value) => value === null || value === undefined ? null : String(value);
2
+ const number = (value) => Number(value);
3
+ const jsonValue = (value) => JSON.parse(String(value));
4
+ const json = (value) => JSON.stringify(value);
5
+ function campaign(row) {
6
+ return {
7
+ campaignRef: String(row.campaign_ref),
8
+ discussionRef: String(row.discussion_ref),
9
+ goalRevision: number(row.goal_revision),
10
+ goalSummary: String(row.goal_summary),
11
+ scope: jsonValue(row.scope_json),
12
+ constraints: jsonValue(row.constraints_json),
13
+ status: row.status,
14
+ version: number(row.version),
15
+ createdByRef: String(row.created_by_ref),
16
+ createdAt: String(row.created_at),
17
+ closedAt: text(row.closed_at),
18
+ closureSummary: text(row.closure_summary),
19
+ };
20
+ }
21
+ function intent(row) {
22
+ return {
23
+ intentRef: String(row.intent_ref),
24
+ campaignRef: text(row.campaign_ref),
25
+ discussionRef: String(row.discussion_ref),
26
+ goalRevision: number(row.goal_revision),
27
+ intentRevision: number(row.intent_revision),
28
+ predecessorTaskId: text(row.predecessor_task_id),
29
+ title: String(row.title),
30
+ objective: String(row.objective),
31
+ scope: jsonValue(row.scope_json),
32
+ constraints: jsonValue(row.constraints_json),
33
+ plan: jsonValue(row.plan_json),
34
+ materializedTaskId: text(row.materialized_task_id),
35
+ version: number(row.version),
36
+ createdByRef: String(row.created_by_ref),
37
+ createdAt: String(row.created_at),
38
+ materializedAt: text(row.materialized_at),
39
+ };
40
+ }
41
+ function productDocument(row) {
42
+ return {
43
+ documentRef: String(row.document_ref),
44
+ campaignRef: String(row.campaign_ref),
45
+ intentRef: text(row.intent_ref),
46
+ documentType: String(row.document_type),
47
+ filePath: String(row.file_path),
48
+ contentHash: String(row.content_hash),
49
+ revision: number(row.revision),
50
+ updatedByRef: String(row.updated_by_ref),
51
+ updatedAt: String(row.updated_at),
52
+ };
53
+ }
54
+ function authorization(row) {
55
+ return {
56
+ authorizationRef: String(row.authorization_ref),
57
+ campaignRef: String(row.campaign_ref),
58
+ discussionRef: String(row.discussion_ref),
59
+ goalRevision: number(row.goal_revision),
60
+ goalSummary: String(row.goal_summary),
61
+ scope: jsonValue(row.scope_json),
62
+ constraints: jsonValue(row.constraints_json),
63
+ version: number(row.version),
64
+ createdByRef: String(row.created_by_ref),
65
+ createdAt: String(row.created_at),
66
+ revokedAt: text(row.revoked_at),
67
+ revokedByRef: text(row.revoked_by_ref),
68
+ revokeReason: text(row.revoke_reason),
69
+ };
70
+ }
71
+ function gapReview(row) {
72
+ return {
73
+ reviewRef: String(row.review_ref),
74
+ campaignRef: String(row.campaign_ref),
75
+ intentRef: String(row.intent_ref),
76
+ taskId: String(row.task_id),
77
+ discussionRef: String(row.discussion_ref),
78
+ gapIdentity: String(row.gap_identity),
79
+ progressEvidenceRefs: jsonValue(row.progress_evidence_refs_json),
80
+ terminalTaskVersion: number(row.terminal_task_version),
81
+ createdByRef: String(row.created_by_ref),
82
+ createdAt: String(row.created_at),
83
+ };
84
+ }
85
+ function taskLink(row) {
86
+ return {
87
+ taskId: String(row.task_id),
88
+ sourceProductIntentRef: text(row.source_product_intent_ref),
89
+ campaignRef: text(row.campaign_ref),
90
+ };
91
+ }
92
+ export function createPhase4Repositories(db) {
93
+ const get = (sql, ...params) => db.prepare(sql).get(...params);
94
+ const all = (sql, ...params) => db.prepare(sql).all(...params);
95
+ return {
96
+ productCampaigns: {
97
+ get: (campaignRef) => {
98
+ const row = get("SELECT * FROM product_campaigns WHERE campaign_ref=?", campaignRef);
99
+ return row ? campaign(row) : undefined;
100
+ },
101
+ insert: (value) => {
102
+ db.prepare("INSERT INTO product_campaigns(campaign_ref,discussion_ref,goal_revision,goal_summary,scope_json,constraints_json,status,version,created_by_ref,created_at,closed_at,closure_summary) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)").run(value.campaignRef, value.discussionRef, value.goalRevision, value.goalSummary, json(value.scope), json(value.constraints), value.status, value.version, value.createdByRef, value.createdAt, value.closedAt, value.closureSummary);
103
+ },
104
+ update: (value) => {
105
+ const result = db.prepare("UPDATE product_campaigns SET discussion_ref=?,goal_revision=?,goal_summary=?,scope_json=?,constraints_json=?,status=?,version=?,closed_at=?,closure_summary=? WHERE campaign_ref=? AND version=?").run(value.discussionRef, value.goalRevision, value.goalSummary, json(value.scope), json(value.constraints), value.status, value.version, value.closedAt, value.closureSummary, value.campaignRef, value.version - 1);
106
+ if (Number(result.changes) !== 1)
107
+ throw new Error("PRODUCT_CAMPAIGN_VERSION_CONFLICT");
108
+ },
109
+ },
110
+ productIntents: {
111
+ get: (intentRef) => {
112
+ const row = get("SELECT * FROM product_intents WHERE intent_ref=?", intentRef);
113
+ return row ? intent(row) : undefined;
114
+ },
115
+ insert: (value) => {
116
+ db.prepare("INSERT INTO product_intents(intent_ref,campaign_ref,discussion_ref,goal_revision,intent_revision,predecessor_task_id,title,objective,scope_json,constraints_json,plan_json,materialized_task_id,version,created_by_ref,created_at,materialized_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)").run(value.intentRef, value.campaignRef, value.discussionRef, value.goalRevision, value.intentRevision, value.predecessorTaskId, value.title, value.objective, json(value.scope), json(value.constraints), json(value.plan), value.materializedTaskId, value.version, value.createdByRef, value.createdAt, value.materializedAt);
117
+ },
118
+ update: (value) => {
119
+ const result = db.prepare("UPDATE product_intents SET campaign_ref=?,discussion_ref=?,goal_revision=?,intent_revision=?,predecessor_task_id=?,title=?,objective=?,scope_json=?,constraints_json=?,plan_json=?,materialized_task_id=?,version=?,materialized_at=? WHERE intent_ref=? AND version=?").run(value.campaignRef, value.discussionRef, value.goalRevision, value.intentRevision, value.predecessorTaskId, value.title, value.objective, json(value.scope), json(value.constraints), json(value.plan), value.materializedTaskId, value.version, value.materializedAt, value.intentRef, value.version - 1);
120
+ if (Number(result.changes) !== 1)
121
+ throw new Error("PRODUCT_INTENT_REVISION_CONFLICT");
122
+ },
123
+ listByCampaign: (campaignRef) => all("SELECT * FROM product_intents WHERE campaign_ref=? ORDER BY created_at,intent_ref", campaignRef).map(intent),
124
+ },
125
+ productDocuments: {
126
+ get: (documentRef) => {
127
+ const row = get("SELECT * FROM product_documents WHERE document_ref=?", documentRef);
128
+ return row ? productDocument(row) : undefined;
129
+ },
130
+ insert: (value) => {
131
+ db.prepare("INSERT INTO product_documents(document_ref,campaign_ref,intent_ref,document_type,file_path,content_hash,revision,updated_by_ref,updated_at) VALUES (?,?,?,?,?,?,?,?,?)").run(value.documentRef, value.campaignRef, value.intentRef, value.documentType, value.filePath, value.contentHash, value.revision, value.updatedByRef, value.updatedAt);
132
+ },
133
+ update: (value) => {
134
+ const result = db.prepare("UPDATE product_documents SET campaign_ref=?,intent_ref=?,document_type=?,file_path=?,content_hash=?,revision=?,updated_by_ref=?,updated_at=? WHERE document_ref=? AND revision=?").run(value.campaignRef, value.intentRef, value.documentType, value.filePath, value.contentHash, value.revision, value.updatedByRef, value.updatedAt, value.documentRef, value.revision - 1);
135
+ if (Number(result.changes) !== 1)
136
+ throw new Error("PRODUCT_DOCUMENT_REVISION_CONFLICT");
137
+ },
138
+ bindIntent: (input) => {
139
+ const result = db.prepare("UPDATE product_documents SET intent_ref=? WHERE document_ref=? AND intent_ref IS NULL AND revision=? AND campaign_ref=(SELECT campaign_ref FROM product_intents WHERE intent_ref=?)").run(input.intentRef, input.documentRef, input.expectedRevision, input.intentRef);
140
+ if (Number(result.changes) !== 1)
141
+ throw new Error("PRODUCT_DOCUMENT_BIND_CONFLICT");
142
+ },
143
+ listByIntent: (intentRef) => all("SELECT * FROM product_documents WHERE intent_ref=? ORDER BY document_type,document_ref", intentRef).map(productDocument),
144
+ },
145
+ campaignAuthorizations: {
146
+ get: (authorizationRef) => {
147
+ const row = get("SELECT * FROM campaign_authorizations WHERE authorization_ref=?", authorizationRef);
148
+ return row ? authorization(row) : undefined;
149
+ },
150
+ insert: (value) => {
151
+ db.prepare("INSERT INTO campaign_authorizations(authorization_ref,campaign_ref,discussion_ref,goal_revision,goal_summary,scope_json,constraints_json,version,created_by_ref,created_at,revoked_at,revoked_by_ref,revoke_reason) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)").run(value.authorizationRef, value.campaignRef, value.discussionRef, value.goalRevision, value.goalSummary, json(value.scope), json(value.constraints), value.version, value.createdByRef, value.createdAt, value.revokedAt, value.revokedByRef, value.revokeReason);
152
+ },
153
+ update: (value) => {
154
+ const result = db.prepare("UPDATE campaign_authorizations SET campaign_ref=?,discussion_ref=?,goal_revision=?,goal_summary=?,scope_json=?,constraints_json=?,version=?,revoked_at=?,revoked_by_ref=?,revoke_reason=? WHERE authorization_ref=? AND version=?").run(value.campaignRef, value.discussionRef, value.goalRevision, value.goalSummary, json(value.scope), json(value.constraints), value.version, value.revokedAt, value.revokedByRef, value.revokeReason, value.authorizationRef, value.version - 1);
155
+ if (Number(result.changes) !== 1)
156
+ throw new Error("CAMPAIGN_AUTHORIZATION_VERSION_CONFLICT");
157
+ },
158
+ listByCampaign: (campaignRef) => all("SELECT * FROM campaign_authorizations WHERE campaign_ref=? ORDER BY created_at,authorization_ref", campaignRef).map(authorization),
159
+ },
160
+ productGapReviews: {
161
+ getByIntent: (intentRef) => {
162
+ const row = get("SELECT * FROM product_gap_reviews WHERE intent_ref=?", intentRef);
163
+ return row ? gapReview(row) : undefined;
164
+ },
165
+ getByTask: (taskId) => {
166
+ const row = get("SELECT * FROM product_gap_reviews WHERE task_id=?", taskId);
167
+ return row ? gapReview(row) : undefined;
168
+ },
169
+ insert: (value) => {
170
+ db.prepare("INSERT INTO product_gap_reviews(review_ref,campaign_ref,intent_ref,task_id,discussion_ref,gap_identity,progress_evidence_refs_json,terminal_task_version,created_by_ref,created_at) VALUES (?,?,?,?,?,?,?,?,?,?)").run(value.reviewRef, value.campaignRef, value.intentRef, value.taskId, value.discussionRef, value.gapIdentity, json(value.progressEvidenceRefs), value.terminalTaskVersion, value.createdByRef, value.createdAt);
171
+ },
172
+ listByCampaign: (campaignRef) => all("SELECT * FROM product_gap_reviews WHERE campaign_ref=? ORDER BY created_at,review_ref", campaignRef).map(gapReview),
173
+ },
174
+ taskProductLinks: {
175
+ get: (taskId) => {
176
+ const row = get("SELECT task_id,source_product_intent_ref,campaign_ref FROM tasks WHERE task_id=?", taskId);
177
+ return row ? taskLink(row) : undefined;
178
+ },
179
+ set: (value) => {
180
+ const result = db.prepare(`UPDATE tasks
181
+ SET source_product_intent_ref=?, campaign_ref=?
182
+ WHERE task_id=?
183
+ AND (
184
+ (source_product_intent_ref IS NULL AND campaign_ref IS NULL)
185
+ OR (source_product_intent_ref IS ? AND campaign_ref IS ?)
186
+ )`).run(value.sourceProductIntentRef, value.campaignRef, value.taskId, value.sourceProductIntentRef, value.campaignRef);
187
+ if (Number(result.changes) === 1)
188
+ return;
189
+ if (!get("SELECT 1 AS present FROM tasks WHERE task_id=?", value.taskId))
190
+ throw new Error("TASK_NOT_FOUND");
191
+ throw new Error("TASK_PRODUCT_LINK_CONFLICT");
192
+ },
193
+ },
194
+ };
195
+ }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@tomflow/proflow-task-store-sqlite",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
8
8
  "exports": {
9
9
  ".": "./dist/src/index.js",
10
- "./migrations": "./dist/src/migrations.js",
10
+ "./migrations": "./dist/src/phase4-migrations.js",
11
11
  "./deployment/adapter": "./dist/deployment/adapter.js",
12
12
  "./deployment/descriptor": "./dist/deployment/descriptor.js"
13
13
  },
@@ -21,7 +21,7 @@
21
21
  ],
22
22
  "dependencies": {
23
23
  "@tomflow/proflow-module-contract": "^0.1.13",
24
- "@tomflow/proflow-task-orchestration": "^0.1.11"
24
+ "@tomflow/proflow-task-orchestration": "^0.1.14"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@tomflow/proflow-task-migration-runner": "^0.1.11"
@@ -3,7 +3,7 @@
3
3
  "contractVersion": "1.0.0",
4
4
  "moduleRef": "task-store-sqlite",
5
5
  "packageName": "@tomflow/proflow-task-store-sqlite",
6
- "moduleVersion": "0.1.12",
6
+ "moduleVersion": "0.1.14",
7
7
  "kind": "library",
8
8
  "templateVersion": "1.0.0",
9
9
  "platformCompatibility": ">=1.0.0 <2.0.0",