opencastle 0.33.4 → 0.33.6

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.
Files changed (31) hide show
  1. package/dist/cli/convoy/pipeline.d.ts +2 -0
  2. package/dist/cli/convoy/pipeline.d.ts.map +1 -1
  3. package/dist/cli/convoy/pipeline.js +6 -0
  4. package/dist/cli/convoy/pipeline.js.map +1 -1
  5. package/dist/cli/convoy/pipeline.test.js +30 -0
  6. package/dist/cli/convoy/pipeline.test.js.map +1 -1
  7. package/dist/cli/convoy/store.d.ts.map +1 -1
  8. package/dist/cli/convoy/store.js +74 -3
  9. package/dist/cli/convoy/store.js.map +1 -1
  10. package/dist/cli/convoy/store.test.js +26 -14
  11. package/dist/cli/convoy/store.test.js.map +1 -1
  12. package/package.json +1 -1
  13. package/src/cli/convoy/pipeline.test.ts +30 -0
  14. package/src/cli/convoy/pipeline.ts +9 -0
  15. package/src/cli/convoy/store.test.ts +28 -15
  16. package/src/cli/convoy/store.ts +74 -3
  17. package/src/dashboard/dist/data/convoys/demo-api-v2.json +3 -3
  18. package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +10 -10
  19. package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +6 -6
  20. package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +3 -3
  21. package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +1 -1
  22. package/src/dashboard/dist/data/convoys/demo-docs-update.json +3 -3
  23. package/src/dashboard/dist/data/convoys/demo-perf-opt.json +10 -10
  24. package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
  25. package/src/dashboard/public/data/convoys/demo-api-v2.json +3 -3
  26. package/src/dashboard/public/data/convoys/demo-auth-revamp.json +10 -10
  27. package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +6 -6
  28. package/src/dashboard/public/data/convoys/demo-data-pipeline.json +3 -3
  29. package/src/dashboard/public/data/convoys/demo-deploy-ci.json +1 -1
  30. package/src/dashboard/public/data/convoys/demo-docs-update.json +3 -3
  31. package/src/dashboard/public/data/convoys/demo-perf-opt.json +10 -10
@@ -125,6 +125,7 @@ describe('single convoy pipeline', () => {
125
125
  adapter: makeAdapter(),
126
126
  dbPath,
127
127
  _createConvoyEngine: factory,
128
+ _ensureBranch: async () => {},
128
129
  })
129
130
 
130
131
  const result = await pipeline.run()
@@ -147,6 +148,7 @@ describe('single convoy pipeline', () => {
147
148
  adapter: makeAdapter(),
148
149
  dbPath,
149
150
  _createConvoyEngine: factory,
151
+ _ensureBranch: async () => {},
150
152
  })
151
153
 
152
154
  const result = await pipeline.run()
@@ -177,6 +179,7 @@ describe('two-convoy pipeline', () => {
177
179
  adapter: makeAdapter(),
178
180
  dbPath,
179
181
  _createConvoyEngine: factory,
182
+ _ensureBranch: async () => {},
180
183
  }).run()
181
184
 
182
185
  expect(result.status).toBe('done')
@@ -195,6 +198,7 @@ describe('two-convoy pipeline', () => {
195
198
  adapter: makeAdapter(),
196
199
  dbPath,
197
200
  _createConvoyEngine: factory,
201
+ _ensureBranch: async () => {},
198
202
  }).run()
199
203
 
200
204
  expect(vi.mocked(readFile)).toHaveBeenNthCalledWith(
@@ -227,6 +231,7 @@ describe('on_failure: stop', () => {
227
231
  adapter: makeAdapter(),
228
232
  dbPath,
229
233
  _createConvoyEngine: factory,
234
+ _ensureBranch: async () => {},
230
235
  }).run()
231
236
 
232
237
  expect(result.status).toBe('failed')
@@ -251,6 +256,7 @@ describe('on_failure: stop', () => {
251
256
  adapter: makeAdapter(),
252
257
  dbPath,
253
258
  _createConvoyEngine: factory,
259
+ _ensureBranch: async () => {},
254
260
  }).run()
255
261
 
256
262
  expect(result.status).toBe('failed')
@@ -277,6 +283,7 @@ describe('on_failure: continue', () => {
277
283
  adapter: makeAdapter(),
278
284
  dbPath,
279
285
  _createConvoyEngine: factory,
286
+ _ensureBranch: async () => {},
280
287
  }).run()
281
288
 
282
289
  expect(result.status).toBe('failed')
@@ -305,6 +312,7 @@ describe('hybrid pipeline (chained + own tasks)', () => {
305
312
  adapter: makeAdapter(),
306
313
  dbPath,
307
314
  _createConvoyEngine: factory,
315
+ _ensureBranch: async () => {},
308
316
  }).run()
309
317
 
310
318
  expect(result.status).toBe('done')
@@ -327,6 +335,7 @@ describe('hybrid pipeline (chained + own tasks)', () => {
327
335
  adapter: makeAdapter(),
328
336
  dbPath,
329
337
  _createConvoyEngine: factory,
338
+ _ensureBranch: async () => {},
330
339
  }).run()
331
340
 
332
341
  expect(result.summary.totalConvoys).toBe(1)
@@ -349,6 +358,7 @@ describe('token aggregation', () => {
349
358
  adapter: makeAdapter(),
350
359
  dbPath,
351
360
  _createConvoyEngine: factory,
361
+ _ensureBranch: async () => {},
352
362
  }).run()
353
363
 
354
364
  expect(result.cost?.total_tokens).toBe(350)
@@ -363,6 +373,7 @@ describe('token aggregation', () => {
363
373
  adapter: makeAdapter(),
364
374
  dbPath,
365
375
  _createConvoyEngine: factory,
376
+ _ensureBranch: async () => {},
366
377
  }).run()
367
378
 
368
379
  expect(result.cost).toBeUndefined()
@@ -379,6 +390,7 @@ describe('token aggregation', () => {
379
390
  adapter: makeAdapter(),
380
391
  dbPath,
381
392
  _createConvoyEngine: factory,
393
+ _ensureBranch: async () => {},
382
394
  }).run()
383
395
 
384
396
  const store = createConvoyStore(dbPath)
@@ -404,6 +416,7 @@ describe('shared branch', () => {
404
416
  adapter: makeAdapter(),
405
417
  dbPath,
406
418
  _createConvoyEngine: factory,
419
+ _ensureBranch: async () => {},
407
420
  }).run()
408
421
 
409
422
  const calls = factory.mock.calls as [ConvoyEngineOptions][]
@@ -424,6 +437,7 @@ describe('pipeline convoy linking', () => {
424
437
  adapter: makeAdapter(),
425
438
  dbPath,
426
439
  _createConvoyEngine: factory,
440
+ _ensureBranch: async () => {},
427
441
  }).run()
428
442
 
429
443
  const calls = factory.mock.calls as [ConvoyEngineOptions][]
@@ -455,6 +469,7 @@ describe('pipeline record persistence', () => {
455
469
  adapter: makeAdapter(),
456
470
  dbPath,
457
471
  _createConvoyEngine: factory,
472
+ _ensureBranch: async () => {},
458
473
  }).run()
459
474
 
460
475
  expect(statusDuringRun).toBe('running')
@@ -477,6 +492,7 @@ describe('pipeline record persistence', () => {
477
492
  adapter: makeAdapter(),
478
493
  dbPath,
479
494
  _createConvoyEngine: factory,
495
+ _ensureBranch: async () => {},
480
496
  }).run()
481
497
 
482
498
  const store = createConvoyStore(dbPath)
@@ -528,6 +544,7 @@ describe('pipeline resume', () => {
528
544
  adapter: makeAdapter(),
529
545
  dbPath,
530
546
  _createConvoyEngine: resumeFactory,
547
+ _ensureBranch: async () => {},
531
548
  }).resume(pipelineId)
532
549
 
533
550
  expect(resumeResult.convoyResults).toHaveLength(2)
@@ -571,6 +588,7 @@ describe('pipeline resume', () => {
571
588
  adapter: makeAdapter(),
572
589
  dbPath,
573
590
  _createConvoyEngine: resumeFactory,
591
+ _ensureBranch: async () => {},
574
592
  }).resume(pipelineId)
575
593
 
576
594
  expect(result.cost?.total_tokens).toBe(77)
@@ -606,6 +624,7 @@ describe('pipeline resume', () => {
606
624
  adapter: makeAdapter(),
607
625
  dbPath,
608
626
  _createConvoyEngine: resumeFactory,
627
+ _ensureBranch: async () => {},
609
628
  }).resume(pipelineId)
610
629
 
611
630
  expect(result.status).toBe('failed')
@@ -620,6 +639,7 @@ describe('pipeline resume', () => {
620
639
  adapter: makeAdapter(),
621
640
  dbPath,
622
641
  _createConvoyEngine: makeEngineFactory([]),
642
+ _ensureBranch: async () => {},
623
643
  })
624
644
 
625
645
  await expect(pipeline.resume('nonexistent-id')).rejects.toThrow(
@@ -668,6 +688,7 @@ describe('pipeline resume', () => {
668
688
  adapter: makeAdapter(),
669
689
  dbPath,
670
690
  _createConvoyEngine: factory,
691
+ _ensureBranch: async () => {},
671
692
  }).resume(pipelineId)
672
693
 
673
694
  expect(mockEngine.resume).toHaveBeenCalledWith(runningConvoyId)
@@ -716,6 +737,7 @@ describe('pipeline resume', () => {
716
737
  adapter: makeAdapter(),
717
738
  dbPath,
718
739
  _createConvoyEngine: factory,
740
+ _ensureBranch: async () => {},
719
741
  }).resume(pipelineId)
720
742
 
721
743
  expect(mockEngine.retryFailed).toHaveBeenCalledWith(failedConvoyId)
@@ -748,6 +770,7 @@ describe('getCurrentBranch fallback', () => {
748
770
  basePath: tmpDir, // not a git repo → getCurrentBranch returns 'main'
749
771
  dbPath,
750
772
  _createConvoyEngine: factory,
773
+ _ensureBranch: async () => {},
751
774
  }).run()
752
775
 
753
776
  expect(result.status).toBe('done')
@@ -789,6 +812,7 @@ describe('getCurrentBranch fallback', () => {
789
812
  basePath: tmpDir,
790
813
  dbPath,
791
814
  _createConvoyEngine: resumeFactory,
815
+ _ensureBranch: async () => {},
792
816
  }).resume(pipelineId)
793
817
 
794
818
  expect(result.status).toBe('done')
@@ -808,6 +832,7 @@ describe('path traversal protection', () => {
808
832
  adapter: makeAdapter(),
809
833
  dbPath,
810
834
  _createConvoyEngine: factory,
835
+ _ensureBranch: async () => {},
811
836
  }).run()
812
837
 
813
838
  expect(result.status).toBe('failed')
@@ -833,6 +858,7 @@ describe('path traversal protection', () => {
833
858
  adapter: makeAdapter(),
834
859
  dbPath,
835
860
  _createConvoyEngine: factory,
861
+ _ensureBranch: async () => {},
836
862
  }).run()
837
863
 
838
864
  expect(result.status).toBe('failed')
@@ -858,6 +884,7 @@ describe('path traversal protection', () => {
858
884
  adapter: makeAdapter(),
859
885
  dbPath,
860
886
  _createConvoyEngine: factory,
887
+ _ensureBranch: async () => {},
861
888
  }).run()
862
889
 
863
890
  expect(result.status).toBe('done')
@@ -886,6 +913,7 @@ describe('missing convoy spec file', () => {
886
913
  adapter: makeAdapter(),
887
914
  dbPath,
888
915
  _createConvoyEngine: factory,
916
+ _ensureBranch: async () => {},
889
917
  }).run()
890
918
 
891
919
  expect(result.status).toBe('failed')
@@ -919,6 +947,7 @@ describe('missing convoy spec file', () => {
919
947
  adapter: makeAdapter(),
920
948
  dbPath,
921
949
  _createConvoyEngine: factory,
950
+ _ensureBranch: async () => {},
922
951
  }).run()
923
952
 
924
953
  expect(result.status).toBe('failed')
@@ -952,6 +981,7 @@ describe('invalid convoy YAML', () => {
952
981
  adapter: makeAdapter(),
953
982
  dbPath,
954
983
  _createConvoyEngine: factory,
984
+ _ensureBranch: async () => {},
955
985
  }).run()
956
986
 
957
987
  expect(result.status).toBe('failed')
@@ -49,6 +49,8 @@ export interface PipelineOrchestratorOptions {
49
49
  verbose?: boolean
50
50
  /** Injectable engine factory (used in tests). */
51
51
  _createConvoyEngine?: (opts: ConvoyEngineOptions) => ConvoyEngine
52
+ /** Injectable branch handler (used in tests). */
53
+ _ensureBranch?: (branchName: string, basePath: string) => Promise<void>
52
54
  }
53
55
 
54
56
  // ── Internal helpers ──────────────────────────────────────────────────────────
@@ -85,6 +87,7 @@ export function createPipelineOrchestrator(
85
87
  const basePath = resolve(options.basePath ?? process.cwd())
86
88
  const dbPath = options.dbPath ?? resolve(basePath, '.opencastle', 'convoy.db')
87
89
  const engineFactory = options._createConvoyEngine ?? createConvoyEngine
90
+ const branchFn = options._ensureBranch ?? ensureBranch
88
91
 
89
92
  async function getCurrentBranch(): Promise<string> {
90
93
  try {
@@ -146,6 +149,12 @@ export function createPipelineOrchestrator(
146
149
  const branch = spec.branch ?? (await getCurrentBranch())
147
150
  const convoySpecs = spec.depends_on_convoy ?? []
148
151
 
152
+ // Switch branch BEFORE any DB writes — otherwise the convoy.db modification
153
+ // from insertPipeline() causes ensureBranch's dirty check to fail.
154
+ if (spec.branch !== undefined) {
155
+ await branchFn(spec.branch, basePath)
156
+ }
157
+
149
158
  mkdirSync(dirname(dbPath), { recursive: true })
150
159
  const store = createConvoyStore(dbPath)
151
160
  try {
@@ -99,11 +99,11 @@ describe('DB creation', () => {
99
99
  expect(row.journal_mode).toBe('wal')
100
100
  })
101
101
 
102
- it('sets schema version to 11', () => {
102
+ it('sets schema version to 12', () => {
103
103
  const db = new DatabaseSync(dbPath)
104
104
  const row = db.prepare('PRAGMA user_version').get() as { user_version: number }
105
105
  db.close()
106
- expect(row.user_version).toBe(11)
106
+ expect(row.user_version).toBe(12)
107
107
  })
108
108
 
109
109
  it('creates all required tables', () => {
@@ -131,7 +131,7 @@ describe('DB creation', () => {
131
131
  store2.close()
132
132
  // Reassign so afterEach does not double-close
133
133
  store = createConvoyStore(dbPath)
134
- expect(row.user_version).toBe(11)
134
+ expect(row.user_version).toBe(12)
135
135
  })
136
136
  })
137
137
 
@@ -208,8 +208,8 @@ describe('schema migration', () => {
208
208
  verifyDb.close()
209
209
 
210
210
  expect(cols.map(c => c.name)).toContain('adapter')
211
- // v1 chains through v2→v3→v4→...→v7→v8→v9→v10→v11 in one init, so final version is 11
212
- expect(version.user_version).toBe(11)
211
+ // v1 chains through v2→v3→v4→...→v7→v8→v9→v10→v11→v12 in one init, so final version is 12
212
+ expect(version.user_version).toBe(12)
213
213
  })
214
214
 
215
215
  it('schema migration v2 to v3 adds cost columns', () => {
@@ -295,7 +295,7 @@ describe('schema migration', () => {
295
295
  expect(convoyColNames).toContain('total_tokens')
296
296
  expect(convoyColNames).toContain('total_cost_usd')
297
297
 
298
- expect(version.user_version).toBe(11)
298
+ expect(version.user_version).toBe(12)
299
299
  })
300
300
 
301
301
  it('schema migration v1 to v3 chains correctly in a single init', () => {
@@ -381,7 +381,7 @@ describe('schema migration', () => {
381
381
  expect(convoyColNames).toContain('total_tokens')
382
382
  expect(convoyColNames).toContain('total_cost_usd')
383
383
 
384
- expect(version.user_version).toBe(11)
384
+ expect(version.user_version).toBe(12)
385
385
  })
386
386
 
387
387
  it('schema migration v3 to v4 creates pipeline table and adds pipeline_id to convoy', () => {
@@ -464,7 +464,7 @@ describe('schema migration', () => {
464
464
 
465
465
  expect(convoyCols.map(c => c.name)).toContain('pipeline_id')
466
466
  expect(tables.map(t => t.name)).toContain('pipeline')
467
- expect(version.user_version).toBe(11)
467
+ expect(version.user_version).toBe(12)
468
468
  })
469
469
  })
470
470
 
@@ -580,6 +580,20 @@ describe('task CRUD', () => {
580
580
  expect(tasks[1].phase).toBe(1)
581
581
  })
582
582
 
583
+ it('allows same task ID in different convoys', () => {
584
+ store.insertConvoy(makeConvoy({ id: 'convoy-a' }))
585
+ store.insertConvoy(makeConvoy({ id: 'convoy-b' }))
586
+ store.insertTask(makeTask({ id: 'shared-task', convoy_id: 'convoy-a' }))
587
+ store.insertTask(makeTask({ id: 'shared-task', convoy_id: 'convoy-b' }))
588
+
589
+ const taskA = store.getTask('shared-task', 'convoy-a')
590
+ const taskB = store.getTask('shared-task', 'convoy-b')
591
+ expect(taskA).toBeDefined()
592
+ expect(taskB).toBeDefined()
593
+ expect(taskA!.convoy_id).toBe('convoy-a')
594
+ expect(taskB!.convoy_id).toBe('convoy-b')
595
+ })
596
+
583
597
  it('updates task status', () => {
584
598
  store.insertTask(makeTask())
585
599
  store.updateTaskStatus('task-1', 'convoy-1', 'running')
@@ -1444,7 +1458,7 @@ describe('schema migration v5 → v6', () => {
1444
1458
  v5Verify.close()
1445
1459
  migratedStore.close()
1446
1460
 
1447
- expect(row.user_version).toBe(11)
1461
+ expect(row.user_version).toBe(12)
1448
1462
  expect(taskStepTable?.name).toBe('task_step')
1449
1463
  expect(convoy?.id).toBe('convoy-auto')
1450
1464
  expect(task?.id).toBe('task-auto')
@@ -1614,7 +1628,7 @@ describe('schema migration v6→v7 (drift detection columns)', () => {
1614
1628
 
1615
1629
  expect(cols.map(c => c.name)).toContain('drift_score')
1616
1630
  expect(cols.map(c => c.name)).toContain('drift_retried')
1617
- expect(version.user_version).toBe(11)
1631
+ expect(version.user_version).toBe(12)
1618
1632
  })
1619
1633
 
1620
1634
  it('new databases include drift_score and drift_retried in CREATE TABLE', () => {
@@ -1846,10 +1860,9 @@ describe('migration full chain v4→v10', () => {
1846
1860
  migratedStore.close()
1847
1861
 
1848
1862
  const verifyDb = new DatabaseSync(chainDbPath)
1849
-
1850
- // Verify user_version = 11
1863
+ verifyDb.exec('PRAGMA foreign_keys = 0')
1851
1864
  const version = (verifyDb.prepare('PRAGMA user_version').get() as { user_version: number }).user_version
1852
- expect(version).toBe(11)
1865
+ expect(version).toBe(12)
1853
1866
 
1854
1867
  // Verify all new tables exist
1855
1868
  const tables = (verifyDb.prepare("SELECT name FROM sqlite_master WHERE type='table'").all() as Array<{ name: string }>).map(t => t.name)
@@ -1884,7 +1897,7 @@ describe('migration full chain v4→v10', () => {
1884
1897
  const eventCount = (verifyDb.prepare('SELECT COUNT(*) AS cnt FROM event WHERE convoy_id = :id').get({ id: 'convoy-chain' }) as { cnt: number }).cnt
1885
1898
  expect(eventCount).toBe(1)
1886
1899
 
1887
- // Verify FK constraints work: insert a task_step referencing the seeded task_id
1900
+ // Verify task_step table accepts inserts
1888
1901
  expect(() => {
1889
1902
  verifyDb.prepare(
1890
1903
  `INSERT INTO task_step (task_id, step_index, prompt, gates, status)
@@ -2528,7 +2541,7 @@ describe('v9→v10 migration', () => {
2528
2541
  // Verify version = 11
2529
2542
  const verifyDb = new DatabaseSync(migDb)
2530
2543
  const version = (verifyDb.prepare('PRAGMA user_version').get() as { user_version: number }).user_version
2531
- expect(version).toBe(11)
2544
+ expect(version).toBe(12)
2532
2545
 
2533
2546
  // Verify new REAL columns exist
2534
2547
  const convoyCols = (verifyDb.prepare('PRAGMA table_info(convoy)').all() as Array<{ name: string }>).map(c => c.name)
@@ -17,7 +17,7 @@ import type {
17
17
  TaskStepRecord,
18
18
  } from './types.js'
19
19
 
20
- const SCHEMA_VERSION = 11
20
+ const SCHEMA_VERSION = 12
21
21
 
22
22
  // ── Size limits (bytes) ────────────────────────────────────────────────────────
23
23
  const LIMIT_SPEC_YAML = 256 * 1024 // 256 KB
@@ -178,6 +178,7 @@ class ConvoyStoreImpl implements ConvoyStore {
178
178
  constructor(dbPath: string) {
179
179
  this.dbPath = dbPath
180
180
  this.db = new DatabaseSync(dbPath)
181
+ this.db.exec('PRAGMA foreign_keys = 0')
181
182
  this.db.exec('PRAGMA journal_mode = WAL')
182
183
  this.db.exec('PRAGMA synchronous = NORMAL')
183
184
  this.initSchema()
@@ -222,7 +223,7 @@ class ConvoyStoreImpl implements ConvoyStore {
222
223
  );
223
224
 
224
225
  CREATE TABLE IF NOT EXISTS task (
225
- id TEXT PRIMARY KEY,
226
+ id TEXT NOT NULL,
226
227
  convoy_id TEXT NOT NULL REFERENCES convoy(id),
227
228
  phase INTEGER NOT NULL,
228
229
  prompt TEXT NOT NULL,
@@ -265,7 +266,8 @@ class ConvoyStoreImpl implements ConvoyStore {
265
266
  outputs TEXT,
266
267
  inputs TEXT,
267
268
  discovered_issues TEXT,
268
- contract_result TEXT
269
+ contract_result TEXT,
270
+ PRIMARY KEY (id, convoy_id)
269
271
  );
270
272
 
271
273
  CREATE UNIQUE INDEX IF NOT EXISTS idx_task_idempotency ON task(convoy_id, idempotency_key)
@@ -424,6 +426,10 @@ class ConvoyStoreImpl implements ConvoyStore {
424
426
  migrateSchema(this.db, this.dbPath, 10, 11)
425
427
  version = 11
426
428
  }
429
+ if (version === 11) {
430
+ migrateSchema(this.db, this.dbPath, 11, 12)
431
+ version = 12
432
+ }
427
433
  }
428
434
 
429
435
  insertConvoy(
@@ -1356,6 +1362,71 @@ export function migrateSchema(db: DatabaseSync, dbPath: string, fromVersion: num
1356
1362
  ALTER TABLE task ADD COLUMN compaction_count INTEGER NOT NULL DEFAULT 0;
1357
1363
  `)
1358
1364
  }
1365
+ if (v === 11) {
1366
+ db.exec(`
1367
+ CREATE TABLE task_new (
1368
+ id TEXT NOT NULL,
1369
+ convoy_id TEXT NOT NULL REFERENCES convoy(id),
1370
+ phase INTEGER NOT NULL,
1371
+ prompt TEXT NOT NULL,
1372
+ agent TEXT NOT NULL DEFAULT 'developer',
1373
+ adapter TEXT,
1374
+ model TEXT,
1375
+ timeout_ms INTEGER NOT NULL DEFAULT 1800000,
1376
+ status TEXT NOT NULL DEFAULT 'pending',
1377
+ worker_id TEXT,
1378
+ worktree TEXT,
1379
+ output TEXT,
1380
+ exit_code INTEGER,
1381
+ started_at TEXT,
1382
+ finished_at TEXT,
1383
+ retries INTEGER NOT NULL DEFAULT 0,
1384
+ max_retries INTEGER NOT NULL DEFAULT 1,
1385
+ files TEXT,
1386
+ depends_on TEXT,
1387
+ prompt_tokens INTEGER,
1388
+ completion_tokens INTEGER,
1389
+ total_tokens INTEGER,
1390
+ cost_usd TEXT,
1391
+ cost_usd_num REAL,
1392
+ gates TEXT,
1393
+ on_exhausted TEXT NOT NULL DEFAULT 'dlq',
1394
+ injected INTEGER NOT NULL DEFAULT 0,
1395
+ provenance TEXT,
1396
+ idempotency_key TEXT,
1397
+ current_step INTEGER,
1398
+ total_steps INTEGER,
1399
+ review_level TEXT,
1400
+ review_verdict TEXT,
1401
+ review_tokens INTEGER,
1402
+ review_model TEXT,
1403
+ panel_attempts INTEGER NOT NULL DEFAULT 0,
1404
+ dispute_id TEXT,
1405
+ drift_score REAL,
1406
+ drift_retried INTEGER NOT NULL DEFAULT 0,
1407
+ compaction_count INTEGER NOT NULL DEFAULT 0,
1408
+ outputs TEXT,
1409
+ inputs TEXT,
1410
+ discovered_issues TEXT,
1411
+ contract_result TEXT,
1412
+ PRIMARY KEY (id, convoy_id)
1413
+ );
1414
+ INSERT INTO task_new SELECT
1415
+ id, convoy_id, phase, prompt, agent, adapter, model, timeout_ms,
1416
+ status, worker_id, worktree, output, exit_code, started_at, finished_at,
1417
+ retries, max_retries, files, depends_on, prompt_tokens, completion_tokens,
1418
+ total_tokens, cost_usd, cost_usd_num, gates, on_exhausted, injected,
1419
+ provenance, idempotency_key, current_step, total_steps, review_level,
1420
+ review_verdict, review_tokens, review_model, panel_attempts, dispute_id,
1421
+ drift_score, drift_retried, compaction_count, outputs, inputs,
1422
+ discovered_issues, contract_result
1423
+ FROM task;
1424
+ DROP TABLE task;
1425
+ ALTER TABLE task_new RENAME TO task;
1426
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_task_idempotency ON task(convoy_id, idempotency_key)
1427
+ WHERE idempotency_key IS NOT NULL;
1428
+ `)
1429
+ }
1359
1430
  db.exec('COMMIT')
1360
1431
  } catch (err) {
1361
1432
  try { db.exec('ROLLBACK') } catch { /* ignore */ }
@@ -51,21 +51,21 @@
51
51
  "name": "docs/api-v2-contract.json",
52
52
  "type": "json",
53
53
  "task_id": "api-t1",
54
- "created_at": "2026-04-06T08:25:17.588Z"
54
+ "created_at": "2026-04-06T12:18:29.969Z"
55
55
  },
56
56
  {
57
57
  "id": "artifact-demo-api-v2-reports-security-gate-failure-md",
58
58
  "name": "reports/security-gate-failure.md",
59
59
  "type": "summary",
60
60
  "task_id": "api-t3",
61
- "created_at": "2026-04-06T08:25:17.588Z"
61
+ "created_at": "2026-04-06T12:18:29.969Z"
62
62
  },
63
63
  {
64
64
  "id": "artifact-demo-api-v2-src-api-rate-limiter-ts",
65
65
  "name": "src/api/rate-limiter.ts",
66
66
  "type": "file",
67
67
  "task_id": "api-t2",
68
- "created_at": "2026-04-06T08:25:17.588Z"
68
+ "created_at": "2026-04-06T12:18:29.969Z"
69
69
  }
70
70
  ],
71
71
  "has_more_events": false,
@@ -42,28 +42,28 @@
42
42
  "name": "libs/auth/src/jwt-middleware.ts",
43
43
  "type": "file",
44
44
  "task_id": "auth-t2",
45
- "created_at": "2026-04-06T08:25:17.587Z"
45
+ "created_at": "2026-04-06T12:18:29.968Z"
46
46
  },
47
47
  {
48
48
  "id": "artifact-demo-auth-revamp-libs-auth-src-rls-policies-sql",
49
49
  "name": "libs/auth/src/rls-policies.sql",
50
50
  "type": "file",
51
51
  "task_id": "auth-t3",
52
- "created_at": "2026-04-06T08:25:17.587Z"
53
- },
54
- {
55
- "id": "artifact-demo-auth-revamp-reports-auth-review-summary-md",
56
- "name": "reports/auth-review-summary.md",
57
- "type": "summary",
58
- "task_id": "auth-t5",
59
- "created_at": "2026-04-06T08:25:17.587Z"
52
+ "created_at": "2026-04-06T12:18:29.968Z"
60
53
  },
61
54
  {
62
55
  "id": "artifact-demo-auth-revamp-tests-auth-integration-test-ts",
63
56
  "name": "tests/auth/integration.test.ts",
64
57
  "type": "file",
65
58
  "task_id": "auth-t4",
66
- "created_at": "2026-04-06T08:25:17.587Z"
59
+ "created_at": "2026-04-06T12:18:29.968Z"
60
+ },
61
+ {
62
+ "id": "artifact-demo-auth-revamp-reports-auth-review-summary-md",
63
+ "name": "reports/auth-review-summary.md",
64
+ "type": "summary",
65
+ "task_id": "auth-t5",
66
+ "created_at": "2026-04-06T12:18:29.969Z"
67
67
  }
68
68
  ],
69
69
  "has_more_events": false,
@@ -51,42 +51,42 @@
51
51
  "name": "reports/panel-review-dashboard.md",
52
52
  "type": "summary",
53
53
  "task_id": "ui-t7",
54
- "created_at": "2026-04-06T08:25:17.588Z"
54
+ "created_at": "2026-04-06T12:18:29.969Z"
55
55
  },
56
56
  {
57
57
  "id": "artifact-demo-dashboard-ui-reports-visual-regression-json",
58
58
  "name": "reports/visual-regression.json",
59
59
  "type": "json",
60
60
  "task_id": "ui-t6",
61
- "created_at": "2026-04-06T08:25:17.588Z"
61
+ "created_at": "2026-04-06T12:18:29.969Z"
62
62
  },
63
63
  {
64
64
  "id": "artifact-demo-dashboard-ui-src-components-DonutChart-tsx",
65
65
  "name": "src/components/DonutChart.tsx",
66
66
  "type": "file",
67
67
  "task_id": "ui-t3",
68
- "created_at": "2026-04-06T08:25:17.588Z"
68
+ "created_at": "2026-04-06T12:18:29.969Z"
69
69
  },
70
70
  {
71
71
  "id": "artifact-demo-dashboard-ui-src-components-KpiCard-tsx",
72
72
  "name": "src/components/KpiCard.tsx",
73
73
  "type": "file",
74
74
  "task_id": "ui-t2",
75
- "created_at": "2026-04-06T08:25:17.588Z"
75
+ "created_at": "2026-04-06T12:18:29.969Z"
76
76
  },
77
77
  {
78
78
  "id": "artifact-demo-dashboard-ui-src-components-design-tokens-ts",
79
79
  "name": "src/components/design-tokens.ts",
80
80
  "type": "file",
81
81
  "task_id": "ui-t1",
82
- "created_at": "2026-04-06T08:25:17.588Z"
82
+ "created_at": "2026-04-06T12:18:29.969Z"
83
83
  },
84
84
  {
85
85
  "id": "artifact-demo-dashboard-ui-src-styles-animations-css",
86
86
  "name": "src/styles/animations.css",
87
87
  "type": "file",
88
88
  "task_id": "ui-t4",
89
- "created_at": "2026-04-06T08:25:17.588Z"
89
+ "created_at": "2026-04-06T12:18:29.969Z"
90
90
  }
91
91
  ],
92
92
  "has_more_events": false,
@@ -42,21 +42,21 @@
42
42
  "name": "src/etl/pipeline.ts",
43
43
  "type": "file",
44
44
  "task_id": "etl-t2",
45
- "created_at": "2026-04-06T08:25:17.589Z"
45
+ "created_at": "2026-04-06T12:18:29.970Z"
46
46
  },
47
47
  {
48
48
  "id": "artifact-demo-data-pipeline-src-etl-schema-ts",
49
49
  "name": "src/etl/schema.ts",
50
50
  "type": "file",
51
51
  "task_id": "etl-t1",
52
- "created_at": "2026-04-06T08:25:17.589Z"
52
+ "created_at": "2026-04-06T12:18:29.970Z"
53
53
  },
54
54
  {
55
55
  "id": "artifact-demo-data-pipeline-tests-etl-pipeline-test-ts",
56
56
  "name": "tests/etl/pipeline.test.ts",
57
57
  "type": "file",
58
58
  "task_id": "etl-t3",
59
- "created_at": "2026-04-06T08:25:17.589Z"
59
+ "created_at": "2026-04-06T12:18:29.970Z"
60
60
  }
61
61
  ],
62
62
  "has_more_events": false,
@@ -51,7 +51,7 @@
51
51
  "name": ".github/workflows/ci.yml",
52
52
  "type": "file",
53
53
  "task_id": "ci-t1",
54
- "created_at": "2026-04-06T08:25:17.589Z"
54
+ "created_at": "2026-04-06T12:18:29.970Z"
55
55
  }
56
56
  ],
57
57
  "has_more_events": false,