@cosmicdrift/kumiko-framework 0.2.3 → 0.3.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 (167) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/package.json +124 -39
  3. package/src/__tests__/full-stack.integration.ts +2 -2
  4. package/src/api/auth-routes.ts +5 -5
  5. package/src/api/jwt.ts +2 -2
  6. package/src/api/route-registrars.ts +1 -1
  7. package/src/api/routes.ts +3 -3
  8. package/src/api/server.ts +6 -7
  9. package/src/compliance/profiles.ts +8 -8
  10. package/src/db/assert-exists-in.ts +2 -2
  11. package/src/db/cursor.ts +3 -3
  12. package/src/db/event-store-executor.ts +19 -13
  13. package/src/db/located-timestamp.ts +1 -1
  14. package/src/db/money.ts +12 -2
  15. package/src/db/pg-error.ts +1 -1
  16. package/src/db/row-helpers.ts +1 -1
  17. package/src/db/table-builder.ts +3 -5
  18. package/src/db/tenant-db.ts +9 -9
  19. package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
  20. package/src/engine/__tests__/build-target.test.ts +135 -0
  21. package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
  22. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  23. package/src/engine/__tests__/event-helpers.test.ts +4 -4
  24. package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
  25. package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
  26. package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
  27. package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
  28. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
  29. package/src/engine/__tests__/raw-table.test.ts +2 -2
  30. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
  31. package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
  32. package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
  33. package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
  34. package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
  35. package/src/engine/__tests__/steps-read.test.ts +142 -0
  36. package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
  37. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
  38. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
  39. package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
  40. package/src/engine/__tests__/steps-workflow.test.ts +198 -0
  41. package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
  42. package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
  43. package/src/engine/boot-validator/api-ext.ts +77 -0
  44. package/src/engine/boot-validator/config-deps.ts +163 -0
  45. package/src/engine/boot-validator/entity-handler.ts +466 -0
  46. package/src/engine/boot-validator/index.ts +159 -0
  47. package/src/engine/boot-validator/ownership.ts +198 -0
  48. package/src/engine/boot-validator/pii-retention.ts +155 -0
  49. package/src/engine/boot-validator/screens-nav.ts +624 -0
  50. package/src/engine/boot-validator.ts +1 -1804
  51. package/src/engine/build-app-schema.ts +1 -1
  52. package/src/engine/build-target.ts +99 -0
  53. package/src/engine/codemod/index.ts +15 -0
  54. package/src/engine/codemod/pipeline-codemod.ts +641 -0
  55. package/src/engine/config-helpers.ts +9 -19
  56. package/src/engine/constants.ts +1 -1
  57. package/src/engine/define-feature.ts +88 -9
  58. package/src/engine/define-handler.ts +89 -3
  59. package/src/engine/define-roles.ts +2 -2
  60. package/src/engine/define-step.ts +28 -0
  61. package/src/engine/define-workflow.ts +110 -0
  62. package/src/engine/entity-handlers.ts +10 -9
  63. package/src/engine/event-helpers.ts +4 -4
  64. package/src/engine/factories.ts +12 -12
  65. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
  66. package/src/engine/feature-ast/extractors/index.ts +74 -0
  67. package/src/engine/feature-ast/extractors/round1.ts +110 -0
  68. package/src/engine/feature-ast/extractors/round2.ts +253 -0
  69. package/src/engine/feature-ast/extractors/round3.ts +471 -0
  70. package/src/engine/feature-ast/extractors/round4.ts +1365 -0
  71. package/src/engine/feature-ast/extractors/round5.ts +72 -0
  72. package/src/engine/feature-ast/extractors/round6.ts +66 -0
  73. package/src/engine/feature-ast/extractors/shared.ts +177 -0
  74. package/src/engine/feature-ast/parse.ts +7 -0
  75. package/src/engine/feature-ast/patch.ts +9 -1
  76. package/src/engine/feature-ast/patcher.ts +10 -3
  77. package/src/engine/feature-ast/patterns.ts +49 -1
  78. package/src/engine/feature-ast/render.ts +17 -1
  79. package/src/engine/index.ts +45 -2
  80. package/src/engine/pattern-library/__tests__/library.test.ts +6 -0
  81. package/src/engine/pattern-library/library.ts +42 -2
  82. package/src/engine/pipeline.ts +88 -0
  83. package/src/engine/projection-helpers.ts +1 -1
  84. package/src/engine/read-claim.ts +1 -1
  85. package/src/engine/registry.ts +30 -2
  86. package/src/engine/resolve-config-or-param.ts +4 -0
  87. package/src/engine/run-pipeline.ts +162 -0
  88. package/src/engine/schema-builder.ts +2 -4
  89. package/src/engine/state-machine.ts +1 -1
  90. package/src/engine/steps/_drizzle-boundary.ts +19 -0
  91. package/src/engine/steps/_duration-utils.ts +33 -0
  92. package/src/engine/steps/_no-return-guard.ts +21 -0
  93. package/src/engine/steps/_resolver-utils.ts +42 -0
  94. package/src/engine/steps/_step-dispatch-constants.ts +38 -0
  95. package/src/engine/steps/aggregate-append-event.ts +56 -0
  96. package/src/engine/steps/aggregate-create.ts +56 -0
  97. package/src/engine/steps/aggregate-update.ts +68 -0
  98. package/src/engine/steps/branch.ts +84 -0
  99. package/src/engine/steps/call-feature.ts +49 -0
  100. package/src/engine/steps/compute.ts +41 -0
  101. package/src/engine/steps/for-each.ts +111 -0
  102. package/src/engine/steps/mail-send.ts +44 -0
  103. package/src/engine/steps/read-find-many.ts +51 -0
  104. package/src/engine/steps/read-find-one.ts +58 -0
  105. package/src/engine/steps/retry.ts +87 -0
  106. package/src/engine/steps/return.ts +34 -0
  107. package/src/engine/steps/unsafe-projection-delete.ts +46 -0
  108. package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
  109. package/src/engine/steps/wait-for-event.ts +71 -0
  110. package/src/engine/steps/wait.ts +69 -0
  111. package/src/engine/steps/webhook-send.ts +71 -0
  112. package/src/engine/system-user.ts +1 -1
  113. package/src/engine/types/feature.ts +92 -1
  114. package/src/engine/types/handlers.ts +18 -10
  115. package/src/engine/types/identifiers.ts +1 -0
  116. package/src/engine/types/index.ts +12 -1
  117. package/src/engine/types/step.ts +334 -0
  118. package/src/engine/types/target-ref.ts +21 -0
  119. package/src/engine/types/tree-node.ts +130 -0
  120. package/src/engine/types/workspace.ts +7 -0
  121. package/src/engine/validate-projection-allowlist.ts +161 -0
  122. package/src/event-store/snapshot.ts +1 -1
  123. package/src/event-store/upcaster-dead-letter.ts +1 -1
  124. package/src/event-store/upcaster.ts +1 -1
  125. package/src/files/file-routes.ts +1 -1
  126. package/src/files/types.ts +2 -2
  127. package/src/jobs/job-runner.ts +10 -10
  128. package/src/lifecycle/lifecycle.ts +0 -3
  129. package/src/logging/index.ts +1 -0
  130. package/src/logging/pino-logger.ts +11 -7
  131. package/src/logging/utils.ts +24 -0
  132. package/src/observability/prometheus-meter.ts +7 -5
  133. package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
  134. package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
  135. package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
  136. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
  137. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
  138. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
  139. package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
  140. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
  141. package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
  142. package/src/pipeline/append-event-core.ts +22 -6
  143. package/src/pipeline/dispatcher-utils.ts +188 -0
  144. package/src/pipeline/dispatcher.ts +63 -283
  145. package/src/pipeline/distributed-lock.ts +1 -1
  146. package/src/pipeline/entity-cache.ts +2 -2
  147. package/src/pipeline/event-consumer-state.ts +0 -13
  148. package/src/pipeline/event-dispatcher.ts +4 -4
  149. package/src/pipeline/index.ts +0 -2
  150. package/src/pipeline/lifecycle-pipeline.ts +6 -12
  151. package/src/pipeline/msp-rebuild.ts +5 -5
  152. package/src/pipeline/multi-stream-apply-context.ts +6 -7
  153. package/src/pipeline/projection-rebuild.ts +2 -2
  154. package/src/pipeline/projection-state.ts +0 -12
  155. package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
  156. package/src/rate-limit/resolver.ts +1 -1
  157. package/src/search/in-memory-adapter.ts +1 -1
  158. package/src/search/meilisearch-adapter.ts +3 -3
  159. package/src/search/types.ts +1 -1
  160. package/src/secrets/leak-guard.ts +2 -2
  161. package/src/stack/request-helper.ts +9 -5
  162. package/src/stack/test-stack.ts +1 -1
  163. package/src/testing/handler-context.ts +4 -4
  164. package/src/testing/http-cookies.ts +1 -1
  165. package/src/time/tz-context.ts +1 -2
  166. package/src/ui-types/index.ts +4 -0
  167. package/src/engine/feature-ast/extractors.ts +0 -2602
package/CHANGELOG.md CHANGED
@@ -1,5 +1,57 @@
1
1
  # @cosmicdrift/kumiko-framework
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0.3.0 bringt zwei neue Subsysteme (Step-Engine Tier-3 + Visual-Tree) plus
8
+ eine AST-Codemod-Pipeline als Vorarbeit für den L2-AI-Layer.
9
+
10
+ ### Breaking Changes
11
+
12
+ - `skipTransitionGuard` → `unsafeSkipTransitionGuard` (Rename in
13
+ feature-ast + engine). Der `unsafe`-Prefix macht die Tragweite des
14
+ Casts sichtbar und ist konsistent zur `unsafeProjectionUpsert`- und
15
+ `r.rawTable`-Konvention. Migration: 1:1-Ersetzung, keine Verhaltens-Änderung.
16
+
17
+ ### Features
18
+
19
+ - **Step-Engine M.4 — Tier-3 Workflow-Engine.** Neue Step-Vocabulary
20
+ `wait`, `waitForEvent`, `retry` ermöglicht persistierte Long-Running-Flows
21
+ über Job-Boundaries hinweg. Q7 Snapshot-at-Start hängt jedem Step-Run
22
+ einen SHA-256-Fingerprint des Aggregat-Zustands an, sodass Replays
23
+ deterministisch gegen den ursprünglichen Eingangszustand laufen.
24
+ - **Visual-Tree V.1.x — Tree-API + Editor-Panel.** Neue `VisualTree`-
25
+ Component plus TreeProvider-Pattern; erste TreeProviders für
26
+ `text-content` und `legal-pages` (CMS-light + Impressum/Privacy).
27
+ Fundament für den späteren No-Code-Designer (~3000 LOC, 98 Tests).
28
+ - **Codemod-Pipeline.** AST-basierte Patcher-Module für strukturelle
29
+ Feature-Edits — wird vom kommenden L2-AI-Layer als Tool-Surface
30
+ verwendet, ist aber eigenständig nutzbar für ts-morph-style Migrationen.
31
+ - **user-data-rights Sample-Recipe.** DSGVO Art. 15/17/18/20 vollständig
32
+ als Sample-Recipe (`samples/recipes/`) inklusive README — zeigt die
33
+ Export- und Forget-Pipeline gegen den `compliance-profiles`-Default
34
+ (`eu-dsgvo`).
35
+
36
+ ### Fixes
37
+
38
+ - `tier-engine`: auto-default-tier-Hook benutzt jetzt `ctx.db.raw` für
39
+ Event-Store-Operationen (#37, vorher: stiller Bug, 22 Tage live).
40
+ - `engine`: unsafe-projection-upsert nutzt `as never` statt `as any` —
41
+ schmaler Cast-Surface, weniger Compiler-Knebel.
42
+ - `visual-tree`: runtime-isolation marker für client-konsumierte Files,
43
+ damit der Multi-Entry-Build den richtigen Bundle-Split bekommt.
44
+ - `feature-ast`: vollständiger `unsafeSkipTransitionGuard`-Rename (war
45
+ in zwei Modulen noch der alte Name).
46
+ - `framework`: Error-Reasons + `noConsole`-Lint + No-Date-API-Guard
47
+ wieder push-ready.
48
+
49
+ ### Library-Updates
50
+
51
+ hono 4.12, jose 6.2, stripe 22.1, meilisearch 0.58, marked 18,
52
+ bun-types 1.3.13, lucide-react 1.14, bullmq 5.76, ioredis 5.10,
53
+ i18next 26.0, react + radix-ui-primitives auf aktuelle Minors.
54
+
3
55
  ## 0.2.3
4
56
 
5
57
  ## 0.2.2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-framework",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
4
4
  "description": "Framework core — engine, pipeline, API, DB, and every other bit that makes Kumiko go.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -28,56 +28,141 @@
28
28
  "runtime": "runtime"
29
29
  },
30
30
  "exports": {
31
- "./compliance": "./src/compliance/index.ts",
32
- "./engine": "./src/engine/index.ts",
33
- "./engine/types": "./src/engine/types/index.ts",
34
- "./errors": "./src/errors/index.ts",
35
- "./db": "./src/db/index.ts",
36
- "./event-store": "./src/event-store/index.ts",
37
- "./event-store/admin-api": "./src/event-store/admin-api.ts",
38
- "./pipeline": "./src/pipeline/index.ts",
39
- "./api": "./src/api/index.ts",
40
- "./i18n": "./src/i18n/index.ts",
41
- "./auth": "./src/auth/index.ts",
42
- "./files": "./src/files/index.ts",
43
- "./jobs": "./src/jobs/index.ts",
44
- "./migrations": "./src/migrations/index.ts",
45
- "./entrypoint": "./src/entrypoint/index.ts",
46
- "./random": "./src/random/index.ts",
47
- "./redis": "./src/redis/index.ts",
48
- "./search": "./src/search/index.ts",
49
- "./search/meilisearch": "./src/search/meilisearch-adapter.ts",
50
- "./secrets": "./src/secrets/index.ts",
51
- "./stack": "./src/stack/index.ts",
52
- "./testing": "./src/testing/index.ts",
53
- "./testing/handler-context": "./src/testing/handler-context.ts",
54
- "./testing/e2e-generator": "./src/testing/e2e-generator.ts",
55
- "./time": "./src/time/index.ts",
56
- "./ui-types": "./src/ui-types/index.ts",
57
- "./utils": "./src/utils/index.ts"
31
+ "./compliance": {
32
+ "types": "./src/compliance/index.ts",
33
+ "default": "./src/compliance/index.ts"
34
+ },
35
+ "./engine": {
36
+ "types": "./src/engine/index.ts",
37
+ "default": "./src/engine/index.ts"
38
+ },
39
+ "./engine/types": {
40
+ "types": "./src/engine/types/index.ts",
41
+ "default": "./src/engine/types/index.ts"
42
+ },
43
+ "./engine/codemod": {
44
+ "types": "./src/engine/codemod/index.ts",
45
+ "default": "./src/engine/codemod/index.ts"
46
+ },
47
+ "./errors": {
48
+ "types": "./src/errors/index.ts",
49
+ "default": "./src/errors/index.ts"
50
+ },
51
+ "./db": {
52
+ "types": "./src/db/index.ts",
53
+ "default": "./src/db/index.ts"
54
+ },
55
+ "./event-store": {
56
+ "types": "./src/event-store/index.ts",
57
+ "default": "./src/event-store/index.ts"
58
+ },
59
+ "./event-store/admin-api": {
60
+ "types": "./src/event-store/admin-api.ts",
61
+ "default": "./src/event-store/admin-api.ts"
62
+ },
63
+ "./pipeline": {
64
+ "types": "./src/pipeline/index.ts",
65
+ "default": "./src/pipeline/index.ts"
66
+ },
67
+ "./api": {
68
+ "types": "./src/api/index.ts",
69
+ "default": "./src/api/index.ts"
70
+ },
71
+ "./i18n": {
72
+ "types": "./src/i18n/index.ts",
73
+ "default": "./src/i18n/index.ts"
74
+ },
75
+ "./auth": {
76
+ "types": "./src/auth/index.ts",
77
+ "default": "./src/auth/index.ts"
78
+ },
79
+ "./files": {
80
+ "types": "./src/files/index.ts",
81
+ "default": "./src/files/index.ts"
82
+ },
83
+ "./jobs": {
84
+ "types": "./src/jobs/index.ts",
85
+ "default": "./src/jobs/index.ts"
86
+ },
87
+ "./migrations": {
88
+ "types": "./src/migrations/index.ts",
89
+ "default": "./src/migrations/index.ts"
90
+ },
91
+ "./entrypoint": {
92
+ "types": "./src/entrypoint/index.ts",
93
+ "default": "./src/entrypoint/index.ts"
94
+ },
95
+ "./random": {
96
+ "types": "./src/random/index.ts",
97
+ "default": "./src/random/index.ts"
98
+ },
99
+ "./redis": {
100
+ "types": "./src/redis/index.ts",
101
+ "default": "./src/redis/index.ts"
102
+ },
103
+ "./search": {
104
+ "types": "./src/search/index.ts",
105
+ "default": "./src/search/index.ts"
106
+ },
107
+ "./search/meilisearch": {
108
+ "types": "./src/search/meilisearch-adapter.ts",
109
+ "default": "./src/search/meilisearch-adapter.ts"
110
+ },
111
+ "./secrets": {
112
+ "types": "./src/secrets/index.ts",
113
+ "default": "./src/secrets/index.ts"
114
+ },
115
+ "./stack": {
116
+ "types": "./src/stack/index.ts",
117
+ "default": "./src/stack/index.ts"
118
+ },
119
+ "./testing": {
120
+ "types": "./src/testing/index.ts",
121
+ "default": "./src/testing/index.ts"
122
+ },
123
+ "./testing/handler-context": {
124
+ "types": "./src/testing/handler-context.ts",
125
+ "default": "./src/testing/handler-context.ts"
126
+ },
127
+ "./testing/e2e-generator": {
128
+ "types": "./src/testing/e2e-generator.ts",
129
+ "default": "./src/testing/e2e-generator.ts"
130
+ },
131
+ "./time": {
132
+ "types": "./src/time/index.ts",
133
+ "default": "./src/time/index.ts"
134
+ },
135
+ "./ui-types": {
136
+ "types": "./src/ui-types/index.ts",
137
+ "default": "./src/ui-types/index.ts"
138
+ },
139
+ "./utils": {
140
+ "types": "./src/utils/index.ts",
141
+ "default": "./src/utils/index.ts"
142
+ }
58
143
  },
59
144
  "dependencies": {
60
- "bullmq": "^5.73.5",
61
- "bun-types": "^1.3.12",
145
+ "bullmq": "^5.76.7",
146
+ "bun-types": "^1.3.13",
62
147
  "drizzle-kit": "^0.31.10",
63
148
  "drizzle-orm": "^0.45.2",
64
- "hono": "^4.12.12",
65
- "i18next": "^26.0.4",
66
- "ioredis": "^5.6.0",
67
- "jose": "^6.0.11",
68
- "meilisearch": "^0.57.0",
149
+ "hono": "^4.12.18",
150
+ "i18next": "^26.1.0",
151
+ "ioredis": "^5.10.1",
152
+ "jose": "^6.2.3",
153
+ "meilisearch": "^0.58.0",
69
154
  "pino": "^10.3.1",
70
155
  "postgres": "^3.4.9",
71
156
  "temporal-polyfill": "^0.3.2",
72
157
  "ts-morph": "^28.0.0",
73
158
  "uuid": "^14.0.0",
74
- "zod": "^4.3.6"
159
+ "zod": "^4.4.3"
75
160
  },
76
161
  "devDependencies": {
77
- "@cosmicdrift/kumiko-dispatcher-live": "0.2.3",
162
+ "@cosmicdrift/kumiko-dispatcher-live": "0.3.0",
78
163
  "@types/uuid": "^11.0.0",
79
- "bun-types": "^1.2.9",
80
- "drizzle-kit": "^0.31.0",
164
+ "bun-types": "^1.3.13",
165
+ "drizzle-kit": "^0.31.10",
81
166
  "pino-pretty": "^13.1.3"
82
167
  },
83
168
  "publishConfig": {
@@ -46,11 +46,11 @@ let USER_CREATED_EVENT: string;
46
46
  const domainEventSubscriberCalls: Array<{ type: string; payload: unknown }> = [];
47
47
 
48
48
  async function emitUserCreated(
49
- ctx: Pick<HandlerContext, "appendEventUnsafe">,
49
+ ctx: Pick<HandlerContext, "unsafeAppendEvent">,
50
50
  id: EntityId,
51
51
  email: string,
52
52
  ): Promise<void> {
53
- await ctx.appendEventUnsafe({
53
+ await ctx.unsafeAppendEvent({
54
54
  aggregateId: String(id),
55
55
  aggregateType: "user",
56
56
  type: USER_CREATED_EVENT,
@@ -650,7 +650,7 @@ export function createAuthRoutes(
650
650
  }
651
651
  const result = await dispatcher.write(inv.acceptWithLoginHandler, parsed.data, GUEST_USER);
652
652
  if (!result.isSuccess) {
653
- const status = result.error.httpStatus as 400 | 401 | 403 | 422 | 500;
653
+ const status = result.error.httpStatus as 400 | 401 | 403 | 422 | 500; // @cast-boundary engine-payload
654
654
  return c.json({ isSuccess: false, error: result.error }, status);
655
655
  }
656
656
  const data = result.data as {
@@ -658,7 +658,7 @@ export function createAuthRoutes(
658
658
  session: SessionUser;
659
659
  tenantId: TenantId;
660
660
  role: string;
661
- };
661
+ }; // @cast-boundary engine-payload
662
662
  let sessionForJwt: SessionUser = data.session;
663
663
  if (config.sessionCreator) {
664
664
  const sid = await config.sessionCreator(data.session, requestMeta(c));
@@ -690,7 +690,7 @@ export function createAuthRoutes(
690
690
  }
691
691
  const result = await dispatcher.write(inv.signupCompleteHandler, parsed.data, GUEST_USER);
692
692
  if (!result.isSuccess) {
693
- const status = result.error.httpStatus as 400 | 401 | 403 | 422 | 500;
693
+ const status = result.error.httpStatus as 400 | 401 | 403 | 422 | 500; // @cast-boundary engine-payload
694
694
  return c.json({ isSuccess: false, error: result.error }, status);
695
695
  }
696
696
  const data = result.data as {
@@ -698,7 +698,7 @@ export function createAuthRoutes(
698
698
  session: SessionUser;
699
699
  tenantId: TenantId;
700
700
  role: string;
701
- };
701
+ }; // @cast-boundary engine-payload
702
702
  let sessionForJwt: SessionUser = data.session;
703
703
  if (config.sessionCreator) {
704
704
  const sid = await config.sessionCreator(data.session, requestMeta(c));
@@ -967,7 +967,7 @@ function registerTokenConfirmRoute(opts: {
967
967
  }
968
968
  const result = await opts.dispatcher.write(opts.confirmHandler, parsed.data, GUEST_USER);
969
969
  if (!result.isSuccess) {
970
- const status = result.error.httpStatus as 400 | 401 | 403 | 422 | 500;
970
+ const status = result.error.httpStatus as 400 | 401 | 403 | 422 | 500; // @cast-boundary engine-payload
971
971
  return c.json({ isSuccess: false, error: result.error }, status);
972
972
  }
973
973
  return c.json({ isSuccess: true });
package/src/api/jwt.ts CHANGED
@@ -50,8 +50,8 @@ export function createJwtHelper(secret: string, issuer = "kumiko"): JwtHelper {
50
50
  const { payload } = await jose.jwtVerify(token, encodedSecret, { issuer });
51
51
  const result: JwtPayload = {
52
52
  sub: String(payload.sub),
53
- tenantId: payload["tenantId"] as string,
54
- roles: payload["roles"] as string[],
53
+ tenantId: payload["tenantId"] as string, // @cast-boundary dynamic-key
54
+ roles: payload["roles"] as string[], // @cast-boundary dynamic-key
55
55
  };
56
56
  const claims = payload["claims"];
57
57
  if (claims && typeof claims === "object") {
@@ -68,7 +68,7 @@ function constantTimeEqual(a: string, b: string): boolean {
68
68
  // Prometheus-compatible meter (future OTLP bridge) works without an
69
69
  // explicit union — if it exposes `snapshot()` it's serialisable.
70
70
  function isPrometheusMeter(m: Meter): m is PrometheusMeter {
71
- return typeof (m as { snapshot?: unknown }).snapshot === "function";
71
+ return typeof (m as { snapshot?: unknown }).snapshot === "function"; // @cast-boundary schema-walk
72
72
  }
73
73
 
74
74
  // Mount `/metrics` (or the caller-supplied path). Takes just the Meter —
package/src/api/routes.ts CHANGED
@@ -73,7 +73,7 @@ export function createApiRoutes(dispatcher: Dispatcher) {
73
73
  failedIndex: result.failedIndex,
74
74
  results: result.results,
75
75
  },
76
- err.httpStatus as ContentfulStatusCode,
76
+ err.httpStatus as ContentfulStatusCode, // @cast-boundary engine-payload
77
77
  );
78
78
  }
79
79
  return c.json(result);
@@ -121,7 +121,7 @@ function toKumiko(e: unknown): KumikoError {
121
121
  function writeErrorResponse(c: Context, err: KumikoError, statusOverride?: number) {
122
122
  const requestId = requestContext.get()?.requestId;
123
123
  const { error } = serializeError(err, requestId);
124
- const status = (statusOverride ?? err.httpStatus) as ContentfulStatusCode;
124
+ const status = (statusOverride ?? err.httpStatus) as ContentfulStatusCode; // @cast-boundary engine-payload
125
125
  return c.json({ isSuccess: false, error }, status);
126
126
  }
127
127
 
@@ -130,6 +130,6 @@ function writeErrorResponse(c: Context, err: KumikoError, statusOverride?: numbe
130
130
  function queryErrorResponse(c: Context, err: KumikoError, statusOverride?: number) {
131
131
  const requestId = requestContext.get()?.requestId;
132
132
  const body = serializeError(err, requestId);
133
- const status = (statusOverride ?? err.httpStatus) as ContentfulStatusCode;
133
+ const status = (statusOverride ?? err.httpStatus) as ContentfulStatusCode; // @cast-boundary engine-payload
134
134
  return c.json(body, status);
135
135
  }
package/src/api/server.ts CHANGED
@@ -42,7 +42,7 @@ import {
42
42
  globalIpRateLimit,
43
43
  } from "../rate-limit";
44
44
  import type { SearchAdapter } from "../search/types";
45
- import { generateId } from "../utils";
45
+ import { assertUnreachable, generateId } from "../utils";
46
46
  import { PUBLIC_API_PATHS } from "./api-constants";
47
47
  import { type AnonymousAccessConfig, authMiddleware } from "./auth-middleware";
48
48
  import { type AuthRoutesConfig, createAuthRoutes } from "./auth-routes";
@@ -383,7 +383,7 @@ export function buildServer(options: ServerOptions): KumikoServer {
383
383
  // can pause this consumer when the feature is globally disabled. Events
384
384
  // queue up in the store and replay cleanly from the same cursor on resume.
385
385
  ...(options.registry.getMultiStreamProjectionFeature(msp.name) && {
386
- featureName: options.registry.getMultiStreamProjectionFeature(msp.name) as string,
386
+ featureName: options.registry.getMultiStreamProjectionFeature(msp.name) as string, // @cast-boundary engine-bridge
387
387
  }),
388
388
  // Copy the continuous-lifecycle error policy straight onto the consumer.
389
389
  // Rebuild uses its own policy (rebuildProjection reads msp.errorMode.rebuild
@@ -409,10 +409,7 @@ export function buildServer(options: ServerOptions): KumikoServer {
409
409
  // Hand the raw DbRunner to apply(): MSPs write to their projection
410
410
  // table directly, they don't go through the TenantDb wrapper.
411
411
  const rawRunner =
412
- event.tenantId === SYSTEM_TENANT_ID
413
- ? baseDb
414
- : // @cast-boundary engine-bridge — TenantDb exposes its raw DbRunner via .raw
415
- (scopedDb as { raw: typeof baseDb }).raw;
412
+ event.tenantId === SYSTEM_TENANT_ID ? baseDb : (scopedDb as { raw: typeof baseDb }).raw; // @cast-boundary engine-bridge
416
413
  // Saga/process-manager ctx: apply can call ctx.appendEvent to cascade
417
414
  // a follow-up event onto another aggregate. Uses the triggering event's
418
415
  // tenantId + userId so the causal chain stays tenant-consistent.
@@ -563,7 +560,7 @@ export function buildServer(options: ServerOptions): KumikoServer {
563
560
  app.route("/api", createSseRoute(sseBroker));
564
561
 
565
562
  if (options.files) {
566
- const fileDb = options.files.db ?? (options.context.db as FileRoutesOptions["db"]);
563
+ const fileDb = options.files.db ?? (options.context.db as FileRoutesOptions["db"]); // @cast-boundary engine-bridge
567
564
  if (!fileDb) throw new Error("files option requires db in context or files.db");
568
565
  app.route(
569
566
  "/api",
@@ -605,6 +602,8 @@ export function buildServer(options: ServerOptions): KumikoServer {
605
602
  // Hono-on() für die Methoden ohne Convenience-Method.
606
603
  app.on(route.method, route.path, honoHandler);
607
604
  break;
605
+ default:
606
+ assertUnreachable(route.method, "http method");
608
607
  }
609
608
  }
610
609
  }
@@ -243,7 +243,7 @@ const RAW_PROFILES: Readonly<Record<ComplianceProfileKey, ComplianceProfileRaw>>
243
243
  },
244
244
  tenantDestroyGracePeriod: { days: 30 },
245
245
  },
246
- };
246
+ } satisfies Readonly<Record<ComplianceProfileKey, ComplianceProfileRaw>>;
247
247
 
248
248
  // Raw-Profile (vor extends-Resolution) — `extends`-Profile dürfen
249
249
  // Required-Felder weglassen, sie kommen vom Base-Profile dazu.
@@ -262,11 +262,11 @@ type ComplianceProfileRaw = Partial<Omit<ComplianceProfile, "key" | "region" | "
262
262
  * Default-Fallback fuer "noch keine Wahl getroffen", mit sichtbarer
263
263
  * Warning. Production-Tenants sollen ein echtes Profile waehlen.
264
264
  */
265
- export const SELECTABLE_PROFILE_KEYS: readonly ComplianceProfileKey[] = [
265
+ export const SELECTABLE_PROFILE_KEYS = [
266
266
  "eu-dsgvo",
267
267
  "swiss-dsg",
268
268
  "de-hr-dsgvo-hgb",
269
- ];
269
+ ] as const satisfies readonly ComplianceProfileKey[];
270
270
 
271
271
  /**
272
272
  * Top-Level-Properties des `ComplianceProfile`-Type, die ein Tenant-
@@ -339,7 +339,7 @@ function deepMerge<T extends Record<string, unknown>>(
339
339
  out[k] = v;
340
340
  }
341
341
  }
342
- return out as T;
342
+ return out as T; // @cast-boundary generic-record
343
343
  }
344
344
 
345
345
  /**
@@ -355,7 +355,7 @@ function deepMerge<T extends Record<string, unknown>>(
355
355
  function resolveExtends(key: ComplianceProfileKey): ComplianceProfile {
356
356
  const raw = RAW_PROFILES[key];
357
357
  if (!raw.extends) {
358
- return raw as ComplianceProfile;
358
+ return raw as ComplianceProfile; // @cast-boundary schema-walk
359
359
  }
360
360
 
361
361
  const base = RAW_PROFILES[raw.extends];
@@ -366,7 +366,7 @@ function resolveExtends(key: ComplianceProfileKey): ComplianceProfile {
366
366
  }
367
367
 
368
368
  return deepMerge(
369
- base as Record<string, unknown>,
369
+ base as Record<string, unknown>, // @cast-boundary generic-record
370
370
  raw as unknown as Record<string, unknown>,
371
371
  ) as unknown as ComplianceProfile;
372
372
  }
@@ -379,7 +379,7 @@ function resolveExtends(key: ComplianceProfileKey): ComplianceProfile {
379
379
  export const COMPLIANCE_PROFILES: Readonly<Record<ComplianceProfileKey, ComplianceProfile>> =
380
380
  Object.fromEntries(
381
381
  (Object.keys(RAW_PROFILES) as ComplianceProfileKey[]).map((k) => [k, resolveExtends(k)]),
382
- ) as Readonly<Record<ComplianceProfileKey, ComplianceProfile>>;
382
+ ) as Readonly<Record<ComplianceProfileKey, ComplianceProfile>>; // @cast-boundary dynamic-key
383
383
 
384
384
  // --- Effective-Profile-Resolver ---
385
385
 
@@ -421,7 +421,7 @@ export function resolveComplianceProfile(args: {
421
421
 
422
422
  const merged = deepMerge(
423
423
  base as unknown as Record<string, unknown>,
424
- args.override as Record<string, unknown>,
424
+ args.override as Record<string, unknown>, // @cast-boundary engine-payload
425
425
  ) as unknown as ComplianceProfile;
426
426
  return { profile: merged };
427
427
  }
@@ -1,5 +1,5 @@
1
- import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
1
  import { and, eq, type SQL } from "drizzle-orm";
2
+ import type { TenantId } from "../engine/types/identifiers";
3
3
  import { NotFoundError } from "../errors";
4
4
  import type { DbConnection } from "./connection";
5
5
  import type { TenantDb } from "./tenant-db";
@@ -43,7 +43,7 @@ export async function assertExistsIn(
43
43
  const [row] = await db
44
44
  .select()
45
45
  .from(entity)
46
- .where(and(...conditions) as SQL);
46
+ .where(and(...conditions) as SQL); // @cast-boundary db-operator
47
47
 
48
48
  if (!row) {
49
49
  const entityName = options.entityName ?? String(options.field).replace(/Id$/, "");
package/src/db/cursor.ts CHANGED
@@ -1,5 +1,5 @@
1
- import type { EntityId, TenantId } from "@cosmicdrift/kumiko-framework/engine";
2
1
  import { and, asc, desc, eq, gt, inArray, type SQL, sql } from "drizzle-orm";
2
+ import type { EntityId, TenantId } from "../engine/types/identifiers";
3
3
  import type { SelectQuery as PgSelect } from "./dialect";
4
4
 
5
5
  export type CursorQueryOptions = {
@@ -61,7 +61,7 @@ export function applyCursorQuery<T extends PgSelect>(
61
61
  // werfen.
62
62
  conditions.push(sql`false`);
63
63
  } else {
64
- conditions.push(inArray(table.id, options.filterIds as readonly string[]));
64
+ conditions.push(inArray(table.id, options.filterIds as readonly string[])); // @cast-boundary db-operator
65
65
  }
66
66
  }
67
67
 
@@ -79,5 +79,5 @@ export function applyCursorQuery<T extends PgSelect>(
79
79
  options.sortDirection === "desc" ? result.orderBy(desc(column)) : result.orderBy(asc(column));
80
80
  }
81
81
 
82
- return result as T;
82
+ return result as T; // @cast-boundary engine-bridge
83
83
  }
@@ -1,4 +1,5 @@
1
1
  import { and, asc, desc, eq, gt, inArray, lt, ne, type SQL, sql } from "drizzle-orm";
2
+ import type { AnyPgColumn } from "drizzle-orm/pg-core";
2
3
  import { requestContext } from "../api/request-context";
3
4
  import { checkWriteFieldOwnership } from "../engine/field-access";
4
5
  import {
@@ -32,7 +33,7 @@ import {
32
33
  } from "../event-store";
33
34
  import type { EntityCache } from "../pipeline/entity-cache";
34
35
  import type { SearchAdapter } from "../search/types";
35
- import { generateId } from "../utils";
36
+ import { assertUnreachable, generateId } from "../utils";
36
37
  import { applyEntityEvent } from "./apply-entity-event";
37
38
  import { flattenCompoundTypes, rehydrateCompoundTypes } from "./compound-types";
38
39
  import type { DbRow } from "./connection";
@@ -54,8 +55,11 @@ type Table = TableColumns<any>;
54
55
  // vs-Type-Compat ohnehin Kontrolle was reinkommt.
55
56
  //
56
57
  // Empty-array IN ist explizit "no match" (SQL false), nicht "match all".
57
- // biome-ignore lint/suspicious/noExplicitAny: Drizzle-Column ist generic; siehe oben.
58
- function buildFilterCondition(col: any, op: "eq" | "ne" | "lt" | "gt" | "in", value: unknown): SQL {
58
+ function buildFilterCondition(
59
+ col: AnyPgColumn,
60
+ op: "eq" | "ne" | "lt" | "gt" | "in",
61
+ value: unknown,
62
+ ): SQL {
59
63
  switch (op) {
60
64
  case "eq":
61
65
  return eq(col, value as never); // @cast-boundary db-operator
@@ -70,6 +74,8 @@ function buildFilterCondition(col: any, op: "eq" | "ne" | "lt" | "gt" | "in", va
70
74
  return inArray(col, value as never); // @cast-boundary db-operator
71
75
  }
72
76
  return sql`false`;
77
+ default:
78
+ assertUnreachable(op, "filter op");
73
79
  }
74
80
  }
75
81
 
@@ -281,7 +287,7 @@ export function createEventStoreExecutor(
281
287
  }
282
288
  // Drizzle's variadic `and()` is typed `SQL | undefined`; conditions is
283
289
  // guaranteed non-empty above (we pushed at least one).
284
- return and(...conditions) as SQL;
290
+ return and(...conditions) as SQL; // @cast-boundary db-operator
285
291
  }
286
292
 
287
293
  async function loadById(id: EntityId, db: TenantDb): Promise<Record<string, unknown> | null> {
@@ -295,7 +301,7 @@ export function createEventStoreExecutor(
295
301
  // Respect an explicit id in the payload (seed pattern, SCIM import). Without
296
302
  // one the framework mints a fresh UUIDv7 via generateId. Strip it out of the
297
303
  // event payload so defaults + downstream consumers don't see a redundant id field.
298
- const explicitId = typeof payload["id"] === "string" ? (payload["id"] as string) : undefined;
304
+ const explicitId = typeof payload["id"] === "string" ? (payload["id"] as string) : undefined; // @cast-boundary engine-payload
299
305
  const aggregateId = explicitId ?? generateId();
300
306
  const { id: _id, ...payloadWithoutId } = payload;
301
307
  const data = applyDefaults(payloadWithoutId);
@@ -561,7 +567,7 @@ export function createEventStoreExecutor(
561
567
  isSuccess: true,
562
568
  data: {
563
569
  kind: "save",
564
- id: data["id"] as EntityId,
570
+ id: data["id"] as EntityId, // @cast-boundary engine-payload
565
571
  data,
566
572
  changes: payload.changes,
567
573
  previous,
@@ -793,7 +799,7 @@ export function createEventStoreExecutor(
793
799
  }
794
800
  }
795
801
 
796
- const whereClause = conditions.length > 0 ? (and(...conditions) as SQL) : undefined;
802
+ const whereClause = conditions.length > 0 ? (and(...conditions) as SQL) : undefined; // @cast-boundary db-operator
797
803
  let query = whereClause
798
804
  ? db.select().from(table).where(whereClause)
799
805
  : db.select().from(table);
@@ -822,13 +828,13 @@ export function createEventStoreExecutor(
822
828
  await entityCache.mset(
823
829
  user.tenantId,
824
830
  entityName,
825
- rows.map((r) => ({ id: r["id"] as EntityId, data: r })),
831
+ rows.map((r) => ({ id: r["id"] as EntityId, data: r })), // @cast-boundary engine-payload
826
832
  );
827
833
  }
828
834
 
829
835
  const lastRow = rows[rows.length - 1];
830
836
  const nextCursor =
831
- rows.length === limit && lastRow ? encodeCursor(lastRow["id"] as string) : null;
837
+ rows.length === limit && lastRow ? encodeCursor(lastRow["id"] as string) : null; // @cast-boundary engine-payload
832
838
 
833
839
  // total: extra COUNT(*) — nur wenn explizit angefordert (Pager-UI).
834
840
  // Postgres-Cost ist O(table-scan) ohne Filter, mit Filter so teuer
@@ -842,7 +848,7 @@ export function createEventStoreExecutor(
842
848
  const countQuery = whereClause
843
849
  ? db.select({ count: sql<number>`count(*)::int` }).from(table).where(whereClause)
844
850
  : db.select({ count: sql<number>`count(*)::int` }).from(table);
845
- const countRow = (await countQuery) as Array<{ count: number }>;
851
+ const countRow = (await countQuery) as Array<{ count: number }>; // @cast-boundary db-row
846
852
  total = countRow[0]?.count ?? 0;
847
853
  }
848
854
  }
@@ -872,7 +878,7 @@ export function createEventStoreExecutor(
872
878
  const checked = await db
873
879
  .select()
874
880
  .from(table)
875
- .where(and(idFilter(payload.id), ownership.sql) as SQL)
881
+ .where(and(idFilter(payload.id), ownership.sql) as SQL) // @cast-boundary db-operator
876
882
  .limit(1);
877
883
  if (checked.length === 0) return null;
878
884
  }
@@ -887,11 +893,11 @@ export function createEventStoreExecutor(
887
893
  // thread the ownership clause.
888
894
  const baseFilter = idFilter(payload.id);
889
895
  const whereClause =
890
- ownership.kind === "sql" ? (and(baseFilter, ownership.sql) as SQL) : baseFilter;
896
+ ownership.kind === "sql" ? (and(baseFilter, ownership.sql) as SQL) : baseFilter; // @cast-boundary db-operator
891
897
  const rows = (await db.select().from(table).where(whereClause).limit(1)) as Record<
892
898
  string,
893
899
  unknown
894
- >[];
900
+ >[]; // @cast-boundary db-row
895
901
  const raw = rows[0];
896
902
  if (!raw) return null;
897
903
  const row = rehydrateCompoundTypes(raw, entity);
@@ -51,7 +51,7 @@ export function flattenLocatedTimestamp(
51
51
  `flattenLocatedTimestamp: field "${name}" expects { at, tz } or { utc, tz } object, got ${typeof raw}`,
52
52
  );
53
53
  }
54
- const pair = raw as { at?: string; tz?: string; utc?: string };
54
+ const pair = raw as { at?: string; tz?: string; utc?: string }; // @cast-boundary schema-walk
55
55
 
56
56
  delete result[name];
57
57