@pikku/kysely 0.13.24 → 0.13.27

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 (78) hide show
  1. package/CHANGELOG.md +235 -0
  2. package/dist/src/index.d.ts +4 -1
  3. package/dist/src/index.js +3 -1
  4. package/dist/src/kysely-analytics-service.d.ts +30 -0
  5. package/dist/src/kysely-analytics-service.js +80 -0
  6. package/dist/src/kysely-feature-flag-store.d.ts +37 -0
  7. package/dist/src/kysely-feature-flag-store.js +239 -0
  8. package/dist/src/kysely-tables.d.ts +45 -0
  9. package/dist/src/schema/analytics.schema.d.ts +17 -0
  10. package/dist/src/schema/analytics.schema.js +48 -0
  11. package/dist/src/schema/flag.schema.d.ts +16 -0
  12. package/dist/src/schema/flag.schema.js +62 -0
  13. package/dist/src/schema/index.d.ts +2 -0
  14. package/dist/src/schema/index.js +6 -0
  15. package/package.json +11 -6
  16. package/run-tests.sh +9 -1
  17. package/bin/pikku-kysely-pure.ts +0 -59
  18. package/dist/src/kysely-lock-vault.d.ts +0 -34
  19. package/dist/src/kysely-lock-vault.js +0 -77
  20. package/dist/src/schema/data-lock.schema.d.ts +0 -15
  21. package/dist/src/schema/data-lock.schema.js +0 -27
  22. package/dist/tsconfig.tsbuildinfo +0 -1
  23. package/src/classification-crypto.test.ts +0 -143
  24. package/src/classification-crypto.ts +0 -163
  25. package/src/classification-plugin.pglite.test.ts +0 -226
  26. package/src/classification-plugin.test.ts +0 -265
  27. package/src/classification-plugin.ts +0 -249
  28. package/src/coercion-plugin.test.ts +0 -160
  29. package/src/coercion-plugin.ts +0 -187
  30. package/src/create-audited-kysely.test.ts +0 -560
  31. package/src/create-audited-kysely.ts +0 -254
  32. package/src/index.ts +0 -81
  33. package/src/kysely-agent-run-scores.ts +0 -47
  34. package/src/kysely-agent-run-service.ts +0 -244
  35. package/src/kysely-agent-run-state-service.ts +0 -200
  36. package/src/kysely-agent-storage-service.ts +0 -632
  37. package/src/kysely-audit-service.test.ts +0 -352
  38. package/src/kysely-audit-service.ts +0 -237
  39. package/src/kysely-channel-store.ts +0 -107
  40. package/src/kysely-credential-service.ts +0 -286
  41. package/src/kysely-deployment-service.ts +0 -163
  42. package/src/kysely-eventhub-store.ts +0 -53
  43. package/src/kysely-json.ts +0 -5
  44. package/src/kysely-scope-service.test.ts +0 -418
  45. package/src/kysely-scope-service.ts +0 -425
  46. package/src/kysely-secret-service.ts +0 -242
  47. package/src/kysely-services.test.ts +0 -405
  48. package/src/kysely-session-store.ts +0 -57
  49. package/src/kysely-tables.test.ts +0 -110
  50. package/src/kysely-tables.ts +0 -434
  51. package/src/kysely-virtual-user-run-store.test.ts +0 -369
  52. package/src/kysely-virtual-user-run-store.ts +0 -235
  53. package/src/kysely-virtual-user-schedule-store.test.ts +0 -187
  54. package/src/kysely-virtual-user-schedule-store.ts +0 -187
  55. package/src/kysely-webhook-service.test.ts +0 -153
  56. package/src/kysely-webhook-service.ts +0 -139
  57. package/src/kysely-workflow-mirror.test.ts +0 -259
  58. package/src/kysely-workflow-mirror.ts +0 -374
  59. package/src/kysely-workflow-run-service.ts +0 -305
  60. package/src/kysely-workflow-service.test.ts +0 -544
  61. package/src/kysely-workflow-service.ts +0 -820
  62. package/src/schema/agent.schema.ts +0 -162
  63. package/src/schema/audit.schema.ts +0 -57
  64. package/src/schema/channel.schema.ts +0 -38
  65. package/src/schema/credential.schema.ts +0 -58
  66. package/src/schema/deployment.schema.ts +0 -47
  67. package/src/schema/index.ts +0 -441
  68. package/src/schema/pikku-schema.types.ts +0 -169
  69. package/src/schema/schema.test.ts +0 -591
  70. package/src/schema/scope.schema.ts +0 -110
  71. package/src/schema/secret.schema.ts +0 -49
  72. package/src/schema/session.schema.ts +0 -20
  73. package/src/schema/virtual-user-schedule.schema.ts +0 -48
  74. package/src/schema/virtual-user.schema.ts +0 -106
  75. package/src/schema/webhook.schema.ts +0 -58
  76. package/src/schema/workflow.schema.ts +0 -150
  77. package/src/serialize-plugin.ts +0 -233
  78. package/tsconfig.json +0 -17
@@ -222,6 +222,29 @@ export interface PikkuRolesTable {
222
222
  declared: Generated<boolean>;
223
223
  createdAt: Generated<Date>;
224
224
  }
225
+ /** A feature flag's operator state. What code declared lives beside it. */
226
+ export interface PikkuFeatureFlagsTable {
227
+ name: string;
228
+ description: string | null;
229
+ /** The declared `anyOf` scope list as JSON, or null for a pure switch. */
230
+ anyOf: string | null;
231
+ enabled: Generated<boolean>;
232
+ rolloutPercent: number | null;
233
+ /** False for a flag whose declaration has gone: switchable, but not offered. */
234
+ declared: Generated<boolean>;
235
+ updatedBy: string | null;
236
+ note: string | null;
237
+ updatedAt: Generated<Date>;
238
+ }
239
+ /** One subject's short-circuit, in either direction, over a flag's rollout. */
240
+ export interface PikkuFeatureFlagOverridesTable {
241
+ flag: string;
242
+ subjectId: string;
243
+ subjectKind: string;
244
+ enabled: boolean;
245
+ grantedBy: string | null;
246
+ grantedAt: Generated<Date>;
247
+ }
225
248
  export interface PikkuRoleScopesTable {
226
249
  role: string;
227
250
  scope: string;
@@ -317,6 +340,25 @@ export interface AuditTable {
317
340
  old: string | null;
318
341
  data: string | null;
319
342
  }
343
+ /** One analytics event, as {@link KyselyAnalyticsService} appended it. */
344
+ export interface PikkuAnalyticsEventsTable {
345
+ eventId: string;
346
+ name: string;
347
+ /** ISO 8601, because string ordering is chronological ordering. */
348
+ occurredAt: string;
349
+ source: string;
350
+ functionId: string | null;
351
+ wireType: string | null;
352
+ traceId: string | null;
353
+ userId: string | null;
354
+ orgId: string | null;
355
+ pikkuUserId: string | null;
356
+ /** The device id for a visitor with no session yet. */
357
+ anonymousId: string | null;
358
+ vendorIds: string | null;
359
+ consent: string | null;
360
+ props: string | null;
361
+ }
320
362
  /**
321
363
  * One turn of a run's transcript.
322
364
  *
@@ -366,6 +408,8 @@ export interface KyselyPikkuDB {
366
408
  pikkuRoleScopes: PikkuRoleScopesTable;
367
409
  pikkuUserRole: PikkuUserRoleTable;
368
410
  pikkuUserScope: PikkuUserScopeTable;
411
+ pikkuFeatureFlags: PikkuFeatureFlagsTable;
412
+ pikkuFeatureFlagOverrides: PikkuFeatureFlagOverridesTable;
369
413
  channels: ChannelsTable;
370
414
  channelSubscriptions: ChannelSubscriptionsTable;
371
415
  workflowRuns: WorkflowRunsTable;
@@ -393,4 +437,5 @@ export interface KyselyPikkuDB {
393
437
  virtualUserRunStep: VirtualUserRunStepTable;
394
438
  virtualUserSchedule: VirtualUserScheduleTable;
395
439
  audit: AuditTable;
440
+ pikkuAnalyticsEvents: PikkuAnalyticsEventsTable;
396
441
  }
@@ -0,0 +1,17 @@
1
+ import type { PikkuSchema } from './pikku-schema.types.js';
2
+ /**
3
+ * The `pikkuAnalyticsEvents` table {@link KyselyAnalyticsService} appends to.
4
+ *
5
+ * Gated by `ownedBy` so it arrives only for a project that declares events: a
6
+ * database that measures nothing should not carry a table for it.
7
+ *
8
+ * Every column is text, and `occurredAt` is an ISO 8601 string rather than a
9
+ * timestamp, for the same reason the audit trail is: a warehouse loader reading
10
+ * these rows has no shared type to agree on, and string ordering of ISO 8601 is
11
+ * chronological ordering.
12
+ *
13
+ * The identity is spread across columns rather than left inside the JSON
14
+ * because it is what every query groups by — one visitor's funnel, one org's
15
+ * usage — while `props` differs per event and is only ever read back whole.
16
+ */
17
+ export declare const analyticsSchema: PikkuSchema;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * The `pikkuAnalyticsEvents` table {@link KyselyAnalyticsService} appends to.
3
+ *
4
+ * Gated by `ownedBy` so it arrives only for a project that declares events: a
5
+ * database that measures nothing should not carry a table for it.
6
+ *
7
+ * Every column is text, and `occurredAt` is an ISO 8601 string rather than a
8
+ * timestamp, for the same reason the audit trail is: a warehouse loader reading
9
+ * these rows has no shared type to agree on, and string ordering of ISO 8601 is
10
+ * chronological ordering.
11
+ *
12
+ * The identity is spread across columns rather than left inside the JSON
13
+ * because it is what every query groups by — one visitor's funnel, one org's
14
+ * usage — while `props` differs per event and is only ever read back whole.
15
+ */
16
+ export const analyticsSchema = {
17
+ name: 'analytics',
18
+ ownedBy: ['analyticsService'],
19
+ statements: [
20
+ (db) => db.schema
21
+ .createTable('pikkuAnalyticsEvents')
22
+ .addColumn('eventId', 'text', (col) => col.primaryKey())
23
+ .addColumn('name', 'text', (col) => col.notNull())
24
+ .addColumn('occurredAt', 'text', (col) => col.notNull())
25
+ .addColumn('source', 'text', (col) => col.notNull())
26
+ .addColumn('functionId', 'text')
27
+ .addColumn('wireType', 'text')
28
+ .addColumn('traceId', 'text')
29
+ .addColumn('userId', 'text')
30
+ .addColumn('orgId', 'text')
31
+ .addColumn('pikkuUserId', 'text')
32
+ // The device id for a visitor with no session. Its own column beside
33
+ // `userId` and not folded into it: the whole point of the anonymous id
34
+ // is the traffic that has no user to be attributed to yet.
35
+ .addColumn('anonymousId', 'text')
36
+ .addColumn('vendorIds', 'text')
37
+ .addColumn('consent', 'text')
38
+ .addColumn('props', 'text'),
39
+ (db) => db.schema
40
+ .createIndex('idx_pikku_analytics_events_occurred_at')
41
+ .on('pikkuAnalyticsEvents')
42
+ .column('occurredAt'),
43
+ (db) => db.schema
44
+ .createIndex('idx_pikku_analytics_events_name')
45
+ .on('pikkuAnalyticsEvents')
46
+ .column('name'),
47
+ ],
48
+ };
@@ -0,0 +1,16 @@
1
+ import type { PikkuSchema } from './pikku-schema.types.js';
2
+ /**
3
+ * Feature flags and their per-subject overrides.
4
+ *
5
+ * Two tables and not one because the two halves have different cardinality: a
6
+ * flag is one row an operator flips, an override is an unbounded set keyed by
7
+ * organization or user. Folding the overrides into a JSON column on the flag
8
+ * would make granting one tenant early access a read-modify-write of a blob two
9
+ * consoles can race on.
10
+ *
11
+ * Nothing here references `user` or any organization table. A subject id is
12
+ * stored as opaque text on purpose: pikku does not own the organization
13
+ * concept, and a foreign key would make flags require a table shape that only
14
+ * some projects have.
15
+ */
16
+ export declare const flagSchema: PikkuSchema;
@@ -0,0 +1,62 @@
1
+ import { sql } from 'kysely';
2
+ /**
3
+ * Feature flags and their per-subject overrides.
4
+ *
5
+ * Two tables and not one because the two halves have different cardinality: a
6
+ * flag is one row an operator flips, an override is an unbounded set keyed by
7
+ * organization or user. Folding the overrides into a JSON column on the flag
8
+ * would make granting one tenant early access a read-modify-write of a blob two
9
+ * consoles can race on.
10
+ *
11
+ * Nothing here references `user` or any organization table. A subject id is
12
+ * stored as opaque text on purpose: pikku does not own the organization
13
+ * concept, and a foreign key would make flags require a table shape that only
14
+ * some projects have.
15
+ */
16
+ export const flagSchema = {
17
+ name: 'flag',
18
+ ownedBy: ['featureFlags'],
19
+ statements: [
20
+ (db) => db.schema
21
+ .createTable('pikkuFeatureFlags')
22
+ .addColumn('name', 'text', (col) => col.primaryKey())
23
+ .addColumn('description', 'text')
24
+ // The declared `anyOf` scope list, as JSON. Read by the console to say
25
+ // who would see the feature; never read on the request path, where the
26
+ // capability half comes from the session's own scopes.
27
+ .addColumn('anyOf', 'text')
28
+ // Off until somebody turns it on. A flag exists so a feature can ship
29
+ // dark, so the row a deploy creates has to start closed — the opposite
30
+ // default would make merging the declaration the moment of launch.
31
+ .addColumn('enabled', 'boolean', (col) => col.defaultTo(false).notNull())
32
+ // 0-100, or null for no rollout constraint. Null and 100 are not the
33
+ // same statement: null says the percentage is not part of this flag's
34
+ // story, 100 says a rollout reached everyone and could be wound back.
35
+ .addColumn('rolloutPercent', 'integer')
36
+ // False once the declaration has gone from code: still switchable, no
37
+ // longer offered, awaiting `pikku flags prune`. The same additive
38
+ // contract as `pikkuScopes.declared`.
39
+ .addColumn('declared', 'boolean', (col) => col.defaultTo(true).notNull())
40
+ .addColumn('updatedBy', 'text')
41
+ // Why it was flipped. A kill switch is pulled during an incident, and
42
+ // the one question afterwards is always who turned it off and why.
43
+ .addColumn('note', 'text')
44
+ .addColumn('updatedAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull()),
45
+ (db) => db.schema
46
+ .createTable('pikkuFeatureFlagOverrides')
47
+ .addColumn('flag', 'text', (col) => col.notNull().references('pikkuFeatureFlags.name').onDelete('cascade'))
48
+ .addColumn('subjectId', 'text', (col) => col.notNull())
49
+ // Which id this is, for the console to render. Resolution keys on the
50
+ // id alone, because that is all the resolver has: it prefers the
51
+ // organization and falls back to the user, and by then the two are one
52
+ // string.
53
+ .addColumn('subjectKind', 'text', (col) => col.notNull())
54
+ .addColumn('enabled', 'boolean', (col) => col.notNull())
55
+ .addColumn('grantedBy', 'text')
56
+ .addColumn('grantedAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull())
57
+ .addPrimaryKeyConstraint('pikku_feature_flag_overrides_pk', [
58
+ 'flag',
59
+ 'subjectId',
60
+ ]),
61
+ ],
62
+ };
@@ -18,10 +18,12 @@ export { rawStatement, requiredType, schemaContext, unqualifiedContext, } from '
18
18
  */
19
19
  export declare const pikkuSchemas: PikkuSchema[];
20
20
  export { agentSchema } from './agent.schema.js';
21
+ export { analyticsSchema } from './analytics.schema.js';
21
22
  export { auditSchema } from './audit.schema.js';
22
23
  export { channelSchema } from './channel.schema.js';
23
24
  export { credentialSchema } from './credential.schema.js';
24
25
  export { deploymentSchema } from './deployment.schema.js';
26
+ export { flagSchema } from './flag.schema.js';
25
27
  export { scopeSchema } from './scope.schema.js';
26
28
  export { secretSchema } from './secret.schema.js';
27
29
  export { sessionSchema } from './session.schema.js';
@@ -1,11 +1,13 @@
1
1
  import { CamelCasePlugin } from 'kysely';
2
2
  import { schemaContext, } from './pikku-schema.types.js';
3
3
  import { agentSchema } from './agent.schema.js';
4
+ import { analyticsSchema } from './analytics.schema.js';
4
5
  import { auditSchema } from './audit.schema.js';
5
6
  import { channelSchema } from './channel.schema.js';
6
7
  import { credentialSchema } from './credential.schema.js';
7
8
  import { deploymentSchema } from './deployment.schema.js';
8
9
  import { scopeSchema } from './scope.schema.js';
10
+ import { flagSchema } from './flag.schema.js';
9
11
  import { secretSchema } from './secret.schema.js';
10
12
  import { sessionSchema } from './session.schema.js';
11
13
  import { webhookSchema } from './webhook.schema.js';
@@ -37,15 +39,19 @@ export const pikkuSchemas = [
37
39
  workflowSchema,
38
40
  agentSchema,
39
41
  scopeSchema,
42
+ flagSchema,
40
43
  auditSchema,
44
+ analyticsSchema,
41
45
  virtualUserSchema,
42
46
  virtualUserScheduleSchema,
43
47
  ];
44
48
  export { agentSchema } from './agent.schema.js';
49
+ export { analyticsSchema } from './analytics.schema.js';
45
50
  export { auditSchema } from './audit.schema.js';
46
51
  export { channelSchema } from './channel.schema.js';
47
52
  export { credentialSchema } from './credential.schema.js';
48
53
  export { deploymentSchema } from './deployment.schema.js';
54
+ export { flagSchema } from './flag.schema.js';
49
55
  export { scopeSchema } from './scope.schema.js';
50
56
  export { secretSchema } from './secret.schema.js';
51
57
  export { sessionSchema } from './session.schema.js';
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@pikku/kysely",
3
- "version": "0.13.24",
3
+ "version": "0.13.27",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/pikkujs/pikku.git",
7
+ "directory": "packages/services/kysely"
8
+ },
4
9
  "description": "Kysely-backed Pikku services — workflow state, secrets, sessions, scopes and audit",
5
10
  "author": "yasser.fadl@gmail.com",
6
11
  "license": "MIT",
@@ -18,23 +23,23 @@
18
23
  "test:watch": "bash run-tests.sh --watch",
19
24
  "test:coverage": "bash run-tests.sh --coverage",
20
25
  "release": "npm run build && npm test",
21
- "prepublishOnly": "yarn build"
26
+ "prepublishOnly": "bun run build"
22
27
  },
23
28
  "peerDependencies": {
24
- "@pikku/core": "^0.12.104"
29
+ "@pikku/core": "^0.12.111"
25
30
  },
26
31
  "dependencies": {
27
32
  "kysely": "^0.29.5"
28
33
  },
29
34
  "devDependencies": {
35
+ "@electric-sql/pglite": "^0.5.5",
30
36
  "@types/better-sqlite3": "^9.6.0",
31
37
  "better-sqlite3": "^13.0.3",
32
38
  "kysely-codegen": "^0.20.0",
33
39
  "kysely-postgres-js": "^3.0.0",
34
- "postgres": "^3.4.9",
35
- "typescript": "^6.0.3"
40
+ "postgres": "^3.4.9"
36
41
  },
37
42
  "exports": {
38
43
  ".": "./dist/src/index.js"
39
44
  }
40
- }
45
+ }
package/run-tests.sh CHANGED
@@ -34,10 +34,17 @@ files=($(find src -type f -name "*.test.ts"))
34
34
  # Check if any files matched the pattern
35
35
  if [ ${#files[@]} -eq 0 ]; then
36
36
  echo "No test files found matching pattern: $pattern"
37
+ if [ "${STRICT_TEST_DISCOVERY}" = "1" ]; then
38
+ exit 1
39
+ fi
37
40
  exit 0
38
41
  fi
39
42
 
40
43
  # Construct the node command
44
+ #
45
+ # Every other package runs its tests on bun. This one stays on node because
46
+ # better-sqlite3, which every one of these suites opens its fixture database
47
+ # with, aborts the bun process on load.
41
48
  node_cmd=(node --import tsx --test)
42
49
 
43
50
  # Append options based on flags
@@ -47,7 +54,8 @@ fi
47
54
 
48
55
  if [ "$coverage_mode" = true ]; then
49
56
  node_cmd+=(--test-coverage-include="src/**/*.{ts,js}" --test-coverage-exclude="**/dist/**" --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info)
57
+ export PIKKU_TEST_COVERAGE=1
50
58
  fi
51
59
 
52
60
  # Execute the node command with the expanded list of files
53
- "${node_cmd[@]}" "${files[@]}"
61
+ "${node_cmd[@]}" "${files[@]}"
@@ -1,59 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { mkdir, readFile, writeFile } from 'fs/promises'
4
- import { dirname } from 'path'
5
- import { fileURLToPath } from 'url'
6
-
7
- const __filename = fileURLToPath(import.meta.url)
8
- const __dirname = dirname(__filename)
9
-
10
- const main = async (output: string, input?: string) => {
11
- let execDirectory = process.cwd()
12
- let inputPath = `${__dirname}/../node_modules/kysely-codegen/dist/db.d.ts`
13
- if (input) {
14
- inputPath = `${execDirectory}/${input}`
15
- }
16
-
17
- let kysely: string
18
- try {
19
- kysely = await readFile(inputPath, 'utf8')
20
- } catch {
21
- console.error(`Could not read file ${input}`)
22
- process.exit(1)
23
- }
24
-
25
- kysely = kysely
26
- .replace('import type { ColumnType } from "kysely";', '')
27
- .replace(
28
- new RegExp('^export\\s+type\\s+Generated<[^>]+>.*?;\\s*$', 'ms'),
29
- ''
30
- )
31
- .replace(/Generated<(.*)>/g, '$1')
32
- .replace(
33
- /export type Numeric =.*;/,
34
- 'export type Numeric = string | number'
35
- )
36
- .replace(/export type Timestamp =.*;/, 'export type Timestamp = Date')
37
- .replace(
38
- /export type ArrayType<T> = ArrayTypeImpl<T> extends \(infer U\)\[\]\s*\?\s*U\[\]\s*:\s*ArrayTypeImpl<T>;\s*export type ArrayTypeImpl<T> = T extends ColumnType<infer S, infer I, infer U>\s*\?\s*ColumnType<S\[\], I\[\], U\[\]>\s*:\s*T\[\];/gs,
39
- ''
40
- )
41
- .replace(/ArrayType<([^>]+)>/g, '$1[]')
42
-
43
- console.log('Writing kysely-pure.gen.ts')
44
-
45
- const outputPath = `${execDirectory}/${output}`
46
- const outputDirectory = dirname(outputPath)
47
- await mkdir(outputDirectory, { recursive: true })
48
- await writeFile(outputPath, kysely, 'utf-8')
49
- console.log(outputPath)
50
- }
51
-
52
- if (process.argv.length !== 4) {
53
- console.error('Expected the following usage:')
54
- console.error('node kysely-pure.js <output> <input>')
55
- process.exit(1)
56
- }
57
-
58
- const [, , output, input] = process.argv
59
- main(output!, input)
@@ -1,34 +0,0 @@
1
- import type { Kysely } from 'kysely';
2
- import type { LockRecord, LockVault } from '@pikku/core/classification';
3
- import type { KyselyPikkuDB } from './kysely-tables.js';
4
- /**
5
- * Lock records kept in the application's own database.
6
- *
7
- * This is what makes a sealed column survive a restart. `createMemoryLockVault`
8
- * is fine for a test, but its salts die with the process and a sealed row whose
9
- * salt is gone is not recoverable by anyone — so a real deployment stores them
10
- * next to the rows they protect, in the same backup and the same restore.
11
- *
12
- * The table holds no plaintext key material, only salts and verifiers, which is
13
- * why it is readable while the store is locked.
14
- */
15
- export declare class KyselyLockVault implements LockVault {
16
- private readonly db;
17
- constructor(db: Kysely<KyselyPikkuDB>);
18
- read(): Promise<LockRecord[]>;
19
- /**
20
- * Replace the whole record set.
21
- *
22
- * Whole-set rather than per-row because the callers that write are
23
- * initialization and rotation, and both produce a complete keyring: applying
24
- * one as a partial update would leave records from the previous key behind,
25
- * and a stale salt is indistinguishable from a current one.
26
- */
27
- write(records: LockRecord[]): Promise<void>;
28
- /**
29
- * Asked through introspection rather than by matching the driver's error
30
- * text, so the answer does not depend on which engine phrased the failure or
31
- * on the language it phrased it in.
32
- */
33
- private tableExists;
34
- }
@@ -1,77 +0,0 @@
1
- import { unsafeAsWrapped } from '@pikku/core/classification';
2
- /**
3
- * Lock records kept in the application's own database.
4
- *
5
- * This is what makes a sealed column survive a restart. `createMemoryLockVault`
6
- * is fine for a test, but its salts die with the process and a sealed row whose
7
- * salt is gone is not recoverable by anyone — so a real deployment stores them
8
- * next to the rows they protect, in the same backup and the same restore.
9
- *
10
- * The table holds no plaintext key material, only salts and verifiers, which is
11
- * why it is readable while the store is locked.
12
- */
13
- export class KyselyLockVault {
14
- db;
15
- constructor(db) {
16
- this.db = db;
17
- }
18
- async read() {
19
- try {
20
- const rows = await this.db
21
- .selectFrom('dataLocks')
22
- .select(['keyId', 'keyVersion', 'salt', 'verifier'])
23
- .execute();
24
- return rows.map((row) => ({
25
- keyId: row.keyId,
26
- // SQLite hands back whatever it stored and postgres can widen an
27
- // integer to a string over the wire, while `keyVersion` is compared
28
- // and stamped as a number.
29
- keyVersion: Number(row.keyVersion),
30
- salt: row.salt,
31
- verifier: unsafeAsWrapped(row.verifier),
32
- }));
33
- }
34
- catch (cause) {
35
- if (await this.tableExists()) {
36
- throw cause;
37
- }
38
- // No table yet is a legitimate first run — the migration has not been
39
- // applied, nobody has chosen a passphrase, and the server should come up
40
- // and offer to initialize rather than refuse to start.
41
- return [];
42
- }
43
- }
44
- /**
45
- * Replace the whole record set.
46
- *
47
- * Whole-set rather than per-row because the callers that write are
48
- * initialization and rotation, and both produce a complete keyring: applying
49
- * one as a partial update would leave records from the previous key behind,
50
- * and a stale salt is indistinguishable from a current one.
51
- */
52
- async write(records) {
53
- await this.db.transaction().execute(async (trx) => {
54
- await trx.deleteFrom('dataLocks').execute();
55
- if (records.length) {
56
- await trx
57
- .insertInto('dataLocks')
58
- .values(records.map((record) => ({
59
- keyId: record.keyId,
60
- keyVersion: record.keyVersion,
61
- salt: record.salt,
62
- verifier: record.verifier,
63
- })))
64
- .execute();
65
- }
66
- });
67
- }
68
- /**
69
- * Asked through introspection rather than by matching the driver's error
70
- * text, so the answer does not depend on which engine phrased the failure or
71
- * on the language it phrased it in.
72
- */
73
- async tableExists() {
74
- const tables = await this.db.introspection.getTables();
75
- return tables.some((table) => table.name === 'data_locks');
76
- }
77
- }
@@ -1,15 +0,0 @@
1
- import type { PikkuSchema } from './pikku-schema.types.js';
2
- /**
3
- * Where the passphrase's salts and verifiers live.
4
- *
5
- * Deliberately the one table in a classified deployment that holds nothing
6
- * encrypted. It has to be readable before anything has been unlocked — a store
7
- * that needed its own key to find out how to open itself could never be opened
8
- * — and that costs nothing, because a salt and a verifier reveal no key
9
- * material.
10
- *
11
- * Losing this table is losing the data: every sealed row is opened by a KEK
12
- * derived from the salt kept here, so a deployment that ships without the
13
- * migration silently turns its own rows into noise the first time it restarts.
14
- */
15
- export declare const dataLockSchema: PikkuSchema;
@@ -1,27 +0,0 @@
1
- import { sql } from 'kysely';
2
- /**
3
- * Where the passphrase's salts and verifiers live.
4
- *
5
- * Deliberately the one table in a classified deployment that holds nothing
6
- * encrypted. It has to be readable before anything has been unlocked — a store
7
- * that needed its own key to find out how to open itself could never be opened
8
- * — and that costs nothing, because a salt and a verifier reveal no key
9
- * material.
10
- *
11
- * Losing this table is losing the data: every sealed row is opened by a KEK
12
- * derived from the salt kept here, so a deployment that ships without the
13
- * migration silently turns its own rows into noise the first time it restarts.
14
- */
15
- export const dataLockSchema = {
16
- name: 'data-lock',
17
- ownedBy: ['dataLock'],
18
- statements: [
19
- (db) => db.schema
20
- .createTable('dataLocks')
21
- .addColumn('keyId', 'varchar(255)', (col) => col.primaryKey())
22
- .addColumn('keyVersion', 'integer', (col) => col.notNull())
23
- .addColumn('salt', 'varchar(64)', (col) => col.notNull())
24
- .addColumn('verifier', 'text', (col) => col.notNull())
25
- .addColumn('createdAt', 'timestamp', (col) => col.defaultTo(sql `CURRENT_TIMESTAMP`).notNull()),
26
- ],
27
- };
@@ -1 +0,0 @@
1
- {"root":["../src/classification-crypto.ts","../src/classification-plugin.ts","../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"}