@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
@@ -0,0 +1,427 @@
1
+ // Compliance-Profiles — DSGVO + sektorspezifische Regelwerke.
2
+ //
3
+ // Tenant-Admin waehlt ein Profile beim Onboarding (Pflicht). Profile
4
+ // buendelt User-Rights (Forget-Grace, Restriction, Auskunftsfrist),
5
+ // Notification-Sprache, Breach-Disclosure, Audit-Log-Retention,
6
+ // Sub-Processor-Anforderungen und Tenant-Destroy-Grace.
7
+ //
8
+ // MVP-Set: 3 Profile + 1 Default-Fallback. Erweiterung via `extends`
9
+ // trivial — neue Laender (uk-gdpr, ca-pipeda, us-ccpa, hipaa-healthcare)
10
+ // kommen on-demand wenn Customer fragt.
11
+ //
12
+ // Edge-Case-Decisions (advisor-pinned 2026-05-06):
13
+ //
14
+ // 1. Default-Fallback: `resolveComplianceProfile(undefined)` returnt
15
+ // `minimal-no-region` mit `warning: "no-profile-selected"`. Caller
16
+ // sieht das und kann Onboarding-Banner triggern. Nie still
17
+ // resolven.
18
+ //
19
+ // 2. extends-Chain-Tiefe: nur 1-Level. de-hr-dsgvo-hgb extends
20
+ // eu-dsgvo, aber eu-dsgvo darf NICHT selbst extends haben. Boot-
21
+ // Validator (Sprint 1.3) prueft.
22
+ //
23
+ // 3. Deep-merge-Semantik vom Override: rekursiv. Override
24
+ // `{ userRights: { gracePeriod: { days: 60 } } }` auf eu-dsgvo
25
+ // laesst andere userRights-Felder unveraendert. Top-Level-Replace
26
+ // gibt es NICHT — Override muss strukturell exakt das Pfad-Tree
27
+ // treffen.
28
+ //
29
+ // 4. Required-Field-Override: Override-Type ist `DeepPartial<...>`.
30
+ // TypeScript verhindert null-Drops zur Compile-Time. Runtime-Cast
31
+ // durch Tenant-Admin-Override-Endpoint laeuft durch Zod-Schema
32
+ // mit allen-Properties-optional (kein nullable).
33
+ //
34
+ // Siehe docs/plans/datenschutz/compliance-profiles.md.
35
+
36
+ import type { BundleTier } from "./sub-processors";
37
+
38
+ // --- Profile-Schema ---
39
+
40
+ export type ComplianceProfileKey =
41
+ | "eu-dsgvo"
42
+ | "swiss-dsg"
43
+ | "de-hr-dsgvo-hgb"
44
+ | "minimal-no-region";
45
+
46
+ export type DurationSpec = { readonly days: number } | { readonly hours: number };
47
+
48
+ export type AuthorityNotificationDeadline =
49
+ | DurationSpec
50
+ | "as-soon-as-feasible"
51
+ | "in-most-expedient-time"
52
+ | "manual";
53
+
54
+ export type UserNotificationRequiredPolicy =
55
+ | "if-high-risk"
56
+ | "if-real-risk-of-significant-harm"
57
+ | "if-serious-risk-of-injury"
58
+ | "always-if-encrypted-data-or-pii"
59
+ | "always-without-undue-delay"
60
+ | "manual";
61
+
62
+ export interface ComplianceProfile {
63
+ readonly key: ComplianceProfileKey;
64
+ readonly region: string;
65
+ readonly label: string;
66
+ readonly extends?: ComplianceProfileKey;
67
+
68
+ readonly userRights: {
69
+ readonly gracePeriod: DurationSpec;
70
+ readonly restrictionAllowed: boolean;
71
+ readonly objectionAllowed: boolean;
72
+ readonly portabilityFormat: readonly string[];
73
+ readonly auskunftFrist: DurationSpec;
74
+ readonly employeeAccessRight?: boolean;
75
+ readonly explicitConsentForAutomatedDecision?: boolean;
76
+ readonly doNotSellRequired?: boolean;
77
+ /**
78
+ * DSGVO Art. 15 + 20 Async-Export — Pipeline-Konfiguration. Spec:
79
+ * docs/plans/architecture/user-data-rights.md "Async Export-Pipeline".
80
+ */
81
+ readonly exportDownloadTtl: DurationSpec;
82
+ readonly exportStaleTimeoutMinutes: number;
83
+ readonly exportStorageCleanupGraceHours: number;
84
+ };
85
+
86
+ readonly notifications: {
87
+ readonly languages: readonly string[];
88
+ readonly languageDefault?: string;
89
+ readonly mandatoryBreachNotification: boolean;
90
+ };
91
+
92
+ readonly breach: {
93
+ readonly authorityNotificationDeadline: AuthorityNotificationDeadline;
94
+ readonly authorityContact: string;
95
+ readonly userNotificationRequired: UserNotificationRequiredPolicy;
96
+ readonly worksCouncilNotificationRequired?: boolean;
97
+ readonly mandatoryRegisterOfBreaches?: boolean;
98
+ };
99
+
100
+ readonly auditLog: {
101
+ readonly retention: DurationSpec | { readonly months: number } | { readonly years: number };
102
+ readonly reportFrequency: "quarterly" | "yearly" | "annual-required" | "manual";
103
+ };
104
+
105
+ readonly subProcessor: {
106
+ readonly consentRequired: boolean;
107
+ readonly changeNotificationLeadDays: number;
108
+ readonly mandatoryBaaWithSubProcessors?: boolean;
109
+ readonly worksCouncilApprovalRequired?: boolean;
110
+ readonly tierFilter?: readonly BundleTier[];
111
+ };
112
+
113
+ readonly tenantDestroyGracePeriod: DurationSpec;
114
+
115
+ readonly forgetDiscovery?: {
116
+ readonly enabled: boolean;
117
+ readonly mode?: "manual-redact" | "auto-redact-strict";
118
+ };
119
+ }
120
+
121
+ // --- Profile-Definitions (raw, before extends-Resolution) ---
122
+
123
+ const RAW_PROFILES: Readonly<Record<ComplianceProfileKey, ComplianceProfileRaw>> = {
124
+ "eu-dsgvo": {
125
+ key: "eu-dsgvo",
126
+ region: "EU",
127
+ label: "EU — DSGVO Standard",
128
+ userRights: {
129
+ gracePeriod: { days: 30 },
130
+ restrictionAllowed: true,
131
+ objectionAllowed: true,
132
+ portabilityFormat: ["json"],
133
+ auskunftFrist: { days: 30 },
134
+ exportDownloadTtl: { days: 7 },
135
+ exportStaleTimeoutMinutes: 30,
136
+ exportStorageCleanupGraceHours: 24,
137
+ },
138
+ notifications: {
139
+ languages: ["de", "en"],
140
+ mandatoryBreachNotification: true,
141
+ },
142
+ breach: {
143
+ authorityNotificationDeadline: { hours: 72 },
144
+ authorityContact: "BlnBDI Berlin",
145
+ userNotificationRequired: "if-high-risk",
146
+ },
147
+ auditLog: {
148
+ retention: { months: 24 },
149
+ reportFrequency: "quarterly",
150
+ },
151
+ subProcessor: {
152
+ consentRequired: false,
153
+ changeNotificationLeadDays: 30,
154
+ },
155
+ tenantDestroyGracePeriod: { days: 30 },
156
+ forgetDiscovery: { enabled: false },
157
+ },
158
+
159
+ "swiss-dsg": {
160
+ key: "swiss-dsg",
161
+ region: "CH",
162
+ label: "Schweiz — Bundesgesetz über den Datenschutz (rev. 2023)",
163
+ extends: "eu-dsgvo",
164
+ notifications: {
165
+ languages: ["de", "fr", "it", "en"],
166
+ mandatoryBreachNotification: true,
167
+ },
168
+ breach: {
169
+ authorityNotificationDeadline: { hours: 72 },
170
+ authorityContact: "EDÖB Bern",
171
+ userNotificationRequired: "if-high-risk",
172
+ },
173
+ },
174
+
175
+ "de-hr-dsgvo-hgb": {
176
+ key: "de-hr-dsgvo-hgb",
177
+ region: "DE",
178
+ label: "Deutschland HR — DSGVO + HGB + Personalakten",
179
+ extends: "eu-dsgvo",
180
+ userRights: {
181
+ gracePeriod: { days: 30 },
182
+ restrictionAllowed: true,
183
+ objectionAllowed: true,
184
+ portabilityFormat: ["json"],
185
+ auskunftFrist: { days: 30 },
186
+ employeeAccessRight: true,
187
+ exportDownloadTtl: { days: 7 },
188
+ exportStaleTimeoutMinutes: 30,
189
+ exportStorageCleanupGraceHours: 24,
190
+ },
191
+ notifications: {
192
+ languages: ["de"],
193
+ mandatoryBreachNotification: true,
194
+ },
195
+ breach: {
196
+ authorityNotificationDeadline: { hours: 72 },
197
+ authorityContact: "Landes-Datenschutzbehörde",
198
+ userNotificationRequired: "if-high-risk",
199
+ worksCouncilNotificationRequired: true,
200
+ },
201
+ auditLog: {
202
+ retention: { years: 10 },
203
+ reportFrequency: "yearly",
204
+ },
205
+ subProcessor: {
206
+ consentRequired: false,
207
+ changeNotificationLeadDays: 30,
208
+ worksCouncilApprovalRequired: true,
209
+ },
210
+ tenantDestroyGracePeriod: { days: 60 },
211
+ },
212
+
213
+ "minimal-no-region": {
214
+ key: "minimal-no-region",
215
+ region: "—",
216
+ label: "Minimal — kein Compliance-Profile (NICHT für Production)",
217
+ userRights: {
218
+ gracePeriod: { days: 30 },
219
+ restrictionAllowed: false,
220
+ objectionAllowed: false,
221
+ portabilityFormat: ["json"],
222
+ auskunftFrist: { days: 30 },
223
+ exportDownloadTtl: { days: 7 },
224
+ exportStaleTimeoutMinutes: 30,
225
+ exportStorageCleanupGraceHours: 24,
226
+ },
227
+ notifications: {
228
+ languages: ["en"],
229
+ mandatoryBreachNotification: false,
230
+ },
231
+ breach: {
232
+ authorityNotificationDeadline: "manual",
233
+ authorityContact: "",
234
+ userNotificationRequired: "manual",
235
+ },
236
+ auditLog: {
237
+ retention: { months: 3 },
238
+ reportFrequency: "manual",
239
+ },
240
+ subProcessor: {
241
+ consentRequired: false,
242
+ changeNotificationLeadDays: 30,
243
+ },
244
+ tenantDestroyGracePeriod: { days: 30 },
245
+ },
246
+ } satisfies Readonly<Record<ComplianceProfileKey, ComplianceProfileRaw>>;
247
+
248
+ // Raw-Profile (vor extends-Resolution) — `extends`-Profile dürfen
249
+ // Required-Felder weglassen, sie kommen vom Base-Profile dazu.
250
+ type ComplianceProfileRaw = Partial<Omit<ComplianceProfile, "key" | "region" | "label">> & {
251
+ readonly key: ComplianceProfileKey;
252
+ readonly region: string;
253
+ readonly label: string;
254
+ readonly extends?: ComplianceProfileKey;
255
+ };
256
+
257
+ // --- Tenant-Auswählbare Liste (ohne minimal-no-region) ---
258
+
259
+ /**
260
+ * Profile-Schluessel die der Tenant-Admin im Onboarding waehlen darf.
261
+ * `minimal-no-region` ist bewusst NICHT in der Liste — es ist der
262
+ * Default-Fallback fuer "noch keine Wahl getroffen", mit sichtbarer
263
+ * Warning. Production-Tenants sollen ein echtes Profile waehlen.
264
+ */
265
+ export const SELECTABLE_PROFILE_KEYS = [
266
+ "eu-dsgvo",
267
+ "swiss-dsg",
268
+ "de-hr-dsgvo-hgb",
269
+ ] as const satisfies readonly ComplianceProfileKey[];
270
+
271
+ /**
272
+ * Top-Level-Properties des `ComplianceProfile`-Type, die ein Tenant-
273
+ * Override modifizieren darf. Identifikations-Felder (key, region,
274
+ * label, extends) sind ausgeschlossen — wer die overriden wuerde,
275
+ * würde die Profile-Identität zerstoeren.
276
+ *
277
+ * Single source of truth fuer:
278
+ * - set-profile-Handler-Whitelist (Tippfehler-Reject)
279
+ * - Snapshot-Test gegen Profile-Top-Level (Drift-Guard)
280
+ *
281
+ * Wenn ein neues Top-Level-Property zu `ComplianceProfile` kommt
282
+ * (z.B. `dataSubjectRights` oder `crossBorderTransferRules`), MUSS es
283
+ * hier ergaenzt werden — sonst lehnt set-profile valide Overrides ab.
284
+ * Der Snapshot-Test in profiles.test.ts faengt Drift in beide
285
+ * Richtungen.
286
+ */
287
+ export const OVERRIDABLE_PROFILE_KEYS: ReadonlySet<string> = new Set([
288
+ "userRights",
289
+ "notifications",
290
+ "breach",
291
+ "auditLog",
292
+ "subProcessor",
293
+ "tenantDestroyGracePeriod",
294
+ "forgetDiscovery",
295
+ ]);
296
+
297
+ // --- Extends-Resolver (deep-merge) ---
298
+
299
+ type DeepReadonly<T> = T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]> } : T;
300
+
301
+ export type ComplianceProfileOverride = DeepReadonly<DeepPartial<ComplianceProfile>>;
302
+
303
+ type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]> } : T;
304
+
305
+ function isPlainObject(v: unknown): v is Record<string, unknown> {
306
+ return typeof v === "object" && v !== null && !Array.isArray(v);
307
+ }
308
+
309
+ // Pfade die als ganzes Atom ersetzt werden (NICHT rekursiv gemergt).
310
+ // Notwendig fuer Diskriminierte-Union-Types wo das Patch ein Schwester-
311
+ // Property statt einer Override sein kann — z.B. retention von
312
+ // { months: 24 } auf { years: 10 } wuerde sonst zu { months: 24, years: 10 }
313
+ // werden, semantisch nonsense.
314
+ const ATOMIC_PATHS: ReadonlySet<string> = new Set([
315
+ "userRights.gracePeriod",
316
+ "userRights.auskunftFrist",
317
+ "userRights.exportDownloadTtl",
318
+ "tenantDestroyGracePeriod",
319
+ "breach.authorityNotificationDeadline",
320
+ "auditLog.retention",
321
+ ]);
322
+
323
+ function deepMerge<T extends Record<string, unknown>>(
324
+ base: T,
325
+ patch: Record<string, unknown>,
326
+ path = "",
327
+ ): T {
328
+ const out: Record<string, unknown> = { ...base };
329
+ for (const [k, v] of Object.entries(patch)) {
330
+ if (v === undefined) continue;
331
+ const fullPath = path ? `${path}.${k}` : k;
332
+ const existing = out[k];
333
+ if (ATOMIC_PATHS.has(fullPath)) {
334
+ // Atomic — replace komplett statt rekursiv mergen.
335
+ out[k] = v;
336
+ } else if (isPlainObject(existing) && isPlainObject(v)) {
337
+ out[k] = deepMerge(existing, v, fullPath);
338
+ } else {
339
+ out[k] = v;
340
+ }
341
+ }
342
+ return out as T; // @cast-boundary generic-record
343
+ }
344
+
345
+ /**
346
+ * Resolved ein Profile inklusive `extends`-Auflösung. Wirft wenn die
347
+ * extends-Chain tiefer als 1 Level ist (vermeidet Cycles + macht
348
+ * Boot-Validation einfach).
349
+ *
350
+ * Edge-Case (gepinnt): das `extends`-Target darf selbst KEIN extends
351
+ * haben. Wer doch eine Mehrstufen-Hierarchie braucht, definiert die
352
+ * Stufen explizit (z.B. `de-hr-strict extends eu-dsgvo`, statt
353
+ * `de-hr-strict extends de-hr-dsgvo-hgb extends eu-dsgvo`).
354
+ */
355
+ function resolveExtends(key: ComplianceProfileKey): ComplianceProfile {
356
+ const raw = RAW_PROFILES[key];
357
+ if (!raw.extends) {
358
+ return raw as ComplianceProfile; // @cast-boundary schema-walk
359
+ }
360
+
361
+ const base = RAW_PROFILES[raw.extends];
362
+ if (base.extends) {
363
+ throw new Error(
364
+ `Compliance-Profile "${key}" extends "${raw.extends}" which itself extends "${base.extends}" — chain depth >1 not supported. Define a flat extends-hierarchy instead.`,
365
+ );
366
+ }
367
+
368
+ return deepMerge(
369
+ base as Record<string, unknown>, // @cast-boundary generic-record
370
+ raw as unknown as Record<string, unknown>,
371
+ ) as unknown as ComplianceProfile;
372
+ }
373
+
374
+ /**
375
+ * Pre-baked Profile-Liste (extends bereits aufgelöst). Beim Modul-Load
376
+ * einmal berechnet — wirft bei Definition-Fehlern (Cycle, missing target)
377
+ * sofort, nicht erst beim ersten Resolver-Call.
378
+ */
379
+ export const COMPLIANCE_PROFILES: Readonly<Record<ComplianceProfileKey, ComplianceProfile>> =
380
+ Object.fromEntries(
381
+ (Object.keys(RAW_PROFILES) as ComplianceProfileKey[]).map((k) => [k, resolveExtends(k)]),
382
+ ) as Readonly<Record<ComplianceProfileKey, ComplianceProfile>>; // @cast-boundary dynamic-key
383
+
384
+ // --- Effective-Profile-Resolver ---
385
+
386
+ export interface EffectiveComplianceProfile {
387
+ readonly profile: ComplianceProfile;
388
+ readonly warning?: "no-profile-selected";
389
+ }
390
+
391
+ /**
392
+ * Liefert das effektive Compliance-Profile fuer einen Tenant inklusive
393
+ * Tenant-Override.
394
+ *
395
+ * Edge-Case-Verhalten (gepinnt):
396
+ * - selection=undefined → minimal-no-region + warning="no-profile-selected"
397
+ * - selection=valid + override=undefined → effective profile, kein warning
398
+ * - selection=valid + override → deep-merged effective, kein warning
399
+ *
400
+ * Production-Marker: das frueher hier vorgesehene "minimal-in-production"-
401
+ * warning ist entfallen weil set-profile (Sprint 1.7 X1) minimal-no-region
402
+ * nicht mehr als Tenant-Wahl akzeptiert. Wer Production-spezifisches
403
+ * Verhalten braucht (z.B. Block-bei-Default), addiert den Marker spaeter
404
+ * bei Bedarf.
405
+ */
406
+ export function resolveComplianceProfile(args: {
407
+ readonly selection?: ComplianceProfileKey;
408
+ readonly override?: ComplianceProfileOverride;
409
+ }): EffectiveComplianceProfile {
410
+ if (!args.selection) {
411
+ return {
412
+ profile: COMPLIANCE_PROFILES["minimal-no-region"],
413
+ warning: "no-profile-selected",
414
+ };
415
+ }
416
+
417
+ const base = COMPLIANCE_PROFILES[args.selection];
418
+ if (!args.override) {
419
+ return { profile: base };
420
+ }
421
+
422
+ const merged = deepMerge(
423
+ base as unknown as Record<string, unknown>,
424
+ args.override as Record<string, unknown>, // @cast-boundary engine-payload
425
+ ) as unknown as ComplianceProfile;
426
+ return { profile: merged };
427
+ }
@@ -0,0 +1,152 @@
1
+ // Sub-Processor-Liste der Kumiko-Plattform.
2
+ //
3
+ // Auftragsverarbeiter im Sinne von DSGVO Art. 28 die Kumiko fuer den
4
+ // Plattform-Betrieb einsetzt. Wird oeffentlich exposed unter
5
+ // /api/compliance/sub-processors (JSON, Sprint 1)
6
+ // /api/compliance/sub-processors.rss (RSS, Sprint 1)
7
+ // kumiko.so/subprocessors (HTML, Marketing-Repo)
8
+ //
9
+ // Tenant-Admins muessen ueber Add/Change/Remove informiert werden mit
10
+ // Lead-Time aus dem Compliance-Profile (typisch 30d). Cron-Job kommt
11
+ // in Sprint 1 (compliance-profiles).
12
+ //
13
+ // Quelle: docs/plans/datenschutz/compliance-profiles.md "Sub-Processor-
14
+ // Management" + docs/plans/datenschutz/legal-artifacts.md.
15
+
16
+ /**
17
+ * Bundle-Tier-Marker fuer SubProcessor.appliesTo. "all-tiers" matched
18
+ * unabhaengig vom konkreten Tenant-Bundle (z.B. Hetzner als Hosting-
19
+ * Provider). Konkrete Tier-Namen koennen per-Tenant gefiltert werden.
20
+ */
21
+ export type BundleTier = "all-tiers" | "standard" | "business" | "enterprise";
22
+
23
+ /**
24
+ * Beschreibt einen Auftragsverarbeiter (Art. 28 DSGVO) der von Kumiko
25
+ * fuer den Plattform-Betrieb eingesetzt wird.
26
+ */
27
+ export interface SubProcessor {
28
+ /** Voller juristischer Name. */
29
+ readonly name: string;
30
+ /** Was macht der Sub-Processor fuer uns? */
31
+ readonly purpose: string;
32
+ /** Sitz / Datenverarbeitungs-Region. */
33
+ readonly region: string;
34
+ /** Link zum Auftragsverarbeitungsvertrag (DPA/AVV). */
35
+ readonly dpa: string;
36
+ /** Wann wurde der Sub-Processor zu unserer Plattform hinzugefuegt? */
37
+ readonly addedAt: string;
38
+ /**
39
+ * Welche Bundle-Tiers nutzen diesen Sub-Processor?
40
+ * Tenants nicht-betroffener Tiers brauchen keine Notification bei
41
+ * Aenderungen.
42
+ */
43
+ readonly appliesTo: readonly BundleTier[];
44
+ /**
45
+ * Standard Contractual Clauses (SCC) fuer Drittlandsuebermittlung
46
+ * abgeschlossen. Pflicht fuer alle Sub-Processors mit Sitz ausserhalb
47
+ * EU/EWR.
48
+ */
49
+ readonly sccRequired?: boolean;
50
+ /**
51
+ * Tenant muss explizit aktivieren (z.B. AI-Feature). Ohne Opt-In
52
+ * werden keine Daten an diesen Sub-Processor gesendet.
53
+ */
54
+ readonly optInOnly?: boolean;
55
+ /**
56
+ * Business Associate Agreement fuer HIPAA-Customers verfuegbar.
57
+ * Relevant nur fuer hipaa-healthcare Compliance-Profile.
58
+ */
59
+ readonly hipaaBaaAvailable?: boolean;
60
+ /**
61
+ * Geplant aber noch nicht aktiv (Vorbereitung fuer kommenden Sprint).
62
+ * Wird im Sub-Processor-Endpoint als "planned"-Sektion separat
63
+ * ausgegeben damit Tenants schon Lead-Time bekommen.
64
+ */
65
+ readonly status?: "active" | "planned";
66
+ }
67
+
68
+ /**
69
+ * Plattform-weite Sub-Processor-Liste.
70
+ *
71
+ * Reihenfolge: nach addedAt (aelteste zuerst). Bei Aenderungen
72
+ * Snapshot-Test im Test-File explizit updaten — der detektiert
73
+ * stille Aenderungen.
74
+ */
75
+ export const KUMIKO_SUB_PROCESSORS: readonly SubProcessor[] = [
76
+ {
77
+ name: "Hetzner Online GmbH",
78
+ purpose: "Cloud-Infrastructure (CNPG-Postgres-Cluster, K8s-Pods, Volumes, Object-Storage)",
79
+ region: "EU (Germany)",
80
+ dpa: "https://www.hetzner.com/legal/dpa",
81
+ addedAt: "2024-01-01",
82
+ appliesTo: ["all-tiers"],
83
+ status: "active",
84
+ },
85
+ {
86
+ name: "Cloudflare, Inc.",
87
+ purpose: "DNS, CDN, DDoS-Protection, WAF",
88
+ region: "Global (US-headquartered)",
89
+ dpa: "https://www.cloudflare.com/cloudflare-customer-dpa",
90
+ addedAt: "2024-01-01",
91
+ appliesTo: ["all-tiers"],
92
+ sccRequired: true,
93
+ status: "active",
94
+ },
95
+ {
96
+ name: "Sendinblue SAS (Brevo)",
97
+ purpose: "Transactional Email Delivery",
98
+ region: "EU (France)",
99
+ dpa: "https://www.brevo.com/legal/dpa/",
100
+ addedAt: "2024-03-01",
101
+ appliesTo: ["standard", "business", "enterprise"],
102
+ status: "active",
103
+ },
104
+ {
105
+ name: "Heinlein Hosting (Mailbox.org)",
106
+ purpose: "Marketing Email Delivery",
107
+ region: "EU (Germany)",
108
+ dpa: "https://mailbox.org/de/datenschutzerklaerung",
109
+ addedAt: "2024-03-01",
110
+ appliesTo: ["all-tiers"],
111
+ status: "active",
112
+ },
113
+ {
114
+ name: "Anthropic PBC",
115
+ purpose: "AI Model Inference (L2 Composition Layer, AI-Foundation)",
116
+ region: "US",
117
+ dpa: "https://www.anthropic.com/legal/dpa",
118
+ addedAt: "2026-06-01",
119
+ appliesTo: ["business", "enterprise"],
120
+ sccRequired: true,
121
+ optInOnly: true,
122
+ hipaaBaaAvailable: true,
123
+ status: "planned",
124
+ },
125
+ {
126
+ name: "Stripe, Inc.",
127
+ purpose: "Payment Processing (Subscription-Stripe-Plugin)",
128
+ region: "Global (US-headquartered)",
129
+ dpa: "https://stripe.com/legal/dpa",
130
+ addedAt: "2026-06-01",
131
+ appliesTo: ["all-tiers"],
132
+ sccRequired: true,
133
+ status: "planned",
134
+ },
135
+ ];
136
+
137
+ /**
138
+ * Filter helper — nur aktive Sub-Processors (kein status="planned").
139
+ * Genutzt vom oeffentlichen JSON-Endpoint (Sprint 1).
140
+ */
141
+ export function getActiveSubProcessors(): readonly SubProcessor[] {
142
+ return KUMIKO_SUB_PROCESSORS.filter((sp) => sp.status !== "planned");
143
+ }
144
+
145
+ /**
146
+ * Filter helper — nur geplante Sub-Processors (status="planned"). Werden
147
+ * separat als "demnaechst aktiv"-Sektion ausgegeben damit Tenant-Admins
148
+ * schon Lead-Time fuer Compliance-Profile-Konfiguration bekommen.
149
+ */
150
+ export function getPlannedSubProcessors(): readonly SubProcessor[] {
151
+ return KUMIKO_SUB_PROCESSORS.filter((sp) => sp.status === "planned");
152
+ }