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.
- package/dist/cli/convoy/pipeline.d.ts +2 -0
- package/dist/cli/convoy/pipeline.d.ts.map +1 -1
- package/dist/cli/convoy/pipeline.js +6 -0
- package/dist/cli/convoy/pipeline.js.map +1 -1
- package/dist/cli/convoy/pipeline.test.js +30 -0
- package/dist/cli/convoy/pipeline.test.js.map +1 -1
- package/dist/cli/convoy/store.d.ts.map +1 -1
- package/dist/cli/convoy/store.js +74 -3
- package/dist/cli/convoy/store.js.map +1 -1
- package/dist/cli/convoy/store.test.js +26 -14
- package/dist/cli/convoy/store.test.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/convoy/pipeline.test.ts +30 -0
- package/src/cli/convoy/pipeline.ts +9 -0
- package/src/cli/convoy/store.test.ts +28 -15
- package/src/cli/convoy/store.ts +74 -3
- package/src/dashboard/dist/data/convoys/demo-api-v2.json +3 -3
- package/src/dashboard/dist/data/convoys/demo-auth-revamp.json +10 -10
- package/src/dashboard/dist/data/convoys/demo-dashboard-ui.json +6 -6
- package/src/dashboard/dist/data/convoys/demo-data-pipeline.json +3 -3
- package/src/dashboard/dist/data/convoys/demo-deploy-ci.json +1 -1
- package/src/dashboard/dist/data/convoys/demo-docs-update.json +3 -3
- package/src/dashboard/dist/data/convoys/demo-perf-opt.json +10 -10
- package/src/dashboard/node_modules/.vite/deps/_metadata.json +6 -6
- package/src/dashboard/public/data/convoys/demo-api-v2.json +3 -3
- package/src/dashboard/public/data/convoys/demo-auth-revamp.json +10 -10
- package/src/dashboard/public/data/convoys/demo-dashboard-ui.json +6 -6
- package/src/dashboard/public/data/convoys/demo-data-pipeline.json +3 -3
- package/src/dashboard/public/data/convoys/demo-deploy-ci.json +1 -1
- package/src/dashboard/public/data/convoys/demo-docs-update.json +3 -3
- 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
|
|
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(
|
|
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(
|
|
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
|
|
212
|
-
expect(version.user_version).toBe(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
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)
|
package/src/cli/convoy/store.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
TaskStepRecord,
|
|
18
18
|
} from './types.js'
|
|
19
19
|
|
|
20
|
-
const SCHEMA_VERSION =
|
|
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
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
59
|
+
"created_at": "2026-04-06T12:18:29.970Z"
|
|
60
60
|
}
|
|
61
61
|
],
|
|
62
62
|
"has_more_events": false,
|