@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.
Files changed (75) hide show
  1. package/CHANGELOG.md +174 -0
  2. package/dist/src/index.d.ts +3 -2
  3. package/dist/src/index.js +2 -1
  4. package/dist/src/kysely-agent-run-state-service.js +2 -2
  5. package/dist/src/kysely-agent-storage-service.js +2 -2
  6. package/dist/src/kysely-audit-service.js +2 -2
  7. package/dist/src/kysely-channel-store.js +2 -2
  8. package/dist/src/kysely-credential-service.js +2 -2
  9. package/dist/src/kysely-deployment-service.js +2 -2
  10. package/dist/src/kysely-scope-service.js +2 -2
  11. package/dist/src/kysely-secret-service.js +2 -2
  12. package/dist/src/kysely-session-store.js +2 -2
  13. package/dist/src/kysely-tables.d.ts +74 -0
  14. package/dist/src/kysely-virtual-user-run-store.d.ts +7 -19
  15. package/dist/src/kysely-virtual-user-run-store.js +71 -4
  16. package/dist/src/kysely-virtual-user-schedule-store.d.ts +33 -0
  17. package/dist/src/kysely-virtual-user-schedule-store.js +151 -0
  18. package/dist/src/kysely-webhook-service.js +2 -2
  19. package/dist/src/kysely-workflow-mirror.d.ts +1 -1
  20. package/dist/src/kysely-workflow-mirror.js +3 -3
  21. package/dist/src/kysely-workflow-service.js +2 -2
  22. package/dist/src/schema/agent.schema.js +1 -0
  23. package/dist/src/schema/audit.schema.d.ts +5 -4
  24. package/dist/src/schema/audit.schema.js +6 -4
  25. package/dist/src/schema/channel.schema.js +1 -0
  26. package/dist/src/schema/credential.schema.js +1 -0
  27. package/dist/src/schema/index.d.ts +48 -26
  28. package/dist/src/schema/index.js +59 -46
  29. package/dist/src/schema/pikku-schema.types.d.ts +20 -0
  30. package/dist/src/schema/scope.schema.js +1 -0
  31. package/dist/src/schema/virtual-user-schedule.schema.d.ts +15 -0
  32. package/dist/src/schema/virtual-user-schedule.schema.js +43 -0
  33. package/dist/src/schema/virtual-user.schema.d.ts +2 -1
  34. package/dist/src/schema/virtual-user.schema.js +41 -2
  35. package/dist/src/schema/webhook.schema.js +1 -0
  36. package/dist/src/schema/workflow.schema.js +1 -0
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/package.json +2 -2
  39. package/src/index.ts +15 -2
  40. package/src/kysely-agent-run-state-service.ts +2 -2
  41. package/src/kysely-agent-storage-service.ts +2 -2
  42. package/src/kysely-audit-service.test.ts +2 -10
  43. package/src/kysely-audit-service.ts +2 -2
  44. package/src/kysely-channel-store.ts +2 -2
  45. package/src/kysely-credential-service.ts +2 -2
  46. package/src/kysely-deployment-service.ts +2 -2
  47. package/src/kysely-scope-service.test.ts +2 -0
  48. package/src/kysely-scope-service.ts +2 -2
  49. package/src/kysely-secret-service.ts +2 -2
  50. package/src/kysely-services.test.ts +26 -0
  51. package/src/kysely-session-store.ts +2 -2
  52. package/src/kysely-tables.test.ts +1 -12
  53. package/src/kysely-tables.ts +77 -0
  54. package/src/kysely-virtual-user-run-store.test.ts +164 -7
  55. package/src/kysely-virtual-user-run-store.ts +92 -6
  56. package/src/kysely-virtual-user-schedule-store.test.ts +187 -0
  57. package/src/kysely-virtual-user-schedule-store.ts +187 -0
  58. package/src/kysely-webhook-service.test.ts +4 -0
  59. package/src/kysely-webhook-service.ts +2 -2
  60. package/src/kysely-workflow-mirror.test.ts +2 -0
  61. package/src/kysely-workflow-mirror.ts +3 -3
  62. package/src/kysely-workflow-service.test.ts +3 -1
  63. package/src/kysely-workflow-service.ts +2 -2
  64. package/src/schema/agent.schema.ts +1 -0
  65. package/src/schema/audit.schema.ts +6 -4
  66. package/src/schema/channel.schema.ts +1 -0
  67. package/src/schema/credential.schema.ts +1 -0
  68. package/src/schema/index.ts +70 -52
  69. package/src/schema/pikku-schema.types.ts +20 -0
  70. package/src/schema/schema.test.ts +86 -29
  71. package/src/schema/scope.schema.ts +1 -0
  72. package/src/schema/virtual-user-schedule.schema.ts +48 -0
  73. package/src/schema/virtual-user.schema.ts +43 -2
  74. package/src/schema/webhook.schema.ts +1 -0
  75. package/src/schema/workflow.schema.ts +1 -0
@@ -1,6 +1,7 @@
1
1
  import type { PikkuSchema } from './pikku-schema.types.js';
2
2
  /**
3
- * The `virtualUserRun` table {@link KyselyVirtualUserRunStore} writes to.
3
+ * The `virtualUserRun` and `virtualUserRunStep` tables
4
+ * {@link KyselyVirtualUserRunStore} writes to.
4
5
  *
5
6
  * Deliberately not in `pikkuSchemas`: the runtime never needs this table, only
6
7
  * a project that runs virtual users does, and creating it for everyone would
@@ -1,6 +1,7 @@
1
1
  import { sql } from 'kysely';
2
2
  /**
3
- * The `virtualUserRun` table {@link KyselyVirtualUserRunStore} writes to.
3
+ * The `virtualUserRun` and `virtualUserRunStep` tables
4
+ * {@link KyselyVirtualUserRunStore} writes to.
4
5
  *
5
6
  * Deliberately not in `pikkuSchemas`: the runtime never needs this table, only
6
7
  * a project that runs virtual users does, and creating it for everyone would
@@ -17,7 +18,8 @@ import { sql } from 'kysely';
17
18
  * week's does not depend on which database they were found in.
18
19
  */
19
20
  export const virtualUserSchema = {
20
- name: 'virtualUser',
21
+ name: 'virtual-user',
22
+ ownedBy: ['virtualUserRunStore'],
21
23
  statements: [
22
24
  (db) => db.schema
23
25
  .createTable('virtualUserRun')
@@ -32,6 +34,10 @@ export const virtualUserSchema = {
32
34
  .addColumn('goals', 'text', (col) => col.defaultTo('[]').notNull())
33
35
  .addColumn('memory', 'text', (col) => col.defaultTo('{}').notNull())
34
36
  .addColumn('findings', 'text', (col) => col.defaultTo('[]').notNull())
37
+ // What the user set out to do and how far each one got. On the run row
38
+ // rather than beside the steps: there are a handful of them, and every
39
+ // read of the run wants them.
40
+ .addColumn('intents', 'text', (col) => col.defaultTo('[]').notNull())
35
41
  // Counts the engine kept: steps, calls, mutations, tokens, elapsed.
36
42
  .addColumn('tally', 'text')
37
43
  .addColumn('stoppedBy', 'varchar(255)')
@@ -50,5 +56,38 @@ export const virtualUserSchema = {
50
56
  .createIndex('idx_virtual_user_run_persona')
51
57
  .on('virtualUserRun')
52
58
  .columns(['persona', 'createdAt']),
59
+ // One row per turn, kept out of `virtualUserRun` so that listing runs does
60
+ // not drag a budget's worth of transcript along with it. No foreign key
61
+ // onto the run for the same reason `persona` is not one: this table is
62
+ // created by the store at boot on whatever engine the project uses, and a
63
+ // cascade that half the engines enforce differently is worse than the read
64
+ // rule that a step with no run is orphaned.
65
+ (db) => db.schema
66
+ .createTable('virtualUserRunStep')
67
+ .addColumn('runId', 'varchar(36)', (col) => col.notNull())
68
+ // The engine's own step number, which is what the intent records point
69
+ // at — not a surrogate key, so a transcript reads the same after paging.
70
+ // Spelled `stepIndex` because `index` is a reserved word in mysql.
71
+ .addColumn('stepIndex', 'integer', (col) => col.notNull())
72
+ .addColumn('intentId', 'varchar(255)')
73
+ // The action as the engine scheduled it, including the `invalid` shape
74
+ // for a turn the model got wrong — that turn is the interesting one.
75
+ .addColumn('action', 'text', (col) => col.notNull())
76
+ .addColumn('status', 'integer')
77
+ // 0 or 1 rather than a boolean, because a bare sqlite driver cannot
78
+ // bind one at all and `SerializePlugin` is not installed everywhere.
79
+ .addColumn('ok', 'integer')
80
+ // Already truncated by the engine, and stored JSON-encoded rather than
81
+ // raw: a truncated API response usually starts with a brace, which
82
+ // `SerializePlugin` would read back as an object rather than the string
83
+ // the engine actually saw.
84
+ .addColumn('response', 'text')
85
+ .addColumn('findingKinds', 'text')
86
+ .addColumn('tokensIn', 'integer', (col) => col.defaultTo(0).notNull())
87
+ .addColumn('tokensOut', 'integer', (col) => col.defaultTo(0).notNull())
88
+ .addPrimaryKeyConstraint('pk_virtual_user_run_step', [
89
+ 'runId',
90
+ 'stepIndex',
91
+ ]),
53
92
  ],
54
93
  };
@@ -2,6 +2,7 @@ import { sql } from 'kysely';
2
2
  /** Webhook deliveries and one row per delivery attempt. */
3
3
  export const webhookSchema = {
4
4
  name: 'webhook',
5
+ ownedBy: ['webhookService'],
5
6
  statements: [
6
7
  (db) => db.schema
7
8
  .createTable('webhookDelivery')
@@ -16,6 +16,7 @@ import { sql } from 'kysely';
16
16
  */
17
17
  export const workflowSchema = {
18
18
  name: 'workflow',
19
+ ownedBy: ['workflowService', 'workflowRunService'],
19
20
  statements: [
20
21
  (db) => db.schema
21
22
  .createTable('workflowRuns')
@@ -1 +1 @@
1
- {"root":["../src/coercion-plugin.ts","../src/create-audited-kysely.ts","../src/index.ts","../src/kysely-agent-run-scores.ts","../src/kysely-agent-run-service.ts","../src/kysely-agent-run-state-service.ts","../src/kysely-agent-storage-service.ts","../src/kysely-audit-service.ts","../src/kysely-channel-store.ts","../src/kysely-credential-service.ts","../src/kysely-deployment-service.ts","../src/kysely-eventhub-store.ts","../src/kysely-json.ts","../src/kysely-scope-service.ts","../src/kysely-secret-service.ts","../src/kysely-session-store.ts","../src/kysely-tables.ts","../src/kysely-virtual-user-run-store.ts","../src/kysely-webhook-service.ts","../src/kysely-workflow-mirror.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../src/serialize-plugin.ts","../src/schema/agent.schema.ts","../src/schema/audit.schema.ts","../src/schema/channel.schema.ts","../src/schema/credential.schema.ts","../src/schema/deployment.schema.ts","../src/schema/index.ts","../src/schema/pikku-schema.types.ts","../src/schema/scope.schema.ts","../src/schema/secret.schema.ts","../src/schema/session.schema.ts","../src/schema/virtual-user.schema.ts","../src/schema/webhook.schema.ts","../src/schema/workflow.schema.ts","../bin/pikku-kysely-pure.ts"],"version":"6.0.3"}
1
+ {"root":["../src/coercion-plugin.ts","../src/create-audited-kysely.ts","../src/index.ts","../src/kysely-agent-run-scores.ts","../src/kysely-agent-run-service.ts","../src/kysely-agent-run-state-service.ts","../src/kysely-agent-storage-service.ts","../src/kysely-audit-service.ts","../src/kysely-channel-store.ts","../src/kysely-credential-service.ts","../src/kysely-deployment-service.ts","../src/kysely-eventhub-store.ts","../src/kysely-json.ts","../src/kysely-scope-service.ts","../src/kysely-secret-service.ts","../src/kysely-session-store.ts","../src/kysely-tables.ts","../src/kysely-virtual-user-run-store.ts","../src/kysely-virtual-user-schedule-store.ts","../src/kysely-webhook-service.ts","../src/kysely-workflow-mirror.ts","../src/kysely-workflow-run-service.ts","../src/kysely-workflow-service.ts","../src/serialize-plugin.ts","../src/schema/agent.schema.ts","../src/schema/audit.schema.ts","../src/schema/channel.schema.ts","../src/schema/credential.schema.ts","../src/schema/deployment.schema.ts","../src/schema/index.ts","../src/schema/pikku-schema.types.ts","../src/schema/scope.schema.ts","../src/schema/secret.schema.ts","../src/schema/session.schema.ts","../src/schema/virtual-user-schedule.schema.ts","../src/schema/virtual-user.schema.ts","../src/schema/webhook.schema.ts","../src/schema/workflow.schema.ts","../bin/pikku-kysely-pure.ts"],"version":"6.0.3"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/kysely",
3
- "version": "0.13.19",
3
+ "version": "0.13.21",
4
4
  "description": "Kysely-backed Pikku services — workflow state, secrets, sessions, scopes and audit",
5
5
  "author": "yasser.fadl@gmail.com",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  "prepublishOnly": "yarn build"
22
22
  },
23
23
  "peerDependencies": {
24
- "@pikku/core": "^0.12.87"
24
+ "@pikku/core": "^0.12.90"
25
25
  },
26
26
  "dependencies": {
27
27
  "kysely": "^0.29.5"
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export {
18
18
  } from './create-audited-kysely.js'
19
19
  export { KyselyAuditService } from './kysely-audit-service.js'
20
20
  export { KyselyVirtualUserRunStore } from './kysely-virtual-user-run-store.js'
21
+ export { KyselyVirtualUserScheduleStore } from './kysely-virtual-user-schedule-store.js'
21
22
 
22
23
  export {
23
24
  SerializePlugin,
@@ -32,12 +33,22 @@ export {
32
33
  type CreateCoercionPluginOptions,
33
34
  } from './coercion-plugin.js'
34
35
  export {
36
+ agentSchema,
35
37
  auditSchema,
38
+ channelSchema,
39
+ credentialSchema,
40
+ deploymentSchema,
41
+ scopeSchema,
42
+ secretSchema,
43
+ sessionSchema,
44
+ virtualUserSchema,
45
+ webhookSchema,
46
+ workflowSchema,
36
47
  pikkuSchemas,
48
+ requiredPikkuSchemas,
37
49
  applyPikkuSchemas,
38
50
  compilePikkuSchemas,
39
- ensurePikkuSchema,
40
- type EnsureOutcome,
51
+ requirePikkuSchema,
41
52
  resolveRequirements,
42
53
  type PikkuSchema,
43
54
  type RequiredTypes,
@@ -51,4 +62,6 @@ export type { AgentRunService, AgentRunRow } from '@pikku/core/agent'
51
62
  export type {
52
63
  VirtualUserRunStore,
53
64
  VirtualUserRunRecord,
65
+ VirtualUserScheduleStore,
66
+ VirtualUserScheduleRecord,
54
67
  } from '@pikku/core/virtual-user'
@@ -9,7 +9,7 @@ import type {
9
9
  SaveScoreInput,
10
10
  } from '@pikku/core/services'
11
11
  import type { PendingApproval } from '@pikku/core/agent'
12
- import { ensurePikkuSchema } from './schema/index.js'
12
+ import { requirePikkuSchema } from './schema/index.js'
13
13
  import { agentSchema } from './schema/agent.schema.js'
14
14
  import { getRunScores, saveRunScore } from './kysely-agent-run-scores.js'
15
15
 
@@ -20,7 +20,7 @@ export class KyselyAgentRunStateService implements AgentRunStateService {
20
20
 
21
21
  async init(): Promise<void> {
22
22
  if (this.initialized) return
23
- await ensurePikkuSchema(this.db, agentSchema)
23
+ await requirePikkuSchema(this.db, agentSchema)
24
24
  this.initialized = true
25
25
  }
26
26
 
@@ -14,7 +14,7 @@ import type { Kysely } from 'kysely'
14
14
  import type { KyselyPikkuDB } from './kysely-tables.js'
15
15
  import { parseJson } from './kysely-json.js'
16
16
  import { getRunScores, saveRunScore } from './kysely-agent-run-scores.js'
17
- import { ensurePikkuSchema } from './schema/index.js'
17
+ import { requirePikkuSchema } from './schema/index.js'
18
18
  import { agentSchema } from './schema/agent.schema.js'
19
19
 
20
20
  export class KyselyAgentStorageService
@@ -26,7 +26,7 @@ export class KyselyAgentStorageService
26
26
 
27
27
  public async init(): Promise<void> {
28
28
  if (this.initialized) return
29
- await ensurePikkuSchema(this.db, agentSchema)
29
+ await requirePikkuSchema(this.db, agentSchema)
30
30
  this.initialized = true
31
31
  }
32
32
 
@@ -309,21 +309,13 @@ describe('KyselyAuditService against a CamelCasePlugin connection', () => {
309
309
  })
310
310
 
311
311
  describe('KyselyAuditService.init', () => {
312
- test('creates the audit table on a database that has none', async () => {
312
+ test('refuses to boot against a database that has none', async () => {
313
313
  const fresh = new Kysely<any>({
314
314
  dialect: new SqliteDialect({ database: new Database(':memory:') }),
315
315
  plugins: [new CamelCasePlugin()],
316
316
  })
317
317
  const freshService = new KyselyAuditService(fresh)
318
- await freshService.init()
319
- await freshService.write([
320
- event({ eventId: 'e1', type: 't', occurredAt: '2026-01-01' }),
321
- ])
322
- const { events } = await freshService.query()
323
- assert.deepEqual(
324
- events.map((e) => e.eventId),
325
- ['e1']
326
- )
318
+ await assert.rejects(freshService.init(), /pikku db generate/)
327
319
  })
328
320
 
329
321
  // Two boots against one database, and the second must not wipe the first.
@@ -6,7 +6,7 @@ import type {
6
6
  AuditQueryResult,
7
7
  } from '@pikku/core/services'
8
8
  import type { Kysely } from 'kysely'
9
- import { ensurePikkuSchema } from './schema/index.js'
9
+ import { requirePikkuSchema } from './schema/index.js'
10
10
  import { auditSchema } from './schema/audit.schema.js'
11
11
 
12
12
  const jsonOrNull = (v: unknown): string | null =>
@@ -103,7 +103,7 @@ export class KyselyAuditService implements AuditService {
103
103
  */
104
104
  public async init(): Promise<void> {
105
105
  if (this.initialized) return
106
- await ensurePikkuSchema(this.db, auditSchema)
106
+ await requirePikkuSchema(this.db, auditSchema)
107
107
  this.initialized = true
108
108
  }
109
109
 
@@ -3,7 +3,7 @@ import type { Channel } from '@pikku/core/channel'
3
3
  import type { Kysely } from 'kysely'
4
4
  import type { KyselyPikkuDB } from './kysely-tables.js'
5
5
  import { parseJson } from './kysely-json.js'
6
- import { ensurePikkuSchema } from './schema/index.js'
6
+ import { requirePikkuSchema } from './schema/index.js'
7
7
  import { channelSchema } from './schema/channel.schema.js'
8
8
 
9
9
  export class KyselyChannelStore extends ChannelStore {
@@ -15,7 +15,7 @@ export class KyselyChannelStore extends ChannelStore {
15
15
 
16
16
  public async init(): Promise<void> {
17
17
  if (this.initialized) return
18
- await ensurePikkuSchema(this.db, channelSchema)
18
+ await requirePikkuSchema(this.db, channelSchema)
19
19
  this.initialized = true
20
20
  }
21
21
 
@@ -8,7 +8,7 @@ import {
8
8
  import type { CredentialService } from '@pikku/core/services'
9
9
  import type { Kysely } from 'kysely'
10
10
  import type { KyselyPikkuDB } from './kysely-tables.js'
11
- import { ensurePikkuSchema } from './schema/index.js'
11
+ import { requirePikkuSchema } from './schema/index.js'
12
12
  import { credentialSchema } from './schema/credential.schema.js'
13
13
 
14
14
  export interface KyselyCredentialServiceConfig {
@@ -42,7 +42,7 @@ export class KyselyCredentialService implements CredentialService {
42
42
 
43
43
  public async init(): Promise<void> {
44
44
  if (this.initialized) return
45
- await ensurePikkuSchema(this.db, credentialSchema)
45
+ await requirePikkuSchema(this.db, credentialSchema)
46
46
  this.initialized = true
47
47
  }
48
48
 
@@ -9,7 +9,7 @@ import type { SecretService } from '@pikku/core/services'
9
9
  import { getAllFunctionNames } from '@pikku/core/function'
10
10
  import type { Kysely } from 'kysely'
11
11
  import type { KyselyPikkuDB } from './kysely-tables.js'
12
- import { ensurePikkuSchema } from './schema/index.js'
12
+ import { requirePikkuSchema } from './schema/index.js'
13
13
  import { deploymentSchema } from './schema/deployment.schema.js'
14
14
 
15
15
  export class KyselyDeploymentService implements DeploymentService {
@@ -31,7 +31,7 @@ export class KyselyDeploymentService implements DeploymentService {
31
31
 
32
32
  public async init(): Promise<void> {
33
33
  if (this.initialized) return
34
- await ensurePikkuSchema(this.db, deploymentSchema)
34
+ await requirePikkuSchema(this.db, deploymentSchema)
35
35
  this.initialized = true
36
36
  }
37
37
 
@@ -5,6 +5,7 @@ import Database from 'better-sqlite3'
5
5
  import { SerializePlugin } from './serialize-plugin.js'
6
6
  import type { KyselyPikkuDB } from './kysely-tables.js'
7
7
  import { KyselyScopeService } from './kysely-scope-service.js'
8
+ import { applyPikkuSchemas, scopeSchema } from './schema/index.js'
8
9
 
9
10
  const SCOPES = [
10
11
  { id: 'admin' },
@@ -41,6 +42,7 @@ beforeEach(async () => {
41
42
  plugins: [new CamelCasePlugin(), new SerializePlugin()],
42
43
  })
43
44
  await createUserTable(db)
45
+ await applyPikkuSchemas(db, [scopeSchema])
44
46
  service = new KyselyScopeService(db)
45
47
  await service.init()
46
48
  await service.syncScopes(SCOPES)
@@ -8,7 +8,7 @@ import {
8
8
  } from '@pikku/core/errors'
9
9
  import type { Kysely } from 'kysely'
10
10
  import type { KyselyPikkuDB } from './kysely-tables.js'
11
- import { ensurePikkuSchema } from './schema/index.js'
11
+ import { requirePikkuSchema } from './schema/index.js'
12
12
  import { scopeSchema } from './schema/scope.schema.js'
13
13
 
14
14
  /**
@@ -30,7 +30,7 @@ export class KyselyScopeService implements ScopeService {
30
30
 
31
31
  public async init(): Promise<void> {
32
32
  if (this.initialized) return
33
- await ensurePikkuSchema(this.db, scopeSchema)
33
+ await requirePikkuSchema(this.db, scopeSchema)
34
34
  this.initialized = true
35
35
  }
36
36
 
@@ -10,7 +10,7 @@ import type { SecretService, SecretValues } from '@pikku/core/services'
10
10
  import type { SecretValue } from '@pikku/core/classification'
11
11
  import type { Kysely } from 'kysely'
12
12
  import type { KyselyPikkuDB } from './kysely-tables.js'
13
- import { ensurePikkuSchema } from './schema/index.js'
13
+ import { requirePikkuSchema } from './schema/index.js'
14
14
  import { secretSchema } from './schema/secret.schema.js'
15
15
 
16
16
  export interface KyselySecretServiceConfig {
@@ -44,7 +44,7 @@ export class KyselySecretService implements SecretService {
44
44
 
45
45
  public async init(): Promise<void> {
46
46
  if (this.initialized) return
47
- await ensurePikkuSchema(this.db, secretSchema)
47
+ await requirePikkuSchema(this.db, secretSchema)
48
48
  this.initialized = true
49
49
  }
50
50
 
@@ -18,6 +18,30 @@ import { KyselyAgentRunService } from './kysely-agent-run-service.js'
18
18
  import { KyselySecretService } from './kysely-secret-service.js'
19
19
  import { KyselyCredentialService } from './kysely-credential-service.js'
20
20
  import { KyselySessionStore } from './kysely-session-store.js'
21
+ import {
22
+ agentSchema,
23
+ applyPikkuSchemas,
24
+ channelSchema,
25
+ credentialSchema,
26
+ deploymentSchema,
27
+ secretSchema,
28
+ sessionSchema,
29
+ workflowSchema,
30
+ } from './schema/index.js'
31
+
32
+ /**
33
+ * Every schema the services under test boot against. They no longer create
34
+ * their own tables, so the harness stands in for `pikku db migrate`.
35
+ */
36
+ const SERVICE_SCHEMAS = [
37
+ channelSchema,
38
+ workflowSchema,
39
+ deploymentSchema,
40
+ agentSchema,
41
+ secretSchema,
42
+ credentialSchema,
43
+ sessionSchema,
44
+ ]
21
45
 
22
46
  function createSqliteDb(): Kysely<KyselyPikkuDB> {
23
47
  return new Kysely<KyselyPikkuDB>({
@@ -345,6 +369,7 @@ describe('Kysely Services - SQLite', () => {
345
369
 
346
370
  before(async () => {
347
371
  db = createSqliteDb()
372
+ await applyPikkuSchemas(db, SERVICE_SCHEMAS)
348
373
  })
349
374
 
350
375
  after(async () => {
@@ -365,6 +390,7 @@ describe(
365
390
  before(async () => {
366
391
  db = createPostgresDb()!
367
392
  await dropAllTables(db)
393
+ await applyPikkuSchemas(db, SERVICE_SCHEMAS)
368
394
  })
369
395
 
370
396
  after(async () => {
@@ -3,7 +3,7 @@ import type { CoreUserSession } from '@pikku/core/types'
3
3
  import type { Kysely } from 'kysely'
4
4
  import type { KyselyPikkuDB } from './kysely-tables.js'
5
5
  import { parseJson } from './kysely-json.js'
6
- import { ensurePikkuSchema } from './schema/index.js'
6
+ import { requirePikkuSchema } from './schema/index.js'
7
7
  import { sessionSchema } from './schema/session.schema.js'
8
8
 
9
9
  export class KyselySessionStore implements SessionStore {
@@ -13,7 +13,7 @@ export class KyselySessionStore implements SessionStore {
13
13
 
14
14
  public async init(): Promise<void> {
15
15
  if (this.initialized) return
16
- await ensurePikkuSchema(this.db, sessionSchema)
16
+ await requirePikkuSchema(this.db, sessionSchema)
17
17
  this.initialized = true
18
18
  }
19
19
 
@@ -5,15 +5,7 @@ import { join } from 'node:path'
5
5
  import { Kysely, SqliteDialect } from 'kysely'
6
6
  import Database from 'better-sqlite3'
7
7
 
8
- import { applyPikkuSchemas, ensurePikkuSchema } from './schema/index.js'
9
- import { virtualUserSchema } from './schema/virtual-user.schema.js'
10
-
11
- /**
12
- * Opt-in schemas that still have an interface in `KyselyPikkuDB`, so the drift
13
- * check covers them too. A table being opt-in says when it is created, not
14
- * whether its type is allowed to disagree with its DDL.
15
- */
16
- const OPTIONAL_SCHEMAS = [virtualUserSchema]
8
+ import { applyPikkuSchemas } from './schema/index.js'
17
9
 
18
10
  /**
19
11
  * `KyselyPikkuDB` and the schema declaration describe the same tables, and
@@ -73,9 +65,6 @@ const declaredSchema = async (): Promise<Map<string, Set<string>>> => {
73
65
  .addColumn('id', 'text', (col) => col.primaryKey())
74
66
  .execute()
75
67
  await applyPikkuSchemas(db)
76
- for (const schema of OPTIONAL_SCHEMAS) {
77
- await ensurePikkuSchema(db, schema)
78
- }
79
68
 
80
69
  const tables = await db.introspection.getTables()
81
70
  return new Map(
@@ -311,6 +311,7 @@ export interface VirtualUserRunTable {
311
311
  goals: Generated<string>
312
312
  memory: Generated<string>
313
313
  findings: Generated<string>
314
+ intents: Generated<string>
314
315
  tally: string | null
315
316
  stoppedBy: string | null
316
317
  error: string | null
@@ -324,6 +325,79 @@ export interface VirtualUserRunTable {
324
325
  finishedAt: Date | string | null
325
326
  }
326
327
 
328
+ /**
329
+ * One audit event. Every column is text on every engine so a locally-run
330
+ * project and a deployed stage write rows the same reader can read; the JSON
331
+ * columns (`tables`, `changedCols`, `event`, `old`, `data`) are serialised by
332
+ * {@link KyselyAuditService} rather than by a plugin.
333
+ */
334
+ export interface AuditTable {
335
+ auditId: string
336
+ /** ISO 8601, because string ordering is chronological ordering. */
337
+ occurredAt: string
338
+ type: string
339
+ source: Generated<string>
340
+ outcome: string | null
341
+ functionId: string | null
342
+ wireType: string | null
343
+ traceId: string | null
344
+ transactionId: string | null
345
+ queryId: string | null
346
+ userId: string | null
347
+ orgId: string | null
348
+ pikkuUserId: string | null
349
+ tables: string | null
350
+ changedCols: string | null
351
+ event: string | null
352
+ old: string | null
353
+ data: string | null
354
+ }
355
+
356
+ /**
357
+ * One turn of a run's transcript.
358
+ *
359
+ * Its own table rather than a column on {@link VirtualUserRunTable}: a run at a
360
+ * 500-step budget carries more transcript than every other column together, and
361
+ * the run list would pay for it on every row.
362
+ */
363
+ export interface VirtualUserRunStepTable {
364
+ runId: string
365
+ /** The engine's step number. `stepIndex` because `index` is reserved in mysql. */
366
+ stepIndex: number
367
+ intentId: string | null
368
+ /** JSON: the action the engine scheduled, or the `invalid` shape. */
369
+ action: string
370
+ status: number | null
371
+ /** 0 or 1: not every driver in this package can bind a boolean. */
372
+ ok: number | null
373
+ /** JSON-encoded, so a brace-leading response is not read back as an object. */
374
+ response: string | null
375
+ /** JSON array, null when the turn produced no finding. */
376
+ findingKinds: string | null
377
+ tokensIn: Generated<number>
378
+ tokensOut: Generated<number>
379
+ }
380
+
381
+ /**
382
+ * One persona's cadence. Nullable timestamps and 0/1 flags for the same driver
383
+ * reasons as {@link VirtualUserRunTable}.
384
+ */
385
+ export interface VirtualUserScheduleTable {
386
+ persona: string
387
+ /** 0 or 1: not every driver in this package can bind a boolean. */
388
+ enabled: Generated<number>
389
+ disposition: string
390
+ goals: Generated<string>
391
+ /** JSON, or null for the engine's own default budget. */
392
+ budget: string | null
393
+ /** BIGINT, so a driver may hand either back. */
394
+ minIntervalMs: string | number | bigint
395
+ maxIntervalMs: string | number | bigint
396
+ nextRunAt: Date | string
397
+ lastRunId: string | null
398
+ lastRunAt: Date | string | null
399
+ }
400
+
327
401
  export interface KyselyPikkuDB {
328
402
  pikkuScopes: PikkuScopesTable
329
403
  pikkuRoles: PikkuRolesTable
@@ -354,4 +428,7 @@ export interface KyselyPikkuDB {
354
428
  webhookDelivery: WebhookDeliveryTable
355
429
  webhookDeliveryAttempt: WebhookDeliveryAttemptTable
356
430
  virtualUserRun: VirtualUserRunTable
431
+ virtualUserRunStep: VirtualUserRunStepTable
432
+ virtualUserSchedule: VirtualUserScheduleTable
433
+ audit: AuditTable
357
434
  }