@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.
- package/CHANGELOG.md +54 -0
- package/package.json +124 -38
- package/src/__tests__/full-stack.integration.ts +2 -2
- package/src/api/auth-routes.ts +5 -5
- package/src/api/jwt.ts +2 -2
- package/src/api/route-registrars.ts +1 -1
- package/src/api/routes.ts +3 -3
- package/src/api/server.ts +6 -7
- package/src/auth/__tests__/roles.test.ts +24 -0
- package/src/auth/index.ts +7 -0
- package/src/auth/roles.ts +42 -0
- package/src/compliance/__tests__/duration-spec.test.ts +72 -0
- package/src/compliance/__tests__/profiles.test.ts +308 -0
- package/src/compliance/__tests__/sub-processors.test.ts +139 -0
- package/src/compliance/duration-spec.ts +44 -0
- package/src/compliance/index.ts +31 -0
- package/src/compliance/override-schema.ts +136 -0
- package/src/compliance/profiles.ts +427 -0
- package/src/compliance/sub-processors.ts +152 -0
- package/src/db/__tests__/big-int-field.test.ts +131 -0
- package/src/db/assert-exists-in.ts +2 -2
- package/src/db/cursor.ts +3 -3
- package/src/db/event-store-executor.ts +19 -13
- package/src/db/located-timestamp.ts +1 -1
- package/src/db/money.ts +12 -2
- package/src/db/pg-error.ts +1 -1
- package/src/db/row-helpers.ts +1 -1
- package/src/db/table-builder.ts +20 -5
- package/src/db/tenant-db.ts +9 -9
- package/src/engine/__tests__/_pipeline-test-utils.ts +23 -0
- package/src/engine/__tests__/boot-validator-api-exposure.test.ts +142 -0
- package/src/engine/__tests__/boot-validator-pii-retention.test.ts +570 -0
- package/src/engine/__tests__/boot-validator-s0-integration.test.ts +160 -0
- package/src/engine/__tests__/build-target.test.ts +135 -0
- package/src/engine/__tests__/codemod-pipeline.test.ts +551 -0
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/event-helpers.test.ts +4 -4
- package/src/engine/__tests__/pipeline-engine.test.ts +215 -0
- package/src/engine/__tests__/pipeline-handler.integration.ts +894 -0
- package/src/engine/__tests__/pipeline-observability.integration.ts +142 -0
- package/src/engine/__tests__/pipeline-performance.integration.ts +152 -0
- package/src/engine/__tests__/pipeline-sub-pipelines.test.ts +288 -0
- package/src/engine/__tests__/raw-table.test.ts +2 -2
- package/src/engine/__tests__/steps-aggregate-append-event.test.ts +115 -0
- package/src/engine/__tests__/steps-aggregate-create.test.ts +92 -0
- package/src/engine/__tests__/steps-aggregate-update.test.ts +127 -0
- package/src/engine/__tests__/steps-call-feature.test.ts +123 -0
- package/src/engine/__tests__/steps-mail-send.test.ts +136 -0
- package/src/engine/__tests__/steps-read.test.ts +142 -0
- package/src/engine/__tests__/steps-resolver-utils.test.ts +50 -0
- package/src/engine/__tests__/steps-unsafe-projection-delete.test.ts +69 -0
- package/src/engine/__tests__/steps-unsafe-projection-upsert.test.ts +117 -0
- package/src/engine/__tests__/steps-webhook-send.test.ts +135 -0
- package/src/engine/__tests__/steps-workflow.test.ts +198 -0
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +491 -0
- package/src/engine/__tests__/visual-tree-patterns.test.ts +251 -0
- package/src/engine/boot-validator/api-ext.ts +77 -0
- package/src/engine/boot-validator/config-deps.ts +163 -0
- package/src/engine/boot-validator/entity-handler.ts +466 -0
- package/src/engine/boot-validator/index.ts +159 -0
- package/src/engine/boot-validator/ownership.ts +198 -0
- package/src/engine/boot-validator/pii-retention.ts +155 -0
- package/src/engine/boot-validator/screens-nav.ts +624 -0
- package/src/engine/boot-validator.ts +1 -1528
- package/src/engine/build-app-schema.ts +1 -1
- package/src/engine/build-target.ts +99 -0
- package/src/engine/codemod/index.ts +15 -0
- package/src/engine/codemod/pipeline-codemod.ts +641 -0
- package/src/engine/config-helpers.ts +9 -19
- package/src/engine/constants.ts +1 -1
- package/src/engine/define-feature.ts +127 -9
- package/src/engine/define-handler.ts +89 -3
- package/src/engine/define-roles.ts +2 -2
- package/src/engine/define-step.ts +28 -0
- package/src/engine/define-workflow.ts +110 -0
- package/src/engine/entity-handlers.ts +10 -9
- package/src/engine/event-helpers.ts +4 -4
- package/src/engine/extension-names.ts +105 -0
- package/src/engine/extensions/user-data.ts +106 -0
- package/src/engine/factories.ts +26 -16
- package/src/engine/feature-ast/__tests__/visual-tree-parse.test.ts +184 -0
- package/src/engine/feature-ast/extractors/index.ts +74 -0
- package/src/engine/feature-ast/extractors/round1.ts +110 -0
- package/src/engine/feature-ast/extractors/round2.ts +253 -0
- package/src/engine/feature-ast/extractors/round3.ts +471 -0
- package/src/engine/feature-ast/extractors/round4.ts +1365 -0
- package/src/engine/feature-ast/extractors/round5.ts +72 -0
- package/src/engine/feature-ast/extractors/round6.ts +66 -0
- package/src/engine/feature-ast/extractors/shared.ts +177 -0
- package/src/engine/feature-ast/parse.ts +13 -0
- package/src/engine/feature-ast/patch.ts +9 -1
- package/src/engine/feature-ast/patcher.ts +10 -3
- package/src/engine/feature-ast/patterns.ts +71 -1
- package/src/engine/feature-ast/render.ts +31 -1
- package/src/engine/index.ts +66 -2
- package/src/engine/pattern-library/__tests__/library.test.ts +11 -0
- package/src/engine/pattern-library/library.ts +78 -2
- package/src/engine/pipeline.ts +88 -0
- package/src/engine/projection-helpers.ts +1 -1
- package/src/engine/read-claim.ts +1 -1
- package/src/engine/registry.ts +30 -2
- package/src/engine/resolve-config-or-param.ts +4 -0
- package/src/engine/run-pipeline.ts +162 -0
- package/src/engine/schema-builder.ts +10 -4
- package/src/engine/state-machine.ts +1 -1
- package/src/engine/steps/_drizzle-boundary.ts +19 -0
- package/src/engine/steps/_duration-utils.ts +33 -0
- package/src/engine/steps/_no-return-guard.ts +21 -0
- package/src/engine/steps/_resolver-utils.ts +42 -0
- package/src/engine/steps/_step-dispatch-constants.ts +38 -0
- package/src/engine/steps/aggregate-append-event.ts +56 -0
- package/src/engine/steps/aggregate-create.ts +56 -0
- package/src/engine/steps/aggregate-update.ts +68 -0
- package/src/engine/steps/branch.ts +84 -0
- package/src/engine/steps/call-feature.ts +49 -0
- package/src/engine/steps/compute.ts +41 -0
- package/src/engine/steps/for-each.ts +111 -0
- package/src/engine/steps/mail-send.ts +44 -0
- package/src/engine/steps/read-find-many.ts +51 -0
- package/src/engine/steps/read-find-one.ts +58 -0
- package/src/engine/steps/retry.ts +87 -0
- package/src/engine/steps/return.ts +34 -0
- package/src/engine/steps/unsafe-projection-delete.ts +46 -0
- package/src/engine/steps/unsafe-projection-upsert.ts +69 -0
- package/src/engine/steps/wait-for-event.ts +71 -0
- package/src/engine/steps/wait.ts +69 -0
- package/src/engine/steps/webhook-send.ts +71 -0
- package/src/engine/system-user.ts +1 -1
- package/src/engine/types/feature.ts +143 -1
- package/src/engine/types/fields.ts +134 -10
- package/src/engine/types/handlers.ts +18 -10
- package/src/engine/types/identifiers.ts +1 -0
- package/src/engine/types/index.ts +15 -1
- package/src/engine/types/step.ts +334 -0
- package/src/engine/types/target-ref.ts +21 -0
- package/src/engine/types/tree-node.ts +130 -0
- package/src/engine/types/workspace.ts +7 -0
- package/src/engine/validate-projection-allowlist.ts +161 -0
- package/src/event-store/snapshot.ts +1 -1
- package/src/event-store/upcaster-dead-letter.ts +1 -1
- package/src/event-store/upcaster.ts +1 -1
- package/src/files/__tests__/read-stream.test.ts +105 -0
- package/src/files/__tests__/write-stream.test.ts +233 -0
- package/src/files/__tests__/zip-stream.test.ts +357 -0
- package/src/files/file-routes.ts +1 -1
- package/src/files/in-memory-provider.ts +38 -0
- package/src/files/index.ts +3 -0
- package/src/files/local-provider.ts +58 -1
- package/src/files/types.ts +36 -8
- package/src/files/zip-stream.ts +251 -0
- package/src/jobs/job-runner.ts +10 -10
- package/src/lifecycle/lifecycle.ts +0 -3
- package/src/logging/index.ts +1 -0
- package/src/logging/pino-logger.ts +11 -7
- package/src/logging/utils.ts +24 -0
- package/src/observability/prometheus-meter.ts +7 -5
- package/src/pipeline/__tests__/archive-stream.integration.ts +1 -1
- package/src/pipeline/__tests__/causation-chain.integration.ts +1 -1
- package/src/pipeline/__tests__/domain-events-projections.integration.ts +3 -3
- package/src/pipeline/__tests__/event-define-event-strict.integration.ts +4 -4
- package/src/pipeline/__tests__/load-aggregate-query.integration.ts +1 -1
- package/src/pipeline/__tests__/msp-multi-hop.integration.ts +3 -3
- package/src/pipeline/__tests__/msp-rebuild.integration.ts +3 -3
- package/src/pipeline/__tests__/multi-stream-projection.integration.ts +2 -2
- package/src/pipeline/__tests__/query-projection.integration.ts +5 -5
- package/src/pipeline/append-event-core.ts +22 -6
- package/src/pipeline/dispatcher-utils.ts +188 -0
- package/src/pipeline/dispatcher.ts +63 -283
- package/src/pipeline/distributed-lock.ts +1 -1
- package/src/pipeline/entity-cache.ts +2 -2
- package/src/pipeline/event-consumer-state.ts +0 -13
- package/src/pipeline/event-dispatcher.ts +4 -4
- package/src/pipeline/index.ts +0 -2
- package/src/pipeline/lifecycle-pipeline.ts +6 -12
- package/src/pipeline/msp-rebuild.ts +5 -5
- package/src/pipeline/multi-stream-apply-context.ts +6 -7
- package/src/pipeline/projection-rebuild.ts +2 -2
- package/src/pipeline/projection-state.ts +0 -12
- package/src/rate-limit/__tests__/resolver.integration.ts +8 -4
- package/src/rate-limit/resolver.ts +1 -1
- package/src/search/in-memory-adapter.ts +1 -1
- package/src/search/meilisearch-adapter.ts +3 -3
- package/src/search/types.ts +1 -1
- package/src/secrets/leak-guard.ts +2 -2
- package/src/stack/request-helper.ts +9 -5
- package/src/stack/test-stack.ts +1 -1
- package/src/testing/handler-context.ts +4 -4
- package/src/testing/http-cookies.ts +1 -1
- package/src/time/tz-context.ts +1 -2
- package/src/ui-types/index.ts +4 -0
- 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
|
+
}
|