@cosmicdrift/kumiko-framework 0.387.0 → 0.388.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 (240) hide show
  1. package/AGENTS.md +6 -1
  2. package/agents/examples/anonymous-access.md +1 -1
  3. package/agents/examples/apex-landing.md +204 -0
  4. package/agents/examples/api-worker-split.md +64 -0
  5. package/agents/examples/app-config.md +14 -0
  6. package/agents/examples/auth-mfa.md +115 -0
  7. package/agents/examples/env-composition.md +28 -1
  8. package/agents/examples/legal-pages.md +1 -1
  9. package/agents/examples/prod-boot-options.md +16 -0
  10. package/agents/examples/rate-limiting.md +48 -0
  11. package/agents/examples/record-detail-layout.md +692 -0
  12. package/agents/examples/seed-migration.md +212 -0
  13. package/agents/examples/wizard-form.md +941 -0
  14. package/agents/examples/writeform-section.md +548 -0
  15. package/agents/features.md +19 -2
  16. package/agents/widgets.md +1 -0
  17. package/dist/api/client-error-route.js +8 -2
  18. package/dist/api/index.d.ts +3 -1
  19. package/dist/api/index.js +3 -1
  20. package/dist/api/request-context.d.ts +1 -1
  21. package/dist/api/request-context.js +1 -1
  22. package/dist/api/server-boot-checks.d.ts +10 -0
  23. package/dist/api/server-boot-checks.js +157 -0
  24. package/dist/api/server-event-dispatcher.d.ts +26 -0
  25. package/dist/api/server-event-dispatcher.js +208 -0
  26. package/dist/api/server-extra-routes.d.ts +11 -0
  27. package/dist/api/server-extra-routes.js +203 -0
  28. package/dist/api/server-middleware.d.ts +20 -0
  29. package/dist/api/server-middleware.js +211 -0
  30. package/dist/api/server-routes.d.ts +19 -0
  31. package/dist/api/server-routes.js +293 -0
  32. package/dist/api/server-websocket-routes.d.ts +4 -0
  33. package/dist/api/server-websocket-routes.js +108 -0
  34. package/dist/api/server-wiring.d.ts +32 -0
  35. package/dist/api/server-wiring.js +236 -0
  36. package/dist/api/server.d.ts +10 -15
  37. package/dist/api/server.js +48 -1251
  38. package/dist/api/session-cookies.js +4 -4
  39. package/dist/api/sse-route.js +16 -12
  40. package/dist/crypto/owner-transition-rekey.d.ts +5 -1
  41. package/dist/crypto/owner-transition-rekey.js +1 -1
  42. package/dist/crypto/pii-field-encryption.d.ts +5 -1
  43. package/dist/crypto/pii-field-encryption.js +3 -1
  44. package/dist/db/aggregate-sql.d.ts +21 -0
  45. package/dist/db/aggregate-sql.js +155 -0
  46. package/dist/db/closed-connection.d.ts +9 -0
  47. package/dist/db/closed-connection.js +95 -0
  48. package/dist/db/dialect.js +1 -1
  49. package/dist/db/distinct-on.d.ts +1 -1
  50. package/dist/db/driver-row-coercion.d.ts +2 -0
  51. package/dist/db/driver-row-coercion.js +186 -0
  52. package/dist/db/driver-value-preparation.d.ts +16 -0
  53. package/dist/db/driver-value-preparation.js +99 -0
  54. package/dist/db/entity-field-encryption.d.ts +1 -0
  55. package/dist/db/entity-field-encryption.js +1 -1
  56. package/dist/db/entity-table-meta.js +103 -110
  57. package/dist/db/event-store-executor-context.d.ts +2 -1
  58. package/dist/db/event-store-executor-read-aggregate.d.ts +8 -0
  59. package/dist/db/event-store-executor-read-aggregate.js +112 -0
  60. package/dist/db/event-store-executor-read-filter.d.ts +31 -0
  61. package/dist/db/event-store-executor-read-filter.js +203 -0
  62. package/dist/db/event-store-executor-read-paging.d.ts +14 -0
  63. package/dist/db/event-store-executor-read-paging.js +99 -0
  64. package/dist/db/event-store-executor-read-rows.d.ts +6 -0
  65. package/dist/db/event-store-executor-read-rows.js +32 -0
  66. package/dist/db/event-store-executor-read-search.d.ts +28 -0
  67. package/dist/db/event-store-executor-read-search.js +221 -0
  68. package/dist/db/event-store-executor-read-sort.d.ts +41 -0
  69. package/dist/db/event-store-executor-read-sort.js +130 -0
  70. package/dist/db/event-store-executor-read-where.d.ts +24 -0
  71. package/dist/db/event-store-executor-read-where.js +81 -0
  72. package/dist/db/event-store-executor-read.d.ts +2 -9
  73. package/dist/db/event-store-executor-read.js +48 -701
  74. package/dist/db/event-store-executor-write-steps.d.ts +20 -0
  75. package/dist/db/event-store-executor-write-steps.js +100 -0
  76. package/dist/db/event-store-executor-write.js +395 -499
  77. package/dist/db/index.d.ts +6 -2
  78. package/dist/db/index.js +5 -1
  79. package/dist/db/list-aggregate-labels.d.ts +14 -0
  80. package/dist/db/list-aggregate-labels.js +26 -0
  81. package/dist/db/migrate-generator.js +71 -86
  82. package/dist/db/migrate-runner.js +4 -1
  83. package/dist/db/query.d.ts +2 -15
  84. package/dist/db/query.js +8 -761
  85. package/dist/db/reference-data.js +6 -1
  86. package/dist/db/table-builder.js +113 -166
  87. package/dist/db/table-info.d.ts +10 -0
  88. package/dist/db/table-info.js +45 -0
  89. package/dist/db/temporal-value-guards.d.ts +6 -0
  90. package/dist/db/temporal-value-guards.js +15 -0
  91. package/dist/db/tenant-db-runner.d.ts +1 -0
  92. package/dist/db/tenant-db-runner.js +3 -0
  93. package/dist/db/tenant-db.js +39 -9
  94. package/dist/db/where-clause.d.ts +6 -0
  95. package/dist/db/where-clause.js +236 -0
  96. package/dist/derivatives/delete-stored-file.js +2 -1
  97. package/dist/engine/boot-validator/boot-check.js +1 -1
  98. package/dist/engine/boot-validator/detail-screens.js +2 -2
  99. package/dist/engine/boot-validator/entity-handler.d.ts +1 -0
  100. package/dist/engine/boot-validator/entity-handler.js +17 -0
  101. package/dist/engine/boot-validator/form-derivations.js +35 -1
  102. package/dist/engine/boot-validator/i18n-keys.js +5 -3
  103. package/dist/engine/boot-validator/index.js +2 -0
  104. package/dist/engine/boot-validator/pii-retention.js +233 -155
  105. package/dist/engine/boot-validator/screens-actions.d.ts +1 -0
  106. package/dist/engine/boot-validator/screens-actions.js +8 -1
  107. package/dist/engine/boot-validator/screens-dashboard.js +2 -5
  108. package/dist/engine/boot-validator/screens-forms.js +2 -6
  109. package/dist/engine/boot-validator/screens-kind-config-edit.d.ts +3 -0
  110. package/dist/engine/boot-validator/screens-kind-config-edit.js +88 -0
  111. package/dist/engine/boot-validator/screens-kind-entity-edit.d.ts +3 -0
  112. package/dist/engine/boot-validator/screens-kind-entity-edit.js +55 -0
  113. package/dist/engine/boot-validator/screens-kind-entity-list.d.ts +3 -0
  114. package/dist/engine/boot-validator/screens-kind-entity-list.js +228 -0
  115. package/dist/engine/boot-validator/screens-kind-projection-detail.d.ts +3 -0
  116. package/dist/engine/boot-validator/screens-kind-projection-detail.js +235 -0
  117. package/dist/engine/boot-validator/screens-kind-projection-list.d.ts +3 -0
  118. package/dist/engine/boot-validator/screens-kind-projection-list.js +52 -0
  119. package/dist/engine/boot-validator/screens-kind-secret-mint.d.ts +3 -0
  120. package/dist/engine/boot-validator/screens-kind-secret-mint.js +28 -0
  121. package/dist/engine/boot-validator/screens-kind-shared.d.ts +37 -0
  122. package/dist/engine/boot-validator/screens-kind-shared.js +142 -0
  123. package/dist/engine/boot-validator/screens-lists.d.ts +2 -1
  124. package/dist/engine/boot-validator/screens-lists.js +12 -9
  125. package/dist/engine/boot-validator/screens.d.ts +0 -2
  126. package/dist/engine/boot-validator/screens.js +99 -860
  127. package/dist/engine/build-app-schema.js +56 -96
  128. package/dist/engine/define-feature.js +9 -2
  129. package/dist/engine/define-handler.js +1 -0
  130. package/dist/engine/entity-handlers.d.ts +13 -0
  131. package/dist/engine/entity-handlers.js +10 -21
  132. package/dist/engine/entity-list-runtime-options.d.ts +8 -0
  133. package/dist/engine/entity-list-runtime-options.js +18 -0
  134. package/dist/engine/feature-ast/extractors/handlers.js +1 -0
  135. package/dist/engine/feature-ast/extractors/round2.js +25 -6
  136. package/dist/engine/feature-ast/patch.js +4 -182
  137. package/dist/engine/feature-ast/pattern-change-schema.d.ts +3 -0
  138. package/dist/engine/feature-ast/pattern-change-schema.js +1 -0
  139. package/dist/engine/feature-ast/pattern-id-matchers.d.ts +7 -0
  140. package/dist/engine/feature-ast/pattern-id-matchers.js +210 -0
  141. package/dist/engine/feature-ast/patterns.d.ts +9 -0
  142. package/dist/engine/feature-ast/patterns.js +43 -51
  143. package/dist/engine/feature-ast/render.js +48 -87
  144. package/dist/engine/feature-entity-handlers.js +3 -0
  145. package/dist/engine/form-derivations.js +18 -7
  146. package/dist/engine/handler-field-references.js +1 -1
  147. package/dist/engine/project-app-schema-for-roles.d.ts +2 -0
  148. package/dist/engine/project-app-schema-for-roles.js +6 -4
  149. package/dist/engine/registry-ingest.js +1 -1
  150. package/dist/engine/schema-builder.js +228 -212
  151. package/dist/engine/screen-helpers.js +1 -1
  152. package/dist/engine/screen-kind.d.ts +7 -0
  153. package/dist/engine/screen-kind.js +1 -0
  154. package/dist/engine/strip-disabled-features-from-app-schema.js +51 -14
  155. package/dist/engine/validate-projection-allowlist.js +2 -1
  156. package/dist/entrypoint/index.d.ts +4 -1
  157. package/dist/entrypoint/index.js +7 -1
  158. package/dist/errors/docs-url.d.ts +1 -1
  159. package/dist/errors/docs-url.js +22 -2
  160. package/dist/es-ops/context.js +1 -1
  161. package/dist/event-store/admin-api.js +1 -1
  162. package/dist/event-store/event-store.js +10 -4
  163. package/dist/files/bind-pending-file-refs.js +3 -3
  164. package/dist/files/create-file-ref-from-bytes.js +4 -1
  165. package/dist/files/file-handle.js +2 -3
  166. package/dist/files/file-routes.js +11 -8
  167. package/dist/files/resolve-content-type.js +43 -0
  168. package/dist/files/upload-ledger.js +7 -0
  169. package/dist/i18n/required-surface-keys.js +182 -206
  170. package/dist/internal/stack-support.d.ts +2 -1
  171. package/dist/internal/stack-support.js +1 -1
  172. package/dist/jobs/job-runner-context.d.ts +32 -0
  173. package/dist/jobs/job-runner-context.js +305 -0
  174. package/dist/jobs/job-runner-execution.d.ts +7 -0
  175. package/dist/jobs/job-runner-execution.js +160 -0
  176. package/dist/jobs/job-runner-gates.d.ts +19 -0
  177. package/dist/jobs/job-runner-gates.js +198 -0
  178. package/dist/jobs/job-runner-helpers.d.ts +17 -3
  179. package/dist/jobs/job-runner-helpers.js +61 -2
  180. package/dist/jobs/job-runner-scope.d.ts +44 -0
  181. package/dist/jobs/job-runner-scope.js +102 -0
  182. package/dist/jobs/job-runner-types.d.ts +12 -0
  183. package/dist/jobs/job-runner.d.ts +0 -1
  184. package/dist/jobs/job-runner.js +47 -657
  185. package/dist/observability/escape-hatch-report.d.ts +3 -0
  186. package/dist/observability/escape-hatch-report.js +12 -1
  187. package/dist/pipeline/cascade-handler.js +165 -138
  188. package/dist/pipeline/dispatch-query.js +10 -3
  189. package/dist/pipeline/dispatch-shared.d.ts +13 -3
  190. package/dist/pipeline/dispatch-shared.js +58 -710
  191. package/dist/pipeline/dispatch-write.js +296 -189
  192. package/dist/pipeline/event-dispatcher-admin.js +2 -1
  193. package/dist/pipeline/event-dispatcher-delivery.js +3 -5
  194. package/dist/pipeline/handler-context-auth.d.ts +9 -0
  195. package/dist/pipeline/handler-context-auth.js +54 -0
  196. package/dist/pipeline/handler-context-config.d.ts +14 -0
  197. package/dist/pipeline/handler-context-config.js +113 -0
  198. package/dist/pipeline/handler-context-db.d.ts +24 -0
  199. package/dist/pipeline/handler-context-db.js +149 -0
  200. package/dist/pipeline/handler-context-dispatch.d.ts +21 -0
  201. package/dist/pipeline/handler-context-dispatch.js +89 -0
  202. package/dist/pipeline/handler-context-events.d.ts +43 -0
  203. package/dist/pipeline/handler-context-events.js +237 -0
  204. package/dist/pipeline/handler-context-notify.d.ts +12 -0
  205. package/dist/pipeline/handler-context-notify.js +55 -0
  206. package/dist/pipeline/handler-context-observability.d.ts +7 -0
  207. package/dist/pipeline/handler-context-observability.js +21 -0
  208. package/dist/pipeline/handler-context-scope.d.ts +48 -0
  209. package/dist/pipeline/handler-context-scope.js +88 -0
  210. package/dist/pipeline/notify-scope.js +12 -1
  211. package/dist/pipeline/pre-handler-gates.js +20 -4
  212. package/dist/pipeline/pre-transaction-reservations.d.ts +1 -0
  213. package/dist/pipeline/pre-transaction-reservations.js +9 -3
  214. package/dist/pipeline/projection-rebuild.js +2 -1
  215. package/dist/schema-cli/apply-command.d.ts +2 -0
  216. package/dist/schema-cli/apply-command.js +97 -0
  217. package/dist/schema-cli/baseline-command.d.ts +2 -0
  218. package/dist/schema-cli/baseline-command.js +33 -0
  219. package/dist/schema-cli/generate-command.d.ts +2 -0
  220. package/dist/schema-cli/generate-command.js +90 -0
  221. package/dist/schema-cli/shared.d.ts +39 -0
  222. package/dist/schema-cli/shared.js +95 -0
  223. package/dist/schema-cli/status-command.d.ts +2 -0
  224. package/dist/schema-cli/status-command.js +73 -0
  225. package/dist/schema-cli/validate-boot-input.d.ts +5 -0
  226. package/dist/schema-cli/validate-boot-input.js +130 -0
  227. package/dist/schema-cli/validate-command.d.ts +2 -0
  228. package/dist/schema-cli/validate-command.js +130 -0
  229. package/dist/schema-cli.d.ts +2 -18
  230. package/dist/schema-cli.js +47 -604
  231. package/dist/search/meilisearch-adapter.js +2 -1
  232. package/dist/upgrade-cli.js +149 -121
  233. package/package.json +4 -4
  234. package/src/changes.json +310 -0
  235. package/src/scripts/codemod/feature-layout.ts +10 -2
  236. package/src/scripts/codemod/object-form.ts +41 -4
  237. package/src/scripts/codemod/package-merge.ts +58 -8
  238. package/src/scripts/codemod/pii-personal-migration.ts +175 -145
  239. package/src/scripts/codemod/testing-injection.ts +122 -42
  240. package/src/scripts/codemod/testing-move.ts +29 -7
@@ -97,3 +97,215 @@ Wenn eine kaputte Migration den Boot blockiert: setze `skippable: true` im seed-
97
97
  ## See also
98
98
 
99
99
  - **Driver-Use-Case:** `seeds/2026-02-01-fix-admin-roles.ts` in this recipe
100
+
101
+ ## Source files
102
+
103
+ ### scripts/smoke.ts
104
+
105
+ ```ts
106
+ // biome-ignore-all lint/suspicious/noConsole: CLI tool output
107
+ //
108
+ // Lokaler Smoke-Test für seed-migrations.
109
+ //
110
+ // Was er prüft (offline, ohne Pod-Deploy):
111
+ // 1. Alle seed-files in ./seeds/ können dynamic-import geladen werden
112
+ // → catched syntax-errors + missing default-exports + falsche paths
113
+ // 2. Jeder handler-QN den die seeds referenzieren ist in der echten
114
+ // App-Registry registriert → catched camelCase-typos + drift
115
+ // (Bug-Klasse 3 aus Phase 1 retro)
116
+ // 3. Jeder system-user (mit oder ohne tenantIdOverride) hat Access auf
117
+ // die referenzierten Handler → catched access-rule-Drift
118
+ // (Bug-Klasse 4 aus Phase 1 retro)
119
+ //
120
+ // Schreibt NICHTS in die DB. Reine Read-only Validation.
121
+ //
122
+ // Usage:
123
+ // bun scripts/smoke.ts (gegen ./seeds/)
124
+ // bun scripts/smoke.ts --seeds-dir foo (custom path)
125
+ //
126
+ // In CI: vor `build`-Step laufen lassen — fängt 80% der Phase-1-Bugs
127
+ // lokal in <5 Sekunden statt nach 8-Minuten-Docker-Build im Pod-Boot.
128
+
129
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
130
+ import { resolve } from "node:path";
131
+ // App-spezifisch: nimm die EFFEKTIVEN Features die deine App zur Runtime
132
+ // nutzt. Im typischen Setup mit `runProdApp({ features, auth })`:
133
+ //
134
+ // import { composeFeatures } from "@cosmicdrift/kumiko-dev-server";
135
+ // import { APP_FEATURES } from "../src/run-config";
136
+ // const composed = composeFeatures(APP_FEATURES, { includeBundled: true, ... });
137
+ // const features = composed.features;
138
+ //
139
+ // Bei reiner App-Feature-Setup (kein auth-mode) reicht ein direkter
140
+ // Import der App-Features. Hier im Recipe der minimale set für die
141
+ // referenzierten Recipe-Seeds (tenant + config) — App-Author tauscht
142
+ // gegen sein eigenes Feature-Set.
143
+ import { createConfigFeature } from "@cosmicdrift/kumiko-bundled-features/config";
144
+ import { createTenantFeature } from "@cosmicdrift/kumiko-bundled-features/tenant";
145
+ import {
146
+ createRegistry,
147
+ createSystemUser,
148
+ SYSTEM_TENANT_ID,
149
+ } from "@cosmicdrift/kumiko-framework/engine";
150
+
151
+ const features = [createConfigFeature(), createTenantFeature()];
152
+
153
+ const seedsDir = (() => {
154
+ const idx = process.argv.indexOf("--seeds-dir");
155
+ return idx >= 0 ? resolve(process.argv[idx + 1] ?? "./seeds") : resolve("./seeds");
156
+ })();
157
+
158
+ if (!existsSync(seedsDir)) {
159
+ console.log(`✓ no seeds-dir at ${seedsDir} — nothing to smoke-test`);
160
+ process.exit(0);
161
+ }
162
+
163
+ const seedFiles = readdirSync(seedsDir)
164
+ .filter((n) => n.endsWith(".ts") && !n.startsWith("_") && !n.startsWith("."))
165
+ .sort();
166
+
167
+ if (seedFiles.length === 0) {
168
+ console.log(`✓ ${seedsDir} is empty — nothing to smoke-test`);
169
+ process.exit(0);
170
+ }
171
+
172
+ const registry = createRegistry(features);
173
+
174
+ let failed = 0;
175
+
176
+ for (const file of seedFiles) {
177
+ const filePath = resolve(seedsDir, file);
178
+ console.log(`\nseed: ${file}`);
179
+
180
+ // 1. Module-load
181
+ try {
182
+ const mod = await import(filePath);
183
+ if (!mod.default || typeof mod.default.run !== "function") {
184
+ console.error(" ✗ missing valid SeedMigration default-export");
185
+ failed++;
186
+ continue;
187
+ }
188
+ console.log(` ✓ loads: "${mod.default.description}"`);
189
+ } catch (err) {
190
+ console.error(` ✗ load fail: ${err instanceof Error ? err.message : String(err)}`);
191
+ failed++;
192
+ continue;
193
+ }
194
+
195
+ // 2 + 3. QN-Resolution + Access (regex-based, gleiche Logik wie runner's
196
+ // dry-run-validator). Pattern matched inline-string-QNs:
197
+ const source = readFileSync(filePath, "utf-8");
198
+ const qnPattern = /systemWriteAs\s*\(\s*["']([^"']+)["']/g;
199
+ const qns = new Set<string>();
200
+ for (const m of source.matchAll(qnPattern)) {
201
+ const qn = m[1];
202
+ if (qn) qns.add(qn);
203
+ }
204
+
205
+ for (const qn of qns) {
206
+ const handler = registry.getWriteHandler(qn);
207
+ if (!handler) {
208
+ console.error(` ✗ handler "${qn}" not registered`);
209
+ failed++;
210
+ continue;
211
+ }
212
+ const accessRoles = (handler.access as { roles?: readonly string[] }).roles ?? [];
213
+ const systemUserRoles = createSystemUser(SYSTEM_TENANT_ID).roles;
214
+ const hasAccess = systemUserRoles.some((r) => accessRoles.includes(r));
215
+ if (!hasAccess) {
216
+ console.error(
217
+ ` ✗ system-user roles=${JSON.stringify([...systemUserRoles])} NOT in access.roles=${JSON.stringify([...accessRoles])} for "${qn}"`,
218
+ );
219
+ failed++;
220
+ continue;
221
+ }
222
+ console.log(` ✓ "${qn}" registered + accessible`);
223
+ }
224
+ }
225
+
226
+ if (failed > 0) {
227
+ console.error(`\n${failed} issue(s) — fix before push.`);
228
+ process.exit(1);
229
+ }
230
+ console.log(`\n✓ all ${seedFiles.length} seed-file(s) pass smoke.`);
231
+ ```
232
+
233
+ ### seeds/2026-01-01-initial-tenants.ts
234
+
235
+ ```ts
236
+ // Initial-Seed: Demo-Tenants anlegen.
237
+ //
238
+ // Note: in real apps wäre "initial tenants" wahrscheinlich nicht als
239
+ // seed-migration sondern als `r.config({seeds})` oder `auth.admin`-
240
+ // Config umgesetzt — die laufen jedes-Boot idempotent. Hier ist es als
241
+ // seed-migration gezeigt um das Pattern zu demonstrieren.
242
+ //
243
+ // Pattern: jede seed-migration arbeitet write-only via `systemWriteAs`
244
+ // damit alles als Event-Stream persistiert wird. Read-only Lookups
245
+ // (find*) am Context oder direkt via ctx.db.
246
+
247
+ import type { SeedMigration } from "@cosmicdrift/kumiko-framework/es-ops";
248
+
249
+ export default {
250
+ description: "initial demo tenants (alice-corp, beta-inc)",
251
+ run: async (ctx) => {
252
+ // Idempotent-Check: wenn Tenants schon existieren (z.B. via auth.admin-
253
+ // seed), skip diese Migration. Verhindert Konflikt beim re-boot wenn
254
+ // jemand den Marker manuell gelöscht hat.
255
+ const existing = await ctx.findTenants();
256
+ if (existing.some((t) => t.tenantKey === "alice-corp")) return;
257
+
258
+ await ctx.systemWriteAs("tenant:write:create", {
259
+ name: "Alice Corp",
260
+ tenantKey: "alice-corp",
261
+ });
262
+ await ctx.systemWriteAs("tenant:write:create", {
263
+ name: "Beta Inc",
264
+ tenantKey: "beta-inc",
265
+ });
266
+ },
267
+ } satisfies SeedMigration;
268
+ ```
269
+
270
+ ### seeds/2026-02-01-fix-admin-roles.ts
271
+
272
+ ```ts
273
+ // Driver-Pattern: ergänze fehlende Rolle auf existierenden Memberships.
274
+ // Spiegelt den realen publicstatus-Bug 2026-05-20 (admin@publicstatus.eu
275
+ // hatte "Admin" aber nicht "TenantAdmin" → text-content + legal-pages
276
+ // access_denied weil bundled-features TenantAdmin erwarten).
277
+ //
278
+ // Funktioniert für jede App mit dem gleichen Pattern: User-Lookup per
279
+ // E-Mail → alle Memberships durchgehen → bei Bedarf via existierendem
280
+ // updateMemberRoles-Handler korrigieren.
281
+
282
+ import type { SeedMigration } from "@cosmicdrift/kumiko-framework/es-ops";
283
+
284
+ export default {
285
+ description: "ergänze TenantAdmin-Rolle für admin@example.com auf allen Memberships",
286
+ run: async (ctx) => {
287
+ const admin = await ctx.findUserByEmail("admin@example.com");
288
+ if (!admin) return; // idempotent: admin nicht (mehr) vorhanden
289
+
290
+ for (const m of await ctx.findMembershipsOfUser(admin.id)) {
291
+ if (m.roles.includes("TenantAdmin")) continue; // bereits korrigiert
292
+
293
+ await ctx.systemWriteAs(
294
+ "tenant:write:update-member-roles", // kebab-case QN — wird vom Dry-Run-Validator gecheckt
295
+ {
296
+ userId: admin.id,
297
+ tenantId: m.tenantId,
298
+ roles: Array.from(new Set([...m.roles, "TenantAdmin"])),
299
+ },
300
+ // tenantIdOverride: stream-tenant aus kumiko_events.v1 — NICHT
301
+ // m.tenantId (payload-tenant). Beide stimmen oft überein, weichen
302
+ // aber ab wenn das Aggregate ursprünglich von einem fremden
303
+ // Executor angelegt wurde (seedTenantMembership by=systemAdmin —
304
+ // publicstatus-Pattern). findMembershipsOfUser liefert beide,
305
+ // kein eigener JOIN nötig.
306
+ m.streamTenantId,
307
+ );
308
+ }
309
+ },
310
+ } satisfies SeedMigration;
311
+ ```