@cosmicdrift/kumiko-framework 0.292.0 → 0.294.0

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 (46) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/schema-cli-rebuild.integration.test.ts +0 -2
  3. package/src/changes.json +12 -0
  4. package/src/crypto/__tests__/kek-source.test.ts +143 -0
  5. package/src/crypto/kek-source.ts +34 -43
  6. package/src/db/__tests__/apply-entity-event-tenant.integration.test.ts +0 -2
  7. package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +1 -2
  8. package/src/db/__tests__/blind-index.integration.test.ts +0 -2
  9. package/src/db/__tests__/entity-field-encryption.test.ts +29 -0
  10. package/src/db/__tests__/event-store-executor-list.integration.test.ts +0 -2
  11. package/src/db/__tests__/event-store-executor-money-rehydrate.integration.test.ts +0 -2
  12. package/src/db/__tests__/event-store-executor-reference-search.integration.test.ts +0 -2
  13. package/src/db/__tests__/event-store-executor-reference-sort.integration.test.ts +0 -2
  14. package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +0 -2
  15. package/src/db/__tests__/event-store-executor.integration.test.ts +1 -2
  16. package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +0 -2
  17. package/src/db/__tests__/unique-violation-mapping.integration.test.ts +0 -2
  18. package/src/db/entity-field-encryption.ts +5 -3
  19. package/src/db/queries/__tests__/event-store-idempotency-index.integration.test.ts +0 -2
  20. package/src/engine/boot-validator/index.ts +5 -1
  21. package/src/entrypoint/__tests__/entrypoint-attach-dispatcher.integration.test.ts +1 -2
  22. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +1 -2
  23. package/src/entrypoint/__tests__/split-deploy.integration.test.ts +1 -2
  24. package/src/env/__tests__/compose-env-schema.test.ts +97 -1
  25. package/src/env/index.ts +63 -3
  26. package/src/es-ops/__tests__/context.integration.test.ts +0 -2
  27. package/src/es-ops/__tests__/runner.integration.test.ts +0 -2
  28. package/src/event-store/__tests__/admin-api.integration.test.ts +1 -2
  29. package/src/event-store/__tests__/backfill-pii.integration.test.ts +0 -2
  30. package/src/event-store/__tests__/event-store.integration.test.ts +0 -2
  31. package/src/event-store/__tests__/get-stream-version-perf.integration.test.ts +1 -2
  32. package/src/event-store/__tests__/perf.integration.test.ts +1 -8
  33. package/src/event-store/__tests__/provenance-append.integration.test.ts +1 -2
  34. package/src/event-store/__tests__/snapshot.integration.test.ts +0 -2
  35. package/src/event-store/__tests__/upcaster-dead-letter.integration.test.ts +1 -2
  36. package/src/event-store/__tests__/upcaster.integration.test.ts +1 -2
  37. package/src/files/__tests__/files.integration.test.ts +1 -2
  38. package/src/jobs/__tests__/job-write-as.integration.test.ts +1 -2
  39. package/src/migrations/__tests__/pending-rebuilds.integration.test.ts +0 -2
  40. package/src/pipeline/__tests__/blind-index-rebuild-guard.integration.test.ts +0 -2
  41. package/src/pipeline/__tests__/cascade-handler.integration.test.ts +0 -2
  42. package/src/pipeline/__tests__/perf-rebuild.integration.test.ts +0 -2
  43. package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +1 -2
  44. package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +1 -2
  45. package/src/search/__tests__/reindex-entity.integration.test.ts +0 -2
  46. package/src/search/__tests__/search-pii-derived-index.integration.test.ts +0 -2
@@ -14,7 +14,7 @@ import { z } from "zod";
14
14
  import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
15
15
  import { asRawClient } from "../../db/query";
16
16
  import { createRegistry, defineFeature } from "../../engine";
17
- import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
17
+ import { createArchivedStreamsTable } from "../../event-store";
18
18
  import { createJobRunner } from "../../jobs/job-runner";
19
19
  import { createEventConsumerStateTable } from "../../pipeline";
20
20
  import { createTestRedis, type TestRedis } from "../../stack";
@@ -63,7 +63,6 @@ let testRedis: TestRedis;
63
63
 
64
64
  beforeAll(async () => {
65
65
  [testDb, testRedis] = await Promise.all([createTestDb(), createTestRedis()]);
66
- await createEventsTable(testDb.db);
67
66
  await createArchivedStreamsTable(testDb.db);
68
67
  await createEventConsumerStateTable(testDb.db);
69
68
  });
@@ -16,7 +16,7 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
16
16
  import { z } from "zod";
17
17
  import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
18
18
  import { createRegistry, defineFeature } from "../../engine";
19
- import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
19
+ import { createArchivedStreamsTable } from "../../event-store";
20
20
  import { createNoopProvider, createPrometheusMeter } from "../../observability";
21
21
  import { createEventConsumerStateTable } from "../../pipeline";
22
22
  import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
@@ -79,7 +79,6 @@ let testRedis: TestRedis;
79
79
 
80
80
  beforeAll(async () => {
81
81
  [testDb, testRedis] = await Promise.all([createTestDb(), createTestRedis()]);
82
- await createEventsTable(testDb.db);
83
82
  await createArchivedStreamsTable(testDb.db);
84
83
  await createEventConsumerStateTable(testDb.db);
85
84
  });
@@ -17,7 +17,7 @@ import { z } from "zod";
17
17
  import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
18
18
  import { asRawClient } from "../../db/query";
19
19
  import { createRegistry, defineFeature } from "../../engine";
20
- import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
20
+ import { createArchivedStreamsTable } from "../../event-store";
21
21
  import { createEventConsumerStateTable } from "../../pipeline";
22
22
  import { createTestRedis, type TestRedis, TestUsers } from "../../stack";
23
23
  import { createAllInOneEntrypoint, createApiEntrypoint, createWorkerEntrypoint } from "../index";
@@ -125,7 +125,6 @@ let testRedis: TestRedis;
125
125
 
126
126
  beforeAll(async () => {
127
127
  [testDb, testRedis] = await Promise.all([createTestDb(), createTestRedis()]);
128
- await createEventsTable(testDb.db);
129
128
  await createArchivedStreamsTable(testDb.db);
130
129
  await createEventConsumerStateTable(testDb.db);
131
130
  });
@@ -1,7 +1,15 @@
1
1
  import { describe, expect, it } from "bun:test";
2
2
  import { z } from "zod";
3
3
  import { defineFeature } from "../../engine/define-feature";
4
- import { camelCase, composeEnvSchema, KumikoBootError, parseEnv, pulumiConfigKey } from "../index";
4
+ import {
5
+ camelCase,
6
+ composeEnvSchema,
7
+ KumikoBootError,
8
+ kmsSlotsOf,
9
+ parseEnv,
10
+ pulumiConfigKey,
11
+ readKumikoMeta,
12
+ } from "../index";
5
13
 
6
14
  describe("composeEnvSchema", () => {
7
15
  it("merges per-feature schemas and tags sources", () => {
@@ -295,3 +303,91 @@ describe("pulumiConfigKey + camelCase", () => {
295
303
  expect(pulumiConfigKey("JWT_SECRET", undefined, undefined)).toBe("jwtSecret");
296
304
  });
297
305
  });
306
+
307
+ describe("kms env slots", () => {
308
+ const schema = z.object({
309
+ MASTER_KEY: z
310
+ .string()
311
+ .min(1)
312
+ .meta({ kumiko: { kms: true } }),
313
+ MASTER_KEY_CIPHERTEXT: z.string().min(1).optional(),
314
+ PLAIN: z.string().min(1).optional(),
315
+ });
316
+
317
+ it("kmsSlotsOf lists exactly the fields declaring kms", () => {
318
+ expect(kmsSlotsOf(schema)).toEqual(["MASTER_KEY"]);
319
+ });
320
+
321
+ it("rejects a non-true kms value in the meta", () => {
322
+ const bad = z.string().meta({ kumiko: { kms: "yes" } });
323
+ expect(readKumikoMeta(bad)).toEqual({});
324
+ });
325
+
326
+ it("parseEnv accepts a required kms slot that is present only as ciphertext", () => {
327
+ expect(() => parseEnv(schema, { MASTER_KEY_CIPHERTEXT: "abc" })).not.toThrow();
328
+ });
329
+
330
+ it("parseEnv still requires a kms slot when no ciphertext twin is set", () => {
331
+ expect(() => parseEnv(schema, {})).toThrow(KumikoBootError);
332
+ });
333
+
334
+ it("parseEnv still validates a kms slot that is set", () => {
335
+ expect(() => parseEnv(schema, { MASTER_KEY: "" })).toThrow(KumikoBootError);
336
+ });
337
+ });
338
+
339
+ describe("composeEnvSchema kms twins", () => {
340
+ const kmsField = z
341
+ .string()
342
+ .min(1)
343
+ .meta({ kumiko: { kms: true } });
344
+
345
+ it("declares the ciphertext twin of every kms field, owned by the same source", () => {
346
+ const secrets = defineFeature("secrets", (r) => {
347
+ r.envSchema(z.object({ KEY_V1: kmsField }));
348
+ });
349
+ const { schema, sources } = composeEnvSchema({
350
+ features: [secrets],
351
+ extend: z.object({ KEY_V2: kmsField }),
352
+ });
353
+
354
+ expect(Object.keys(schema.shape).sort()).toEqual([
355
+ "KEY_V1",
356
+ "KEY_V1_CIPHERTEXT",
357
+ "KEY_V2",
358
+ "KEY_V2_CIPHERTEXT",
359
+ ]);
360
+ expect(sources["KEY_V1_CIPHERTEXT"]).toBe("secrets");
361
+ expect(sources["KEY_V2_CIPHERTEXT"]).toBe("app");
362
+ expect(() => parseEnv(schema, {})).toThrow(KumikoBootError);
363
+ expect(() =>
364
+ parseEnv(schema, { KEY_V1_CIPHERTEXT: "a", KEY_V2_CIPHERTEXT: "b" }),
365
+ ).not.toThrow();
366
+ });
367
+
368
+ it("keeps a twin the app already declared instead of conflicting", () => {
369
+ const declared = z.string().min(1).optional().describe("hand-written twin");
370
+ const { schema } = composeEnvSchema({
371
+ features: [],
372
+ extend: z.object({ KEY: kmsField, KEY_CIPHERTEXT: declared }),
373
+ });
374
+
375
+ expect(schema.shape["KEY_CIPHERTEXT"]).toBe(declared);
376
+ });
377
+
378
+ it("adds the twin for a kms field of an optional feature too", () => {
379
+ const secrets = defineFeature("secrets", (r) => {
380
+ r.envSchema(z.object({ KEY: kmsField }));
381
+ });
382
+ const { schema } = composeEnvSchema({ features: [secrets], optionalFeatures: ["secrets"] });
383
+
384
+ expect(Object.keys(schema.shape).sort()).toEqual(["KEY", "KEY_CIPHERTEXT"]);
385
+ expect(kmsSlotsOf(schema)).toEqual(["KEY"]);
386
+ });
387
+
388
+ it("adds nothing for a schema without kms fields", () => {
389
+ const { schema } = composeEnvSchema({ features: [], extend: z.object({ PLAIN: z.string() }) });
390
+
391
+ expect(Object.keys(schema.shape)).toEqual(["PLAIN"]);
392
+ });
393
+ });
package/src/env/index.ts CHANGED
@@ -34,12 +34,18 @@ export type KumikoEnvMeta = {
34
34
  /** Force the `--secret` flag in `pulumi config set`. Default false. */
35
35
  readonly secret?: boolean;
36
36
  };
37
+ /** The value may be delivered as a Scaleway Key Manager ciphertext in
38
+ * `<NAME>_CIPHERTEXT`. `runProdApp` decrypts it at boot when `<NAME>` itself
39
+ * is unset; a set plaintext always wins. A flag, not an object: every slot
40
+ * uses the same `_CIPHERTEXT` twin and one shared key (`PLATFORM_KEK_KMS_*`). */
41
+ readonly kms?: true;
37
42
  };
38
43
 
39
44
  function isKumikoMeta(value: unknown): value is KumikoEnvMeta {
40
45
  if (value === null || typeof value !== "object") return false;
41
46
  // @cast-boundary schema-walk — runtime-shape narrowing of zod-meta payload
42
- const v = value as { pulumi?: unknown };
47
+ const v = value as { pulumi?: unknown; kms?: unknown };
48
+ if (v.kms !== undefined && v.kms !== true) return false;
43
49
  if (v.pulumi === undefined) return true;
44
50
  if (v.pulumi === null || typeof v.pulumi !== "object") return false;
45
51
  // @cast-boundary schema-walk
@@ -60,6 +66,31 @@ export function readKumikoMeta(field: z.ZodType): KumikoEnvMeta {
60
66
  return {};
61
67
  }
62
68
 
69
+ /** Env names whose schema field declares `kms: true` — the slots
70
+ * `resolvePlatformKeks` resolves from a ciphertext. */
71
+ export function kmsSlotsOf(schema: z.ZodObject<z.ZodRawShape>): readonly string[] {
72
+ return Object.entries(zodShape(schema))
73
+ .filter(([, field]) => readKumikoMeta(field).kms === true)
74
+ .map(([name]) => name);
75
+ }
76
+
77
+ // A ciphertext-only slot is satisfied by its `_CIPHERTEXT` twin: the plaintext
78
+ // only exists after the boot-time decrypt, so requiring it here would reject
79
+ // exactly the deployment this meta enables.
80
+ function relaxCiphertextOnlySlots<S extends z.ZodObject<z.ZodRawShape>>(
81
+ schema: S,
82
+ env: Readonly<Record<string, string>>,
83
+ ): z.ZodObject<z.ZodRawShape> {
84
+ const shape = zodShape(schema);
85
+ const relaxed: Record<string, z.ZodType> = {};
86
+ for (const name of kmsSlotsOf(schema)) {
87
+ const field = shape[name];
88
+ if (field && env[name] === undefined && env[`${name}_CIPHERTEXT`])
89
+ relaxed[name] = field.optional();
90
+ }
91
+ return Object.keys(relaxed).length === 0 ? schema : schema.extend(relaxed);
92
+ }
93
+
63
94
  // --- Field-classification helpers (Zod v4 introspection) ---
64
95
 
65
96
  export type EnvFieldClass = "required" | "optional" | "withDefault";
@@ -144,10 +175,23 @@ export type ComposedEnvSchema = {
144
175
  readonly sources: Readonly<Record<string, string>>;
145
176
  };
146
177
 
178
+ // `.optional()` returns a new Zod instance that drops `.meta()`, which would
179
+ // hide the slot from `kmsSlotsOf` on the composed schema.
180
+ function optionalKeepingKmsMeta(field: z.ZodType): z.ZodType {
181
+ const optional = field.optional();
182
+ return readKumikoMeta(field).kms === true
183
+ ? optional.meta({ kumiko: readKumikoMeta(field) })
184
+ : optional;
185
+ }
186
+
147
187
  export function composeEnvSchema(options: ComposeEnvSchemaOptions): ComposedEnvSchema {
148
188
  const optionalSet = new Set(options.optionalFeatures ?? []);
149
189
  const merged: Record<string, z.ZodType> = {};
150
190
  const sources: Record<string, string> = {};
191
+ const kmsFields: { readonly name: string; readonly source: string }[] = [];
192
+ const noteKms = (name: string, field: z.ZodType, source: string): void => {
193
+ if (readKumikoMeta(field).kms === true) kmsFields.push({ name, source });
194
+ };
151
195
 
152
196
  // Framework-core first so a feature that accidentally declares the same
153
197
  // var (e.g. PORT) gets a clear conflict error citing "framework-core"
@@ -159,6 +203,7 @@ export function composeEnvSchema(options: ComposeEnvSchemaOptions): ComposedEnvS
159
203
  for (const [key, field] of Object.entries(zodShape(options.core))) {
160
204
  merged[key] = field;
161
205
  sources[key] = "framework-core";
206
+ noteKms(key, field, "framework-core");
162
207
  }
163
208
  }
164
209
 
@@ -178,8 +223,9 @@ export function composeEnvSchema(options: ComposeEnvSchemaOptions): ComposedEnvS
178
223
  },
179
224
  ]);
180
225
  }
181
- merged[key] = wrap ? field.optional() : field;
226
+ merged[key] = wrap ? optionalKeepingKmsMeta(field) : field;
182
227
  sources[key] = feature.name;
228
+ noteKms(key, field, feature.name);
183
229
  }
184
230
  }
185
231
 
@@ -198,9 +244,23 @@ export function composeEnvSchema(options: ComposeEnvSchemaOptions): ComposedEnvS
198
244
  }
199
245
  merged[key] = field;
200
246
  sources[key] = "app";
247
+ noteKms(key, field, "app");
201
248
  }
202
249
  }
203
250
 
251
+ // The twin follows from the slot, so a versioned family (`…_V2`) needs no
252
+ // second declaration. An already declared twin (apps that predate this) stays.
253
+ for (const { name, source } of kmsFields) {
254
+ const twin = `${name}_CIPHERTEXT`;
255
+ if (merged[twin] !== undefined) continue;
256
+ merged[twin] = z
257
+ .string()
258
+ .min(1)
259
+ .optional()
260
+ .describe(`Key-Manager ciphertext of ${name}; used when ${name} is unset.`);
261
+ sources[twin] = source;
262
+ }
263
+
204
264
  return {
205
265
  schema: z.object(merged),
206
266
  sources,
@@ -282,7 +342,7 @@ export function parseEnv<S extends z.ZodObject<z.ZodRawShape>>(
282
342
  if (v !== undefined) cleaned[k] = v;
283
343
  }
284
344
 
285
- const result = schema.safeParse(cleaned);
345
+ const result = relaxCiphertextOnlySlots(schema, cleaned).safeParse(cleaned);
286
346
  if (result.success) {
287
347
  // @cast-boundary schema-walk — z.infer<S> erasure across safeParse result
288
348
  return result.data as z.infer<S>;
@@ -21,7 +21,6 @@ import { join } from "node:path";
21
21
  import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
22
22
  import { asRawClient, selectMany } from "../../db/query";
23
23
  import { createRegistry } from "../../engine";
24
- import { createEventsTable } from "../../event-store";
25
24
  import { createDispatcher, type Dispatcher } from "../../pipeline";
26
25
  import { ensureTemporalPolyfill } from "../../time/polyfill";
27
26
  import { createSeedMigrationContext } from "../context";
@@ -38,7 +37,6 @@ let dispatcher: Dispatcher;
38
37
  beforeAll(async () => {
39
38
  await ensureTemporalPolyfill();
40
39
  testDb = await createTestDb();
41
- await createEventsTable(testDb.db);
42
40
  await createEsOperationsTable(testDb.db);
43
41
 
44
42
  const registry = createRegistry([]);
@@ -29,7 +29,6 @@ import {
29
29
  SYSTEM_TENANT_ID,
30
30
  } from "../../engine";
31
31
  import { VersionConflictError } from "../../errors";
32
- import { createEventsTable } from "../../event-store";
33
32
  import { createDispatcher, type Dispatcher } from "../../pipeline";
34
33
  import { createTestDb, type TestDb, unsafeCreateEntityTable } from "../../stack";
35
34
  import { ensureTemporalPolyfill } from "../../time/polyfill";
@@ -89,7 +88,6 @@ let registry: ReturnType<typeof createRegistry>;
89
88
  beforeAll(async () => {
90
89
  await ensureTemporalPolyfill();
91
90
  testDb = await createTestDb();
92
- await createEventsTable(testDb.db);
93
91
  await createEsOperationsTable(testDb.db);
94
92
  await unsafeCreateEntityTable(testDb.db, probeEntity, "esops-probe");
95
93
  registry = createRegistry([seedTestFeature]);
@@ -17,7 +17,7 @@ import { generateId as uuid } from "../../utils";
17
17
  import { appendRaw, appendRawBatch, type RawEventToAppend } from "../admin-api";
18
18
  import { IdempotentAppendConflictError, VersionConflictError } from "../errors";
19
19
  import { append, loadAggregate } from "../event-store";
20
- import { createEventsTable, eventsTable } from "../events-schema";
20
+ import { eventsTable } from "../events-schema";
21
21
 
22
22
  // Test-only spy: wrap a DbConnection's `.unsafe()` to capture the SQL
23
23
  // string of every query the framework runs. Used to assert batching
@@ -47,7 +47,6 @@ const legacyUser = "legacy-user-42";
47
47
 
48
48
  beforeAll(async () => {
49
49
  testDb = await createTestDb();
50
- await createEventsTable(testDb.db);
51
50
  });
52
51
 
53
52
  afterAll(async () => {
@@ -34,7 +34,6 @@ import type { Registry, TenantId } from "../../engine/types";
34
34
  import { createTestDb, type TestDb, unsafeCreateEntityTable } from "../../stack";
35
35
  import { generateId } from "../../utils";
36
36
  import { append, loadAggregate } from "../event-store";
37
- import { createEventsTable } from "../events-schema";
38
37
 
39
38
  const TENANT = "00000000-0000-4000-8000-000000000001" as TenantId;
40
39
  const BIDX_KEY = Buffer.alloc(32, 5).toString("base64");
@@ -145,7 +144,6 @@ beforeAll(async () => {
145
144
  surveyFeature,
146
145
  signalsFeature,
147
146
  ]);
148
- await createEventsTable(testDb.db);
149
147
  await unsafeCreateEntityTable(testDb.db, contactEntity, "contact");
150
148
  await unsafeCreateEntityTable(testDb.db, noteEntity, "note");
151
149
  await unsafeCreateEntityTable(testDb.db, surveyEntity, "survey");
@@ -5,7 +5,6 @@ import { ensureTemporalPolyfill } from "../../time/polyfill";
5
5
  import { generateId as uuid } from "../../utils";
6
6
  import {
7
7
  append,
8
- createEventsTable,
9
8
  IdempotentAppendConflictError,
10
9
  loadAggregate,
11
10
  loadAggregateAsOf,
@@ -25,7 +24,6 @@ const userA = uuid();
25
24
  beforeAll(async () => {
26
25
  await ensureTemporalPolyfill();
27
26
  testDb = await createTestDb();
28
- await createEventsTable(testDb.db);
29
27
  });
30
28
 
31
29
  afterAll(async () => {
@@ -15,7 +15,7 @@ import { insertMany } from "../../bun-db/query";
15
15
  import type { TenantId } from "../../engine/types";
16
16
  import { ensureTemporalPolyfill } from "../../time/polyfill";
17
17
  import { generateId as uuid } from "../../utils";
18
- import { createEventsTable, eventsTable, getStreamVersion } from "../index";
18
+ import { eventsTable, getStreamVersion } from "../index";
19
19
 
20
20
  let testDb: BunTestDb;
21
21
  const tenantId: TenantId = uuid();
@@ -24,7 +24,6 @@ const userId = uuid();
24
24
  beforeAll(async () => {
25
25
  await ensureTemporalPolyfill();
26
26
  testDb = await createTestDb();
27
- await createEventsTable(testDb.db);
28
27
  });
29
28
 
30
29
  afterAll(async () => {
@@ -24,13 +24,7 @@ import { asRawClient } from "../../db/query";
24
24
  import type { TenantId } from "../../engine/types";
25
25
  import { ensureTemporalPolyfill } from "../../time/polyfill";
26
26
  import { generateId as uuid } from "../../utils";
27
- import {
28
- append,
29
- createEventsTable,
30
- loadAggregate,
31
- loadAggregateWithSnapshot,
32
- saveSnapshot,
33
- } from "../index";
27
+ import { append, loadAggregate, loadAggregateWithSnapshot, saveSnapshot } from "../index";
34
28
 
35
29
  let testDb: BunTestDb;
36
30
  const tenantId = uuid() as TenantId;
@@ -39,7 +33,6 @@ const userId = uuid();
39
33
  beforeAll(async () => {
40
34
  await ensureTemporalPolyfill();
41
35
  testDb = await createTestDb();
42
- await createEventsTable(testDb.db);
43
36
  });
44
37
 
45
38
  afterAll(async () => {
@@ -5,7 +5,7 @@ import { createTenantDb, type TenantDb } from "../../db/tenant-db";
5
5
  import { AccessDeniedError, InternalError } from "../../errors";
6
6
  import { ensureTemporalPolyfill } from "../../time/polyfill";
7
7
  import { generateId as uuid } from "../../utils";
8
- import { createEventsTable, loadAggregate, VersionConflictError } from "../index";
8
+ import { loadAggregate, VersionConflictError } from "../index";
9
9
  import { appendProvenanceEvent, type ProvenanceEventInput } from "../provenance-append";
10
10
 
11
11
  let testDb: BunTestDb;
@@ -37,7 +37,6 @@ function provenanceEvent(overrides: Partial<ProvenanceEventInput>): ProvenanceEv
37
37
  beforeAll(async () => {
38
38
  await ensureTemporalPolyfill();
39
39
  testDb = await createTestDb();
40
- await createEventsTable(testDb.db);
41
40
  tdb = createTenantDb(testDb.db, tenantA);
42
41
  });
43
42
 
@@ -22,7 +22,6 @@ import { generateId as uuid } from "../../utils";
22
22
  import {
23
23
  append,
24
24
  archiveStream,
25
- createEventsTable,
26
25
  loadAggregate,
27
26
  loadAggregateWithSnapshot,
28
27
  loadLatestSnapshot,
@@ -56,7 +55,6 @@ const reducer: SnapshotReducer<CounterState> = (state, event) => {
56
55
  beforeAll(async () => {
57
56
  await ensureTemporalPolyfill();
58
57
  bun = await createTestDb();
59
- await createEventsTable(bun.db);
60
58
  });
61
59
 
62
60
  afterAll(async () => {
@@ -11,7 +11,7 @@ import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db
11
11
  import { asRawClient, insertOne, selectMany } from "../../db/query";
12
12
  import { ensureTemporalPolyfill } from "../../time/polyfill";
13
13
  import type { StoredEvent } from "../event-store";
14
- import { createEventsTable, eventsTable } from "../events-schema";
14
+ import { eventsTable } from "../events-schema";
15
15
  import { type EventUpcasters, makeUpcastCtx, upcastStoredEvents } from "../upcaster";
16
16
  import {
17
17
  createUpcasterDeadLetterTable,
@@ -81,7 +81,6 @@ const passthroughUpcasters: EventUpcasters = new Map([
81
81
  beforeAll(async () => {
82
82
  await ensureTemporalPolyfill();
83
83
  testDb = await createTestDb();
84
- await createEventsTable(testDb.db);
85
84
  await createUpcasterDeadLetterTable(testDb.db);
86
85
  });
87
86
 
@@ -25,7 +25,7 @@ import {
25
25
  unsafeCreateEntityTable,
26
26
  unsafePushTables,
27
27
  } from "../../stack";
28
- import { append, createEventsTable } from "../index";
28
+ import { append } from "../index";
29
29
  import { upcastStoredEvent } from "../upcaster";
30
30
 
31
31
  // --- Fixture entity + projection table ---
@@ -113,7 +113,6 @@ const orderExecutor = createEventStoreExecutor(orderTable, orderEntity, {
113
113
  beforeAll(async () => {
114
114
  testDb = await createTestDb();
115
115
  await unsafeCreateEntityTable(testDb.db, orderEntity, "upcast-order");
116
- await createEventsTable(testDb.db);
117
116
  const { createProjectionStateTable } = await import("../../pipeline");
118
117
  await createProjectionStateTable(testDb.db);
119
118
  await unsafePushTables(testDb.db, { upcastOrderSummary: orderSummaryTable });
@@ -14,7 +14,7 @@ import {
14
14
  defineFeature,
15
15
  type SessionUser,
16
16
  } from "../../engine";
17
- import { createEventsTable, loadAggregate } from "../../event-store";
17
+ import { loadAggregate } from "../../event-store";
18
18
  import {
19
19
  createTestDb,
20
20
  createTestUser,
@@ -79,7 +79,6 @@ beforeAll(async () => {
79
79
  await unsafeCreateEntityTable(testDb.db, testTenantEntity);
80
80
  // Event-store table: the upload route appends files:event:uploaded in the
81
81
  // same tx as the FileRef insert. Without events, upload would 500.
82
- await createEventsTable(testDb.db);
83
82
 
84
83
  const registry = createRegistry([tenantFeature]);
85
84
  const storageProvider = createLocalProvider(storagePath);
@@ -21,7 +21,7 @@ import {
21
21
  type WriteResult,
22
22
  } from "../../engine";
23
23
  import { createWorkerEntrypoint } from "../../entrypoint";
24
- import { createArchivedStreamsTable, createEventsTable } from "../../event-store";
24
+ import { createArchivedStreamsTable } from "../../event-store";
25
25
  import { createEventConsumerStateTable } from "../../pipeline";
26
26
  import { createTestRedis, type TestRedis } from "../../stack";
27
27
  import { waitFor } from "../../testing";
@@ -110,7 +110,6 @@ let testRedis: TestRedis;
110
110
 
111
111
  beforeAll(async () => {
112
112
  [testDb, testRedis] = await Promise.all([createTestDb(), createTestRedis()]);
113
- await createEventsTable(testDb.db);
114
113
  await createArchivedStreamsTable(testDb.db);
115
114
  await createEventConsumerStateTable(testDb.db);
116
115
  });
@@ -20,7 +20,6 @@ import {
20
20
  defineFeature,
21
21
  type ProjectionDefinition,
22
22
  } from "../../engine";
23
- import { createEventsTable } from "../../event-store";
24
23
  import type { JobRunner } from "../../jobs/job-runner";
25
24
  import { createProjectionStateTable } from "../../pipeline";
26
25
  import {
@@ -97,7 +96,6 @@ let markerDir: string;
97
96
  beforeAll(async () => {
98
97
  testDb = await createTestDb();
99
98
  await unsafeCreateEntityTable(testDb.db, itemEntity, "pending-item");
100
- await createEventsTable(testDb.db);
101
99
  await createProjectionStateTable(testDb.db);
102
100
  await unsafePushTables(testDb.db, {
103
101
  readPendingCounts: countsTable,
@@ -10,7 +10,6 @@ import { asRawClient } from "../../db/query";
10
10
  import { buildEntityTable } from "../../db/table-builder";
11
11
  import { createTenantDb, type TenantDb } from "../../db/tenant-db";
12
12
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
13
- import { createEventsTable } from "../../event-store";
14
13
  import { createProjectionStateTable, rebuildProjection } from "../../pipeline";
15
14
  import { createTestDb, type TestDb, TestUsers, unsafeCreateEntityTable } from "../../stack";
16
15
 
@@ -38,7 +37,6 @@ const crud = createEventStoreExecutor(personTable, personEntity, { entityName: "
38
37
  beforeAll(async () => {
39
38
  testDb = await createTestDb();
40
39
  await unsafeCreateEntityTable(testDb.db, personEntity, "person");
41
- await createEventsTable(testDb.db);
42
40
  await createProjectionStateTable(testDb.db);
43
41
  tdb = createTenantDb(testDb.db, admin.tenantId);
44
42
  });
@@ -11,7 +11,6 @@ import {
11
11
  defineFeature,
12
12
  type Registry,
13
13
  } from "../../engine";
14
- import { createEventsTable } from "../../event-store";
15
14
  import { TestUsers, unsafeCreateEntityTable } from "../../stack";
16
15
  import { ensureTemporalPolyfill } from "../../time/polyfill";
17
16
  import { createCascadeDeleteHook } from "../cascade-handler";
@@ -96,7 +95,6 @@ const memberEntity = createEntity({
96
95
  beforeAll(async () => {
97
96
  await ensureTemporalPolyfill();
98
97
  testDb = await createTestDb();
99
- await createEventsTable(testDb.db);
100
98
  tdb = createTenantDb(testDb.db, admin.tenantId);
101
99
 
102
100
  await unsafeCreateEntityTable(testDb.db, departmentEntity);
@@ -16,7 +16,6 @@ import { integer, table as pgTable, uuid as pgUuid } from "../../db/dialect";
16
16
  import { asRawClient } from "../../db/query";
17
17
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
18
18
  import type { ProjectionDefinition } from "../../engine/types";
19
- import { createEventsTable } from "../../event-store";
20
19
  import { createProjectionStateTable, rebuildProjection } from "../../pipeline";
21
20
  import { TestUsers, unsafePushTables } from "../../stack";
22
21
  import { ensureTemporalPolyfill } from "../../time/polyfill";
@@ -68,7 +67,6 @@ const qualifiedProjectionName = "perfrebuild:projection:task-count";
68
67
  beforeAll(async () => {
69
68
  await ensureTemporalPolyfill();
70
69
  testDb = await createTestDb();
71
- await createEventsTable(testDb.db);
72
70
  await createProjectionStateTable(testDb.db);
73
71
  await unsafePushTables(testDb.db, { perf_rebuild_task_count: taskCountTable });
74
72
  });
@@ -26,7 +26,7 @@ import {
26
26
  defineFeature,
27
27
  } from "../../engine";
28
28
  import type { ProjectionDefinition } from "../../engine/types";
29
- import { archiveStream, createEventsTable } from "../../event-store";
29
+ import { archiveStream } from "../../event-store";
30
30
  import {
31
31
  createProjectionStateTable,
32
32
  getAllProjectionProgress,
@@ -104,7 +104,6 @@ const executor = createEventStoreExecutor(itemTable, itemEntity, { entityName: "
104
104
  beforeAll(async () => {
105
105
  testDb = await createTestDb();
106
106
  await unsafeCreateEntityTable(testDb.db, itemEntity, "rebuild-item");
107
- await createEventsTable(testDb.db);
108
107
  await createProjectionStateTable(testDb.db);
109
108
  await unsafePushTables(testDb.db, { rebuildItemsPerGroup: itemsPerGroupTable });
110
109
  tdb = createTenantDb(testDb.db, admin.tenantId);
@@ -23,7 +23,7 @@ import { buildEntityTable } from "../../db/table-builder";
23
23
  import { createTenantDb, type TenantDb } from "../../db/tenant-db";
24
24
  import { createEntity, createTextField, defineApply, defineFeature } from "../../engine";
25
25
  import type { ProjectionDefinition } from "../../engine/types";
26
- import { append, createEventsTable } from "../../event-store";
26
+ import { append } from "../../event-store";
27
27
  import { listRebuildDeadLetters } from "../../event-store/rebuild-dead-letter";
28
28
  import {
29
29
  createProjectionStateTable,
@@ -140,7 +140,6 @@ const executor = createEventStoreExecutor(itemTable, itemEntity, { entityName: "
140
140
  beforeAll(async () => {
141
141
  stack = await setupTestStack({ features: [feature] });
142
142
  await unsafeCreateEntityTable(stack.db, itemEntity, "poison-item");
143
- await createEventsTable(stack.db);
144
143
  await createProjectionStateTable(stack.db);
145
144
  await unsafePushTables(stack.db, {
146
145
  poisonCounter: counterTable,
@@ -17,7 +17,6 @@ import {
17
17
  createTenantDb,
18
18
  } from "@cosmicdrift/kumiko-framework/db";
19
19
  import { createEntity, createTextField, defineFeature } from "@cosmicdrift/kumiko-framework/engine";
20
- import { createEventsTable } from "@cosmicdrift/kumiko-framework/event-store";
21
20
  import { reindexEntity } from "@cosmicdrift/kumiko-framework/search";
22
21
  import {
23
22
  setupTestStack,
@@ -67,7 +66,6 @@ beforeAll(async () => {
67
66
  stack = await setupTestStack({ features: [widgetFeature, contactFeature] });
68
67
  await unsafeCreateEntityTable(stack.db, widgetEntity);
69
68
  await unsafeCreateEntityTable(stack.db, contactEntity, "contact");
70
- await createEventsTable(stack.db);
71
69
  // Shared across both pii tests below (not per-test) — reindexEntity scans
72
70
  // the whole tenant table regardless of which test created which row, so a
73
71
  // fresh KMS instance per test would make earlier rows' subject keys
@@ -11,7 +11,6 @@ import {
11
11
  } from "../../crypto";
12
12
  import { asRawClient, buildEntityTable, createEventStoreExecutor, createTenantDb } from "../../db";
13
13
  import { createEntity, createRegistry, createTextField, defineFeature } from "../../engine";
14
- import { createEventsTable } from "../../event-store";
15
14
  import { setupTestStack, type TestStack, TestUsers, unsafeCreateEntityTable } from "../../stack";
16
15
  import { purgeSearchDocumentsForSubject } from "../purge-subject";
17
16
 
@@ -53,7 +52,6 @@ const admin = TestUsers.admin;
53
52
  beforeAll(async () => {
54
53
  stack = await setupTestStack({ features: [contactFeature] });
55
54
  await unsafeCreateEntityTable(stack.db, contactEntity, "contact");
56
- await createEventsTable(stack.db);
57
55
  });
58
56
 
59
57
  afterAll(async () => {