@cosmicdrift/kumiko-framework 0.306.0 → 0.308.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 (178) hide show
  1. package/package.json +4 -4
  2. package/src/__tests__/anonymous-access.integration.test.ts +1 -1
  3. package/src/__tests__/error-contract.integration.test.ts +1 -1
  4. package/src/__tests__/field-access.integration.test.ts +1 -1
  5. package/src/__tests__/fixtures/migrate-open-to-all/manual-only.expected.ts +1 -1
  6. package/src/__tests__/fixtures/migrate-open-to-all/manual-only.input.ts +1 -1
  7. package/src/__tests__/fixtures/migrate-open-to-all/test-simple.expected.ts +1 -1
  8. package/src/__tests__/fixtures/migrate-open-to-all/test-simple.input.ts +1 -1
  9. package/src/__tests__/full-stack.integration.test.ts +1 -1
  10. package/src/__tests__/transition-guard.integration.test.ts +1 -1
  11. package/src/api/__tests__/api.test.ts +1 -1
  12. package/src/api/__tests__/batch.integration.test.ts +1 -1
  13. package/src/api/__tests__/body-limit.test.ts +1 -1
  14. package/src/api/__tests__/dispatcher-live.integration.test.ts +1 -1
  15. package/src/api/__tests__/extra-routes.integration.test.ts +1 -1
  16. package/src/api/__tests__/http-route-rate-limit.integration.test.ts +1 -1
  17. package/src/api/__tests__/nested-write-ownership.integration.test.ts +1 -1
  18. package/src/api/__tests__/nested-write.integration.test.ts +1 -1
  19. package/src/api/__tests__/pii-leak-guard.integration.test.ts +1 -1
  20. package/src/api/__tests__/redis-sse-broker.integration.test.ts +133 -0
  21. package/src/api/__tests__/request-locale.integration.test.ts +1 -1
  22. package/src/api/__tests__/server-boot-guards.test.ts +1 -1
  23. package/src/api/__tests__/server-error-logging.test.ts +1 -1
  24. package/src/api/__tests__/sse-broker.test.ts +144 -1
  25. package/src/api/__tests__/stream-escape-hatch.integration.test.ts +1 -1
  26. package/src/api/auth-routes.ts +1 -1
  27. package/src/api/redis-sse-broker.ts +57 -3
  28. package/src/api/request-context.ts +24 -0
  29. package/src/api/server.ts +11 -0
  30. package/src/api/sse-broker.ts +69 -12
  31. package/src/changes.json +63 -0
  32. package/src/compliance/override-schema.ts +1 -1
  33. package/src/crypto/__tests__/event-pii.test.ts +1 -1
  34. package/src/crypto/kms-adapter.ts +1 -1
  35. package/src/db/__tests__/tenant-db-where-merge.integration.test.ts +1 -1
  36. package/src/db/queries/event-consumer.ts +155 -9
  37. package/src/db/queries/event-store.ts +69 -0
  38. package/src/db/tenant-db.ts +43 -5
  39. package/src/derivatives/__tests__/derivatives-context.integration.test.ts +1 -1
  40. package/src/engine/__tests__/boot-validator-action-wiring.test.ts +1 -1
  41. package/src/engine/__tests__/boot-validator-dashboard.test.ts +1 -1
  42. package/src/engine/__tests__/boot-validator-nav-area.test.ts +1 -1
  43. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +1 -1
  44. package/src/engine/__tests__/boot-validator-projection-list.test.ts +1 -1
  45. package/src/engine/__tests__/boot-validator-query-output-schema.test.ts +1 -1
  46. package/src/engine/__tests__/boot-validator-query-refs.test.ts +1 -1
  47. package/src/engine/__tests__/boot-validator-ref-entity.test.ts +1 -1
  48. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +1 -1
  49. package/src/engine/__tests__/boot-validator.test.ts +35 -1
  50. package/src/engine/__tests__/build-app-schema.test.ts +1 -1
  51. package/src/engine/__tests__/build-config-feature-schema-secrets.test.ts +1 -1
  52. package/src/engine/__tests__/define-feature-entity-mapping.test.ts +1 -1
  53. package/src/engine/__tests__/engine.test.ts +1 -1
  54. package/src/engine/__tests__/event-helpers.test.ts +1 -1
  55. package/src/engine/__tests__/event-migration-declarative.test.ts +1 -1
  56. package/src/engine/__tests__/extend-entity-projection.test.ts +1 -1
  57. package/src/engine/__tests__/extends-registrar.test.ts +1 -1
  58. package/src/engine/__tests__/hook-phases.test.ts +1 -1
  59. package/src/engine/__tests__/lifecycle-hooks.test.ts +1 -1
  60. package/src/engine/__tests__/pipeline-engine.test.ts +1 -1
  61. package/src/engine/__tests__/pipeline-handler.integration.test.ts +1 -1
  62. package/src/engine/__tests__/pipeline-observability.integration.test.ts +1 -1
  63. package/src/engine/__tests__/pipeline-performance.integration.test.ts +1 -1
  64. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +1 -1
  65. package/src/engine/__tests__/post-query-hook.test.ts +1 -1
  66. package/src/engine/__tests__/projection-detail-actions.test.ts +1 -1
  67. package/src/engine/__tests__/projection-detail-singleton.test.ts +1 -1
  68. package/src/engine/__tests__/projection-detail-tabs.test.ts +1 -1
  69. package/src/engine/__tests__/read-steps-tenant-filter.integration.test.ts +1 -1
  70. package/src/engine/__tests__/registry.test.ts +1 -1
  71. package/src/engine/__tests__/screen.test.ts +1 -1
  72. package/src/engine/__tests__/validate-projection-allowlist.test.ts +1 -1
  73. package/src/engine/boot-validator/__tests__/access-declarations.test.ts +1 -1
  74. package/src/engine/boot-validator/__tests__/action-icon.test.ts +81 -0
  75. package/src/engine/boot-validator/__tests__/anonymous-rate-limit-required.test.ts +1 -1
  76. package/src/engine/boot-validator/__tests__/no-all-role-in-handler-access.test.ts +1 -1
  77. package/src/engine/boot-validator/__tests__/parent-ref.test.ts +1 -1
  78. package/src/engine/boot-validator/__tests__/rate-limit-disabled-reason.test.ts +1 -1
  79. package/src/engine/boot-validator/screens.ts +83 -0
  80. package/src/engine/define-handler.ts +2 -1
  81. package/src/engine/entity-handlers.ts +2 -1
  82. package/src/engine/feature-ast/__tests__/pattern-change-schema.test.ts +1 -1
  83. package/src/engine/feature-ast/pattern-change-schema.ts +1 -1
  84. package/src/engine/feature-builder-state.ts +2 -1
  85. package/src/engine/feature-config-events-jobs.ts +2 -1
  86. package/src/engine/feature-entity-handlers.ts +2 -1
  87. package/src/engine/schema-builder.ts +1 -1
  88. package/src/entrypoint/__tests__/entrypoint-attach-dispatcher.integration.test.ts +1 -1
  89. package/src/entrypoint/__tests__/entrypoint-job-wiring.integration.test.ts +1 -1
  90. package/src/entrypoint/__tests__/split-deploy.integration.test.ts +1 -1
  91. package/src/env/__tests__/compose-env-schema.test.ts +1 -1
  92. package/src/env/__tests__/dry-run.test.ts +1 -1
  93. package/src/env/__tests__/parse-env-dry-run.test.ts +1 -1
  94. package/src/env/_zod-introspect.ts +1 -1
  95. package/src/env/dry-run.ts +1 -1
  96. package/src/env/index.ts +1 -1
  97. package/src/errors/__tests__/classes.test.ts +1 -1
  98. package/src/es-ops/__tests__/runner.integration.test.ts +1 -1
  99. package/src/event-store/__tests__/backfill-pii.integration.test.ts +1 -1
  100. package/src/event-store/__tests__/event-attribution.integration.test.ts +54 -4
  101. package/src/event-store/__tests__/upcaster.integration.test.ts +1 -1
  102. package/src/event-store/admin-api.ts +5 -0
  103. package/src/event-store/event-store.ts +16 -7
  104. package/src/jobs/__tests__/job-backoff.integration.test.ts +1 -1
  105. package/src/jobs/__tests__/job-event-trigger.integration.test.ts +1 -1
  106. package/src/jobs/__tests__/job-multi-trigger.integration.test.ts +1 -1
  107. package/src/jobs/__tests__/job-public-intake-origin.integration.test.ts +536 -0
  108. package/src/jobs/__tests__/job-retention.integration.test.ts +1 -1
  109. package/src/jobs/__tests__/job-write-as.integration.test.ts +1 -1
  110. package/src/jobs/__tests__/jobs.integration.test.ts +1 -1
  111. package/src/jobs/job-runner.ts +61 -6
  112. package/src/observability/__tests__/metrics-wiring.test.ts +1 -1
  113. package/src/observability/__tests__/observability.integration.test.ts +1 -1
  114. package/src/observability/metrics-wiring.ts +1 -1
  115. package/src/pipeline/__tests__/archive-stream.integration.test.ts +1 -1
  116. package/src/pipeline/__tests__/causation-chain.integration.test.ts +1 -1
  117. package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +1 -1
  118. package/src/pipeline/__tests__/ctx-systemdb.integration.test.ts +1 -1
  119. package/src/pipeline/__tests__/dispatcher-utils.test.ts +8 -0
  120. package/src/pipeline/__tests__/dispatcher.test.ts +1 -1
  121. package/src/pipeline/__tests__/domain-events-projections.integration.test.ts +1 -1
  122. package/src/pipeline/__tests__/escape-hatch-audit.integration.test.ts +1 -1
  123. package/src/pipeline/__tests__/escape-hatch.integration.test.ts +1 -1
  124. package/src/pipeline/__tests__/event-dedup.integration.test.ts +1 -1
  125. package/src/pipeline/__tests__/event-define-event-strict.integration.test.ts +1 -1
  126. package/src/pipeline/__tests__/event-dispatcher-batch-delivery.test.ts +178 -0
  127. package/src/pipeline/__tests__/event-dispatcher-batch-handler.integration.test.ts +143 -0
  128. package/src/pipeline/__tests__/event-dispatcher-commit-order.integration.test.ts +278 -0
  129. package/src/pipeline/__tests__/event-dispatcher-delivery-max-attempts.test.ts +1 -0
  130. package/src/pipeline/__tests__/event-dispatcher-idle.integration.test.ts +94 -0
  131. package/src/pipeline/__tests__/event-dispatcher-lifecycle.integration.test.ts +6 -6
  132. package/src/pipeline/__tests__/event-dispatcher-per-consumer-turns.integration.test.ts +126 -0
  133. package/src/pipeline/__tests__/event-dispatcher-start-from.integration.test.ts +103 -3
  134. package/src/pipeline/__tests__/fetch-for-writing.integration.test.ts +1 -1
  135. package/src/pipeline/__tests__/idempotency-transient-failure.integration.test.ts +1 -1
  136. package/src/pipeline/__tests__/job-trigger-consumer.integration.test.ts +1 -1
  137. package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +1 -1
  138. package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +1 -1
  139. package/src/pipeline/__tests__/msp-multi-hop.integration.test.ts +1 -1
  140. package/src/pipeline/__tests__/msp-rebuild.integration.test.ts +116 -1
  141. package/src/pipeline/__tests__/multi-stream-projection.integration.test.ts +1 -1
  142. package/src/pipeline/__tests__/pending-gap-ranges.test.ts +94 -0
  143. package/src/pipeline/__tests__/post-query-hook.integration.test.ts +1 -1
  144. package/src/pipeline/__tests__/public-intake-runtime-gate.integration.test.ts +262 -3
  145. package/src/pipeline/__tests__/query-projection.integration.test.ts +1 -1
  146. package/src/pipeline/__tests__/rate-limit-default.integration.test.ts +1 -1
  147. package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +1 -1
  148. package/src/pipeline/__tests__/search-consumer-named-events.integration.test.ts +1 -1
  149. package/src/pipeline/__tests__/system-identity-switch.integration.test.ts +1 -1
  150. package/src/pipeline/__tests__/try-append-event.integration.test.ts +1 -1
  151. package/src/pipeline/__tests__/unsafe-raw-escape-hatch.integration.test.ts +1 -1
  152. package/src/pipeline/dispatch-batch.ts +49 -19
  153. package/src/pipeline/dispatch-stream.ts +12 -3
  154. package/src/pipeline/dispatcher-utils.ts +21 -2
  155. package/src/pipeline/dispatcher.ts +71 -6
  156. package/src/pipeline/event-consumer-state.ts +26 -0
  157. package/src/pipeline/event-dispatcher-admin.ts +32 -5
  158. package/src/pipeline/event-dispatcher-delivery.ts +212 -65
  159. package/src/pipeline/event-dispatcher.ts +253 -49
  160. package/src/pipeline/index.ts +1 -0
  161. package/src/pipeline/msp-rebuild.ts +39 -1
  162. package/src/pipeline/pending-gap-ranges.ts +120 -0
  163. package/src/pipeline/system-hooks.ts +226 -64
  164. package/src/pipeline/write-origin.ts +31 -10
  165. package/src/rate-limit/__tests__/dispatcher-l3.integration.test.ts +1 -1
  166. package/src/search/__tests__/derive-search-adapter-config.test.ts +65 -0
  167. package/src/search/__tests__/meilisearch-adapter.integration.test.ts +91 -0
  168. package/src/search/__tests__/search-consumer-batch.integration.test.ts +306 -0
  169. package/src/search/derive-search-adapter-config.ts +27 -0
  170. package/src/search/index.ts +1 -0
  171. package/src/search/meilisearch-adapter.ts +96 -14
  172. package/src/secrets/__tests__/contains-secret.test.ts +1 -1
  173. package/src/stack/__tests__/request-helper.integration.test.ts +1 -1
  174. package/src/stack/__tests__/setup-test-stack-jobs.integration.test.ts +1 -1
  175. package/src/stack/__tests__/setup-test-stack-metrics.integration.test.ts +1 -1
  176. package/src/stack/test-stack.ts +1 -1
  177. package/src/testing/__tests__/e2e-generator.test.ts +1 -1
  178. package/src/testing/e2e-generator.ts +1 -1
@@ -4,7 +4,10 @@
4
4
  // into validateColumnRendererForm, splitting them would create a
5
5
  // same-folder require cycle.
6
6
 
7
+ import { resolveActionIcon } from "@cosmicdrift/kumiko-types/action-icon";
7
8
  import { NO_WIDGET_FIELD_TYPES } from "@cosmicdrift/kumiko-types/fields";
9
+ import type { IconKey } from "@cosmicdrift/kumiko-types/nav-icon";
10
+ import { NAV_ICON_KEYS } from "@cosmicdrift/kumiko-types/nav-icon";
8
11
  import { rowMetaFieldNames } from "../../db/table-builder";
9
12
  import { LIST_ROW_META_COLUMNS } from "../../ui-types/list-row-meta";
10
13
  import { parseRefTarget } from "../parse-ref-target";
@@ -319,6 +322,41 @@ function validateWizardLayout(
319
322
  // are NOT validated here — the r.uiComponent registry that would resolve
320
323
  // them ships in M4/M5. Until then those are kept opaque on purpose.
321
324
 
325
+ // every action rendered in a Card title row (screen-level
326
+ // `actions`, section-level `actions`, `emptyState.action`) must draw an
327
+ // icon — declared or id-derived (resolveActionIcon, same resolver the
328
+ // renderer uses). Uncovered here: entityList/projectionList rowActions —
329
+ // those render inside a DataTable row, not a Card title row, so they keep
330
+ // today's "icon optional" behavior.
331
+ //
332
+ // Registration check (not just "resolved is not undefined"): action.icon is
333
+ // typed IconKey, a closed union — but that only protects call sites this
334
+ // repo's own `tsc --build` actually covers (samples and external consumers
335
+ // aren't in that project graph). The renderer's actionIconFor
336
+ // (renderer-web/src/primitives/index.tsx) already falls back to "no icon"
337
+ // for an unregistered key instead of crashing — this check makes the same
338
+ // gap fail loudly at boot instead of silently at render.
339
+ function isRegisteredIcon(icon: IconKey): boolean {
340
+ return (NAV_ICON_KEYS as readonly string[]).includes(icon);
341
+ }
342
+
343
+ export function validateActionHasIcon(
344
+ featureName: string,
345
+ screenId: string,
346
+ screenType: "projectionDetail" | "entityEdit",
347
+ context: string,
348
+ action: { readonly id: string; readonly icon?: IconKey },
349
+ ): void {
350
+ const resolved = resolveActionIcon(action.id, action.icon);
351
+ if (resolved === undefined || !isRegisteredIcon(resolved)) {
352
+ throw new Error(
353
+ `[Feature ${featureName}] Screen "${screenId}" (${screenType}) ${context} "${action.id}" has no ` +
354
+ `resolvable icon — set icon to a key registered in NAV_ICONS, or use an id that resolves one of ` +
355
+ `its kebab segments via the default icon map (resolveActionIcon).`,
356
+ );
357
+ }
358
+ }
359
+
322
360
  // Tier 2.7e-3: deklarative Feld-Referenzen einer Action gegen die Entity-
323
361
  // Felder pinnen — ein Tippfehler in pick/map-Quellfeldern oder
324
362
  // visible.field erzeugte sonst still `undefined` im Payload bzw. dauerhaft
@@ -1137,6 +1175,17 @@ export function validateScreens(
1137
1175
  }
1138
1176
  if (screen.layout.mode === "tabs") {
1139
1177
  validateTabSections(feature.name, screenId, "projectionDetail", screen.layout.sections);
1178
+ for (const section of screen.layout.sections) {
1179
+ const isFieldsSection = section.kind === undefined || section.kind === "fields";
1180
+ if (isFieldsSection && section.groups !== undefined && section.actions !== undefined) {
1181
+ throw new Error(
1182
+ `[Feature ${feature.name}] Screen "${screenId}" (projectionDetail) section "${section.id}" ` +
1183
+ `declares both groups and actions in tabs mode — actions has no single group's Card to sit ` +
1184
+ `in, and an extra title-less Card around the group cards would nest a Card inside a Card. ` +
1185
+ `Move the actions onto one group's title, or drop groups for a flat fields grid.`,
1186
+ );
1187
+ }
1188
+ }
1140
1189
  }
1141
1190
  if (screen.metrics !== undefined) {
1142
1191
  for (const metric of screen.metrics) {
@@ -1166,6 +1215,27 @@ export function validateScreens(
1166
1215
  }
1167
1216
  }
1168
1217
  for (const section of screen.layout.sections) {
1218
+ const sectionLabel = section.title ?? section.id ?? "(untitled)";
1219
+ if (section.actions !== undefined) {
1220
+ for (const action of section.actions) {
1221
+ validateActionHasIcon(
1222
+ feature.name,
1223
+ screenId,
1224
+ "projectionDetail",
1225
+ `section "${sectionLabel}" action`,
1226
+ action,
1227
+ );
1228
+ }
1229
+ }
1230
+ if (section.kind === "relatedList" && section.emptyState?.action !== undefined) {
1231
+ validateActionHasIcon(
1232
+ feature.name,
1233
+ screenId,
1234
+ "projectionDetail",
1235
+ `section "${sectionLabel}" emptyState action`,
1236
+ section.emptyState.action,
1237
+ );
1238
+ }
1169
1239
  if (isExtensionEditSection(section)) {
1170
1240
  // projectionDetail is read-only (no composed form submit) — an
1171
1241
  // extension that persists through the host's Save button has
@@ -1402,6 +1472,7 @@ export function validateScreens(
1402
1472
  // a detail screen has no row to click.
1403
1473
  if (screen.actions !== undefined) {
1404
1474
  for (const action of screen.actions) {
1475
+ validateActionHasIcon(feature.name, screenId, "projectionDetail", "action", action);
1405
1476
  if (action.kind === "navigate" && action.rowClick === true) {
1406
1477
  throw new Error(
1407
1478
  `[Feature ${feature.name}] Screen "${qualifyEntityName(feature.name, "screen", screenId)}" ` +
@@ -1902,6 +1973,18 @@ export function validateScreens(
1902
1973
  );
1903
1974
  }
1904
1975
  for (const section of screen.layout.sections) {
1976
+ if (section.actions !== undefined) {
1977
+ const sectionLabel = section.title ?? section.id ?? "(untitled)";
1978
+ for (const action of section.actions) {
1979
+ validateActionHasIcon(
1980
+ feature.name,
1981
+ screenId,
1982
+ "entityEdit",
1983
+ `section "${sectionLabel}" action`,
1984
+ action,
1985
+ );
1986
+ }
1987
+ }
1905
1988
  if (isExtensionEditSection(section)) {
1906
1989
  if (section.component?.react === undefined && section.component?.native === undefined) {
1907
1990
  throw new Error(
@@ -1,6 +1,7 @@
1
1
  import type { CursorResult } from "@cosmicdrift/kumiko-types/cursor-types";
2
2
  import { ENTITY_CONVENTION_QUERY_BRAND } from "@cosmicdrift/kumiko-types/handlers";
3
- import type { ZodType, z } from "zod";
3
+ import type * as z from "zod";
4
+ import type { ZodType } from "zod";
4
5
  import type { ContainsSecret } from "../secrets/types";
5
6
  import { runPipeline } from "./run-pipeline";
6
7
  import type { HandlerContext, KumikoEventTypeMap, WriteEvent, WriteResult } from "./types";
@@ -1,5 +1,6 @@
1
1
  import { ENTITY_CONVENTION_QUERY_BRAND } from "@cosmicdrift/kumiko-types/handlers";
2
- import { type ZodType, z } from "zod";
2
+ import type { ZodType } from "zod";
3
+ import * as z from "zod";
3
4
  import type { DbRow } from "../db/connection";
4
5
  import {
5
6
  collectReferenceFields,
@@ -6,7 +6,7 @@
6
6
  import { describe, expect, test } from "bun:test";
7
7
  import { resolve } from "node:path";
8
8
  import { Project, type SourceFile } from "ts-morph";
9
- import type { z } from "zod";
9
+ import type * as z from "zod";
10
10
  import { parseFeatureFile, parseSourceFile } from "../parse";
11
11
  import { applyChanges, type PatternId } from "../patch";
12
12
  import {
@@ -22,7 +22,7 @@
22
22
  // additionally checked for field-type membership via the shared
23
23
  // `findUnknownEntityFieldTypes` helper (kept in sync with `extractEntity`).
24
24
 
25
- import { z } from "zod";
25
+ import * as z from "zod";
26
26
  import { type LifecycleHookType, LifecycleHookTypes } from "../constants";
27
27
  import type {
28
28
  ConfigKeyDefinition,
@@ -1,4 +1,5 @@
1
- import type { ZodType, z } from "zod";
1
+ import type * as z from "zod";
2
+ import type { ZodType } from "zod";
2
3
  import { LifecycleHookTypes } from "./constants";
3
4
  import type {
4
5
  AuthClaimsFn,
@@ -1,5 +1,6 @@
1
1
  import { normalizeEventPiiSubject } from "@cosmicdrift/kumiko-types/handlers";
2
- import { ZodObject, type ZodType, type z } from "zod";
2
+ import type * as z from "zod";
3
+ import { ZodObject, type ZodType } from "zod";
3
4
  import type { FeatureBuilderState } from "./feature-builder-state";
4
5
  import { resolveName } from "./handler-helpers";
5
6
  import { splitNamedDefinition, unwrapArrayForm } from "./object-form";
@@ -1,5 +1,6 @@
1
1
  import { ENTITY_CONVENTION_QUERY_BRAND } from "@cosmicdrift/kumiko-types/handlers";
2
- import type { ZodType, z } from "zod";
2
+ import type * as z from "zod";
3
+ import type { ZodType } from "zod";
3
4
  import { toTableName } from "../db/table-builder";
4
5
  import {
5
6
  isEntityConventionQueryHandler,
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import * as z from "zod";
2
2
  import { moneyPayloadToMinorUnits } from "../db/money";
3
3
  import { isValidIanaTimeZone } from "../time";
4
4
  import { assertUnreachable } from "../utils";
@@ -10,7 +10,7 @@
10
10
  // throws the #2043 stub).
11
11
 
12
12
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
13
- import { z } from "zod";
13
+ import * as 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";
@@ -13,7 +13,7 @@
13
13
  // regressing to the Welle-2.5 state.
14
14
 
15
15
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
16
- import { z } from "zod";
16
+ import * as z from "zod";
17
17
  import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
18
18
  import { createRegistry, defineFeature } from "../../engine";
19
19
  import { createArchivedStreamsTable } from "../../event-store";
@@ -13,7 +13,7 @@
13
13
  // fires with systemConsumers explicitly disabled).
14
14
 
15
15
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
16
- import { z } from "zod";
16
+ import * as 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";
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "bun:test";
2
- import { z } from "zod";
2
+ import * as z from "zod";
3
3
  import { defineFeature } from "../../engine/define-feature";
4
4
  import {
5
5
  camelCase,
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, it } from "bun:test";
2
- import { z } from "zod";
2
+ import * as z from "zod";
3
3
  import { defineFeature } from "../../engine/define-feature";
4
4
  import { prometheusMetricsEnvSchema } from "../../observability/metrics-wiring";
5
5
  import { renderDryRun } from "../dry-run";
@@ -2,7 +2,7 @@
2
2
  // undefined. parseEnvDryRun liefert stattdessen ein ehrliches Partial.
3
3
 
4
4
  import { describe, expect, test } from "bun:test";
5
- import { z } from "zod";
5
+ import * as z from "zod";
6
6
  import { parseEnvDryRun } from "../index";
7
7
 
8
8
  const schema = z.object({
@@ -6,7 +6,7 @@
6
6
  // All helpers accept the *runtime* Zod instance — TypeScript wrapper
7
7
  // (`z.ZodType`) and core (`$ZodType`) are the same object at runtime.
8
8
 
9
- import type { z } from "zod";
9
+ import type * as z from "zod";
10
10
 
11
11
  export type ZodDef = {
12
12
  readonly innerType?: z.ZodType;
@@ -8,7 +8,7 @@
8
8
  // The same schema introspection (`classifyField`, `readKumikoMeta`) used
9
9
  // by parseEnv drives the output here — single source of truth.
10
10
 
11
- import type { z } from "zod";
11
+ import type * as z from "zod";
12
12
  import { zodShape } from "./_zod-introspect";
13
13
  import {
14
14
  type ComposedEnvSchema,
package/src/env/index.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  // Without meta, defaults: `camelCase(envVarName)` for the Pulumi key,
15
15
  // `<set-me>` placeholder for the value, no `--secret` flag.
16
16
 
17
- import { z } from "zod";
17
+ import * as z from "zod";
18
18
  import type { FeatureDefinition } from "../engine/types/feature";
19
19
  import { zodDef, zodDescription, zodMeta, zodShape, zodShapeField } from "./_zod-introspect";
20
20
 
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from "bun:test";
2
- import { z } from "zod";
2
+ import * as z from "zod";
3
3
  import {
4
4
  AccessDeniedError,
5
5
  buildErrorLog,
@@ -17,7 +17,7 @@ import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:tes
17
17
  import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
18
18
  import { tmpdir } from "node:os";
19
19
  import { join } from "node:path";
20
- import { z } from "zod";
20
+ import * as z from "zod";
21
21
  import { createEventStoreExecutor } from "../../db/event-store-executor";
22
22
  import { asRawClient, insertOne, selectMany } from "../../db/query";
23
23
  import { buildEntityTable } from "../../db/table-builder";
@@ -12,7 +12,7 @@ import {
12
12
  resetBlindIndexKeyForTests,
13
13
  resetPiiSubjectKmsForTests,
14
14
  } from "@cosmicdrift/kumiko-framework/testing";
15
- import { z } from "zod";
15
+ import * as z from "zod";
16
16
  import {
17
17
  configureBlindIndexKey,
18
18
  configurePiiSubjectKms,
@@ -14,12 +14,13 @@
14
14
  // and stay readable without the new fields.
15
15
 
16
16
  import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test";
17
- import { UNATTRIBUTED_ORIGIN } from "@cosmicdrift/kumiko-types/event-store-types";
18
- import { z } from "zod";
17
+ import { UNATTRIBUTED_ORIGIN, type WriteOrigin } from "@cosmicdrift/kumiko-types/event-store-types";
18
+ import * as z from "zod";
19
19
  import { createEventStoreExecutor } from "../../db/event-store-executor";
20
20
  import { selectMany } from "../../db/query";
21
21
  import { buildEntityTable } from "../../db/table-builder";
22
22
  import { createEntity, createTextField, defineFeature } from "../../engine";
23
+ import type { TenantId } from "../../engine/types";
23
24
  import {
24
25
  resetEventStore,
25
26
  setupTestStack,
@@ -43,8 +44,11 @@ const orderTable = buildEntityTable("attr-order", orderEntity);
43
44
 
44
45
  const PLACED = "attribution:event:placed";
45
46
  const CONFIRMED = "attribution:event:confirmed";
47
+ const PROBED = "attribution:event:probed";
46
48
  const PLACE_HANDLER = "attribution:write:order:place";
49
+ const PROBE_HANDLER = "attribution:write:order:probe";
47
50
  const CONFIRMER_MSP = "attribution:projection:confirmer";
51
+ const TENANT_ID = "00000000-0000-4000-8000-000000000002" as TenantId;
48
52
 
49
53
  const attributionFeature = defineFeature("attribution", (r) => {
50
54
  r.entity("attr-order", orderEntity);
@@ -75,6 +79,26 @@ const attributionFeature = defineFeature("attribution", (r) => {
75
79
  { access: { roles: ["Admin"] } },
76
80
  );
77
81
 
82
+ const probed = r.defineEvent("probed", z.object({ orderId: z.uuid() }), { piiFields: "none" });
83
+
84
+ // Anonymous, no public-intake declared, no PII field written (nothing to gate).
85
+ r.writeHandler(
86
+ "order:probe",
87
+ z.object({ item: z.string() }),
88
+ async (event, ctx) => {
89
+ const created = await orderExecutor.create({ item: event.payload.item }, event.user, ctx.db);
90
+ if (!created.isSuccess) return created;
91
+ await ctx.unsafeAppendEvent({
92
+ aggregateId: String(created.data.id),
93
+ aggregateType: "attr-order",
94
+ type: probed.name,
95
+ payload: { orderId: String(created.data.id) },
96
+ });
97
+ return created;
98
+ },
99
+ { access: { roles: ["anonymous"] } },
100
+ );
101
+
78
102
  r.multiStreamProjection({
79
103
  name: "confirmer",
80
104
  apply: {
@@ -95,7 +119,11 @@ let stack: TestStack;
95
119
  const admin = TestUsers.admin;
96
120
 
97
121
  beforeAll(async () => {
98
- stack = await setupTestStack({ features: [attributionFeature], systemHooks: [] });
122
+ stack = await setupTestStack({
123
+ features: [attributionFeature],
124
+ systemHooks: [],
125
+ anonymousAccess: { defaultTenantId: TENANT_ID },
126
+ });
99
127
  await unsafeCreateEntityTable(stack.db, orderEntity, "attr-order");
100
128
  });
101
129
 
@@ -107,7 +135,7 @@ afterEach(async () => {
107
135
  await resetEventStore(stack, ["read_attribution_orders"]);
108
136
  });
109
137
 
110
- type Origin = { feature?: string; handler?: string };
138
+ type Origin = { feature?: string; handler?: string; writeOrigin?: WriteOrigin };
111
139
 
112
140
  async function originOf(type: string): Promise<Origin> {
113
141
  const rows = await selectMany(stack.db, eventsTable);
@@ -165,6 +193,28 @@ describe("#3043 — event attribution from the execution scope", () => {
165
193
  });
166
194
  });
167
195
 
196
+ test("authenticated write: the event carries no writeOrigin at all", async () => {
197
+ await stack.http.writeOk(PLACE_HANDLER, { item: "cog" }, admin);
198
+
199
+ const origin = await originOf(PLACED);
200
+ expect(origin.writeOrigin).toBeUndefined();
201
+ });
202
+
203
+ test("anonymous non-intake write: the event is stamped with the gated origin", async () => {
204
+ const res = await stack.http.raw("POST", "/api/write", {
205
+ type: PROBE_HANDLER,
206
+ payload: { item: "washer" },
207
+ });
208
+ expect(res.status).toBe(200);
209
+
210
+ const origin = await originOf(PROBED);
211
+ expect(origin.writeOrigin).toMatchObject({
212
+ rootHandler: PROBE_HANDLER,
213
+ anonymousRoot: true,
214
+ publicIntake: false,
215
+ });
216
+ });
217
+
168
218
  test("appendRaw keeps historical metadata verbatim and stays readable", async () => {
169
219
  const aggregateId = uuid();
170
220
  await appendRaw(stack.db, {
@@ -9,7 +9,7 @@
9
9
  // can never silently hand half-migrated data to consumers.
10
10
 
11
11
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
12
- import { z } from "zod";
12
+ import * as z from "zod";
13
13
  import { integer as pgInteger, table as pgTable, text as pgText } from "../../db/dialect";
14
14
  import { createEventStoreExecutor } from "../../db/event-store-executor";
15
15
  import { asRawClient, insertOne, selectMany } from "../../db/query";
@@ -9,6 +9,7 @@
9
9
 
10
10
  import type { DbRunner } from "../db";
11
11
  import { constraintOf, isUniqueViolation } from "../db/pg-error";
12
+ import { claimXactId } from "../db/queries/event-store";
12
13
  import {
13
14
  eventPredecessorExists,
14
15
  findExistingEventVersion,
@@ -54,6 +55,9 @@ export async function appendRaw(runner: DbRunner, event: RawEventToAppend): Prom
54
55
  const eventVersion = event.eventVersion ?? 1;
55
56
 
56
57
  try {
58
+ // See db/queries/event-store.ts's claimXactId — gap-finality needs a
59
+ // real xact id assigned before this holder's insert.
60
+ await claimXactId(runner);
57
61
  if (event.expectedVersion === 0) {
58
62
  await insertRawFirst(runner, event, newVersion, eventVersion);
59
63
  } else {
@@ -145,6 +149,7 @@ export async function appendRawBatch(
145
149
  });
146
150
 
147
151
  try {
152
+ await claimXactId(runner);
148
153
  await insertRawEventBatch(runner, valuesClauses.join(", "), params);
149
154
  } catch (e) {
150
155
  if (isUniqueViolation(e)) {
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  type EventMetadata,
3
+ isPersonalDataGated,
3
4
  type StoredEvent,
4
5
  UNATTRIBUTED_ORIGIN,
5
6
  } from "@cosmicdrift/kumiko-types/event-store-types";
@@ -15,6 +16,7 @@ import { encryptEventPayloadPii } from "../crypto/event-pii";
15
16
  import type { DbRunner } from "../db";
16
17
  import { constraintOf, isUniqueViolation } from "../db/pg-error";
17
18
  import {
19
+ claimXactId,
18
20
  insertSubsequentEventRow,
19
21
  notifyPgChannel,
20
22
  selectAggregateMaxVersion,
@@ -93,14 +95,16 @@ export async function append(db: DbRunner, event: EventToAppend): Promise<Stored
93
95
  const eventVersion = toStore.eventVersion ?? 1;
94
96
 
95
97
  try {
98
+ await claimXactId(db);
99
+
96
100
  const row =
97
101
  toStore.expectedVersion === 0
98
102
  ? await insertFirstEvent(db, toStore, newVersion, eventVersion)
99
103
  : await insertSubsequentEvent(db, toStore, newVersion, eventVersion);
100
104
 
101
- // NOTIFY fires on commit (PG buffers NOTIFY per TX), so subscribers never
102
- // see a wake-up for an event that later rolled back. Harmless no-op when
103
- // no LISTENer is attached.
105
+ // NOTIFY after the INSERT: outside a transaction each statement commits
106
+ // on its own, so a NOTIFY sent first would wake the dispatcher before
107
+ // the row exists.
104
108
  await notifyPgChannel(db, EVENTS_PUBSUB_CHANNEL);
105
109
 
106
110
  return buildStoredEvent(toStore, newVersion, eventVersion, row);
@@ -127,12 +131,15 @@ export async function append(db: DbRunner, event: EventToAppend): Promise<Stored
127
131
  // bypass this deliberately — they replay historical rows verbatim.
128
132
  function stampOrigin(event: EventToAppend): EventToAppend {
129
133
  const origin = requestContext.get();
134
+ const { writeOrigin: _droppedCallerWriteOrigin, ...restMetadata } = event.metadata;
130
135
  return {
131
136
  ...event,
132
137
  metadata: {
133
- ...event.metadata,
138
+ ...restMetadata,
134
139
  feature: origin?.feature ?? UNATTRIBUTED_ORIGIN,
135
140
  handler: origin?.handler ?? UNATTRIBUTED_ORIGIN,
141
+ ...(origin?.writeOrigin &&
142
+ isPersonalDataGated(origin.writeOrigin) && { writeOrigin: origin.writeOrigin }),
136
143
  },
137
144
  };
138
145
  }
@@ -380,9 +387,11 @@ export async function loadAllEventsByType(
380
387
 
381
388
  // Stream every event for an aggregate_type across all tenants, batchwise
382
389
  // instead of buffered. Memory-bounded: never more than `batchSize` rows
383
- // resident. Cursor walks `events.id` (bigserial monotonic — concurrent
384
- // inserts get distinct ids in commit order, so no duplicates and no skips
385
- // past the cursor).
390
+ // resident. Cursor walks `events.id` (bigserial monotonic, but ids are
391
+ // assigned at INSERT time, not commit time — a sweep can overtake a still-
392
+ // open transaction and its lower id, same gap the event-dispatcher's
393
+ // pending_gaps tracks). Fine for this generator's use case: history sweeps
394
+ // (projection-rebuild, tests) that don't need live, just-committed rows.
386
395
  //
387
396
  // Use case: projection-rebuild on a large event log (>100k events per
388
397
  // aggregate-type). loadAllEventsByType would OOM; this iterator yields
@@ -10,7 +10,7 @@
10
10
  // so a >= assertion is flake-free without inflating waitFor's budget.
11
11
 
12
12
  import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
13
- import { z } from "zod";
13
+ import * as z from "zod";
14
14
  import { defineFeature } from "../../engine";
15
15
  import { setupTestStack, type TestStack, TestUsers } from "../../stack";
16
16
  import { waitFor } from "../../testing";
@@ -1,6 +1,6 @@
1
1
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
2
  import type { Hono } from "hono";
3
- import { z } from "zod";
3
+ import * as z from "zod";
4
4
  import { buildServer, type JwtHelper } from "../../api";
5
5
  import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
6
6
  import { createRegistry, defineFeature, type SessionUser } from "../../engine";
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { afterAll, beforeAll, describe, expect, test } from "bun:test";
10
10
  import type { Hono } from "hono";
11
- import { z } from "zod";
11
+ import * as z from "zod";
12
12
  import { buildServer, type JwtHelper } from "../../api";
13
13
  import { type BunTestDb, createTestDb } from "../../bun-db/__tests__/bun-test-db";
14
14
  import { createRegistry, defineFeature, type SessionUser } from "../../engine";