@cosmicdrift/kumiko-framework 0.367.0 → 0.368.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 (233) hide show
  1. package/AGENTS.md +1 -7
  2. package/README.md +1 -1
  3. package/agents/examples/access-control.md +2 -2
  4. package/agents/examples/anonymous-access-multitenant.md +2 -2
  5. package/agents/examples/anonymous-access.md +3 -3
  6. package/agents/examples/apex-landing.md +1 -1
  7. package/agents/examples/apex-surface-auth.md +6 -7
  8. package/agents/examples/api-worker-split.md +7 -8
  9. package/agents/examples/auth-claims.md +2 -1
  10. package/agents/examples/auth-foundation-providers.md +3 -3
  11. package/agents/examples/auth-mfa.md +3 -4
  12. package/agents/examples/basic-entity.md +3 -4
  13. package/agents/examples/chat-channels.md +4 -4
  14. package/agents/examples/compliance-profiles-demo.md +2 -3
  15. package/agents/examples/cross-feature-events.md +4 -4
  16. package/agents/examples/crypto-shredding-hr.md +14 -9
  17. package/agents/examples/currencies-global.md +3 -4
  18. package/agents/examples/currencies-per-tenant.md +3 -4
  19. package/agents/examples/custom-fields-basic.md +2 -3
  20. package/agents/examples/custom-handlers.md +3 -5
  21. package/agents/examples/dashboard-stats.md +2 -3
  22. package/agents/examples/delivery-notifications.md +5 -6
  23. package/agents/examples/e2e-generator.md +1 -1
  24. package/agents/examples/embedded-entity-form.md +2 -3
  25. package/agents/examples/embedded.md +2 -3
  26. package/agents/examples/encrypted-tenant-config.md +5 -6
  27. package/agents/examples/error-contract.md +3 -4
  28. package/agents/examples/event-sourcing.md +19 -23
  29. package/agents/examples/feature-toggles.md +4 -4
  30. package/agents/examples/field-access.md +3 -4
  31. package/agents/examples/files-post-processing.md +12 -13
  32. package/agents/examples/golden-path.md +208 -0
  33. package/agents/examples/idempotency.md +2 -2
  34. package/agents/examples/inbound-mail-triage.md +7 -7
  35. package/agents/examples/lane-routing.md +3 -3
  36. package/agents/examples/ledger-banking.md +2 -3
  37. package/agents/examples/ledger-invoicing.md +2 -3
  38. package/agents/examples/legal-pages.md +1 -1
  39. package/agents/examples/lifecycle-hooks.md +3 -3
  40. package/agents/examples/managed-config.md +3 -4
  41. package/agents/examples/managed-pages.md +2 -2
  42. package/agents/examples/notes-history-basic.md +4 -5
  43. package/agents/examples/ownership.md +3 -5
  44. package/agents/examples/pdf-audit-export.md +2 -3
  45. package/agents/examples/pipeline-basics.md +2 -2
  46. package/agents/examples/public-share-token.md +3 -3
  47. package/agents/examples/rate-limiting.md +2 -1
  48. package/agents/examples/realtime-sse.md +2 -3
  49. package/agents/examples/record-detail-layout.md +1 -1
  50. package/agents/examples/reference-data.md +3 -3
  51. package/agents/examples/relations.md +2 -2
  52. package/agents/examples/screens-nav.md +1 -1
  53. package/agents/examples/search.md +2 -2
  54. package/agents/examples/secrets-demo.md +8 -8
  55. package/agents/examples/security-baseline.md +2 -2
  56. package/agents/examples/session-revocation.md +7 -8
  57. package/agents/examples/state-machine.md +181 -165
  58. package/agents/examples/tags-basic.md +2 -3
  59. package/agents/examples/tenant-isolation.md +2 -3
  60. package/agents/examples/tenant-settings.md +2 -2
  61. package/agents/examples/tier-admin.md +3 -4
  62. package/agents/examples/timezones.md +3 -3
  63. package/agents/examples/user-data-rights.md +12 -8
  64. package/agents/examples/user-profile.md +3 -3
  65. package/agents/examples/webhook-step.md +2 -3
  66. package/agents/examples/wizard-form.md +4 -5
  67. package/agents/examples/workflow-engine.md +7 -3
  68. package/agents/examples/writeform-section.md +1 -1
  69. package/agents/examples.md +1 -0
  70. package/dist/consumer-cli.js +2 -3
  71. package/dist/crypto/index.d.ts +1 -1
  72. package/dist/crypto/index.js +1 -1
  73. package/dist/crypto/owner-transition-rekey.js +2 -0
  74. package/dist/crypto/pg-kms-adapter.js +1 -1
  75. package/dist/crypto/subject-resolver.d.ts +2 -0
  76. package/dist/crypto/subject-resolver.js +30 -15
  77. package/dist/db/bun-db-deprecated.d.ts +10 -0
  78. package/dist/{bun-db/index.js → db/bun-db-deprecated.js} +4 -3
  79. package/dist/db/config-seed.js +1 -1
  80. package/dist/db/connection.d.ts +23 -5
  81. package/dist/db/connection.js +10 -5
  82. package/dist/db/dialect.js +1 -1
  83. package/dist/db/entity-field-encryption.d.ts +5 -0
  84. package/dist/db/entity-field-encryption.js +29 -0
  85. package/dist/db/entity-table-meta.js +6 -5
  86. package/dist/db/event-store-executor-read.js +26 -5
  87. package/dist/db/index.d.ts +4 -4
  88. package/dist/db/index.js +1 -1
  89. package/dist/db/migrate-runner.js +1 -1
  90. package/dist/db/queries/backfill-pii.js +16 -9
  91. package/dist/db/queries/ddl.js +1 -1
  92. package/dist/db/queries/event-store.d.ts +2 -0
  93. package/dist/db/queries/event-store.js +26 -0
  94. package/dist/db/queries/test-stack.js +1 -1
  95. package/dist/db/query.d.ts +105 -1
  96. package/dist/db/query.js +1450 -3
  97. package/dist/{stack/table-helpers.d.ts → db/table-push.d.ts} +5 -15
  98. package/dist/{stack/table-helpers.js → db/table-push.js} +6 -46
  99. package/dist/db/tenant-db.d.ts +11 -0
  100. package/dist/db/tenant-db.js +51 -12
  101. package/dist/derivatives/derivatives-context.d.ts +1 -1
  102. package/dist/derivatives/derivatives-context.js +1 -1
  103. package/dist/engine/boot-validator/gdpr-storage.js +3 -3
  104. package/dist/engine/boot-validator/pii-retention.js +3 -3
  105. package/dist/engine/boot-validator/record-owned.js +3 -3
  106. package/dist/engine/boot-validator/screens.js +8 -1
  107. package/dist/engine/bridge-stub.d.ts +4 -0
  108. package/dist/engine/bridge-stub.js +62 -0
  109. package/dist/engine/event-helpers.d.ts +2 -1
  110. package/dist/engine/event-helpers.js +1 -0
  111. package/dist/engine/feature-config-events-jobs.js +2 -2
  112. package/dist/engine/index.d.ts +1 -0
  113. package/dist/engine/index.js +1 -0
  114. package/dist/es-ops/operations-schema.js +1 -1
  115. package/dist/event-store/admin-api.js +1 -1
  116. package/dist/event-store/archive.js +1 -1
  117. package/dist/event-store/errors.d.ts +2 -1
  118. package/dist/event-store/errors.js +6 -2
  119. package/dist/event-store/event-store.js +17 -9
  120. package/dist/event-store/events-schema.js +1 -1
  121. package/dist/event-store/rebuild-dead-letter.js +3 -3
  122. package/dist/event-store/snapshot.js +1 -1
  123. package/dist/event-store/upcaster-dead-letter.js +3 -3
  124. package/dist/files/create-file-ref-from-bytes.js +1 -1
  125. package/dist/files/file-routes.js +1 -1
  126. package/dist/internal/stack-support.d.ts +12 -0
  127. package/dist/internal/stack-support.js +12 -0
  128. package/dist/internal/testing.d.ts +4 -0
  129. package/dist/internal/testing.js +5 -0
  130. package/dist/jobs/job-runner.js +23 -11
  131. package/dist/migrations/pending-rebuilds.js +1 -1
  132. package/dist/migrations/pii-event-backfill.js +2 -2
  133. package/dist/pipeline/dispatch-shared.js +43 -23
  134. package/dist/pipeline/dispatch-write.js +6 -2
  135. package/dist/pipeline/dispatcher-utils.js +6 -2
  136. package/dist/pipeline/event-consumer-state.js +1 -1
  137. package/dist/pipeline/projection-rebuild.d.ts +1 -0
  138. package/dist/pipeline/projection-rebuild.js +1 -1
  139. package/dist/pipeline/projection-state.js +1 -1
  140. package/dist/pipeline/system-hooks.js +1 -1
  141. package/dist/schema-cli.js +3 -3
  142. package/dist/search/purge-subject.js +20 -10
  143. package/dist/seeding/index.d.ts +1 -0
  144. package/dist/seeding/index.js +1 -0
  145. package/dist/stack/index.d.ts +10 -9
  146. package/dist/stack/index.js +11 -19
  147. package/dist/testing/e2e-generator.d.ts +5 -49
  148. package/dist/testing/e2e-generator.js +5 -382
  149. package/dist/testing/handler-context.d.ts +5 -4
  150. package/dist/testing/handler-context.js +5 -69
  151. package/dist/testing/index.d.ts +10 -26
  152. package/dist/testing/index.js +10 -34
  153. package/dist/testing/real-providers.d.ts +5 -3
  154. package/dist/testing/real-providers.js +5 -21
  155. package/package.json +37 -9
  156. package/src/changes.json +58 -0
  157. package/src/scripts/codemod/README.md +43 -0
  158. package/src/scripts/codemod/codemod-support.ts +276 -0
  159. package/src/scripts/codemod/db-factory.ts +810 -0
  160. package/src/scripts/codemod/feature-factory.ts +429 -0
  161. package/src/scripts/codemod/s3-imports.ts +274 -0
  162. package/src/scripts/codemod/tenant-db-options.ts +454 -0
  163. package/src/scripts/codemod/testing-move-targets.ts +161 -0
  164. package/src/scripts/codemod/testing-move.ts +520 -0
  165. package/dist/bun-db/connection.d.ts +0 -17
  166. package/dist/bun-db/connection.js +0 -40
  167. package/dist/bun-db/index.d.ts +0 -4
  168. package/dist/bun-db/query.d.ts +0 -106
  169. package/dist/bun-db/query.js +0 -1457
  170. package/dist/db/api.d.ts +0 -19
  171. package/dist/db/api.js +0 -23
  172. package/dist/db/bun-provider.d.ts +0 -2
  173. package/dist/db/bun-provider.js +0 -28
  174. package/dist/db/postgres-provider.d.ts +0 -2
  175. package/dist/db/postgres-provider.js +0 -21
  176. package/dist/db/query-api.d.ts +0 -2
  177. package/dist/db/query-api.js +0 -6
  178. package/dist/stack/db.d.ts +0 -22
  179. package/dist/stack/db.js +0 -67
  180. package/dist/stack/drain-event-consumers.d.ts +0 -4
  181. package/dist/stack/drain-event-consumers.js +0 -51
  182. package/dist/stack/drain-jobs.d.ts +0 -22
  183. package/dist/stack/drain-jobs.js +0 -141
  184. package/dist/stack/event-collector.d.ts +0 -9
  185. package/dist/stack/event-collector.js +0 -12
  186. package/dist/stack/push-entity-projection-tables.d.ts +0 -24
  187. package/dist/stack/push-entity-projection-tables.js +0 -74
  188. package/dist/stack/redis.d.ts +0 -17
  189. package/dist/stack/redis.js +0 -52
  190. package/dist/stack/request-helper.d.ts +0 -35
  191. package/dist/stack/request-helper.js +0 -144
  192. package/dist/stack/test-stack.d.ts +0 -192
  193. package/dist/stack/test-stack.js +0 -429
  194. package/dist/testing/access-assertions.d.ts +0 -2
  195. package/dist/testing/access-assertions.js +0 -13
  196. package/dist/testing/assertions.d.ts +0 -10
  197. package/dist/testing/assertions.js +0 -22
  198. package/dist/testing/boot-validator-fixture.d.ts +0 -3
  199. package/dist/testing/boot-validator-fixture.js +0 -101
  200. package/dist/testing/closed-connection-error.d.ts +0 -7
  201. package/dist/testing/closed-connection-error.js +0 -46
  202. package/dist/testing/db-cleanup.d.ts +0 -9
  203. package/dist/testing/db-cleanup.js +0 -31
  204. package/dist/testing/expect-error.d.ts +0 -2
  205. package/dist/testing/expect-error.js +0 -22
  206. package/dist/testing/file-provider-contract.d.ts +0 -2
  207. package/dist/testing/file-provider-contract.js +0 -102
  208. package/dist/testing/http-cookies.d.ts +0 -7
  209. package/dist/testing/http-cookies.js +0 -47
  210. package/dist/testing/late-bound.d.ts +0 -9
  211. package/dist/testing/late-bound.js +0 -31
  212. package/dist/testing/manual-clock.d.ts +0 -14
  213. package/dist/testing/manual-clock.js +0 -12
  214. package/dist/testing/multipart-helper.d.ts +0 -22
  215. package/dist/testing/multipart-helper.js +0 -77
  216. package/dist/testing/mutable-master-key-provider.d.ts +0 -7
  217. package/dist/testing/mutable-master-key-provider.js +0 -35
  218. package/dist/testing/observability-recorder.d.ts +0 -9
  219. package/dist/testing/observability-recorder.js +0 -29
  220. package/dist/testing/seed.d.ts +0 -9
  221. package/dist/testing/seed.js +0 -21
  222. package/dist/testing/shared-entities.d.ts +0 -286
  223. package/dist/testing/shared-entities.js +0 -56
  224. package/dist/testing/utils.d.ts +0 -1
  225. package/dist/testing/utils.js +0 -1
  226. package/dist/testing/wait-for.d.ts +0 -20
  227. package/dist/testing/wait-for.js +0 -47
  228. package/dist/testing/without-ambient-temporal.d.ts +0 -5
  229. package/dist/testing/without-ambient-temporal.js +0 -17
  230. package/dist/testing/zip-fixture.d.ts +0 -1
  231. package/dist/testing/zip-fixture.js +0 -33
  232. /package/dist/{stack/test-users.d.ts → seeding/seed-users.d.ts} +0 -0
  233. /package/dist/{stack/test-users.js → seeding/seed-users.js} +0 -0
@@ -4,7 +4,7 @@ import { index, instant, integer, jsonb, table as pgTable, primaryKey, sql, text
4
4
  import { ensureSnapshotVersionColumn, upsertSnapshot } from "../db/queries/event-store.js";
5
5
  import { selectMany } from "../db/query.js";
6
6
  import { tableExists } from "../db/schema-inspection.js";
7
- import { unsafePushTables } from "../stack/index.js";
7
+ import { unsafePushTables } from "../db/table-push.js";
8
8
  import { isStreamArchived } from "./archive.js";
9
9
  import { loadEventsAfterVersion } from "./event-store.js";
10
10
  // Marten-aligned snapshot store. A snapshot is a point-in-time materialised
@@ -13,7 +13,7 @@
13
13
  // separate CLI step — not implemented here, tracked as follow-up.
14
14
  import { bigint, index, integer, jsonb, table as pgTable, text, timestamp, uuid, } from "../db/dialect.js";
15
15
  import { tableExists } from "../db/schema-inspection.js";
16
- import { unsafePushTables } from "../stack/index.js";
16
+ import { unsafePushTables } from "../db/table-push.js";
17
17
  export const upcasterDeadLetterTable = pgTable("kumiko_upcaster_dead_letters", {
18
18
  // Surrogate PK. We don't reuse eventId so a single event can land
19
19
  // here multiple times (retry attempts across deploys before the fix
@@ -49,7 +49,7 @@ export async function createUpcasterDeadLetterTable(db) {
49
49
  // ops tooling uses it for correlate-and-replay flows.
50
50
  export async function recordUpcasterDeadLetter(db, args) {
51
51
  const message = args.error instanceof Error ? args.error.message : String(args.error);
52
- const { insertOne } = await import("../bun-db/query.js");
52
+ const { insertOne } = await import("../db/query.js");
53
53
  await insertOne(db, upcasterDeadLetterTable, {
54
54
  eventId: args.event.id,
55
55
  tenantId: args.event.tenantId,
@@ -65,7 +65,7 @@ export async function recordUpcasterDeadLetter(db, args) {
65
65
  // Ops-side query. Loads recent failures, optionally scoped by event-type
66
66
  // to triage a single broken migration without pulling the full table.
67
67
  export async function listDeadLetters(db, options = {}) {
68
- const { selectMany } = await import("../bun-db/query.js");
68
+ const { selectMany } = await import("../db/query.js");
69
69
  const limit = options.limit ?? 100;
70
70
  const where = options.eventType !== undefined ? { eventType: options.eventType } : undefined;
71
71
  const rows = await selectMany(db, upcasterDeadLetterTable, where, {
@@ -1,4 +1,4 @@
1
- import { fetchOne } from "../bun-db/query.js";
1
+ import { fetchOne } from "../db/query.js";
2
2
  import { FrameworkReasons, InternalError, UnprocessableError, writeFailure, } from "../errors/index.js";
3
3
  import { createFileRefRow, DEFAULT_MAX_UPLOAD_SIZE, storeFileAsFileRef, writeAcceptedFileBytes, } from "./create-file-ref.js";
4
4
  import { fileRefsTable } from "./file-ref-table.js";
@@ -2,8 +2,8 @@ import { Hono } from "hono";
2
2
  import { bodyLimit } from "hono/body-limit";
3
3
  import { getUser } from "../api/auth-middleware.js";
4
4
  import { requestContext } from "../api/request-context.js";
5
- import { selectMany } from "../bun-db/index.js";
6
5
  import { collectPiiSubjectFields, configuredPiiSubjectKms, decryptPiiFieldValues, isPiiCiphertext, PII_ERASED_SENTINEL, } from "../crypto/index.js";
6
+ import { selectMany } from "../db/query.js";
7
7
  import { createProjectingTenantDb } from "../db/tenant-db.js";
8
8
  import { createDerivativesContext, resolveFieldVariant, resolveRenderer, } from "../derivatives/index.js";
9
9
  import { SYSTEM_USER_ID } from "../engine/system-user.js";
@@ -0,0 +1,12 @@
1
+ export { createDbConnection } from "../db/connection.js";
2
+ export { createDatabase, databaseExists, dropDatabaseIfExists, truncateTablesRestartIdentity, } from "../db/queries/test-stack.js";
3
+ export { type AnyDb, isClosedConnectionError } from "../db/query.js";
4
+ export { columnNamesOf } from "../db/schema-inspection.js";
5
+ export { schemaTableName } from "../db/table-introspection.js";
6
+ export { addMissingColumns, addMissingIndexes, tableToMeta, unsafeCreateEntityTable, unsafeEnsureEntityTable, unsafePushTables, } from "../db/table-push.js";
7
+ export { SEARCHABLE_FALSE_WHITELIST } from "../engine/boot-validator/entity-list-screens.js";
8
+ export { validateNavBootSubset } from "../engine/boot-validator/index.js";
9
+ export { validateOwnershipBoot } from "../engine/boot-validator/ownership.js";
10
+ export { featureHasI18nSurface, requiredKeysFromFeature } from "../i18n/required-surface-keys.js";
11
+ export type { Lifecycle } from "../lifecycle/index.js";
12
+ export { createRateLimitResolver } from "../rate-limit/index.js";
@@ -0,0 +1,12 @@
1
+ // @runtime dev
2
+ export { createDbConnection } from "../db/connection.js";
3
+ export { createDatabase, databaseExists, dropDatabaseIfExists, truncateTablesRestartIdentity, } from "../db/queries/test-stack.js";
4
+ export { isClosedConnectionError } from "../db/query.js";
5
+ export { columnNamesOf } from "../db/schema-inspection.js";
6
+ export { schemaTableName } from "../db/table-introspection.js";
7
+ export { addMissingColumns, addMissingIndexes, tableToMeta, unsafeCreateEntityTable, unsafeEnsureEntityTable, unsafePushTables, } from "../db/table-push.js";
8
+ export { SEARCHABLE_FALSE_WHITELIST } from "../engine/boot-validator/entity-list-screens.js";
9
+ export { validateNavBootSubset } from "../engine/boot-validator/index.js";
10
+ export { validateOwnershipBoot } from "../engine/boot-validator/ownership.js";
11
+ export { featureHasI18nSurface, requiredKeysFromFeature } from "../i18n/required-surface-keys.js";
12
+ export { createRateLimitResolver } from "../rate-limit/index.js";
@@ -0,0 +1,4 @@
1
+ export { resetBlindIndexKeyForTests } from "../crypto/blind-index.js";
2
+ export { resetEventPiiCatalogForTests } from "../crypto/event-pii.js";
3
+ export { resetPiiSubjectKmsForTests } from "../crypto/pii-field-encryption.js";
4
+ export { resetEntityFieldEncryptionCacheForTests } from "../db/entity-field-encryption.js";
@@ -0,0 +1,5 @@
1
+ // @runtime test
2
+ export { resetBlindIndexKeyForTests } from "../crypto/blind-index.js";
3
+ export { resetEventPiiCatalogForTests } from "../crypto/event-pii.js";
4
+ export { resetPiiSubjectKmsForTests } from "../crypto/pii-field-encryption.js";
5
+ export { resetEntityFieldEncryptionCacheForTests } from "../db/entity-field-encryption.js";
@@ -6,6 +6,7 @@ import { Redis } from "ioredis";
6
6
  import { requestContext } from "../api/request-context.js";
7
7
  import { createTenantDb, createUncheckedSystemDb } from "../db/tenant-db.js";
8
8
  import { createDerivativesContext } from "../derivatives/derivatives-context.js";
9
+ import { bridgeStub } from "../engine/bridge-stub.js";
9
10
  import { qnScope } from "../engine/qualified-name.js";
10
11
  import { createSystemUser } from "../engine/system-user.js";
11
12
  import { SYSTEM_TENANT_ID, } from "../engine/types/index.js";
@@ -19,7 +20,6 @@ import { RedisKeys } from "../pipeline/redis-keys.js";
19
20
  import { createGatedIdentitySwitch } from "../pipeline/system-identity-switch.js";
20
21
  import { TENANT_TIMEZONE_CONFIG_KEY } from "../pipeline/tenant-timezone-config-key.js";
21
22
  import { buildPersonalDataGate, isPersonalDataGated, parseWriteOrigin, } from "../pipeline/write-origin.js";
22
- import { bridgeStub } from "../testing/handler-context.js";
23
23
  import { createTzContext, isValidIanaTimeZone } from "../time/index.js";
24
24
  import { cronRepeatOptions } from "./cron-repeat-options.js";
25
25
  import { resolveMaxPerTenant } from "./max-per-tenant.js";
@@ -429,7 +429,7 @@ export function createJobRunner(options) {
429
429
  return undefined;
430
430
  return async () => {
431
431
  const systemUser = createSystemUser(SYSTEM_TENANT_ID);
432
- const systemModeDb = createTenantDb(db, SYSTEM_TENANT_ID, "system");
432
+ const systemModeDb = createTenantDb({ db, tenantId: SYSTEM_TENANT_ID, mode: "system" });
433
433
  const reportEscapeHatch = createEscapeHatchReporter({
434
434
  handler: ACTIVE_TENANT_IDS_QUERY_NAME,
435
435
  tenantId: SYSTEM_TENANT_ID,
@@ -922,9 +922,14 @@ export function createJobRunner(options) {
922
922
  // unchecked cross-tenant escape hatch for such a job. Gated like jobDb so
923
923
  // ctx.systemDb, built from it, is gated too.
924
924
  const tenantScopedDb = configDb
925
- ? createTenantDb(configDb, tenantId, "system", undefined, undefined, undefined, {
926
- projectionRegistry: registry,
927
- ...(jobPersonalDataGate && { personalDataGate: jobPersonalDataGate }),
925
+ ? createTenantDb({
926
+ db: configDb,
927
+ tenantId,
928
+ mode: "system",
929
+ grants: {
930
+ projectionRegistry: registry,
931
+ ...(jobPersonalDataGate && { personalDataGate: jobPersonalDataGate }),
932
+ },
928
933
  })
929
934
  : undefined;
930
935
  const isSystemJob = registry.isJobSystemScoped(jobName);
@@ -945,12 +950,19 @@ export function createJobRunner(options) {
945
950
  ? createUncheckedSystemDb(tenantScopedDb, undefined, reportEscapeHatch)
946
951
  : undefined;
947
952
  const jobDb = configDb
948
- ? createTenantDb(configDb, tenantId, "tenant", context.tracer, context.meter, undefined, {
949
- unsafeRaw: escapeHatchFor(jobDef.escapeHatch, "unsafeRaw"),
950
- globalWrites: escapeHatchFor(jobDef.escapeHatch, "globalWrites"),
951
- report: reportEscapeHatch,
952
- projectionRegistry: registry,
953
- ...(jobPersonalDataGate && { personalDataGate: jobPersonalDataGate }),
953
+ ? createTenantDb({
954
+ db: configDb,
955
+ tenantId,
956
+ mode: "tenant",
957
+ tracer: context.tracer,
958
+ meter: context.meter,
959
+ grants: {
960
+ unsafeRaw: escapeHatchFor(jobDef.escapeHatch, "unsafeRaw"),
961
+ globalWrites: escapeHatchFor(jobDef.escapeHatch, "globalWrites"),
962
+ report: reportEscapeHatch,
963
+ projectionRegistry: registry,
964
+ ...(jobPersonalDataGate && { personalDataGate: jobPersonalDataGate }),
965
+ },
954
966
  })
955
967
  : undefined;
956
968
  const crossTenantReadsReason = escapeHatchFor(jobDef.escapeHatch, "unsafeRaw")?.reason;
@@ -14,9 +14,9 @@ import { instant, table as pgTable, sql, text } from "../db/dialect.js";
14
14
  import { deleteMany, selectMany, upsertOnConflict } from "../db/query.js";
15
15
  import { readRebuildMarker } from "../db/rebuild-marker.js";
16
16
  import { tableExists } from "../db/schema-inspection.js";
17
+ import { unsafePushTables } from "../db/table-push.js";
17
18
  import { createFallbackLogger } from "../logging/utils.js";
18
19
  import { rebuildMultiStreamProjection, rebuildProjection, } from "../pipeline/index.js";
19
- import { unsafePushTables } from "../stack/index.js";
20
20
  import { buildProjectionTableIndex } from "./projection-table-index.js";
21
21
  export const pendingRebuildsTable = pgTable("kumiko_pending_rebuilds", {
22
22
  tableName: text("table_name").primaryKey(),
@@ -18,8 +18,8 @@ import { alterTableAddColumn } from "../db/queries/ddl.js";
18
18
  import { persistPiiBackfillState, readPiiBackfillState, } from "../db/queries/pii-backfill-state.js";
19
19
  import { upsertOnConflict } from "../db/query.js";
20
20
  import { tableExists } from "../db/schema-inspection.js";
21
+ import { unsafePushTables } from "../db/table-push.js";
21
22
  import { createFallbackLogger } from "../logging/utils.js";
22
- import { unsafePushTables } from "../stack/index.js";
23
23
  import { clearPendingRebuilds, createPendingRebuildsTable, listPendingRebuildRows, pendingRebuildsTable, rebuildProjectionOrMultiStream, } from "./pending-rebuilds.js";
24
24
  import { buildProjectionTableIndex } from "./projection-table-index.js";
25
25
  const PII_EVENT_BACKFILL_LOCK_NAMESPACE = 0x70_69_69_62; // 'piib'
@@ -71,7 +71,7 @@ export function piiAnnotationEntries(registry) {
71
71
  // Bump when the backfill gains a transform stage: completed states keyed by the
72
72
  // old fingerprint must run one more full pass. Kept out of piiAnnotationEntries,
73
73
  // whose emptiness gates no_pii.
74
- const PII_BACKFILL_TRANSFORM_VERSION = "owner-rekey-v1";
74
+ const PII_BACKFILL_TRANSFORM_VERSION = "owner-rekey-v2";
75
75
  export function piiAnnotationFingerprint(registry) {
76
76
  return createHash("sha256")
77
77
  .update([...piiAnnotationEntries(registry), PII_BACKFILL_TRANSFORM_VERSION].join("\n"))
@@ -289,16 +289,24 @@ readOnlyForTenant = false) {
289
289
  });
290
290
  const identitySwitchGrantReason = identitySwitchEscapeHatch?.reason ??
291
291
  (isSystem ? `r.systemScope() feature "${featureName ?? "unknown"}"` : undefined);
292
- const buildTenantScopedDb = (source, signal, foreignTenantId, denyWrites = false) => createTenantDb(source, foreignTenantId ?? user.tenantId, isSystem && foreignTenantId === undefined ? "system" : "tenant", context.tracer, context.meter, signal, {
293
- ...(foreignTenantId === undefined && {
294
- globalWrites: escapeHatchFor(writeEscapeHatch, "globalWrites"),
295
- unsafeRaw: escapeHatchFor(handlerEscapeHatch, "unsafeRaw"),
296
- }),
297
- report: reportEscapeHatch,
298
- memberReadOnly: isMemberResolutionPrincipal(user),
299
- personalDataGate: buildPersonalDataGate(registry, origin),
300
- projectionRegistry: registry,
301
- ...(denyWrites && { writesDeniedReason: "without an open transaction" }),
292
+ const buildTenantScopedDb = (source, signal, foreignTenantId, denyWrites = false) => createTenantDb({
293
+ db: source,
294
+ tenantId: foreignTenantId ?? user.tenantId,
295
+ mode: isSystem && foreignTenantId === undefined ? "system" : "tenant",
296
+ tracer: context.tracer,
297
+ meter: context.meter,
298
+ signal,
299
+ grants: {
300
+ ...(foreignTenantId === undefined && {
301
+ globalWrites: escapeHatchFor(writeEscapeHatch, "globalWrites"),
302
+ unsafeRaw: escapeHatchFor(handlerEscapeHatch, "unsafeRaw"),
303
+ }),
304
+ report: reportEscapeHatch,
305
+ memberReadOnly: isMemberResolutionPrincipal(user),
306
+ personalDataGate: buildPersonalDataGate(registry, origin),
307
+ projectionRegistry: registry,
308
+ ...(denyWrites && { writesDeniedReason: "without an open transaction" }),
309
+ },
302
310
  });
303
311
  // Propagate the request's AbortSignal so every TenantDb query throws when
304
312
  // the client has disconnected — handlers with many sequential queries skip
@@ -410,7 +418,14 @@ readOnlyForTenant = false) {
410
418
  const targetSystemUser = createSystemUser(targetTenantId);
411
419
  return configFactory({
412
420
  user: { id: targetSystemUser.id, tenantId: targetTenantId },
413
- db: createTenantDb(dbSource, targetTenantId, "tenant", context.tracer, context.meter, undefined, { projectionRegistry: registry }),
421
+ db: createTenantDb({
422
+ db: dbSource,
423
+ tenantId: targetTenantId,
424
+ mode: "tenant",
425
+ tracer: context.tracer,
426
+ meter: context.meter,
427
+ grants: { projectionRegistry: registry },
428
+ }),
414
429
  secrets: context.secrets,
415
430
  });
416
431
  }
@@ -886,9 +901,7 @@ readOnlyForTenant = false) {
886
901
  // Finger an typo-resistenten ctx.user-Patterns. Identisch zum
887
902
  // event.user-Wert; Identity-Switches nutzen weiterhin queryAs/writeAs.
888
903
  user,
889
- _userId: user.id,
890
904
  _tenantId: user.tenantId,
891
- _handlerType: type,
892
905
  ...(hasLiveCommitSink && { scheduleAfterCommit }),
893
906
  ...(includeDeleted && { includeDeleted: true }),
894
907
  ...bridge,
@@ -1219,16 +1232,23 @@ function buildAuthClaimsContext(ctx, user) {
1219
1232
  message: "dispatcher.resolveAuthClaims requires a database connection — none is configured.",
1220
1233
  });
1221
1234
  }
1222
- const db = createTenantDb(dbSource, user.tenantId, "tenant", context.tracer, context.meter, undefined, {
1223
- report: createEscapeHatchReporter({
1224
- handler: "r.authClaims hook",
1225
- tenantId: user.tenantId,
1226
- actor: user.id,
1227
- sink: context._escapeHatchAuditSink,
1228
- log: context.log ?? createFallbackLogger("dispatcher"),
1229
- window: ctx.escapeHatchReportWindow,
1230
- }),
1231
- projectionRegistry: ctx.registry,
1235
+ const db = createTenantDb({
1236
+ db: dbSource,
1237
+ tenantId: user.tenantId,
1238
+ mode: "tenant",
1239
+ tracer: context.tracer,
1240
+ meter: context.meter,
1241
+ grants: {
1242
+ report: createEscapeHatchReporter({
1243
+ handler: "r.authClaims hook",
1244
+ tenantId: user.tenantId,
1245
+ actor: user.id,
1246
+ sink: context._escapeHatchAuditSink,
1247
+ log: context.log ?? createFallbackLogger("dispatcher"),
1248
+ window: ctx.escapeHatchReportWindow,
1249
+ }),
1250
+ projectionRegistry: ctx.registry,
1251
+ },
1232
1252
  });
1233
1253
  const configAccessor = context._configAccessorFactory
1234
1254
  ? context._configAccessorFactory({
@@ -163,7 +163,7 @@ async function isParentRowHiddenFromCaller(ctx, parentEntityName, parentId, user
163
163
  const source = resolveDbSource(ctx, tx);
164
164
  if (!table || !source)
165
165
  return true;
166
- const callerDb = createTenantDb(source, user.tenantId, "tenant");
166
+ const callerDb = createTenantDb({ db: source, tenantId: user.tenantId, mode: "tenant" });
167
167
  const visibleRow = await callerDb.fetchOne(table, { id: parentId });
168
168
  if (visibleRow === undefined)
169
169
  return true;
@@ -181,7 +181,11 @@ async function completeParentRow(ctx, parentEntityName, parentEntity, parentRow,
181
181
  const source = resolveDbSource(ctx, tx);
182
182
  if (!table || !source || typeof parentRow["id"] !== "string")
183
183
  return parentRow;
184
- const stored = await createTenantDb(source, user.tenantId, "tenant").fetchOne(table, {
184
+ const stored = await createTenantDb({
185
+ db: source,
186
+ tenantId: user.tenantId,
187
+ mode: "tenant",
188
+ }).fetchOne(table, {
185
189
  id: parentRow["id"],
186
190
  });
187
191
  return stored ? { ...stored, ...parentRow } : parentRow;
@@ -143,10 +143,14 @@ export function resolveType(type) {
143
143
  // A custom write losing an append race (ctx.appendEvent, stream.append) is a
144
144
  // retryable 409 like the CRUD executor's, not an internal error. Mapped here
145
145
  // because errors/ is client code and must not import the event store.
146
- // currentVersion -1 is the executor's "not looked up" sentinel.
146
+ // currentVersion -1 is the "not looked up" sentinel for a bare 23505 fallback.
147
147
  export function wrapToKumiko(e) {
148
148
  if (e instanceof EventStoreVersionConflictError) {
149
- return new VersionConflictError({ entityId: e.aggregateId, expectedVersion: e.expectedVersion, currentVersion: -1 }, { cause: e });
149
+ return new VersionConflictError({
150
+ entityId: e.aggregateId,
151
+ expectedVersion: e.expectedVersion,
152
+ currentVersion: e.currentVersion ?? -1,
153
+ }, { cause: e });
150
154
  }
151
155
  return toKumikoError(e);
152
156
  }
@@ -1,7 +1,7 @@
1
1
  import { bigint, index, instant, integer, jsonb, table as pgTable, primaryKey, sql, text, } from "../db/dialect.js";
2
2
  import { alterTableAddColumn } from "../db/queries/ddl.js";
3
3
  import { tableExists } from "../db/schema-inspection.js";
4
- import { unsafePushTables } from "../stack/index.js";
4
+ import { unsafePushTables } from "../db/table-push.js";
5
5
  // Reserved sentinel used in the instance_id column for consumers whose
6
6
  // delivery is "shared" — i.e. one cursor across all dispatcher instances
7
7
  // (the default, pre-Welle-2.7 behaviour). Per-instance consumers store
@@ -2,6 +2,7 @@
2
2
  import type { DbConnection } from "../db/connection.js";
3
3
  import type { Registry } from "../engine/types/index.js";
4
4
  import type { Meter } from "../observability/types/metric.js";
5
+ export declare const REBUILD_BATCH_SIZE = 1000;
5
6
  export type RebuildResult = {
6
7
  readonly projection: string;
7
8
  readonly eventsProcessed: number;
@@ -8,7 +8,7 @@ import { projectionStateTable } from "./projection-state.js";
8
8
  // Events replayed per catch-up batch. Each batch is a fresh READ COMMITTED
9
9
  // SELECT, so a batch shorter than this means the currently-committed tail is
10
10
  // drained.
11
- const REBUILD_BATCH_SIZE = 1000;
11
+ export const REBUILD_BATCH_SIZE = 1000;
12
12
  // Cap on UNLOCKED catch-up batches before forcing the cutover fence. Bounds the
13
13
  // lock-free phase under sustained writes that never momentarily quiesce; the
14
14
  // fenced final drain then always terminates (no new event can commit once the
@@ -1,6 +1,6 @@
1
1
  import { bigint, index, instant, table as pgTable, sql, text } from "../db/dialect.js";
2
2
  import { tableExists } from "../db/schema-inspection.js";
3
- import { unsafePushTables } from "../stack/index.js";
3
+ import { unsafePushTables } from "../db/table-push.js";
4
4
  // Framework-level state for every registered projection. One row per qualified
5
5
  // projection name. Written by the rebuild machinery; read by the CLI + any
6
6
  // status dashboard. Lives alongside the events table as framework infra —
@@ -206,7 +206,7 @@ async function readProjectionRowForSearch(ctx, registry, entityName, entity, ten
206
206
  if (entity.softDelete && table["isDeleted"]) {
207
207
  where["isDeleted"] = false;
208
208
  }
209
- const row = await createTenantDb(baseDb, tenantId).fetchOne(table, where);
209
+ const row = await createTenantDb({ db: baseDb, tenantId }).fetchOne(table, where);
210
210
  if (!row)
211
211
  return undefined;
212
212
  const state = rehydrateCompoundTypes(row, entity);
@@ -303,7 +303,7 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
303
303
  out.err(` ${migrationsDir} fehlt — erst schema generate <name>.`);
304
304
  return 1;
305
305
  }
306
- const { db, close } = createDbConnection(dbUrl);
306
+ const { db, close } = createDbConnection({ url: dbUrl });
307
307
  let wiring;
308
308
  try {
309
309
  const result = await runMigrationsFromDir(db, migrationsDir);
@@ -386,7 +386,7 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
386
386
  out.err(` ${migrationsDir} fehlt — erst schema generate <name>.`);
387
387
  return 1;
388
388
  }
389
- const { db, close } = createDbConnection(dbUrl);
389
+ const { db, close } = createDbConnection({ url: dbUrl });
390
390
  try {
391
391
  const result = await baselineMigrations(db, loadMigrationsFromDir(migrationsDir));
392
392
  out.log("");
@@ -418,7 +418,7 @@ export async function runSchemaCli(argv, appCwd, out, options = {}) {
418
418
  return 0;
419
419
  }
420
420
  const local = loadMigrationsFromDir(migrationsDir);
421
- const { db, close } = createDbConnection(dbUrl);
421
+ const { db, close } = createDbConnection({ url: dbUrl });
422
422
  try {
423
423
  // Frische DB ohne je gelaufenes `kumiko schema apply` → tracking-table
424
424
  // fehlt = "nichts applied". Connection-/Permission-Fehler dagegen
@@ -9,7 +9,7 @@
9
9
  // that still carry the subject key in encrypted columns.
10
10
  import { quoteIdent, subjectCiphertextLikePattern } from "../crypto/ciphertext-pattern.js";
11
11
  import { isSelfPiiField } from "../crypto/is-self-pii-field.js";
12
- import { collectSearchableSubjectFields } from "../crypto/subject-resolver.js";
12
+ import { collectSearchableSubjectFields, isRecordKeyedField } from "../crypto/subject-resolver.js";
13
13
  import { resolveTableName } from "../db/entity-table-meta.js";
14
14
  import { executeRawQueryRead } from "../db/queries/raw-sql.js";
15
15
  import { columnNamesOf, tableExists } from "../db/schema-inspection.js";
@@ -63,20 +63,30 @@ function tenantOwnershipPredicates(entity, searchableFields, tenantId, nextParam
63
63
  return finalizePredicateParts(parts, params);
64
64
  }
65
65
  function recordOwnershipPredicates(entity, searchableFields, entityName, subjectEntity, subjectId, nextParam) {
66
- const parts = [];
67
- const params = [];
68
- let recordIdN;
66
+ const ownerlessColumns = new Set();
67
+ let hasRecordOwnedField = false;
69
68
  for (const fieldName of searchableFields) {
70
69
  const field = entity.fields[fieldName];
71
- if (!field)
70
+ if (!field || entityName !== subjectEntity || !isRecordKeyedField(field))
72
71
  continue;
73
- const isMatchingRecordField = entityName === subjectEntity && "recordOwned" in field && field.recordOwned === true;
74
- if (isMatchingRecordField && recordIdN === undefined) {
75
- recordIdN = nextParam();
76
- params.push(subjectId);
77
- parts.push(`${quoteIdent("id")} = $${recordIdN}`);
72
+ if ("recordOwned" in field && field.recordOwned === true) {
73
+ hasRecordOwnedField = true;
74
+ }
75
+ else if ("userOwned" in field && field.userOwned !== undefined) {
76
+ ownerlessColumns.add(toSnakeCase(field.userOwned.ownerField));
78
77
  }
79
78
  }
79
+ if (!hasRecordOwnedField && ownerlessColumns.size === 0)
80
+ return null;
81
+ const idN = nextParam();
82
+ const parts = [];
83
+ if (hasRecordOwnedField)
84
+ parts.push(`${quoteIdent("id")} = $${idN}`);
85
+ // whenAbsent: "self" keys a row on itself only while its owner is empty
86
+ for (const col of ownerlessColumns) {
87
+ parts.push(`(${quoteIdent("id")} = $${idN} AND (${quoteIdent(col)} IS NULL OR ${quoteIdent(col)}::text = ''))`);
88
+ }
89
+ const params = [subjectId];
80
90
  return finalizePredicateParts(parts, params);
81
91
  }
82
92
  /** Build OR predicates for rows owned by `subject` (id / ownerField / tenant_id). */
@@ -1,2 +1,3 @@
1
1
  export { type EventStoreSeedExisting, type RunEventStoreSeedOptions, runEventStoreSeed, } from "./entity-seed.js";
2
+ export { createTestUser, TestUsers, testTenantId, testUserId } from "./seed-users.js";
2
3
  export { DEFAULT_SEED_IF_EXISTS, type SeedIfExists } from "./types.js";
@@ -1,2 +1,3 @@
1
1
  export { runEventStoreSeed, } from "./entity-seed.js";
2
+ export { createTestUser, TestUsers, testTenantId, testUserId } from "./seed-users.js";
2
3
  export { DEFAULT_SEED_IF_EXISTS } from "./types.js";
@@ -1,9 +1,10 @@
1
- export { type CreateTestDbOptions, createTestDb, type TestDb, } from "./db.js";
2
- export { drainEventConsumers } from "./drain-event-consumers.js";
3
- export { createEventCollector, type EventCollector } from "./event-collector.js";
4
- export { pushEntityProjectionTables } from "./push-entity-projection-tables.js";
5
- export { createTestRedis, type TestRedis } from "./redis.js";
6
- export { createRequestHelper, type RequestHelper } from "./request-helper.js";
7
- export { resetEventStore, unsafeCreateEntityTable, unsafeEnsureEntityTable, unsafePushTables, } from "./table-helpers.js";
8
- export { setupTestStack, type TestStack, type TestStackOptions } from "./test-stack.js";
9
- export { createTestUser, TestUsers, testTenantId, testUserId, } from "./test-users.js";
1
+ /**
2
+ * @deprecated Moved to `@cosmicdrift/kumiko-testing/stack`. Run `bun kumiko-upgrade --apply`
3
+ * to rewrite imports. Removed in the first release after 2026-10-29.
4
+ */
5
+ export * from "@cosmicdrift/kumiko-testing/stack";
6
+ /**
7
+ * @deprecated Moved to `@cosmicdrift/kumiko-framework/seeding`. Run `bun kumiko-upgrade --apply`
8
+ * to rewrite imports. Removed in the first release after 2026-10-29.
9
+ */
10
+ export { createTestUser, TestUsers, testTenantId, testUserId } from "../seeding/seed-users.js";
@@ -1,19 +1,11 @@
1
- // Runtime-safe Stack-Builder. Was hier liegt, wird vom dev-server zum Hochfahren
2
- // einer kompletten Kumiko-Instanz genutzt — DB, Redis, Hono-App, Dispatcher,
3
- // SSE-Broker. Die Files heißen historisch `test*` (createTestDb,
4
- // setupTestStack, TestUsers, …), bedienen aber heute Dev- UND Test-Code: das
5
- // ist genau derselbe Hochfahr-Pfad, nur einmal mit ephemeral-DB (test) und
6
- // einmal mit persistent-DB (dev).
7
- //
8
- // Wichtig: dieses Modul darf KEINE vitest-Imports enthalten und keine
9
- // Vitest-only Helper transitiv ziehen — sonst crasht jedes Tooling, das den
10
- // dev-server unter Node lädt (drizzle-kit, build-scripts).
11
- export { createTestDb, } from "./db.js";
12
- export { drainEventConsumers } from "./drain-event-consumers.js";
13
- export { createEventCollector } from "./event-collector.js";
14
- export { pushEntityProjectionTables } from "./push-entity-projection-tables.js";
15
- export { createTestRedis } from "./redis.js";
16
- export { createRequestHelper } from "./request-helper.js";
17
- export { resetEventStore, unsafeCreateEntityTable, unsafeEnsureEntityTable, unsafePushTables, } from "./table-helpers.js";
18
- export { setupTestStack } from "./test-stack.js";
19
- export { createTestUser, TestUsers, testTenantId, testUserId, } from "./test-users.js";
1
+ // @runtime test
2
+ /**
3
+ * @deprecated Moved to `@cosmicdrift/kumiko-testing/stack`. Run `bun kumiko-upgrade --apply`
4
+ * to rewrite imports. Removed in the first release after 2026-10-29.
5
+ */
6
+ export * from "@cosmicdrift/kumiko-testing/stack";
7
+ /**
8
+ * @deprecated Moved to `@cosmicdrift/kumiko-framework/seeding`. Run `bun kumiko-upgrade --apply`
9
+ * to rewrite imports. Removed in the first release after 2026-10-29.
10
+ */
11
+ export { createTestUser, TestUsers, testTenantId, testUserId } from "../seeding/seed-users.js";
@@ -1,49 +1,5 @@
1
- import type * as z from "zod";
2
- import { type Registry } from "../engine/index.js";
3
- export type E2ETestSpec = {
4
- readonly kind: "list-renders";
5
- readonly screenQn: string;
6
- readonly title: string;
7
- readonly urlPath: string;
8
- } | {
9
- readonly kind: "list-has-fixture-row";
10
- readonly screenQn: string;
11
- readonly title: string;
12
- readonly urlPath: string;
13
- readonly writeHandlerQn: string;
14
- readonly fixture: Readonly<Record<string, unknown>>;
15
- readonly identifyingValue: string;
16
- } | {
17
- readonly kind: "edit-validates-required";
18
- readonly screenQn: string;
19
- readonly title: string;
20
- readonly urlPath: string;
21
- readonly requiredFields: readonly string[];
22
- } | {
23
- readonly kind: "edit-save-persists";
24
- readonly screenQn: string;
25
- readonly title: string;
26
- readonly urlPath: string;
27
- readonly listUrlPath?: string;
28
- readonly fills: readonly EditFillOp[];
29
- readonly identifyingValue: string;
30
- readonly identifyingField: string;
31
- };
32
- export type EditFillOp = {
33
- readonly kind: "fill";
34
- readonly field: string;
35
- readonly value: string;
36
- } | {
37
- readonly kind: "check";
38
- readonly field: string;
39
- readonly value: boolean;
40
- } | {
41
- readonly kind: "select";
42
- readonly field: string;
43
- readonly value: string;
44
- };
45
- export type E2EGeneratorOptions = {
46
- readonly tenantPlaceholder?: string;
47
- };
48
- export declare function generateE2ESpec(registry: Registry, options?: E2EGeneratorOptions): readonly E2ETestSpec[];
49
- export declare function generateZodFixture(schema: z.ZodTypeAny): unknown;
1
+ /**
2
+ * @deprecated Moved to `@cosmicdrift/kumiko-testing/helpers/e2e-generator`. Run
3
+ * `bun kumiko-upgrade --apply` to rewrite imports. Removed in the first release after 2026-10-29.
4
+ */
5
+ export * from "@cosmicdrift/kumiko-testing/helpers/e2e-generator";