@cosmicdrift/kumiko-framework 0.385.0 → 0.387.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 (298) hide show
  1. package/LICENSE +21 -57
  2. package/README.md +2 -2
  3. package/agents/widgets.md +39 -39
  4. package/dist/api/api-constants.js +2 -2
  5. package/dist/api/auth-middleware.js +7 -7
  6. package/dist/api/auth-rate-limiters.js +7 -3
  7. package/dist/api/auth-routes-mfa.js +1 -1
  8. package/dist/api/auth-routes-session.js +1 -1
  9. package/dist/api/auth-routes-shared.js +7 -3
  10. package/dist/api/auth-routes-signup.js +1 -1
  11. package/dist/api/client-ip.js +3 -1
  12. package/dist/api/extra-route.js +1 -1
  13. package/dist/api/pii-leak-guard.js +4 -2
  14. package/dist/api/readiness.js +5 -5
  15. package/dist/api/redis-sse-broker.js +8 -6
  16. package/dist/api/request-context.d.ts +2 -0
  17. package/dist/api/request-context.js +16 -7
  18. package/dist/api/routes.js +2 -2
  19. package/dist/api/server.js +34 -33
  20. package/dist/api/sse-broker.js +1 -1
  21. package/dist/api/sse-route.d.ts +13 -13
  22. package/dist/api/sse-route.js +15 -15
  23. package/dist/auth/index.js +4 -4
  24. package/dist/auth/roles.d.ts +4 -4
  25. package/dist/auth/roles.js +24 -24
  26. package/dist/compliance/duration-spec.js +18 -20
  27. package/dist/compliance/index.js +2 -3
  28. package/dist/compliance/override-schema.d.ts +4 -4
  29. package/dist/compliance/override-schema.js +19 -22
  30. package/dist/compliance/profiles.d.ts +26 -29
  31. package/dist/compliance/profiles.js +62 -69
  32. package/dist/compliance/sub-processors.d.ts +28 -30
  33. package/dist/compliance/sub-processors.js +17 -21
  34. package/dist/consumer-cli.js +4 -4
  35. package/dist/crypto/blind-index.js +18 -17
  36. package/dist/crypto/ciphertext-pattern.js +1 -1
  37. package/dist/crypto/event-pii.js +3 -3
  38. package/dist/crypto/kek-source.d.ts +1 -1
  39. package/dist/crypto/kek-source.js +3 -2
  40. package/dist/crypto/kms-adapter.d.ts +1 -1
  41. package/dist/crypto/kms-adapter.js +4 -6
  42. package/dist/crypto/kms-wiring.d.ts +1 -2
  43. package/dist/crypto/kms-wiring.js +2 -2
  44. package/dist/crypto/pg-kms-adapter.js +2 -6
  45. package/dist/crypto/pii-field-encryption.js +4 -4
  46. package/dist/crypto/subject-resolver.d.ts +1 -1
  47. package/dist/crypto/subject-resolver.js +2 -2
  48. package/dist/db/apply-entity-event.d.ts +6 -6
  49. package/dist/db/apply-entity-event.js +55 -55
  50. package/dist/db/blind-index-cleanup.js +7 -7
  51. package/dist/db/collect-table-metas.js +10 -11
  52. package/dist/db/compound-types.d.ts +1 -1
  53. package/dist/db/compound-types.js +12 -12
  54. package/dist/db/cross-tenant-reads.d.ts +8 -0
  55. package/dist/db/cross-tenant-reads.js +23 -0
  56. package/dist/db/cursor.js +3 -4
  57. package/dist/db/dialect.d.ts +2 -2
  58. package/dist/db/dialect.js +2 -2
  59. package/dist/db/eagerload.d.ts +3 -3
  60. package/dist/db/eagerload.js +21 -12
  61. package/dist/db/entity-table-meta.d.ts +2 -2
  62. package/dist/db/entity-table-meta.js +4 -4
  63. package/dist/db/event-store-executor-context.d.ts +1 -1
  64. package/dist/db/event-store-executor-context.js +13 -13
  65. package/dist/db/event-store-executor-read.js +27 -28
  66. package/dist/db/event-store-executor-write.js +65 -70
  67. package/dist/db/located-timestamp.d.ts +11 -11
  68. package/dist/db/located-timestamp.js +29 -30
  69. package/dist/db/migrate-generator.d.ts +1 -1
  70. package/dist/db/migrate-generator.js +26 -19
  71. package/dist/db/migrate-runner.js +25 -25
  72. package/dist/db/money.d.ts +6 -6
  73. package/dist/db/money.js +29 -27
  74. package/dist/db/queries/backfill-pii.js +5 -5
  75. package/dist/db/queries/ddl.js +1 -1
  76. package/dist/db/queries/es-ops.d.ts +1 -1
  77. package/dist/db/queries/es-ops.js +1 -1
  78. package/dist/db/queries/event-consumer.js +3 -3
  79. package/dist/db/queries/event-store.js +4 -2
  80. package/dist/db/queries/projection-rebuild.js +3 -3
  81. package/dist/db/queries/raw-sql.d.ts +2 -2
  82. package/dist/db/queries/raw-sql.js +2 -2
  83. package/dist/db/queries/shadow-swap.js +19 -19
  84. package/dist/db/queries/test-stack.d.ts +4 -1
  85. package/dist/db/queries/test-stack.js +15 -2
  86. package/dist/db/query.d.ts +1 -1
  87. package/dist/db/query.js +6 -6
  88. package/dist/db/rebuild-marker.js +20 -21
  89. package/dist/db/replay-migration-sql.js +2 -2
  90. package/dist/db/schema-inspection.js +4 -5
  91. package/dist/db/table-builder.d.ts +1 -2
  92. package/dist/db/table-builder.js +82 -82
  93. package/dist/db/tenant-db.d.ts +2 -2
  94. package/dist/db/tenant-db.js +9 -2
  95. package/dist/derivatives/delete-stored-file.js +5 -2
  96. package/dist/derivatives/derivatives-context.js +1 -1
  97. package/dist/engine/boot-validator/access-roles.js +4 -3
  98. package/dist/engine/boot-validator/api-ext.js +14 -14
  99. package/dist/engine/boot-validator/config-deps.js +7 -6
  100. package/dist/engine/boot-validator/detail-screens.js +3 -3
  101. package/dist/engine/boot-validator/entity-handler.js +60 -61
  102. package/dist/engine/boot-validator/gdpr-storage.js +4 -4
  103. package/dist/engine/boot-validator/i18n-keys.js +1 -1
  104. package/dist/engine/boot-validator/index.d.ts +2 -2
  105. package/dist/engine/boot-validator/index.js +20 -20
  106. package/dist/engine/boot-validator/nav.js +5 -5
  107. package/dist/engine/boot-validator/ownership.js +12 -9
  108. package/dist/engine/boot-validator/parent-ref.js +1 -1
  109. package/dist/engine/boot-validator/pii-retention.js +50 -57
  110. package/dist/engine/boot-validator/projection-list-screens.js +7 -7
  111. package/dist/engine/boot-validator/query-refs.js +1 -1
  112. package/dist/engine/boot-validator/screens-actions.js +15 -16
  113. package/dist/engine/boot-validator/screens-forms.js +10 -11
  114. package/dist/engine/boot-validator/screens-lists.js +14 -15
  115. package/dist/engine/boot-validator/screens.js +34 -40
  116. package/dist/engine/boot-validator/security-baseline.js +3 -2
  117. package/dist/engine/boot-validator/transfer-graph.js +2 -2
  118. package/dist/engine/boot-validator/workspaces.js +3 -3
  119. package/dist/engine/bridge-stub.js +3 -4
  120. package/dist/engine/build-app-schema.js +75 -75
  121. package/dist/engine/build-config-feature-schema.js +50 -50
  122. package/dist/engine/build-target.js +20 -23
  123. package/dist/engine/codemod/pipeline-codemod.js +20 -13
  124. package/dist/engine/config-helpers.js +4 -4
  125. package/dist/engine/constants.js +2 -2
  126. package/dist/engine/create-app.js +2 -2
  127. package/dist/engine/define-feature.js +12 -11
  128. package/dist/engine/define-handler.js +12 -11
  129. package/dist/engine/define-step.js +1 -1
  130. package/dist/engine/define-workflow.d.ts +5 -5
  131. package/dist/engine/define-workflow.js +6 -6
  132. package/dist/engine/entity-handlers.js +13 -14
  133. package/dist/engine/event-def.js +2 -2
  134. package/dist/engine/extension-names.d.ts +16 -17
  135. package/dist/engine/extension-names.js +37 -38
  136. package/dist/engine/extensions/escape-hatch-usage.js +1 -1
  137. package/dist/engine/extensions/tenant-data.js +2 -2
  138. package/dist/engine/extensions/user-data.d.ts +23 -24
  139. package/dist/engine/factories.d.ts +14 -13
  140. package/dist/engine/factories.js +38 -37
  141. package/dist/engine/feature-ast/entity-field-types.js +1 -1
  142. package/dist/engine/feature-ast/extractors/handlers.js +1 -1
  143. package/dist/engine/feature-ast/extractors/hooks.js +1 -1
  144. package/dist/engine/feature-ast/extractors/shared.d.ts +2 -3
  145. package/dist/engine/feature-ast/extractors/shared.js +2 -3
  146. package/dist/engine/feature-ast/parse.js +13 -13
  147. package/dist/engine/feature-ast/patch.js +1 -1
  148. package/dist/engine/feature-ast/pattern-change-schema.d.ts +8 -0
  149. package/dist/engine/feature-ast/pattern-change-schema.js +1 -1
  150. package/dist/engine/feature-ast/patterns.d.ts +2 -2
  151. package/dist/engine/feature-ast/render.js +2 -2
  152. package/dist/engine/feature-config-events-jobs.js +15 -12
  153. package/dist/engine/feature-entity-handlers.js +9 -5
  154. package/dist/engine/feature-manifest.d.ts +9 -9
  155. package/dist/engine/feature-manifest.js +6 -7
  156. package/dist/engine/feature-ui-extensions.d.ts +3 -3
  157. package/dist/engine/feature-ui-extensions.js +30 -22
  158. package/dist/engine/field-access.js +5 -6
  159. package/dist/engine/index.d.ts +1 -0
  160. package/dist/engine/index.js +4 -4
  161. package/dist/engine/object-form.d.ts +1 -0
  162. package/dist/engine/object-form.js +14 -4
  163. package/dist/engine/ownership.js +3 -3
  164. package/dist/engine/parse-ref-target.js +5 -7
  165. package/dist/engine/pattern-library/index.js +2 -2
  166. package/dist/engine/pattern-library/library.js +2 -2
  167. package/dist/engine/pattern-library/types.js +3 -3
  168. package/dist/engine/pipeline.js +2 -2
  169. package/dist/engine/project-app-schema-for-roles.js +1 -1
  170. package/dist/engine/projection-helpers.js +9 -9
  171. package/dist/engine/qualified-name.js +2 -2
  172. package/dist/engine/registry-facade.js +1 -1
  173. package/dist/engine/registry-ingest.js +12 -12
  174. package/dist/engine/registry-state.js +17 -19
  175. package/dist/engine/registry-validate.js +12 -12
  176. package/dist/engine/run-in.js +2 -2
  177. package/dist/engine/run-pipeline.js +7 -0
  178. package/dist/engine/schema-builder.js +40 -38
  179. package/dist/engine/screen-filter-ops.js +17 -20
  180. package/dist/engine/screen-helpers.js +4 -3
  181. package/dist/engine/state-machine.d.ts +12 -12
  182. package/dist/engine/state-machine.js +7 -7
  183. package/dist/engine/steps/_resolver-utils.js +4 -3
  184. package/dist/engine/steps/for-each.js +4 -4
  185. package/dist/engine/steps/unsafe-projection-delete.js +1 -1
  186. package/dist/engine/steps/unsafe-projection-upsert.js +4 -4
  187. package/dist/engine/steps/wait-for-event.js +4 -4
  188. package/dist/engine/steps/workflow-system-events.js +3 -3
  189. package/dist/engine/tier-resolver-extension.d.ts +29 -27
  190. package/dist/engine/tier-resolver-extension.js +26 -26
  191. package/dist/engine/transfer-adjacency.js +2 -2
  192. package/dist/engine/validate-projection-allowlist.js +7 -27
  193. package/dist/engine/walk-step-instances.d.ts +2 -0
  194. package/dist/engine/walk-step-instances.js +38 -0
  195. package/dist/engine/where-rule-lint.js +1 -1
  196. package/dist/engine/write-handler-qn-extract.js +1 -1
  197. package/dist/entrypoint/index.js +3 -3
  198. package/dist/env/index.d.ts +7 -6
  199. package/dist/env/index.js +10 -9
  200. package/dist/errors/transition-details.js +7 -7
  201. package/dist/errors/write-error-info.d.ts +6 -6
  202. package/dist/errors/write-error-info.js +6 -6
  203. package/dist/es-ops/context.d.ts +9 -9
  204. package/dist/es-ops/context.js +28 -28
  205. package/dist/es-ops/index.js +7 -9
  206. package/dist/es-ops/operations-schema.js +4 -10
  207. package/dist/es-ops/runner.d.ts +11 -11
  208. package/dist/es-ops/runner.js +49 -53
  209. package/dist/es-ops/types.d.ts +55 -55
  210. package/dist/es-ops/types.js +11 -11
  211. package/dist/event-store/admin-api.js +6 -7
  212. package/dist/event-store/errors.js +1 -1
  213. package/dist/event-store/event-store.js +2 -2
  214. package/dist/event-store/rebuild-dead-letter.js +1 -1
  215. package/dist/files/feature.js +9 -10
  216. package/dist/files/file-handle.js +5 -6
  217. package/dist/files/file-ref-entity.js +15 -15
  218. package/dist/files/file-ref-table.js +7 -8
  219. package/dist/files/file-routes.d.ts +7 -0
  220. package/dist/files/file-routes.js +11 -5
  221. package/dist/files/in-memory-provider.js +8 -9
  222. package/dist/files/local-provider.js +18 -25
  223. package/dist/files/provider-resolver.d.ts +7 -7
  224. package/dist/files/storage-tracking.js +2 -2
  225. package/dist/files/zip-stream.d.ts +12 -19
  226. package/dist/files/zip-stream.js +44 -63
  227. package/dist/i18n/required-surface-keys.d.ts +1 -1
  228. package/dist/i18n/required-surface-keys.js +8 -8
  229. package/dist/internal/stack-support.d.ts +1 -1
  230. package/dist/internal/stack-support.js +1 -1
  231. package/dist/jobs/job-runner-helpers.d.ts +1 -2
  232. package/dist/jobs/job-runner-helpers.js +8 -18
  233. package/dist/jobs/job-runner-schedulers.d.ts +1 -1
  234. package/dist/jobs/job-runner-schedulers.js +3 -4
  235. package/dist/jobs/job-runner.js +17 -18
  236. package/dist/lifecycle/lifecycle.js +3 -5
  237. package/dist/lifecycle/signal-handlers.js +5 -4
  238. package/dist/logging/utils.d.ts +2 -1
  239. package/dist/logging/utils.js +38 -10
  240. package/dist/migrations/index.js +1 -1
  241. package/dist/migrations/kumiko-drift.js +14 -13
  242. package/dist/migrations/pending-rebuilds.d.ts +21 -20
  243. package/dist/migrations/pending-rebuilds.js +27 -27
  244. package/dist/migrations/projection-table-index.d.ts +3 -3
  245. package/dist/migrations/projection-table-index.js +8 -8
  246. package/dist/observability/metric-validator.js +1 -1
  247. package/dist/observability/metrics-handle.js +3 -3
  248. package/dist/pipeline/append-event-core.js +11 -2
  249. package/dist/pipeline/cascade-handler.d.ts +1 -1
  250. package/dist/pipeline/dispatch-batch.js +2 -2
  251. package/dist/pipeline/dispatch-query.js +10 -10
  252. package/dist/pipeline/dispatch-shared.d.ts +1 -0
  253. package/dist/pipeline/dispatch-shared.js +42 -19
  254. package/dist/pipeline/dispatch-stream.js +1 -1
  255. package/dist/pipeline/dispatch-write.js +3 -3
  256. package/dist/pipeline/dispatcher-utils.d.ts +1 -1
  257. package/dist/pipeline/dispatcher-utils.js +1 -1
  258. package/dist/pipeline/event-consumer-state.js +2 -2
  259. package/dist/pipeline/event-dispatcher-delivery.js +1 -1
  260. package/dist/pipeline/event-dispatcher.js +17 -28
  261. package/dist/pipeline/irreversible-operation-gate.js +1 -1
  262. package/dist/pipeline/lifecycle-pipeline.js +9 -9
  263. package/dist/pipeline/member-read-only-transaction.js +1 -3
  264. package/dist/pipeline/msp-rebuild.js +1 -1
  265. package/dist/pipeline/projection-rebuild.js +13 -12
  266. package/dist/pipeline/system-hooks.js +39 -41
  267. package/dist/pipeline/system-identity-switch.js +1 -3
  268. package/dist/pipeline/tenant-timezone-cache.js +1 -1
  269. package/dist/random/generate.d.ts +29 -29
  270. package/dist/random/generate.js +30 -30
  271. package/dist/random/words.js +20 -20
  272. package/dist/rate-limit/middleware.js +9 -5
  273. package/dist/redis/client.js +2 -2
  274. package/dist/redis/pubsub-signal.js +14 -17
  275. package/dist/schema-cli.js +116 -22
  276. package/dist/search/index.js +4 -4
  277. package/dist/search/purge-subject.js +4 -5
  278. package/dist/search/reindex-entity.js +6 -9
  279. package/dist/time/boot-tz-warning.d.ts +2 -2
  280. package/dist/time/boot-tz-warning.js +8 -8
  281. package/dist/time/iana.js +10 -10
  282. package/dist/time/index.js +9 -9
  283. package/dist/time/legacy-date.d.ts +2 -2
  284. package/dist/time/legacy-date.js +6 -8
  285. package/dist/time/polyfill.js +1 -1
  286. package/dist/time/tz-context.d.ts +1 -1
  287. package/dist/time/tz-context.js +1 -1
  288. package/dist/ui-types/app-schema.js +7 -7
  289. package/dist/ui-types/list-row-meta.js +4 -4
  290. package/dist/upgrade-cli.js +10 -9
  291. package/dist/utils/compare.js +1 -1
  292. package/package.json +4 -4
  293. package/src/changes.json +54 -0
  294. package/src/scripts/codemod/crypto-shredding-testing-move.ts +2 -2
  295. package/src/scripts/codemod/migrate-cross-tenant.ts +1 -1
  296. package/src/scripts/codemod/migrate-open-to-all.ts +1 -1
  297. package/src/scripts/codemod/pii-personal-migration.ts +17 -8
  298. package/src/scripts/codemod/unprocessable-error-details-reason.ts +2 -2
@@ -1,11 +1,11 @@
1
1
  import { asRawClient, unsafeReadRetrying } from "../query.js";
2
2
  /** Escape hatch for caller-built SQL that may write (or lock). No closed-connection retry —
3
- * retrying an ambiguous write risks double-apply (#1358). Prefer {@link executeRawQueryRead}
3
+ * retrying an ambiguous write risks double-apply. Prefer {@link executeRawQueryRead}
4
4
  * for SELECT-only paths. */
5
5
  export async function executeRawQuery(db, sqlText, params = []) {
6
6
  return await asRawClient(db).unsafe(sqlText, params);
7
7
  }
8
- /** SELECT-only escape hatch with the #1163 closed-connection retry. Do not pass
8
+ /** SELECT-only escape hatch with the closed-connection retry. Do not pass
9
9
  * INSERT/UPDATE/DELETE — retry re-executes the statement. */
10
10
  // @wrapper-known semantic-alias
11
11
  export async function executeRawQueryRead(db, sqlText, params = []) {
@@ -64,10 +64,10 @@ export function rebuildMetaOrThrow(table, projectionName) {
64
64
  return meta;
65
65
  }
66
66
  // Fence against a rebuild running with a registry that does not match the
67
- // migrated live schema (#835): during a rolling deploy, a pod still running
67
+ // migrated live schema: during a rolling deploy, a pod still running
68
68
  // the previous build can pick up an async rebuild job; its shadow — built from
69
69
  // the stale EntityTableMeta — would swap away a freshly-migrated column
70
- // (recurrence class of #494). Compares COLUMN NAMES only; a type-/nullability-
70
+ // (a recurring failure). Compares COLUMN NAMES only; a type-/nullability-
71
71
  // only drift passes (schema regression, not data loss — the boot gate of the
72
72
  // next deploy catches it). A missing live table is fine: nothing to wipe.
73
73
  // Must run BEFORE buildShadowTable; columnNamesOf pins table_schema='public',
@@ -93,7 +93,7 @@ export async function assertLiveColumnsMatchMeta(db, meta, projectionName) {
93
93
  "Rebuilding would swap away the difference. Likely cause: this pod runs a build whose registry is behind (or ahead of) the applied migrations — rolling deploy in progress? — or DDL was applied by hand. " +
94
94
  "Rebuild aborted; retry from a pod whose code matches the migrated schema.");
95
95
  }
96
- // EntityTableMeta carries no RLS/policies, so the swap would silently drop them (#2907).
96
+ // EntityTableMeta carries no RLS/policies, so the swap would silently drop them.
97
97
  export async function assertLiveTableHasNoRowLevelSecurity(tx, tableName, projectionName) {
98
98
  const rows = await asRawClient(tx).unsafe(`SELECT c.relrowsecurity AS rls_enabled, c.relforcerowsecurity AS rls_forced,
99
99
  (SELECT count(*)::int FROM pg_policy p WHERE p.polrelid = c.oid) AS policy_count
@@ -158,7 +158,7 @@ const UNREACHABLE_SAMPLE_LIMIT = 20;
158
158
  // Runs INSIDE the rebuild tx, under the fence, before swapShadowIntoLive. A live
159
159
  // row whose aggregate id has NO event in the projection's source streams is
160
160
  // UNREACHABLE: no replay can ever reconstruct it, so the swap would silently
161
- // drop it. That is the #498 ghost — a row direct-inserted without ever emitting
161
+ // drop it. That is a ghost row direct-inserted without ever emitting
162
162
  // a .created event. The static CI guard cannot see it in data that already
163
163
  // exists in production, or on table identifiers it couldn't resolve; this
164
164
  // catches it at cutover and aborts (tx rolls back, live untouched).
@@ -169,14 +169,14 @@ const UNREACHABLE_SAMPLE_LIMIT = 20;
169
169
  // - a blind-index column recomputed to NULL after the subject's key is
170
170
  // shredded (GDPR erase) — the NULL is the intended end state;
171
171
  // - a `sensitive` column stripped from the event log by design;
172
- // - an archived stream that stops replaying (fw#832) — the row's wipe is the
172
+ // - an archived stream that stops replaying — the row's wipe is the
173
173
  // intended tombstone behavior, reported via backfill's `failed` list;
174
174
  // - a legacy column direct-written before its handler emitted events, healed
175
- // by the #494 backfill-then-rebuild flow.
175
+ // by the backfill-then-rebuild flow.
176
176
  // Checking event existence INCLUDING archived streams leaves every one of them
177
177
  // alone: those rows all have a real event, so they are not ghosts. Column-level
178
- // drift is a SEPARATE, non-blocking check — see countColumnDrift below (#916,
179
- // resolves the #722 open question: observe, don't block).
178
+ // drift is a SEPARATE, non-blocking check — see countColumnDrift below,
179
+ // which resolves the open question: observe, don't block.
180
180
  //
181
181
  // Implicit projections only (caller-gated). aggregate_id and the entity id are
182
182
  // both uuid, so the anti-join probes the events index without a cast.
@@ -298,8 +298,8 @@ async function assertBlindIndexKeyMatchesLive(raw, tableName, quotedTable, bidxC
298
298
  `untouched.`);
299
299
  }
300
300
  // The bidx column is schema-driven, not key-driven: it exists NULL in a
301
- // plaintext install and in the fw#1610 case (KMS configured, no index key),
302
- // both of which are correct as-is. Only a POPULATED column with no key
301
+ // plaintext install and in the case where KMS is configured but no index key
302
+ // is present, both of which are correct as-is. Only a POPULATED column with no key
303
303
  // configured in THIS process means the rebuild is about to overwrite proof
304
304
  // of a real index with NULL — that's the one provable data-loss case.
305
305
  export async function assertNoBlindIndexLoss(tx, tableName, meta, projectionName) {
@@ -331,29 +331,29 @@ export async function assertNoBlindIndexLoss(tx, tableName, meta, projectionName
331
331
  // live-vs-shadow divergence. A blind-index column (`<field>_bidx`) is
332
332
  // recomputed to NULL on GDPR key-shredding; the NULL is the intended end
333
333
  // state, not drift. Everything else that legitimately diverges (archived
334
- // streams, #494 backfill) either never reaches this comparison (archived rows
334
+ // streams, backfill) either never reaches this comparison (archived rows
335
335
  // are absent from the shadow entirely, see swapShadowIntoLive) or IS real
336
- // column drift that the #494 backfill-then-rebuild flow relies on replay to
336
+ // column drift that the backfill-then-rebuild flow relies on replay to
337
337
  // heal — reporting it (without blocking) is correct, not a false positive.
338
338
  const COLUMN_DRIFT_SAMPLE_LIMIT = 20;
339
339
  // Runs INSIDE the rebuild tx, in the same slot as assertNoUnreachableLiveRows
340
340
  // (after replay settles, before swapShadowIntoLive). Non-blocking counterpart
341
341
  // to the ghost-row guard: reports live rows whose column values differ from
342
- // the freshly-replayed shadow, WITHOUT aborting the swap (#916, resolves the
343
- // #722 open question in favor of observe-not-block).
342
+ // the freshly-replayed shadow, WITHOUT aborting the swap (it resolves the
343
+ // open question in favor of observe-not-block).
344
344
  //
345
345
  // Why non-blocking: a legacy column direct-written before its handler emitted
346
- // events (#494) diverges from replay by design — that divergence is exactly
346
+ // events diverges from replay by design — that divergence is exactly
347
347
  // what the backfill-then-rebuild flow relies on replay to heal. Failing hard
348
348
  // here would make rebuild mutually exclusive with that shipped healing path.
349
- // There is no reliable metadata to distinguish "#494 healing in progress" from
349
+ // There is no reliable metadata to distinguish "healing in progress" from
350
350
  // "someone else corrupted this row" short of an open-ended per-column policy
351
351
  // blocklist — wrong-by-default whenever a class is missed. So: surface it,
352
352
  // don't police it. The caller logs the result; ops decides.
353
353
  //
354
- // Caveat: sensitive CUSTOM fields still diverge until #972 (Subject-DEK
355
- // design) — regular sensitive fields carry event-payload ciphertext parity
356
- // post-#973 and don't drift. Both are reported the same as any other column
354
+ // Caveat: sensitive CUSTOM fields still diverge until the Subject-DEK
355
+ // design — regular sensitive fields carry event-payload ciphertext parity
356
+ // post-design and don't drift. Both are reported the same as any other column
357
357
  // drift; this is deliberate (see module comment above), not an oversight.
358
358
  //
359
359
  // Relies on assertLiveColumnsMatchMeta having already run: live/shadow/meta
@@ -2,5 +2,8 @@ import type { AnyDb } from "../query.ts";
2
2
  export { alterTableAddColumn, createIndexIfNotExists, executeDdlStatement } from "./ddl.ts";
3
3
  export declare function truncateTablesRestartIdentity(db: AnyDb, tableNames: readonly string[]): Promise<void>;
4
4
  export declare function databaseExists(db: AnyDb, dbName: string): Promise<boolean>;
5
- export declare function createDatabase(db: AnyDb, dbName: string): Promise<void>;
5
+ export declare function createDatabase(db: AnyDb, dbName: string, templateDbName?: string): Promise<void>;
6
+ export declare function renameDatabase(db: AnyDb, fromName: string, toName: string): Promise<void>;
7
+ export declare function listDatabaseNamesWithPrefix(db: AnyDb, prefix: string): Promise<readonly string[]>;
8
+ export declare function acquireSessionAdvisoryLock(db: AnyDb, key: string): Promise<void>;
6
9
  export declare function dropDatabaseIfExists(db: AnyDb, dbName: string): Promise<void>;
@@ -12,8 +12,21 @@ export async function databaseExists(db, dbName) {
12
12
  const rows = await asRawClient(db).unsafe(`SELECT EXISTS (SELECT 1 FROM pg_database WHERE datname = $1) AS exists`, [dbName]);
13
13
  return rows[0]?.exists === true;
14
14
  }
15
- export async function createDatabase(db, dbName) {
16
- await asRawClient(db).unsafe(`CREATE DATABASE ${quoteTableIdent(dbName)}`);
15
+ export async function createDatabase(db, dbName, templateDbName) {
16
+ const template = templateDbName === undefined ? "" : ` TEMPLATE ${quoteTableIdent(templateDbName)}`;
17
+ await asRawClient(db).unsafe(`CREATE DATABASE ${quoteTableIdent(dbName)}${template}`);
18
+ }
19
+ export async function renameDatabase(db, fromName, toName) {
20
+ await asRawClient(db).unsafe(`ALTER DATABASE ${quoteTableIdent(fromName)} RENAME TO ${quoteTableIdent(toName)}`);
21
+ }
22
+ export async function listDatabaseNamesWithPrefix(db, prefix) {
23
+ const rows = await asRawClient(db).unsafe(`SELECT datname FROM pg_database WHERE starts_with(datname, $1) ORDER BY datname`, [prefix]);
24
+ return rows.map((row) => row.datname);
25
+ }
26
+ // Session-scoped: CREATE DATABASE cannot run inside a transaction, so the
27
+ // xact-scoped variant cannot guard it. Released when the session closes.
28
+ export async function acquireSessionAdvisoryLock(db, key) {
29
+ await asRawClient(db).unsafe(`SELECT pg_advisory_lock(hashtext($1))`, [key]);
17
30
  }
18
31
  export async function dropDatabaseIfExists(db, dbName) {
19
32
  await asRawClient(db).unsafe(`DROP DATABASE IF EXISTS ${quoteTableIdent(dbName)}`);
@@ -24,7 +24,7 @@ export declare function hasBegin(client: RawClient): client is RawClientWithBegi
24
24
  export declare function asRawClient(db: unknown): RawClient;
25
25
  export declare function runnerCanBeginTransaction(db: unknown): boolean;
26
26
  export declare function runInSavepoint<T>(tx: ExecutorManagedRunner, fn: (sp: ExecutorManagedRunner) => Promise<T>): Promise<T>;
27
- export declare function runInSavepoint<T>(tx: unknown, fn: (sp: unknown) => Promise<T>): Promise<T>;
27
+ export declare function runInSavepoint<T>(tx: unknown, fn: (sp: DbRunner) => Promise<T>): Promise<T>;
28
28
  export declare function runInSavepointIfSupported<T>(db: ExecutorManagedRunner, fn: (sp: ExecutorManagedRunner) => Promise<T>): Promise<T>;
29
29
  export declare function runInSavepointIfSupported<T>(db: unknown, fn: (sp: DbRunner) => Promise<T>): Promise<T>;
30
30
  export type AnyDb = DbRunner | unknown;
package/dist/db/query.js CHANGED
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // `table` is an EntityTableMeta; the legacy table objects carrying it under a Symbol are accepted too.
15
15
  // Static import (not getTemporal()): coercion works without an installed
16
- // global and returns the same Temporal classes as the rest of the repo (#1480).
16
+ // global and returns the same Temporal classes as the rest of the repo.
17
17
  import { Temporal } from "@cosmicdrift/kumiko-types/temporal";
18
18
  import { isOneOf, propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
19
19
  import { requestContext } from "../api/request-context.js";
@@ -279,7 +279,7 @@ function isTemporalPlainDateTimeLike(v) {
279
279
  "toPlainDate" in v &&
280
280
  typeof v.toPlainDate === "function");
281
281
  }
282
- // Verified empirically (kumiko-framework#1924): a `date`
282
+ // Verified empirically: a `date`
283
283
  // column comes back as a native JS Date, but ALWAYS anchored at UTC
284
284
  // midnight regardless of process TZ — epochMilliseconds is stable across
285
285
  // zones, only the local wall-clock read of that Date would drift the day
@@ -339,7 +339,7 @@ function coerceDriverRow(row, info) {
339
339
  const out = {};
340
340
  let changed = false;
341
341
  for (const key of Object.keys(row)) {
342
- // Blind-index columns (#818) are pure lookup artifacts — the
342
+ // Blind-index columns are pure lookup artifacts — the
343
343
  // deterministic HMAC must never leak into caller/API responses.
344
344
  if ((key.endsWith("_bidx") || key.endsWith("Bidx")) && info.hasColumn(key)) {
345
345
  changed = true;
@@ -420,7 +420,7 @@ function isSqlExpression(v) {
420
420
  // as-is — no Instant detour. Postgres parses a DATE literal without ever
421
421
  // consulting the session timezone, so this is TZ-independent by construction
422
422
  // (verified empirically — see plainDateFromDriver above for the read side).
423
- // Compat cushion: pre-#1924 code (this field used to be an Instant) may still
423
+ // Compat cushion: code (this field used to be an Instant) may still
424
424
  // hand a Temporal.Instant into a where-filter or write on a `date` column —
425
425
  // same UTC anchor as plainDateFromDriver's read side, so a filter built with
426
426
  // `Temporal.Now.instant()` keeps working instead of binding a raw Instant the
@@ -515,7 +515,7 @@ function buildWhereClause(info, where, startIndex, qualifier) {
515
515
  // multiSelect (and any other jsonb-array-of-scalars column) stores an
516
516
  // array — a filter value is one option, not the whole array, so eq/ne/in
517
517
  // must check array containment (`@>`) instead of scalar `=`/`<>`/`IN`
518
- // against the jsonb column, which Postgres rejects outright (fw#2490).
518
+ // against the jsonb column, which Postgres rejects outright.
519
519
  // Mirrors event-store-executor-read.ts's applyFilter — keep in lock-step.
520
520
  function jsonbContainsAny(col, candidates) {
521
521
  if (candidates.length === 0)
@@ -703,7 +703,7 @@ function buildWhereClause(info, where, startIndex, qualifier) {
703
703
  conditions.push(`${ref(col)} = ${p.literal}`);
704
704
  }
705
705
  else {
706
- // Blind-index OR rewrite (#818): if the column has a bidx
706
+ // Blind-index OR rewrite: if the column has a bidx
707
707
  // counterpart (suffix convention `<col>_bidx`, framework-reserved)
708
708
  // and a key is configured, equality matches both arms —
709
709
  // plaintext legacy rows via the plaintext arm, encrypted rows
@@ -1,20 +1,18 @@
1
- // Rebuild-Marker für den drizzle-freien Migrations-Pfad.
1
+ // Rebuild marker for the drizzle-free migration path.
2
2
  //
3
- // `kumiko schema generate` schreibt neben jedes `NNNN_<name>.sql` einen
4
- // Sibling-Marker `NNNN_<name>.rebuild.json`, der die in dieser Migration
5
- // geänderten/neu angelegten Tabellen listet. `kumiko schema apply` liest den
6
- // Marker für jede frisch applizierte Migration und rebuildet die betroffenen
7
- // Projektionen.
3
+ // `kumiko schema generate` writes a sibling marker
4
+ // `NNNN_<name>.rebuild.json` next to each `NNNN_<name>.sql`, listing the tables
5
+ // changed or created by that migration. `kumiko schema apply` reads the marker
6
+ // for each newly applied migration and rebuilds the affected projections.
8
7
  //
9
- // Bewusst nur **Tabellennamen** (kein Projection-Name): der Generator ist
10
- // registry-frei (kennt die App-Projektionen nicht). Die Auflösung
11
- // Tabelle→Projection passiert app-seitig beim Apply via
12
- // `buildProjectionTableIndex(registry)`. Tabellen ohne zugehörige Projektion
13
- // werden dort einfach übersprungen.
8
+ // Deliberately stores only **table names** (not projection names): the generator
9
+ // is registry-free and does not know the app's projections. The table-to-
10
+ // projection resolution happens in the app during apply via
11
+ // `buildProjectionTableIndex(registry)`. Tables without a corresponding
12
+ // projection are simply skipped there.
14
13
  //
15
- // Marker werden zum generate-Zeitpunkt aus dem strukturierten `SchemaDiff`
16
- // geschrieben — nicht beim Apply aus dem (ggf. hand-editierten) SQL
17
- // re-derived.
14
+ // Markers are written at generate time from the structured `SchemaDiff`, rather
15
+ // than being re-derived during apply from the possibly hand-edited SQL.
18
16
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
19
17
  import { join } from "node:path";
20
18
  import { managedChangeRequiresRecreate } from "./migrate-generator.js";
@@ -29,12 +27,12 @@ function markerPathFor(migrationsDir, migrationId) {
29
27
  // (managedChangeRequiresRecreate: drop/NOT-NULL-w/o-default/unique-index/type-
30
28
  // or nullability-change) — then the table is emptied and must be re-derived from
31
29
  // events. A pure additive nullable column is an in-place `ADD COLUMN` ALTER that
32
- // already brings the table to the target state (same logic #181 applied to
30
+ // already brings the table to the target state (same logic applied to
33
31
  // index-/default-only changes). Rebuilding it anyway is wasted replay AND — the
34
32
  // bug this closes — a co-triggered rebuild can drop the fresh column (rebuild
35
33
  // runs from the rebuilding process's registry meta; on a rolling deploy an older
36
34
  // pod's meta lacks the column → shadow-swap wipes it → phantom migration + boot-
37
- // drift crash; see 0008_add_pending_deletion_request_id / #494 / #835).
35
+ // drift crash).
38
36
  //
39
37
  // If a NEW additive column genuinely needs value-backfill from historical events
40
38
  // (a column DERIVED from existing event fields), opt in explicitly by hand-adding
@@ -60,18 +58,19 @@ export function rebuildTablesFromDiff(diff) {
60
58
  }
61
59
  return [...names].sort();
62
60
  }
63
- // Schreibt `<sqlFilename ohne .sql>.rebuild.json`. Leere Tabellen-Liste →
64
- // kein Marker (z.B. reine Drop-Migration).
61
+ // Writes `<sqlFilename without .sql>.rebuild.json`. An empty table list writes no
62
+ // marker (for example, a drop-only migration).
65
63
  export function writeRebuildMarker(migrationsDir, sqlFilename, tables) {
66
- // skip: leere Tabellen-Liste → kein Marker (z.B. reine Drop-Migration).
64
+ // skip: empty table list means no marker is written (for example, for a
65
+ // migration that only drops tables).
67
66
  if (tables.length === 0)
68
67
  return;
69
68
  const migrationId = sqlFilename.replace(/\.sql$/, "");
70
69
  const marker = { version: MARKER_VERSION, tables };
71
70
  writeFileSync(markerPathFor(migrationsDir, migrationId), `${JSON.stringify(marker, null, 2)}\n`);
72
71
  }
73
- // Liest die Tabellen-Liste für eine applizierte Migration. Kein Marker /
74
- // kaputtes File → leere Liste (Migration ohne Projection-Impact).
72
+ // Reads the table list for an applied migration. A missing or malformed file
73
+ // yields an empty list (the migration has no projection impact).
75
74
  export function readRebuildMarker(migrationsDir, migrationId) {
76
75
  const path = markerPathFor(migrationsDir, migrationId);
77
76
  if (!existsSync(path))
@@ -70,7 +70,7 @@ function parseColumnNames(body) {
70
70
  // through to the fail-loud check as "unparsed". RENAME TO / RENAME COLUMN
71
71
  // are not listed here: they change identity and are applied explicitly.
72
72
  const SHAPE_NEUTRAL_ALTER_CLAUSE_RE = new RegExp([
73
- `ALTER COLUMN\\s+${IDENT}\\s+TYPE\\b`, // #1085 int/bigint-catchup fixes
73
+ `ALTER COLUMN\\s+${IDENT}\\s+TYPE\\b`, // int/bigint-catchup fixes
74
74
  `ALTER COLUMN\\s+${IDENT}\\s+(SET|DROP)\\s+NOT NULL\\b`,
75
75
  `ALTER COLUMN\\s+${IDENT}\\s+(SET DEFAULT\\b|DROP DEFAULT\\b)`,
76
76
  `(ADD|DROP)\\s+CONSTRAINT\\s+${IDENT}`,
@@ -269,7 +269,7 @@ function applyStatement(schema, statement, context) {
269
269
  // Reuses the real runner's file-discovery + statement-splitting
270
270
  // (loadMigrationsFromDir) instead of a second copy, so any future change to
271
271
  // sub-directory handling, numeric sort order, or the .sql filter can't
272
- // silently drift between the runner and this replay (#1522/9).
272
+ // silently drift between the runner and this replay.
273
273
  export function replayMigrationsDir(migrationsDir) {
274
274
  const schema = new Map();
275
275
  const migrations = loadMigrationsFromDir(migrationsDir, expandDestructiveMarkers);
@@ -26,11 +26,10 @@ function resolveUnsafeClient(db) {
26
26
  // await unsafePushTables(db, { events: eventsTable });
27
27
  export async function tableExists(db, fullyQualifiedName) {
28
28
  const unsafe = resolveUnsafeClient(db);
29
- // quote_ident-Round-trip auf SQL-Seite: ohne Quotes folded postgres
30
- // unquoted identifier case-insensitiv (myWidget → mywidget), während die
31
- // generierte DDL den Namen via quoteIdent("myWidget") → "myWidget" case-
32
- // preserved schreibt. quote_ident sorgt für identische Quotierung beidseits.
33
- // Schema-qualifizierte Namen (`public.events`) werden per Split einzeln quotet.
29
+ // SQL-side quote_ident round-trip: unquoted PostgreSQL identifiers are folded case-insensitively
30
+ // (`myWidget` → `mywidget`), while generated DDL preserves the case via
31
+ // `quoteIdent("myWidget")` → `"myWidget"`. quote_ident keeps both sides quoted identically.
32
+ // Schema-qualified names (`public.events`) are quoted one component at a time after splitting.
34
33
  const dotIdx = fullyQualifiedName.indexOf(".");
35
34
  const [sql, params] = dotIdx >= 0
36
35
  ? [
@@ -196,8 +196,7 @@ export type EntityRow<E extends EntityDefinition = EntityDefinition> = string ex
196
196
  export type { ExecutorManagedRunner, ExecutorManagedWriteCapability, Writable, WritableSchemaTable, WritableTable, } from "@cosmicdrift/kumiko-types/executor-brand";
197
197
  export type { EntityTenancy, TenancyBrand } from "@cosmicdrift/kumiko-types/tenancy-brand";
198
198
  type EntityTableTenancy<E extends EntityDefinition> = [NonNullable<E["tenancy"]>] extends [never] ? "tenant" : [NonNullable<E["tenancy"]>] extends ["global"] ? "global" : "tenant";
199
- export type EntityTable<E extends EntityDefinition = EntityDefinition> = TableColumns<// biome-ignore lint/suspicious/noExplicitAny: drizzle's internal table-config stays generic; we layer typed columns on top via the intersection below.
200
- any> & BaseColumnsType<E> & SoftDeleteColumnsType & ColumnsForEntity<E["fields"]> & TenancyBrand<EntityTableTenancy<E>>;
199
+ export type EntityTable<E extends EntityDefinition = EntityDefinition> = TableColumns & BaseColumnsType<E> & SoftDeleteColumnsType & ColumnsForEntity<E["fields"]> & TenancyBrand<EntityTableTenancy<E>>;
201
200
  export declare function buildBaseColumns(softDelete: boolean, idType?: "serial" | "uuid"): {
202
201
  id: ColumnBuilder<string> | ColumnBuilder<number>;
203
202
  tenantId: ColumnBuilder<string>;