@pikku/kysely 0.13.19 → 0.13.21
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 +174 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/index.js +2 -1
- package/dist/src/kysely-agent-run-state-service.js +2 -2
- package/dist/src/kysely-agent-storage-service.js +2 -2
- package/dist/src/kysely-audit-service.js +2 -2
- package/dist/src/kysely-channel-store.js +2 -2
- package/dist/src/kysely-credential-service.js +2 -2
- package/dist/src/kysely-deployment-service.js +2 -2
- package/dist/src/kysely-scope-service.js +2 -2
- package/dist/src/kysely-secret-service.js +2 -2
- package/dist/src/kysely-session-store.js +2 -2
- package/dist/src/kysely-tables.d.ts +74 -0
- package/dist/src/kysely-virtual-user-run-store.d.ts +7 -19
- package/dist/src/kysely-virtual-user-run-store.js +71 -4
- package/dist/src/kysely-virtual-user-schedule-store.d.ts +33 -0
- package/dist/src/kysely-virtual-user-schedule-store.js +151 -0
- package/dist/src/kysely-webhook-service.js +2 -2
- package/dist/src/kysely-workflow-mirror.d.ts +1 -1
- package/dist/src/kysely-workflow-mirror.js +3 -3
- package/dist/src/kysely-workflow-service.js +2 -2
- package/dist/src/schema/agent.schema.js +1 -0
- package/dist/src/schema/audit.schema.d.ts +5 -4
- package/dist/src/schema/audit.schema.js +6 -4
- package/dist/src/schema/channel.schema.js +1 -0
- package/dist/src/schema/credential.schema.js +1 -0
- package/dist/src/schema/index.d.ts +48 -26
- package/dist/src/schema/index.js +59 -46
- package/dist/src/schema/pikku-schema.types.d.ts +20 -0
- package/dist/src/schema/scope.schema.js +1 -0
- package/dist/src/schema/virtual-user-schedule.schema.d.ts +15 -0
- package/dist/src/schema/virtual-user-schedule.schema.js +43 -0
- package/dist/src/schema/virtual-user.schema.d.ts +2 -1
- package/dist/src/schema/virtual-user.schema.js +41 -2
- package/dist/src/schema/webhook.schema.js +1 -0
- package/dist/src/schema/workflow.schema.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/index.ts +15 -2
- package/src/kysely-agent-run-state-service.ts +2 -2
- package/src/kysely-agent-storage-service.ts +2 -2
- package/src/kysely-audit-service.test.ts +2 -10
- package/src/kysely-audit-service.ts +2 -2
- package/src/kysely-channel-store.ts +2 -2
- package/src/kysely-credential-service.ts +2 -2
- package/src/kysely-deployment-service.ts +2 -2
- package/src/kysely-scope-service.test.ts +2 -0
- package/src/kysely-scope-service.ts +2 -2
- package/src/kysely-secret-service.ts +2 -2
- package/src/kysely-services.test.ts +26 -0
- package/src/kysely-session-store.ts +2 -2
- package/src/kysely-tables.test.ts +1 -12
- package/src/kysely-tables.ts +77 -0
- package/src/kysely-virtual-user-run-store.test.ts +164 -7
- package/src/kysely-virtual-user-run-store.ts +92 -6
- package/src/kysely-virtual-user-schedule-store.test.ts +187 -0
- package/src/kysely-virtual-user-schedule-store.ts +187 -0
- package/src/kysely-webhook-service.test.ts +4 -0
- package/src/kysely-webhook-service.ts +2 -2
- package/src/kysely-workflow-mirror.test.ts +2 -0
- package/src/kysely-workflow-mirror.ts +3 -3
- package/src/kysely-workflow-service.test.ts +3 -1
- package/src/kysely-workflow-service.ts +2 -2
- package/src/schema/agent.schema.ts +1 -0
- package/src/schema/audit.schema.ts +6 -4
- package/src/schema/channel.schema.ts +1 -0
- package/src/schema/credential.schema.ts +1 -0
- package/src/schema/index.ts +70 -52
- package/src/schema/pikku-schema.types.ts +20 -0
- package/src/schema/schema.test.ts +86 -29
- package/src/schema/scope.schema.ts +1 -0
- package/src/schema/virtual-user-schedule.schema.ts +48 -0
- package/src/schema/virtual-user.schema.ts +43 -2
- package/src/schema/webhook.schema.ts +1 -0
- package/src/schema/workflow.schema.ts +1 -0
|
@@ -14,9 +14,10 @@ import {
|
|
|
14
14
|
import Database from 'better-sqlite3'
|
|
15
15
|
import {
|
|
16
16
|
pikkuSchemas,
|
|
17
|
+
requiredPikkuSchemas,
|
|
17
18
|
compilePikkuSchemas,
|
|
18
19
|
applyPikkuSchemas,
|
|
19
|
-
|
|
20
|
+
requirePikkuSchema,
|
|
20
21
|
declaredTables,
|
|
21
22
|
unsatisfiedRequirements,
|
|
22
23
|
type PikkuSchema,
|
|
@@ -238,17 +239,32 @@ describe('pikku runtime schema — prerequisites', () => {
|
|
|
238
239
|
})
|
|
239
240
|
})
|
|
240
241
|
|
|
241
|
-
describe('
|
|
242
|
+
describe('requirePikkuSchema — what a service does at boot', () => {
|
|
242
243
|
const memoryDb = () =>
|
|
243
244
|
new Kysely<any>({
|
|
244
245
|
dialect: new SqliteDialect({ database: new Database(':memory:') }),
|
|
245
246
|
})
|
|
246
247
|
|
|
247
|
-
test('
|
|
248
|
+
test('refuses a database the migration never reached, naming the command', async () => {
|
|
248
249
|
const db = memoryDb()
|
|
249
250
|
try {
|
|
250
|
-
assert.
|
|
251
|
-
|
|
251
|
+
await assert.rejects(
|
|
252
|
+
requirePikkuSchema(db, workflowSchema),
|
|
253
|
+
/The 'workflow' schema is not in this database.*pikku db generate/s
|
|
254
|
+
)
|
|
255
|
+
} finally {
|
|
256
|
+
await db.destroy()
|
|
257
|
+
}
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test('creates nothing on the way to that refusal', async () => {
|
|
261
|
+
// The whole point: a service that finds its tables missing stops, and the
|
|
262
|
+
// database it stopped against is exactly as it was.
|
|
263
|
+
const db = memoryDb()
|
|
264
|
+
try {
|
|
265
|
+
await assert.rejects(requirePikkuSchema(db, workflowSchema))
|
|
266
|
+
const tables = await db.introspection.getTables()
|
|
267
|
+
assert.deepEqual(tables, [])
|
|
252
268
|
} finally {
|
|
253
269
|
await db.destroy()
|
|
254
270
|
}
|
|
@@ -256,21 +272,22 @@ describe('ensurePikkuSchema — what a service does at boot', () => {
|
|
|
256
272
|
|
|
257
273
|
test('two services declaring the same schema do not fight over it', async () => {
|
|
258
274
|
// The workflow service and the workflow mirror both own these tables, and
|
|
259
|
-
// both call init().
|
|
260
|
-
//
|
|
275
|
+
// both call init(). Neither creates anything, so both find the migrated
|
|
276
|
+
// tables and return.
|
|
261
277
|
const db = memoryDb()
|
|
262
278
|
try {
|
|
263
|
-
|
|
264
|
-
assert.
|
|
279
|
+
await applyPikkuSchemas(db, [workflowSchema])
|
|
280
|
+
await assert.doesNotReject(requirePikkuSchema(db, workflowSchema))
|
|
281
|
+
await assert.doesNotReject(requirePikkuSchema(db, workflowSchema))
|
|
265
282
|
} finally {
|
|
266
283
|
await db.destroy()
|
|
267
284
|
}
|
|
268
285
|
})
|
|
269
286
|
|
|
270
|
-
test('
|
|
287
|
+
test('a half-applied schema is refused the same way a missing one is', async () => {
|
|
271
288
|
// Something else already owns part of it — a migration, an older release,
|
|
272
|
-
// a hand-run script.
|
|
273
|
-
//
|
|
289
|
+
// a hand-run script. Half-present is not a distinct case: the remedy is the
|
|
290
|
+
// same migration either way, so the sentence names both halves and stops.
|
|
274
291
|
const db = memoryDb()
|
|
275
292
|
try {
|
|
276
293
|
await db.schema
|
|
@@ -279,8 +296,8 @@ describe('ensurePikkuSchema — what a service does at boot', () => {
|
|
|
279
296
|
.execute()
|
|
280
297
|
|
|
281
298
|
await assert.rejects(
|
|
282
|
-
|
|
283
|
-
/
|
|
299
|
+
requirePikkuSchema(db, workflowSchema),
|
|
300
|
+
/workflow_step.*missing.*workflow_runs already there/s
|
|
284
301
|
)
|
|
285
302
|
} finally {
|
|
286
303
|
await db.destroy()
|
|
@@ -288,7 +305,7 @@ describe('ensurePikkuSchema — what a service does at boot', () => {
|
|
|
288
305
|
})
|
|
289
306
|
})
|
|
290
307
|
|
|
291
|
-
describe('
|
|
308
|
+
describe('requirePikkuSchema — a connection bound to a schema', () => {
|
|
292
309
|
const WORKFLOW_TABLES = [
|
|
293
310
|
'workflow_runs',
|
|
294
311
|
'workflow_step',
|
|
@@ -301,7 +318,7 @@ describe('ensurePikkuSchema — a connection bound to a schema', () => {
|
|
|
301
318
|
*
|
|
302
319
|
* Stubbed rather than run against a real database because sqlite has one
|
|
303
320
|
* schema and nothing else here speaks postgres — and introspection is the
|
|
304
|
-
* only thing `
|
|
321
|
+
* only thing `requirePikkuSchema` reads before it decides. DummyDriver takes
|
|
305
322
|
* whatever DDL follows.
|
|
306
323
|
*/
|
|
307
324
|
const introspecting = (
|
|
@@ -352,9 +369,8 @@ describe('ensurePikkuSchema — a connection bound to a schema', () => {
|
|
|
352
369
|
WORKFLOW_TABLES.map((name) => ({ schema: 'app', name }))
|
|
353
370
|
)
|
|
354
371
|
|
|
355
|
-
assert.
|
|
356
|
-
|
|
357
|
-
'present'
|
|
372
|
+
await assert.doesNotReject(
|
|
373
|
+
requirePikkuSchema(db.withSchema('app'), workflowSchema)
|
|
358
374
|
)
|
|
359
375
|
})
|
|
360
376
|
|
|
@@ -365,9 +381,9 @@ describe('ensurePikkuSchema — a connection bound to a schema', () => {
|
|
|
365
381
|
WORKFLOW_TABLES.map((name) => ({ schema: 'public', name }))
|
|
366
382
|
)
|
|
367
383
|
|
|
368
|
-
assert.
|
|
369
|
-
|
|
370
|
-
|
|
384
|
+
await assert.rejects(
|
|
385
|
+
requirePikkuSchema(db.withSchema('app'), workflowSchema),
|
|
386
|
+
/is not in this database/
|
|
371
387
|
)
|
|
372
388
|
})
|
|
373
389
|
|
|
@@ -376,7 +392,7 @@ describe('ensurePikkuSchema — a connection bound to a schema', () => {
|
|
|
376
392
|
WORKFLOW_TABLES.map((name) => ({ schema: 'public', name }))
|
|
377
393
|
)
|
|
378
394
|
|
|
379
|
-
assert.
|
|
395
|
+
await assert.doesNotReject(requirePikkuSchema(db, workflowSchema))
|
|
380
396
|
})
|
|
381
397
|
|
|
382
398
|
test('matches on the bare name when the engine reports no schemas', async () => {
|
|
@@ -387,9 +403,8 @@ describe('ensurePikkuSchema — a connection bound to a schema', () => {
|
|
|
387
403
|
WORKFLOW_TABLES.map((name) => ({ schema: undefined, name }))
|
|
388
404
|
)
|
|
389
405
|
|
|
390
|
-
assert.
|
|
391
|
-
|
|
392
|
-
'present'
|
|
406
|
+
await assert.doesNotReject(
|
|
407
|
+
requirePikkuSchema(db.withSchema('main'), workflowSchema)
|
|
393
408
|
)
|
|
394
409
|
})
|
|
395
410
|
|
|
@@ -414,7 +429,7 @@ describe('ensurePikkuSchema — a connection bound to a schema', () => {
|
|
|
414
429
|
})
|
|
415
430
|
|
|
416
431
|
await assert.rejects(
|
|
417
|
-
|
|
432
|
+
applyPikkuSchemas(db.withSchema('app'), [workflowSchema]),
|
|
418
433
|
(error: Error) => {
|
|
419
434
|
assert.equal(error, pgError, 'the engine error must pass through as-is')
|
|
420
435
|
return true
|
|
@@ -431,7 +446,7 @@ describe('ensurePikkuSchema — a connection bound to a schema', () => {
|
|
|
431
446
|
})
|
|
432
447
|
try {
|
|
433
448
|
await assert.rejects(
|
|
434
|
-
|
|
449
|
+
applyPikkuSchemas(db.withSchema('main'), [workflowSchema]),
|
|
435
450
|
(error: Error) => {
|
|
436
451
|
assert.match(error.message, /bound to a schema/)
|
|
437
452
|
assert.match(error.message, /takes a bare table name/i)
|
|
@@ -448,7 +463,7 @@ describe('ensurePikkuSchema — a connection bound to a schema', () => {
|
|
|
448
463
|
const db = introspecting([{ schema: 'app', name: 'workflow_runs' }])
|
|
449
464
|
|
|
450
465
|
await assert.rejects(
|
|
451
|
-
|
|
466
|
+
requirePikkuSchema(db.withSchema('app'), workflowSchema),
|
|
452
467
|
/app\.workflow_step.*missing.*app\.workflow_runs already there/s
|
|
453
468
|
)
|
|
454
469
|
})
|
|
@@ -532,3 +547,45 @@ describe('compiling into a schema', () => {
|
|
|
532
547
|
)
|
|
533
548
|
})
|
|
534
549
|
})
|
|
550
|
+
|
|
551
|
+
describe('requiredPikkuSchemas', () => {
|
|
552
|
+
test('a project that reaches no service still gets the ungated schemas', () => {
|
|
553
|
+
const names = requiredPikkuSchemas(new Set()).map((s) => s.name)
|
|
554
|
+
assert.deepEqual(names, ['session', 'secret', 'deployment'])
|
|
555
|
+
})
|
|
556
|
+
|
|
557
|
+
test('a service brings in its own schema, in the declared order', () => {
|
|
558
|
+
const names = requiredPikkuSchemas(
|
|
559
|
+
new Set(['channelStore', 'workflowService'])
|
|
560
|
+
).map((s) => s.name)
|
|
561
|
+
assert.deepEqual(names, [
|
|
562
|
+
'channel',
|
|
563
|
+
'session',
|
|
564
|
+
'secret',
|
|
565
|
+
'deployment',
|
|
566
|
+
'workflow',
|
|
567
|
+
])
|
|
568
|
+
})
|
|
569
|
+
|
|
570
|
+
test('any one of a schema s owners is enough to ask for it', () => {
|
|
571
|
+
// A schema several services share is needed as soon as one of them is.
|
|
572
|
+
assert.ok(
|
|
573
|
+
requiredPikkuSchemas(new Set(['workflowRunService'])).some(
|
|
574
|
+
(s) => s.name === 'workflow'
|
|
575
|
+
)
|
|
576
|
+
)
|
|
577
|
+
assert.ok(
|
|
578
|
+
requiredPikkuSchemas(new Set(['eventHub'])).some(
|
|
579
|
+
(s) => s.name === 'channel'
|
|
580
|
+
)
|
|
581
|
+
)
|
|
582
|
+
})
|
|
583
|
+
|
|
584
|
+
test('naming every owner asks for everything', () => {
|
|
585
|
+
const owners = pikkuSchemas.flatMap((s) => s.ownedBy ?? [])
|
|
586
|
+
assert.equal(
|
|
587
|
+
requiredPikkuSchemas(new Set(owners)).length,
|
|
588
|
+
pikkuSchemas.length
|
|
589
|
+
)
|
|
590
|
+
})
|
|
591
|
+
})
|
|
@@ -19,6 +19,7 @@ import { requiredType, type PikkuSchema } from './pikku-schema.types.js'
|
|
|
19
19
|
*/
|
|
20
20
|
export const scopeSchema: PikkuSchema = {
|
|
21
21
|
name: 'scope',
|
|
22
|
+
ownedBy: ['scopeService'],
|
|
22
23
|
requires: [{ table: 'user', column: 'id', owner: 'Better Auth' }],
|
|
23
24
|
statements: [
|
|
24
25
|
(db) =>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { PikkuSchema } from './pikku-schema.types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The `virtualUserSchedule` table {@link KyselyVirtualUserScheduleStore} writes
|
|
5
|
+
* to: one row per persona that keeps using the app on its own.
|
|
6
|
+
*
|
|
7
|
+
* Its own schema rather than a third table in {@link virtualUserSchema}, and
|
|
8
|
+
* owned by its own store: recording what a persona did and deciding that it
|
|
9
|
+
* keeps going on its own are separate opt-ins, and a project that only wants
|
|
10
|
+
* the first should not be handed a cadence table it never writes to.
|
|
11
|
+
*
|
|
12
|
+
* `persona` is the primary key: a persona has one cadence, and a second row for
|
|
13
|
+
* the same one would mean two copies of the same user acting at once — not a
|
|
14
|
+
* heavier test, a different and unreproducible one.
|
|
15
|
+
*/
|
|
16
|
+
export const virtualUserScheduleSchema: PikkuSchema = {
|
|
17
|
+
name: 'virtual-user-schedule',
|
|
18
|
+
ownedBy: ['virtualUserScheduleStore'],
|
|
19
|
+
statements: [
|
|
20
|
+
(db) =>
|
|
21
|
+
db.schema
|
|
22
|
+
.createTable('virtualUserSchedule')
|
|
23
|
+
.addColumn('persona', 'varchar(255)', (col) => col.primaryKey())
|
|
24
|
+
// 0 or 1 rather than a boolean, matching the run tables: a bare sqlite
|
|
25
|
+
// driver cannot bind one, and `SerializePlugin` is not installed
|
|
26
|
+
// everywhere. Off by default, so creating the table spends nothing.
|
|
27
|
+
.addColumn('enabled', 'integer', (col) => col.defaultTo(0).notNull())
|
|
28
|
+
.addColumn('disposition', 'varchar(50)', (col) => col.notNull())
|
|
29
|
+
.addColumn('goals', 'text', (col) => col.defaultTo('[]').notNull())
|
|
30
|
+
.addColumn('budget', 'text')
|
|
31
|
+
// The gap to the next run is drawn between these rather than fixed, so
|
|
32
|
+
// the persona does not keep an appointment.
|
|
33
|
+
.addColumn('minIntervalMs', 'bigint', (col) => col.notNull())
|
|
34
|
+
.addColumn('maxIntervalMs', 'bigint', (col) => col.notNull())
|
|
35
|
+
// The entire schedule. Written before a run is dispatched, so a tick
|
|
36
|
+
// that dies halfway cannot leave the row due for the next one.
|
|
37
|
+
.addColumn('nextRunAt', 'timestamp', (col) => col.notNull())
|
|
38
|
+
.addColumn('lastRunId', 'varchar(36)')
|
|
39
|
+
.addColumn('lastRunAt', 'timestamp'),
|
|
40
|
+
|
|
41
|
+
// The only read that matters at speed: which rows are due, on every tick.
|
|
42
|
+
(db) =>
|
|
43
|
+
db.schema
|
|
44
|
+
.createIndex('idx_virtual_user_schedule_due')
|
|
45
|
+
.on('virtualUserSchedule')
|
|
46
|
+
.columns(['enabled', 'nextRunAt']),
|
|
47
|
+
],
|
|
48
|
+
}
|
|
@@ -2,7 +2,8 @@ import { sql } from 'kysely'
|
|
|
2
2
|
import type { PikkuSchema } from './pikku-schema.types.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* The `virtualUserRun`
|
|
5
|
+
* The `virtualUserRun` and `virtualUserRunStep` tables
|
|
6
|
+
* {@link KyselyVirtualUserRunStore} writes to.
|
|
6
7
|
*
|
|
7
8
|
* Deliberately not in `pikkuSchemas`: the runtime never needs this table, only
|
|
8
9
|
* a project that runs virtual users does, and creating it for everyone would
|
|
@@ -19,7 +20,8 @@ import type { PikkuSchema } from './pikku-schema.types.js'
|
|
|
19
20
|
* week's does not depend on which database they were found in.
|
|
20
21
|
*/
|
|
21
22
|
export const virtualUserSchema: PikkuSchema = {
|
|
22
|
-
name: '
|
|
23
|
+
name: 'virtual-user',
|
|
24
|
+
ownedBy: ['virtualUserRunStore'],
|
|
23
25
|
statements: [
|
|
24
26
|
(db) =>
|
|
25
27
|
db.schema
|
|
@@ -37,6 +39,10 @@ export const virtualUserSchema: PikkuSchema = {
|
|
|
37
39
|
.addColumn('goals', 'text', (col) => col.defaultTo('[]').notNull())
|
|
38
40
|
.addColumn('memory', 'text', (col) => col.defaultTo('{}').notNull())
|
|
39
41
|
.addColumn('findings', 'text', (col) => col.defaultTo('[]').notNull())
|
|
42
|
+
// What the user set out to do and how far each one got. On the run row
|
|
43
|
+
// rather than beside the steps: there are a handful of them, and every
|
|
44
|
+
// read of the run wants them.
|
|
45
|
+
.addColumn('intents', 'text', (col) => col.defaultTo('[]').notNull())
|
|
40
46
|
// Counts the engine kept: steps, calls, mutations, tokens, elapsed.
|
|
41
47
|
.addColumn('tally', 'text')
|
|
42
48
|
.addColumn('stoppedBy', 'varchar(255)')
|
|
@@ -61,5 +67,40 @@ export const virtualUserSchema: PikkuSchema = {
|
|
|
61
67
|
.createIndex('idx_virtual_user_run_persona')
|
|
62
68
|
.on('virtualUserRun')
|
|
63
69
|
.columns(['persona', 'createdAt']),
|
|
70
|
+
|
|
71
|
+
// One row per turn, kept out of `virtualUserRun` so that listing runs does
|
|
72
|
+
// not drag a budget's worth of transcript along with it. No foreign key
|
|
73
|
+
// onto the run for the same reason `persona` is not one: this table is
|
|
74
|
+
// created by the store at boot on whatever engine the project uses, and a
|
|
75
|
+
// cascade that half the engines enforce differently is worse than the read
|
|
76
|
+
// rule that a step with no run is orphaned.
|
|
77
|
+
(db) =>
|
|
78
|
+
db.schema
|
|
79
|
+
.createTable('virtualUserRunStep')
|
|
80
|
+
.addColumn('runId', 'varchar(36)', (col) => col.notNull())
|
|
81
|
+
// The engine's own step number, which is what the intent records point
|
|
82
|
+
// at — not a surrogate key, so a transcript reads the same after paging.
|
|
83
|
+
// Spelled `stepIndex` because `index` is a reserved word in mysql.
|
|
84
|
+
.addColumn('stepIndex', 'integer', (col) => col.notNull())
|
|
85
|
+
.addColumn('intentId', 'varchar(255)')
|
|
86
|
+
// The action as the engine scheduled it, including the `invalid` shape
|
|
87
|
+
// for a turn the model got wrong — that turn is the interesting one.
|
|
88
|
+
.addColumn('action', 'text', (col) => col.notNull())
|
|
89
|
+
.addColumn('status', 'integer')
|
|
90
|
+
// 0 or 1 rather than a boolean, because a bare sqlite driver cannot
|
|
91
|
+
// bind one at all and `SerializePlugin` is not installed everywhere.
|
|
92
|
+
.addColumn('ok', 'integer')
|
|
93
|
+
// Already truncated by the engine, and stored JSON-encoded rather than
|
|
94
|
+
// raw: a truncated API response usually starts with a brace, which
|
|
95
|
+
// `SerializePlugin` would read back as an object rather than the string
|
|
96
|
+
// the engine actually saw.
|
|
97
|
+
.addColumn('response', 'text')
|
|
98
|
+
.addColumn('findingKinds', 'text')
|
|
99
|
+
.addColumn('tokensIn', 'integer', (col) => col.defaultTo(0).notNull())
|
|
100
|
+
.addColumn('tokensOut', 'integer', (col) => col.defaultTo(0).notNull())
|
|
101
|
+
.addPrimaryKeyConstraint('pk_virtual_user_run_step', [
|
|
102
|
+
'runId',
|
|
103
|
+
'stepIndex',
|
|
104
|
+
]),
|
|
64
105
|
],
|
|
65
106
|
}
|