@cosmicdrift/kumiko-framework 0.2.2 → 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 (191) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/package.json +124 -38
  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/auth/__tests__/roles.test.ts +24 -0
  10. package/src/auth/index.ts +7 -0
  11. package/src/auth/roles.ts +42 -0
  12. package/src/compliance/__tests__/duration-spec.test.ts +72 -0
  13. package/src/compliance/__tests__/profiles.test.ts +308 -0
  14. package/src/compliance/__tests__/sub-processors.test.ts +139 -0
  15. package/src/compliance/duration-spec.ts +44 -0
  16. package/src/compliance/index.ts +31 -0
  17. package/src/compliance/override-schema.ts +136 -0
  18. package/src/compliance/profiles.ts +427 -0
  19. package/src/compliance/sub-processors.ts +152 -0
  20. package/src/db/__tests__/big-int-field.test.ts +131 -0
  21. package/src/db/assert-exists-in.ts +2 -2
  22. package/src/db/cursor.ts +3 -3
  23. package/src/db/event-store-executor.ts +19 -13
  24. package/src/db/located-timestamp.ts +1 -1
  25. package/src/db/money.ts +12 -2
  26. package/src/db/pg-error.ts +1 -1
  27. package/src/db/row-helpers.ts +1 -1
  28. package/src/db/table-builder.ts +20 -5
  29. package/src/db/tenant-db.ts +9 -9
  30. package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
  31. package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
  32. package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
  33. package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
  34. package/src/engine/__tests__/build-target.test.ts +135 -0
  35. package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
  36. package/src/engine/__tests__/entity-handlers.test.ts +3 -3
  37. package/src/engine/__tests__/event-helpers.test.ts +4 -4
  38. package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
  39. package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
  40. package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
  41. package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
  42. package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
  43. package/src/engine/__tests__/raw-table.test.ts +2 -2
  44. package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
  45. package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
  46. package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
  47. package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
  48. package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
  49. package/src/engine/__tests__/steps-read.test.ts +142 -0
  50. package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
  51. package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
  52. package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
  53. package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
  54. package/src/engine/__tests__/steps-workflow.test.ts +198 -0
  55. package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
  56. package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
  57. package/src/engine/boot-validator/api-ext.ts +77 -0
  58. package/src/engine/boot-validator/config-deps.ts +163 -0
  59. package/src/engine/boot-validator/entity-handler.ts +466 -0
  60. package/src/engine/boot-validator/index.ts +159 -0
  61. package/src/engine/boot-validator/ownership.ts +198 -0
  62. package/src/engine/boot-validator/pii-retention.ts +155 -0
  63. package/src/engine/boot-validator/screens-nav.ts +624 -0
  64. package/src/engine/boot-validator.ts +1 -1528
  65. package/src/engine/build-app-schema.ts +1 -1
  66. package/src/engine/build-target.ts +99 -0
  67. package/src/engine/codemod/index.ts +15 -0
  68. package/src/engine/codemod/pipeline-codemod.ts +641 -0
  69. package/src/engine/config-helpers.ts +9 -19
  70. package/src/engine/constants.ts +1 -1
  71. package/src/engine/define-feature.ts +127 -9
  72. package/src/engine/define-handler.ts +89 -3
  73. package/src/engine/define-roles.ts +2 -2
  74. package/src/engine/define-step.ts +28 -0
  75. package/src/engine/define-workflow.ts +110 -0
  76. package/src/engine/entity-handlers.ts +10 -9
  77. package/src/engine/event-helpers.ts +4 -4
  78. package/src/engine/extension-names.ts +105 -0
  79. package/src/engine/extensions/user-data.ts +106 -0
  80. package/src/engine/factories.ts +26 -16
  81. package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
  82. package/src/engine/feature-ast/extractors/index.ts +74 -0
  83. package/src/engine/feature-ast/extractors/round1.ts +110 -0
  84. package/src/engine/feature-ast/extractors/round2.ts +253 -0
  85. package/src/engine/feature-ast/extractors/round3.ts +471 -0
  86. package/src/engine/feature-ast/extractors/round4.ts +1365 -0
  87. package/src/engine/feature-ast/extractors/round5.ts +72 -0
  88. package/src/engine/feature-ast/extractors/round6.ts +66 -0
  89. package/src/engine/feature-ast/extractors/shared.ts +177 -0
  90. package/src/engine/feature-ast/parse.ts +13 -0
  91. package/src/engine/feature-ast/patch.ts +9 -1
  92. package/src/engine/feature-ast/patcher.ts +10 -3
  93. package/src/engine/feature-ast/patterns.ts +71 -1
  94. package/src/engine/feature-ast/render.ts +31 -1
  95. package/src/engine/index.ts +66 -2
  96. package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
  97. package/src/engine/pattern-library/library.ts +78 -2
  98. package/src/engine/pipeline.ts +88 -0
  99. package/src/engine/projection-helpers.ts +1 -1
  100. package/src/engine/read-claim.ts +1 -1
  101. package/src/engine/registry.ts +30 -2
  102. package/src/engine/resolve-config-or-param.ts +4 -0
  103. package/src/engine/run-pipeline.ts +162 -0
  104. package/src/engine/schema-builder.ts +10 -4
  105. package/src/engine/state-machine.ts +1 -1
  106. package/src/engine/steps/_drizzle-boundary.ts +19 -0
  107. package/src/engine/steps/_duration-utils.ts +33 -0
  108. package/src/engine/steps/_no-return-guard.ts +21 -0
  109. package/src/engine/steps/_resolver-utils.ts +42 -0
  110. package/src/engine/steps/_step-dispatch-constants.ts +38 -0
  111. package/src/engine/steps/aggregate-append-event.ts +56 -0
  112. package/src/engine/steps/aggregate-create.ts +56 -0
  113. package/src/engine/steps/aggregate-update.ts +68 -0
  114. package/src/engine/steps/branch.ts +84 -0
  115. package/src/engine/steps/call-feature.ts +49 -0
  116. package/src/engine/steps/compute.ts +41 -0
  117. package/src/engine/steps/for-each.ts +111 -0
  118. package/src/engine/steps/mail-send.ts +44 -0
  119. package/src/engine/steps/read-find-many.ts +51 -0
  120. package/src/engine/steps/read-find-one.ts +58 -0
  121. package/src/engine/steps/retry.ts +87 -0
  122. package/src/engine/steps/return.ts +34 -0
  123. package/src/engine/steps/unsafe-projection-delete.ts +46 -0
  124. package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
  125. package/src/engine/steps/wait-for-event.ts +71 -0
  126. package/src/engine/steps/wait.ts +69 -0
  127. package/src/engine/steps/webhook-send.ts +71 -0
  128. package/src/engine/system-user.ts +1 -1
  129. package/src/engine/types/feature.ts +143 -1
  130. package/src/engine/types/fields.ts +134 -10
  131. package/src/engine/types/handlers.ts +18 -10
  132. package/src/engine/types/identifiers.ts +1 -0
  133. package/src/engine/types/index.ts +15 -1
  134. package/src/engine/types/step.ts +334 -0
  135. package/src/engine/types/target-ref.ts +21 -0
  136. package/src/engine/types/tree-node.ts +130 -0
  137. package/src/engine/types/workspace.ts +7 -0
  138. package/src/engine/validate-projection-allowlist.ts +161 -0
  139. package/src/event-store/snapshot.ts +1 -1
  140. package/src/event-store/upcaster-dead-letter.ts +1 -1
  141. package/src/event-store/upcaster.ts +1 -1
  142. package/src/files/__tests__/read-stream.test.ts +105 -0
  143. package/src/files/__tests__/write-stream.test.ts +233 -0
  144. package/src/files/__tests__/zip-stream.test.ts +357 -0
  145. package/src/files/file-routes.ts +1 -1
  146. package/src/files/in-memory-provider.ts +38 -0
  147. package/src/files/index.ts +3 -0
  148. package/src/files/local-provider.ts +58 -1
  149. package/src/files/types.ts +36 -8
  150. package/src/files/zip-stream.ts +251 -0
  151. package/src/jobs/job-runner.ts +10 -10
  152. package/src/lifecycle/lifecycle.ts +0 -3
  153. package/src/logging/index.ts +1 -0
  154. package/src/logging/pino-logger.ts +11 -7
  155. package/src/logging/utils.ts +24 -0
  156. package/src/observability/prometheus-meter.ts +7 -5
  157. package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
  158. package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
  159. package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
  160. package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
  161. package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
  162. package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
  163. package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
  164. package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
  165. package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
  166. package/src/pipeline/append-event-core.ts +22 -6
  167. package/src/pipeline/dispatcher-utils.ts +188 -0
  168. package/src/pipeline/dispatcher.ts +63 -283
  169. package/src/pipeline/distributed-lock.ts +1 -1
  170. package/src/pipeline/entity-cache.ts +2 -2
  171. package/src/pipeline/event-consumer-state.ts +0 -13
  172. package/src/pipeline/event-dispatcher.ts +4 -4
  173. package/src/pipeline/index.ts +0 -2
  174. package/src/pipeline/lifecycle-pipeline.ts +6 -12
  175. package/src/pipeline/msp-rebuild.ts +5 -5
  176. package/src/pipeline/multi-stream-apply-context.ts +6 -7
  177. package/src/pipeline/projection-rebuild.ts +2 -2
  178. package/src/pipeline/projection-state.ts +0 -12
  179. package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
  180. package/src/rate-limit/resolver.ts +1 -1
  181. package/src/search/in-memory-adapter.ts +1 -1
  182. package/src/search/meilisearch-adapter.ts +3 -3
  183. package/src/search/types.ts +1 -1
  184. package/src/secrets/leak-guard.ts +2 -2
  185. package/src/stack/request-helper.ts +9 -5
  186. package/src/stack/test-stack.ts +1 -1
  187. package/src/testing/handler-context.ts +4 -4
  188. package/src/testing/http-cookies.ts +1 -1
  189. package/src/time/tz-context.ts +1 -2
  190. package/src/ui-types/index.ts +4 -0
  191. package/src/engine/feature-ast/extractors.ts +0 -2562
@@ -54,6 +54,7 @@ import type { NavDefinition } from "./nav";
54
54
  import type { MultiStreamProjectionDefinition, ProjectionDefinition } from "./projection";
55
55
  import type { EntityRelations, RelationDefinition } from "./relations";
56
56
  import type { ScreenDefinition } from "./screen";
57
+ import type { TreeActionDef, TreeActionsHandle, TreeChildrenSubscribe } from "./tree-node";
57
58
  import type { WorkspaceDefinition } from "./workspace";
58
59
 
59
60
  // --- Metrics (declared by features via r.metric()) ---
@@ -148,6 +149,14 @@ export type FeatureDefinition = {
148
149
  readonly exports?: unknown;
149
150
  readonly requires: readonly string[];
150
151
  readonly optionalRequires: readonly string[];
152
+ // Read-side projection-tables this feature is allowed to write via
153
+ // r.step.unsafeProjectionUpsert / unsafeProjectionDelete. Declared via
154
+ // r.requires.projection("table_name"). Hard requirement — boot-error
155
+ // if a step targets a non-listed table or one that's already an
156
+ // r.entity-registered aggregate-table. See step-vocabulary.md Q10.
157
+ readonly requiredProjections: ReadonlySet<string>;
158
+ // Tier-2 step kinds opted-in via r.requires.step("webhook.send"). Q9.
159
+ readonly requiredSteps: ReadonlySet<string>;
151
160
  // Declared via r.toggleable({ default }). Presence makes the feature
152
161
  // operator-switchable via the feature-toggles bundled feature; absence
153
162
  // means the feature is always-on (e.g. auth, tenant, user — core infra
@@ -164,6 +173,21 @@ export type FeatureDefinition = {
164
173
  readonly jobs: Readonly<Record<string, JobDefinition>>;
165
174
  readonly registrarExtensions: Readonly<Record<string, RegistrarExtensionDef>>;
166
175
  readonly extensionUsages: readonly RegistrarExtensionRegistration[];
176
+ /**
177
+ * Cross-feature API names this feature exposes via `r.exposesApi(name)`.
178
+ * Pure Marker-Deklaration — die echte Implementation wird als
179
+ * Query-/Write-Handler unter dem QN-Pattern registriert (z.B.
180
+ * `compliance-profiles:query:effective-profile`). Boot-Validator prüft
181
+ * dass jedes `r.usesApi(name)` einen passenden Exposer hier findet —
182
+ * Tippfehler oder Drop-Refactorings werden zu Boot-Fail statt Runtime-Crash.
183
+ */
184
+ readonly exposedApis: ReadonlySet<string>;
185
+ /**
186
+ * Cross-feature API names this feature calls. Pflicht-Boot-Check:
187
+ * jeder Eintrag muss in `exposedApis` irgendeines Features auftauchen
188
+ * UND das Provider-Feature muss in requires/optionalRequires sein.
189
+ */
190
+ readonly usedApis: ReadonlySet<string>;
167
191
  readonly referenceData: readonly ReferenceDataDef[];
168
192
  readonly notifications: Readonly<Record<string, NotificationDefinition>>;
169
193
  readonly events: Readonly<Record<string, EventDef>>;
@@ -209,6 +233,21 @@ export type FeatureDefinition = {
209
233
  // shellWorkspaces consumes the resolved per-workspace nav list at mount
210
234
  // time; engine validates roles + nav refs at boot.
211
235
  readonly workspaces: Readonly<Record<string, WorkspaceDefinition>>;
236
+ // Tree-Actions-Map declared via r.treeActions(). At-most-one per feature
237
+ // (only-once-guard at registration). Erased to `Record<string,
238
+ // TreeActionDef>` for runtime registry-lookup (Visual-Tree-Component
239
+ // dispatching, Pattern-AST consumers). The compile-time-typed surface
240
+ // is the registrar's return value (TreeActionsHandle) which the
241
+ // feature exports via setup-return — buildTarget consumes the handle,
242
+ // not this slot. See visual-tree.md A5 + A7.
243
+ readonly treeActions?: Readonly<Record<string, TreeActionDef>>;
244
+ // Tree-Provider declared via r.tree(). At-most-one per feature.
245
+ // Provider liefert die Top-Level-Knoten dieses Features im Visual-
246
+ // Workspace (navigation: "tree"). Subscribe-Form mit lazy-Eval: erst
247
+ // beim Mount des Workspaces aufgerufen, kann Updates emittieren.
248
+ // Feature ohne treeProvider ist im Visual-Workspace unsichtbar
249
+ // (Zero-Whitelist-Filter aus visual-tree.md A2).
250
+ readonly treeProvider?: TreeChildrenSubscribe;
212
251
  // HTTP-Routes declared via r.httpRoute(). Index is "METHOD path"
213
252
  // (z.B. "GET /feed.xml") — eindeutig pro Feature. Die App-Server-
214
253
  // Boot-Stage iteriert getAllHttpRoutes() und mountet jede Route auf
@@ -235,9 +274,22 @@ type RefOrRefs = NameOrRef | readonly NameOrRef[];
235
274
  * keeping strict-mode alive even when handlers route via `eventDef.name`
236
275
  * instead of hand-typed string literals.
237
276
  */
277
+ /**
278
+ * `r.requires` is a callable+namespace: existing call form takes feature
279
+ * names (`r.requires("auth", "tenant")`), the `.projection` extension
280
+ * declares read-side projection tables that this feature's pipeline
281
+ * steps are allowed to write via `r.step.unsafeProjectionUpsert`.
282
+ * Hard-required for any unsafeProjection-* step usage (see Q10).
283
+ */
284
+ export type RequiresApi = ((...featureNames: string[]) => void) & {
285
+ readonly projection: (tableName: string) => void;
286
+ // Tier-2 step opt-in (Q9). Tier-1 implicit, Tier-2 must be declared.
287
+ readonly step: (stepKind: string) => void;
288
+ };
289
+
238
290
  export type FeatureRegistrar<TFeature extends string = string> = {
239
291
  systemScope(): void;
240
- requires(...featureNames: string[]): void;
292
+ requires: RequiresApi;
241
293
  optionalRequires(...featureNames: string[]): void;
242
294
  // Declare the feature as operator-togglable. `default` is the effective
243
295
  // state when no global-toggle row exists. Must be called at most once per
@@ -360,6 +412,42 @@ export type FeatureRegistrar<TFeature extends string = string> = {
360
412
 
361
413
  useExtension(extensionName: string, entity: NameOrRef, options?: Record<string, unknown>): void;
362
414
 
415
+ /**
416
+ * Marker-Deklaration: dieses Feature stellt eine Cross-Feature-API
417
+ * unter dem genannten Namen bereit. Die eigentliche Implementation
418
+ * wird separat als Query- oder Write-Handler unter dem QN-Pattern
419
+ * registriert; `r.exposesApi` ist reine Boot-Check-Surface.
420
+ *
421
+ * Boot-Validator prüft, dass jedes `r.usesApi(name)` einen passenden
422
+ * Exposer findet, dass das Exposer-Feature in requires/optionalRequires
423
+ * gelisted ist und dass kein API-Name doppelt exposed wird.
424
+ *
425
+ * ```ts
426
+ * defineFeature("compliance-profiles", (r) => {
427
+ * r.exposesApi("compliance.forTenant");
428
+ * r.queryHandler({
429
+ * name: "compliance:query:for-tenant",
430
+ * // ... echte Implementation
431
+ * });
432
+ * });
433
+ * ```
434
+ */
435
+ exposesApi(apiName: string): void;
436
+
437
+ /**
438
+ * Declares that this feature calls a cross-feature API. Boot-Validator
439
+ * checkt dass irgendein anderes Feature `r.exposesApi(apiName)` macht
440
+ * und dass dieses Feature `r.requires/optionalRequires` darauf hat.
441
+ *
442
+ * ```ts
443
+ * defineFeature("user-data-rights", (r) => {
444
+ * r.requires("compliance-profiles");
445
+ * r.usesApi("compliance.forTenant");
446
+ * });
447
+ * ```
448
+ */
449
+ usesApi(apiName: string): void;
450
+
363
451
  // Declare a metric. Short name (without kumiko_<feature>_ prefix) — Framework
364
452
  // qualifies it on boot. Validation (snake_case + typ-suffix) runs at boot.
365
453
  // Usage at runtime: ctx.metrics.inc("created_total", { status: "new" }).
@@ -455,6 +543,44 @@ export type FeatureRegistrar<TFeature extends string = string> = {
455
543
  // a non-empty string is the contract. If you can't write a reason,
456
544
  // declare data via `r.entity()` instead.
457
545
  rawTable(name: string, table: PgTable, options: RawTableOptions): void;
546
+
547
+ // Register the tree-actions schema for this feature — a map of
548
+ // action-name → action-definition (with optional typed args). At-most-
549
+ // one call per feature.
550
+ //
551
+ // Returns a TreeActionsHandle that the feature exports via setup-return
552
+ // (Memory `[EventDef-Exports-Pattern]`). The handle carries the
553
+ // literal-typed action-map that `buildTarget` consumes for compile-
554
+ // time validation:
555
+ //
556
+ // const handle = r.treeActions({
557
+ // edit: { args: { slug: "" as string } },
558
+ // list: {},
559
+ // });
560
+ // return { handle };
561
+ //
562
+ // Without this typed return, the action-map collapses to
563
+ // `Record<string, TreeActionDef>` at the buildTarget call-site and
564
+ // every action becomes accept-anything-string. See visual-tree.md A5.
565
+ //
566
+ // The runtime FeatureDefinition.treeActions slot stores the same map
567
+ // as erased Record (registry lookup, Pattern-AST consumers).
568
+ treeActions<const TActions extends Record<string, TreeActionDef>>(
569
+ actions: TActions,
570
+ ): TreeActionsHandle<TFeature, TActions>;
571
+
572
+ // Register the tree-provider for this feature — the Subscribe-Function
573
+ // that emits the top-level Tree-Knoten when the Visual-Workspace
574
+ // (navigation: "tree") mounts. At-most-one call per feature.
575
+ //
576
+ // Provider receives a TreeContext (Phase-0-stub, opaque) and an
577
+ // emit-function; returns an unsubscribe-function. Initial-emit synchron
578
+ // oder async, weitere Emits beliebig oft (e.g. on entity-update SSE).
579
+ //
580
+ // A feature without r.tree() is invisible in `navigation: "tree"`-
581
+ // workspaces — that's the Zero-Whitelist-Filter from visual-tree.md A2:
582
+ // provider-Vorhandensein ist der Filter, kein Workspace-Mapping.
583
+ tree(provider: TreeChildrenSubscribe): void;
458
584
  };
459
585
 
460
586
  // --- Registry (created from features) ---
@@ -626,4 +752,20 @@ export type Registry = {
626
752
  // validator rejects more than one. Apps without a default fall back to
627
753
  // the first workspace the user has access to.
628
754
  getDefaultWorkspace(): WorkspaceDefinition | undefined;
755
+
756
+ // Tree-Providers declared via r.tree() across all features. Keyed by
757
+ // declaring feature name (NOT qualified — Provider sind feature-bound,
758
+ // ein Feature liefert genau eine Provider-Function). The Visual-Tree
759
+ // component (renderer-web) iteriert getTreeProviders() beim Mount des
760
+ // navigation: "tree"-Workspaces, ruft jeden Provider mit ctx auf,
761
+ // sammelt die emitted TreeNode[] und merged sie zur Top-Level-Liste.
762
+ // See visual-tree.md A2 (Zero-Whitelist) + A4 (Subscribe-Form).
763
+ getTreeProviders(): ReadonlyMap<string, TreeChildrenSubscribe>;
764
+
765
+ // Tree-Actions-Map des Features. Returns the erased Record (compile-
766
+ // time-typed handle wandert über setup-export, nicht hier). Visual-
767
+ // Tree-Component nutzt das für Runtime-Action-Lookup beim Klick auf
768
+ // einen TreeNode.target — der Resolver findet das Feature via
769
+ // TargetRef.featureId und holt sich die zugehörige Action-Definition.
770
+ getTreeActions(featureName: string): Readonly<Record<string, TreeActionDef>> | undefined;
629
771
  };
@@ -5,6 +5,7 @@
5
5
  // accepted at the type layer during migration: features that pass an
6
6
  // array are auto-normalized to { [role]: "all" } at registry build.
7
7
  // Long-term: string[] disappears.
8
+ import type { SQL } from "drizzle-orm";
8
9
  import type { OwnershipMap } from "../ownership";
9
10
 
10
11
  export type FieldAccess = {
@@ -20,6 +21,76 @@ export type FieldAccess = {
20
21
  // custom projections cannot read sensitive field values. See
21
22
  // docs/plans/architecture/projections.md.
22
23
 
24
+ // --- PII / Subject-Key Annotations (DSGVO Art. 17 — Crypto-Shredding) ---
25
+ //
26
+ // Felder die PII enthalten werden in Sprint 3 (crypto-shredding) mit einem
27
+ // Subject-Schluessel encrypted gespeichert. Subject = die natuerliche Person
28
+ // oder der Tenant der die Daten "besitzt". Loeschung erfolgt durch Vernichten
29
+ // des Subject-Keys ("Crypto-Shredding") — der Datensatz bleibt physisch
30
+ // (Audit-Trail bewahrt), ist aber nicht mehr entschluesselbar. Sprint 0
31
+ // fuegt nur die Schema-Marker + Boot-Validation ein; Encrypt/Decrypt-Mechanik
32
+ // kommt in Sprint 3.
33
+ //
34
+ // Drei orthogonale Markierungen:
35
+ // - `pii: true` — Subject = die Entity selbst.
36
+ // Beispiel: user.email gehoert User Marc.
37
+ // - `userOwned: { ownerField }` — Subject = der User der im genannten
38
+ // Field referenziert ist.
39
+ // Beispiel: comment.body gehoert
40
+ // comment.authorId.
41
+ // - `tenantOwned: true` — Subject = der aktuelle Tenant
42
+ // (ctx.tenantId zur Schreibzeit).
43
+ // Beispiel: tenantBranding.brandColor.
44
+ //
45
+ // `anonymize` ist die Pro-Feld-Funktion die der retention-Cleanup-Job
46
+ // (Sprint 2) aufruft wenn die Entity-Strategy "anonymize" lautet oder die
47
+ // `blockDelete`-Frist abgelaufen ist. Beispiel: `() => "[ANONYMIZED]"` oder
48
+ // `() => null`.
49
+ //
50
+ // `allowPlaintext` unterdrueckt PII-Heuristik-Boot-Warnings fuer Felder die
51
+ // zwar PII-Naming haben (email, name, body) aber bewusst Klartext bleiben
52
+ // sollen — z.B. ticket.title als Geschaeftsdaten. Wert ist eine Begruendung
53
+ // wie "is-business-data".
54
+ //
55
+ // `anonymize` darf sync oder async sein — der Cleanup-Job (Sprint 2)
56
+ // awaited den Return. Async-Funktionen sind sinnvoll wenn die Anonymisierung
57
+ // einen Lookup braucht (z.B. konsistente Pseudonyme aus separater Tabelle).
58
+ //
59
+ // Siehe docs/plans/datenschutz/crypto-shredding.md und docs/plans/datenschutz/roadmap.md.
60
+ export type PiiAnnotations = {
61
+ readonly pii?: boolean;
62
+ readonly userOwned?: { readonly ownerField: string };
63
+ readonly tenantOwned?: boolean;
64
+ readonly anonymize?: () => unknown | Promise<unknown>;
65
+ readonly allowPlaintext?: string;
66
+ };
67
+
68
+ // --- Retention (DSGVO Art. 5(1)(e) + HGB/AO Aufbewahrungspflichten) ---
69
+ //
70
+ // Pro Entity definiert der Author eine Default-Retention-Policy. Tenant-
71
+ // Admin uebersteuert sie via Compliance-Profile + Tenant-Override (Sprint 2).
72
+ // Vier Strategien:
73
+ //
74
+ // - "hardDelete" — Row physisch weg nach `keepFor`. Logs, Sessions.
75
+ // - "softDelete" — `deletedAt = now()`. Erlaubt spaetere Restore.
76
+ // - "anonymize" — Felder mit `anonymize`-Funktion ueberschrieben,
77
+ // Row bleibt. Order/Invoice mit gemischter PII +
78
+ // Geschaeftsdaten.
79
+ // - "blockDelete" — Cleanup-Job ignoriert; User-Forget loest stattdessen
80
+ // `anonymize` aus. Buchhaltung, Mandate, Patientenakten.
81
+ //
82
+ // `keepFor` ist eine Duration-String wie "30d", "10y", "6m". Parser
83
+ // kommt im Cleanup-Job (Sprint 2). `reference` ist das Field das den
84
+ // Lebenszeit-Anker liefert (Default: `createdAt`). Sessions z.B. nutzen
85
+ // `lastSeenAt` damit aktive Sessions nicht weggemueht werden.
86
+ //
87
+ // Siehe docs/plans/features/core-data-retention.md und Sprint 2 in roadmap.md.
88
+ export type RetentionDef = {
89
+ readonly keepFor: string;
90
+ readonly strategy: "hardDelete" | "softDelete" | "anonymize" | "blockDelete";
91
+ readonly reference?: string;
92
+ };
93
+
23
94
  export type TextFieldDef = {
24
95
  readonly type: "text";
25
96
  readonly maxLength?: number;
@@ -40,7 +111,7 @@ export type TextFieldDef = {
40
111
  * explizite Höhe. Search/sort/encrypt verhalten sich unverändert
41
112
  * identisch zu single-line — nur die Render-Surface wechselt. */
42
113
  readonly multiline?: boolean | { readonly rows?: number };
43
- };
114
+ } & PiiAnnotations;
44
115
 
45
116
  /**
46
117
  * Long-form text content — source-code, markdown, blog-posts, email-
@@ -74,7 +145,7 @@ export type LongTextFieldDef = {
74
145
  readonly default?: string;
75
146
  readonly access?: FieldAccess;
76
147
  readonly multiline?: boolean | { readonly rows?: number };
77
- };
148
+ } & PiiAnnotations;
78
149
 
79
150
  export type BooleanFieldDef = {
80
151
  readonly type: "boolean";
@@ -95,7 +166,7 @@ export type SelectFieldDef<TOptions extends readonly string[] = readonly string[
95
166
  readonly sensitive?: boolean;
96
167
  readonly default?: TOptions[number];
97
168
  readonly access?: FieldAccess;
98
- };
169
+ } & PiiAnnotations;
99
170
 
100
171
  // Mehrere Werte aus einer festen Options-Liste — UI rendert als
101
172
  // Checkbox-/Multi-Select-Kontrolle. Storage: jsonb-Array<string>;
@@ -119,7 +190,7 @@ export type MultiSelectFieldDef<TOptions extends readonly string[] = readonly st
119
190
  /** Default-Auswahl. Jeder Eintrag muss in `options` sein (Boot-Validator). */
120
191
  readonly default?: readonly TOptions[number][];
121
192
  readonly access?: FieldAccess;
122
- };
193
+ } & PiiAnnotations;
123
194
 
124
195
  export type NumberFieldDef = {
125
196
  readonly type: "number";
@@ -129,7 +200,29 @@ export type NumberFieldDef = {
129
200
  readonly sensitive?: boolean;
130
201
  readonly default?: number;
131
202
  readonly access?: FieldAccess;
132
- };
203
+ } & PiiAnnotations;
204
+
205
+ /**
206
+ * 64-bit-Integer-Spalte fuer Audit-Counter, Byte-Sizes, Event-IDs und
207
+ * andere Werte die >2^31 (~2.1 Mrd) wandern koennen. Storage als
208
+ * Postgres `bigint`, JS-Round-trip als `number` (mode:"number" — sicher
209
+ * bis 2^53 ≈ 9 PB, JSON-serialisierbar). Wer >2^53 braucht (rare),
210
+ * nutzt einen `text`-Field mit eigenem Codec.
211
+ *
212
+ * Vorrang vor `NumberFieldDef`-(integer 32-bit-Cap, ~2.1 GB) immer dann
213
+ * wenn der Wert physisch ueber dieses Limit klettern kann: Bytes,
214
+ * Events, Counters in High-Throughput-Apps, Cumulative-Sums. Money
215
+ * hat dafuer den eigenen `MoneyFieldDef` (mit Currency-Spalte).
216
+ */
217
+ export type BigIntFieldDef = {
218
+ readonly type: "bigInt";
219
+ readonly required?: boolean;
220
+ readonly sortable?: boolean;
221
+ readonly filterable?: boolean;
222
+ readonly sensitive?: boolean;
223
+ readonly default?: number;
224
+ readonly access?: FieldAccess;
225
+ } & PiiAnnotations;
133
226
 
134
227
  export type MoneyFieldDef = {
135
228
  readonly type: "money";
@@ -207,7 +300,7 @@ export type EmbeddedFieldDef = {
207
300
  readonly sensitive?: boolean;
208
301
  readonly schema: Readonly<Record<string, EmbeddedSubFieldDef>>;
209
302
  readonly access?: FieldAccess;
210
- };
303
+ } & PiiAnnotations;
211
304
 
212
305
  // Legacy "date" — JS-Date-Object, semantisch unklar (Wall-Clock vs Instant).
213
306
  // Für neue Felder bevorzuge:
@@ -223,7 +316,7 @@ export type DateFieldDef = {
223
316
  readonly filterable?: boolean;
224
317
  readonly sensitive?: boolean;
225
318
  readonly access?: FieldAccess;
226
- };
319
+ } & PiiAnnotations;
227
320
 
228
321
  // UTC-Instant (Temporal.Instant). Für Ereignisse die zu einem bestimmten
229
322
  // Augenblick passieren, ohne Location-Bezug: createdAt, loginAt, actualPickupAt.
@@ -251,7 +344,7 @@ export type TimestampFieldDef = {
251
344
  * { pickupAt: { type: "timestamp", locatedBy: "pickupTz" }, pickupTz: { type: "tz" } }
252
345
  */
253
346
  readonly locatedBy?: string;
254
- };
347
+ } & PiiAnnotations;
255
348
 
256
349
  // IANA-Zonenname (z.B. "Europe/Berlin", "America/Los_Angeles").
257
350
  // Wird via `Intl.supportedValuesOf("timeZone")` validiert (kommt im
@@ -262,7 +355,7 @@ export type TzFieldDef = {
262
355
  readonly required?: boolean;
263
356
  readonly sensitive?: boolean;
264
357
  readonly access?: FieldAccess;
265
- };
358
+ } & PiiAnnotations;
266
359
 
267
360
  // Wall-Clock-Termin an einem Ort als ATOMARES Konzept.
268
361
  // EIN Feld in der Schema-Definition, ZWEI Spalten in der DB
@@ -289,7 +382,7 @@ export type LocatedTimestampFieldDef = {
289
382
  readonly filterable?: boolean;
290
383
  readonly sensitive?: boolean;
291
384
  readonly access?: FieldAccess;
292
- };
385
+ } & PiiAnnotations;
293
386
 
294
387
  export type FileFieldDef = {
295
388
  readonly type: "file";
@@ -332,6 +425,7 @@ export type FieldDefinition =
332
425
  | SelectFieldDef
333
426
  | MultiSelectFieldDef
334
427
  | NumberFieldDef
428
+ | BigIntFieldDef
335
429
  | MoneyFieldDef
336
430
  | ReferenceFieldDef
337
431
  | EmbeddedFieldDef
@@ -380,6 +474,20 @@ export type EntityIndexDef = {
380
474
  readonly columns: readonly [string, ...string[]];
381
475
  readonly unique?: boolean;
382
476
  readonly name?: string;
477
+ /**
478
+ * Optional SQL-Fragment fuer Partial-Index — `CREATE [UNIQUE] INDEX
479
+ * ... WHERE <condition>`. Postgres-Pattern fuer "Index nur unter
480
+ * bestimmten Bedingungen", typisches Beispiel: ExportJob-Idempotency
481
+ * `UNIQUE(userId) WHERE status IN ('pending', 'running')`.
482
+ *
483
+ * Caller baut das Fragment via drizzle-orm `sql\`...\``-Tagged-
484
+ * Template. table-builder.ts emittiert `.where(def.where)` auf den
485
+ * Drizzle-IndexBuilder — wirkt sowohl fuer unique- als auch fuer
486
+ * non-unique-Indexes (PG erlaubt beides; non-unique partial nutzt
487
+ * man z.B. fuer scharfe BTREE-Indexes nur auf einer Status-Teilmenge
488
+ * statt voller Tabelle).
489
+ */
490
+ readonly where?: SQL;
383
491
  };
384
492
 
385
493
  export type FieldsMap = Readonly<Record<string, FieldDefinition>>;
@@ -419,4 +527,20 @@ export type EntityDefinition<F extends FieldsMap = FieldsMap> = {
419
527
  readonly read?: OwnershipMap;
420
528
  readonly write?: OwnershipMap;
421
529
  };
530
+ /**
531
+ * Default-Retention-Policy fuer diese Entity. Tenant-Admin kann via
532
+ * Compliance-Profile + Tenant-Override (Sprint 2) uebersteuern.
533
+ * Cleanup-Job (Sprint 2) verarbeitet die Strategy:
534
+ *
535
+ * - "hardDelete" → Row physisch weg nach keepFor
536
+ * - "softDelete" → deletedAt = now() (mit core-soft-delete-Feature)
537
+ * - "anonymize" → Felder mit `anonymize`-Funktion ueberschrieben,
538
+ * Row bleibt
539
+ * - "blockDelete" → Cleanup-Job ignoriert; User-Forget loest
540
+ * stattdessen anonymize aus. Buchhaltung, Mandate,
541
+ * Patientenakten.
542
+ *
543
+ * Siehe docs/plans/features/core-data-retention.md.
544
+ */
545
+ readonly retention?: RetentionDef;
422
546
  };
@@ -175,9 +175,9 @@ export function withResponseData<T>(result: WriteResult<unknown>, data: T): Writ
175
175
 
176
176
  // --- Context Types ---
177
177
 
178
- import type { TenantId } from "@cosmicdrift/kumiko-framework/engine";
179
178
  // Forward import: Registry is in feature.ts (circular type import — fine in TS)
180
179
  import type { Registry } from "./feature";
180
+ import type { TenantId } from "./identifiers";
181
181
 
182
182
  // Minimal interface for job event triggers (framework-owned, concrete type in jobs/)
183
183
  export type JobRunnerRef = {
@@ -312,7 +312,7 @@ export type AppContext = SharedContextFields & {
312
312
  // TMap propagates the strict event-type-map through `appendEvent`. Defaults
313
313
  // to the global KumikoEventTypeMap (augmented per app via
314
314
  // `declare module "@cosmicdrift/kumiko-framework/engine"`). Code that bypasses the
315
- // type-map (runtime-pluggable events) uses `appendEventUnsafe`.
315
+ // type-map (runtime-pluggable events) uses `unsafeAppendEvent`.
316
316
  export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedContextFields & {
317
317
  readonly db: TenantDb;
318
318
  readonly registry: Registry;
@@ -353,11 +353,11 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
353
353
  readonly appendEvent: AppendEventFn<TMap>;
354
354
 
355
355
  // Escape-hatch for runtime-pluggable features without a compile-time
356
- // augmentation. See AppendEventUnsafeFn — same runtime as appendEvent,
356
+ // augmentation. See UnsafeAppendEventFn — same runtime as appendEvent,
357
357
  // but the type-surface is `payload: unknown`. Use only when the event-
358
358
  // type is not knowable at compile-time; otherwise the strict path
359
359
  // (appendEvent) is the contract Designer/AI rely on.
360
- readonly appendEventUnsafe: AppendEventUnsafeFn;
360
+ readonly unsafeAppendEvent: UnsafeAppendEventFn;
361
361
 
362
362
  // Marten FetchForWriting equivalent: load the current stream, optionally
363
363
  // enforce expectedVersion, and get a handle that appends further events
@@ -428,11 +428,11 @@ export type HandlerContext<TMap extends object = KumikoEventTypeMap> = SharedCon
428
428
  // name without the feature having to import the drizzle-table directly.
429
429
  //
430
430
  // Auto-applies tenant_id filter when the projection table has a tenant_id
431
- // column (or opt out with { allTenants: true } for system-scoped reads
431
+ // column (or opt out with { unsafeAllTenants: true } for system-scoped reads
432
432
  // like cross-tenant analytics). Unknown projection name throws.
433
433
  readonly queryProjection: <T = Record<string, unknown>>(
434
434
  qualifiedName: string,
435
- options?: { readonly allTenants?: boolean },
435
+ options?: { readonly unsafeAllTenants?: boolean },
436
436
  ) => Promise<readonly T[]>;
437
437
 
438
438
  // Always populated — Noop when no observability provider is configured.
@@ -601,7 +601,7 @@ export type TypedAppendEventArgs<TMap extends object, K extends keyof TMap> = {
601
601
  // Strict-only form. Single overload — `<K extends keyof TMap>` against the
602
602
  // app's pre-bound TMap. No fallback overload: apps that need runtime-pluggable
603
603
  // events (where the type-string isn't known at compile-time) reach for
604
- // `appendEventUnsafe`.
604
+ // `unsafeAppendEvent`.
605
605
  //
606
606
  // Why no fallback overload:
607
607
  // A two-overload form (`(args: AppendEventArgs)` as the second sig)
@@ -619,13 +619,13 @@ export type TypedAppendEventArgs<TMap extends object, K extends keyof TMap> = {
619
619
  // KumikoEventTypeMap>(...)` wrappers. Handlers inside those wrappers
620
620
  // get a strict ctx.appendEvent.
621
621
  // - Cross-package callers (e.g. bundled-features's set.write.ts) that
622
- // can't afford a local wrapper reach for `ctx.appendEventUnsafe`
622
+ // can't afford a local wrapper reach for `ctx.unsafeAppendEvent`
623
623
  // instead — same runtime, looser type-surface.
624
624
  export type AppendEventFn<TMap extends object = KumikoEventTypeMap> = <K extends keyof TMap>(
625
625
  args: TypedAppendEventArgs<TMap, K>,
626
626
  ) => Promise<void>;
627
627
 
628
- export type AppendEventUnsafeFn = (args: AppendEventArgs) => Promise<void>;
628
+ export type UnsafeAppendEventFn = (args: AppendEventArgs) => Promise<void>;
629
629
 
630
630
  // Args for ctx.fetchForWriting — Marten FetchForWriting equivalent. Returns
631
631
  // the current stream state + a handle that appends without re-specifying
@@ -737,8 +737,16 @@ export type WriteHandlerDef = {
737
737
  readonly schema: ZodType;
738
738
  readonly handler: WriteHandlerFn;
739
739
  readonly access?: AccessRule;
740
- readonly skipTransitionGuard?: boolean;
740
+ readonly unsafeSkipTransitionGuard?: boolean;
741
741
  readonly rateLimit?: RateLimitOption;
742
+ // Set when the author wrote a `perform: pipeline(...)` block. Boot-
743
+ // validators (projection-allowlist) and Designer/AI tooling read this
744
+ // to inspect the step list. Absent on free-form handlers.
745
+ // Inline-import is intentional: step.ts imports HandlerContext from
746
+ // this file, a top-level `import type { PipelineDef } from "./step"`
747
+ // would form a type-only circular import that TS resolves but tooling
748
+ // (incremental compile, IDEs) sometimes mis-handles.
749
+ readonly perform?: import("./step").PipelineDef;
742
750
  };
743
751
 
744
752
  export type QueryHandlerDef = {
@@ -1,3 +1,4 @@
1
+ // @runtime client
1
2
  // Domain-identifier type aliases. Used everywhere a tenantId/userId/aggregateId
2
3
  // travels through the framework. One declaration per concept so future
3
4
  // representation changes (branded types, UUID validation, opaque wrappers)
@@ -66,6 +66,7 @@ export type {
66
66
  } from "./feature";
67
67
  export type {
68
68
  AnyFileFieldDef,
69
+ BigIntFieldDef,
69
70
  BooleanFieldDef,
70
71
  DateFieldDef,
71
72
  DefaultCurrency,
@@ -85,7 +86,9 @@ export type {
85
86
  MoneyFieldDef,
86
87
  MultiSelectFieldDef,
87
88
  NumberFieldDef,
89
+ PiiAnnotations,
88
90
  ReferenceFieldDef,
91
+ RetentionDef,
89
92
  SelectFieldDef,
90
93
  TextFieldDef,
91
94
  TimestampFieldDef,
@@ -99,7 +102,6 @@ export type {
99
102
  AppContext,
100
103
  AppendEventArgs,
101
104
  AppendEventFn,
102
- AppendEventUnsafeFn,
103
105
  AuthClaimsContext,
104
106
  AuthClaimsFn,
105
107
  AuthClaimsHookDef,
@@ -130,6 +132,7 @@ export type {
130
132
  RateLimitOption,
131
133
  RateLimitPer,
132
134
  SessionUser,
135
+ UnsafeAppendEventFn,
133
136
  WriteEvent,
134
137
  WriteHandlerDef,
135
138
  WriteHandlerFn,
@@ -208,4 +211,15 @@ export type {
208
211
  ToolbarAction,
209
212
  } from "./screen";
210
213
  export { normalizeEditField, normalizeListColumn } from "./screen";
214
+ export type { TargetRef } from "./target-ref";
215
+ export type {
216
+ Subscribe,
217
+ TreeAction,
218
+ TreeActionDef,
219
+ TreeActionsHandle,
220
+ TreeChildrenSubscribe,
221
+ TreeContext,
222
+ TreeNode,
223
+ TreeNodeState,
224
+ } from "./tree-node";
211
225
  export type { WorkspaceDefinition } from "./workspace";