@cosmicdrift/kumiko-samples 0.403.0 → 0.404.1

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 (77) hide show
  1. package/package.json +1 -1
  2. package/packages/bundled-features/package.json +25 -25
  3. package/packages/bundled-features/src/account-security/README.md +7 -5
  4. package/packages/bundled-features/src/audit/feature.ts +1 -1
  5. package/packages/bundled-features/src/auth-email-password/web/auth-gate.tsx +4 -0
  6. package/packages/bundled-features/src/data-retention/feature.ts +1 -1
  7. package/packages/bundled-features/src/delivery/feature.ts +1 -1
  8. package/packages/bundled-features/src/documents/changes.json +8 -0
  9. package/packages/bundled-features/src/feature-toggles/changes.json +6 -0
  10. package/packages/bundled-features/src/feature-toggles/toggle-sync-signal.ts +6 -2
  11. package/packages/bundled-features/src/form-draft/feature.ts +1 -1
  12. package/packages/bundled-features/src/host-routing/README.md +10 -10
  13. package/packages/bundled-features/src/inbound-mail-foundation/handlers/thread-rollup.ts +1 -0
  14. package/packages/bundled-features/src/inbound-mail-foundation/retention-job.ts +1 -1
  15. package/packages/bundled-features/src/jobs/changes.json +6 -0
  16. package/packages/bundled-features/src/jobs/feature.ts +6 -7
  17. package/packages/bundled-features/src/legal-pages/README.md +21 -13
  18. package/packages/bundled-features/src/renderer-foundation/README.md +6 -4
  19. package/packages/bundled-features/src/renderer-pdf/README.md +5 -1
  20. package/packages/bundled-features/src/template-resolver/README.md +29 -29
  21. package/packages/bundled-features/src/template-resolver/document-sanitizer.ts +3 -2
  22. package/packages/bundled-features/src/template-resolver/web/collection-entry-create-form.tsx +1 -0
  23. package/packages/bundled-features/src/tenant-lifecycle/stages.ts +1 -0
  24. package/packages/bundled-features/src/user-data-rights/COMPLIANCE.md +36 -36
  25. package/packages/bundled-features/src/user-data-rights/README.md +33 -33
  26. package/packages/bundled-features/src/user-data-rights/feature.ts +3 -3
  27. package/packages/bundled-features/src/user-data-rights/web/public-deletion-gate.tsx +1 -0
  28. package/samples/apps/admin-console/README.md +5 -5
  29. package/samples/apps/admin-console/package.json +3 -3
  30. package/samples/apps/cap-billing-demo/README.md +15 -15
  31. package/samples/apps/config-demo/README.md +7 -7
  32. package/samples/apps/config-demo/package.json +2 -2
  33. package/samples/apps/marketing-demo/README.md +2 -2
  34. package/samples/apps/marketing-demo/package.json +2 -2
  35. package/samples/apps/showcase/package.json +3 -3
  36. package/samples/apps/styleguide/package.json +2 -2
  37. package/samples/apps/ui-walkthrough/README.md +8 -9
  38. package/samples/apps/ui-walkthrough/package.json +2 -2
  39. package/samples/apps/use-all-bundled/README.md +2 -2
  40. package/samples/apps/use-all-bundled/package.json +2 -2
  41. package/samples/apps/use-all-bundled/scripts/gen-sample-index.ts +18 -1
  42. package/samples/apps/user-data-rights-demo/README.md +12 -12
  43. package/samples/apps/workspaces/package.json +2 -2
  44. package/samples/recipes/apex-landing/README.md +3 -3
  45. package/samples/recipes/apex-landing/package.json +1 -1
  46. package/samples/recipes/apex-surface-auth/README.md +13 -9
  47. package/samples/recipes/auth-mfa/README.md +2 -4
  48. package/samples/recipes/auth-mfa/package.json +2 -2
  49. package/samples/recipes/boot-check/README.md +2 -2
  50. package/samples/recipes/compliance-profiles-demo/README.md +5 -5
  51. package/samples/recipes/compose-stacks/README.md +8 -8
  52. package/samples/recipes/cross-feature-events/README.md +1 -0
  53. package/samples/recipes/crypto-shredding-hr/README.md +3 -3
  54. package/samples/recipes/custom-fields-basic/README.md +1 -1
  55. package/samples/recipes/documents/README.md +25 -0
  56. package/samples/recipes/embedded-entity-form/package.json +2 -2
  57. package/samples/recipes/encrypted-tenant-config/README.md +8 -8
  58. package/samples/recipes/env-composition/README.md +10 -10
  59. package/samples/recipes/error-contract/README.md +9 -9
  60. package/samples/recipes/event-sourcing/README.md +9 -9
  61. package/samples/recipes/lane-routing/README.md +1 -0
  62. package/samples/recipes/lane-routing/src/feature.ts +8 -0
  63. package/samples/recipes/ledger-invoicing/README.md +6 -6
  64. package/samples/recipes/legal-pages/README.md +16 -16
  65. package/samples/recipes/managed-config/README.md +15 -11
  66. package/samples/recipes/managed-pages/README.md +7 -7
  67. package/samples/recipes/pdf-audit-export/package.json +1 -1
  68. package/samples/recipes/pipeline-basics/README.md +13 -13
  69. package/samples/recipes/public-share-token/README.md +4 -4
  70. package/samples/recipes/record-detail-layout/package.json +3 -3
  71. package/samples/recipes/security-baseline/README.md +4 -1
  72. package/samples/recipes/session-revocation/README.md +1 -1
  73. package/samples/recipes/tenant-settings/README.md +4 -4
  74. package/samples/recipes/tier-admin/README.md +2 -2
  75. package/samples/recipes/user-data-rights/README.md +21 -19
  76. package/samples/recipes/wizard-form/package.json +3 -3
  77. package/samples/recipes/writeform-section/package.json +3 -3
@@ -16,34 +16,34 @@ Dieses Dokument bündelt die **technischen Fakten** zur DSGVO-Pipeline
16
16
 
17
17
  ## 1. Endpoint → Artikel-Mapping
18
18
 
19
- | Artikel | Endpoint / Runner | Wer ruft auf | Was passiert |
20
- |---------|-------------------|--------------|--------------|
21
- | **Art. 15 (Auskunft, light)** | `user-data-rights:query:my-audit-log` | User | Liest eigene Framework-Events aus `kumiko_events` (account-weit über alle Memberships). Filterbar nach `eventType`, `aggregateType`, `from`/`to`. Domain-Entities ohne `ctx.events.append` erscheinen NICHT — die kommen im Export-Bundle (Art. 20). |
22
- | **Art. 15 + 20 (Auskunft + Portabilität)** | `user-data-rights:write:request-export` | User | Async Job → ZIP mit user-Profil + fileRefs + alle EXT_USER_DATA-Provider-Daten (cross-tenant) + signed Magic-Link per Email. Idempotent: nur 1 active Job pro User (`ACTIVE_JOB_CONSTRAINT`). |
23
- | **Art. 15 + 20** | `GET /user-export/by-token` (interstitial) + `POST /user-export/by-token` (exchange) | Anonym (Magic-Link-Pfad) | Email-Link trägt den Token als URL-Fragment (`#token=`, nie an den Server gesendet, issue #1271); Interstitial-Script liest das Fragment client-seitig und tauscht es per POST-Body gegen die signed Storage-URL. Kein `?token=`-Query-Fallback (#1512) — Query-Param landet unveränderlich in Proxy-/Access-Logs. Multi-use innerhalb TTL. Audit: `lastUsedAt`, `lastUsedFromIp`, `lastUsedUserAgent`. |
24
- | **Art. 15 + 20** | `GET /user-export/by-job/:jobId` | User (Session-Auth) | UI-Klick-Pfad. Cross-tenant-same-user: User in Tenant B kann Job aus Tenant A laden wenn er der Owner ist. |
25
- | **Art. 17 (Löschung)** | `user-data-rights:write:request-deletion` | User | Soft-Delete: `status=DeletionRequested` + `gracePeriodEnd = now + profile.gracePeriod`. Cron `run-forget-cleanup` führt nach Grace die Anonymisierung durch. |
26
- | **Art. 17** | `user-data-rights:write:cancel-deletion` | User | Während Grace: status zurück auf `Active`. |
27
- | **Art. 17** | `run-forget-cleanup` (Cron) | System | Findet User mit `status=DeletionRequested AND gracePeriodEnd < now`. Pro User Sub-TX über alle EXT_USER_DATA-Provider mit Strategy aus `retention.policyFor`. user-Hook anonymisiert (PII raus, Sentinel-Email). |
28
- | **Art. 18 (Restriction)** | `user-data-rights:write:restrict-account` | Admin / SystemAdmin | Status-Flip → Auth-Middleware-Guard blockt Logins. `sessions.revokeAllForUser` killt aktive Sessions. |
29
- | **Art. 18** | `user-data-rights:write:lift-restriction` | Admin / SystemAdmin | Restriction aufheben. |
30
- | **Operator (DPO)** | `user-data-rights:query:list-download-attempts` | Admin / SystemAdmin | Brute-Force-Detection: zeigt invalid Download-Versuche (notFound / expired / failed / signedUrlNotSupported) gefiltert nach Result, IP, Zeitraum. |
19
+ | Artikel | Endpoint / Runner | Wer ruft auf | Was passiert |
20
+ | ------------------------------------------ | ------------------------------------------------------------------------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
21
+ | **Art. 15 (Auskunft, light)** | `user-data-rights:query:my-audit-log` | User | Liest eigene Framework-Events aus `kumiko_events` (account-weit über alle Memberships). Filterbar nach `eventType`, `aggregateType`, `from`/`to`. Domain-Entities ohne `ctx.events.append` erscheinen NICHT — die kommen im Export-Bundle (Art. 20). |
22
+ | **Art. 15 + 20 (Auskunft + Portabilität)** | `user-data-rights:write:request-export` | User | Async Job → ZIP mit user-Profil + fileRefs + alle EXT_USER_DATA-Provider-Daten (cross-tenant) + signed Magic-Link per Email. Idempotent: nur 1 active Job pro User (`ACTIVE_JOB_CONSTRAINT`). |
23
+ | **Art. 15 + 20** | `GET /user-export/by-token` (interstitial) + `POST /user-export/by-token` (exchange) | Anonym (Magic-Link-Pfad) | Email-Link trägt den Token als URL-Fragment (`#token=`, nie an den Server gesendet, issue #1271); Interstitial-Script liest das Fragment client-seitig und tauscht es per POST-Body gegen die signed Storage-URL. Kein `?token=`-Query-Fallback (#1512) — Query-Param landet unveränderlich in Proxy-/Access-Logs. Multi-use innerhalb TTL. Audit: `lastUsedAt`, `lastUsedFromIp`, `lastUsedUserAgent`. |
24
+ | **Art. 15 + 20** | `GET /user-export/by-job/:jobId` | User (Session-Auth) | UI-Klick-Pfad. Cross-tenant-same-user: User in Tenant B kann Job aus Tenant A laden wenn er der Owner ist. |
25
+ | **Art. 17 (Löschung)** | `user-data-rights:write:request-deletion` | User | Soft-Delete: `status=DeletionRequested` + `gracePeriodEnd = now + profile.gracePeriod`. Cron `run-forget-cleanup` führt nach Grace die Anonymisierung durch. |
26
+ | **Art. 17** | `user-data-rights:write:cancel-deletion` | User | Während Grace: status zurück auf `Active`. |
27
+ | **Art. 17** | `run-forget-cleanup` (Cron) | System | Findet User mit `status=DeletionRequested AND gracePeriodEnd < now`. Pro User Sub-TX über alle EXT_USER_DATA-Provider mit Strategy aus `retention.policyFor`. user-Hook anonymisiert (PII raus, Sentinel-Email). |
28
+ | **Art. 18 (Restriction)** | `user-data-rights:write:restrict-account` | Admin / SystemAdmin | Status-Flip → Auth-Middleware-Guard blockt Logins. `sessions.revokeAllForUser` killt aktive Sessions. |
29
+ | **Art. 18** | `user-data-rights:write:lift-restriction` | Admin / SystemAdmin | Restriction aufheben. |
30
+ | **Operator (DPO)** | `user-data-rights:query:list-download-attempts` | Admin / SystemAdmin | Brute-Force-Detection: zeigt invalid Download-Versuche (notFound / expired / failed / signedUrlNotSupported) gefiltert nach Result, IP, Zeitraum. |
31
31
 
32
32
  ---
33
33
 
34
34
  ## 2. Speicherorte (Verarbeitungsverzeichnis Art. 30)
35
35
 
36
- | Tabelle | Inhalt | Personenbezug | Retention | Zweck |
37
- |---------|--------|---------------|-----------|-------|
38
- | `read_users` | User-Profil (email, displayName, passwordHash, locale, status, gracePeriodEnd, roles) | direkt | per Domain (typ. blockDelete bei Aufbewahrungspflicht, sonst hardDelete via Forget-Pipeline) | Authentifizierung, Nutzer-Profil |
39
- | `read_tenant_memberships` | User↔Tenant-Verknüpfung + Rollen | direkt (via userId) | per Tenant-Lifecycle | Mehrmandant-Zuordnung |
40
- | `kumiko_events` | Event-Store (alle write-Events) | direkt (`createdBy = userId`) | per Domain-Policy via `data-retention` | Audit-Trail (Art. 15-Selbstauskunft Quelle), Event-Sourcing |
41
- | `read_export_jobs` | Async Export-Status (queued/running/done/failed), userId, requestedAt, doneAt, storageKey | direkt (userId) | per `compliance-profiles` Profil-Default | Idempotenz + Status-Polling |
42
- | `read_export_download_tokens` | Magic-Link-Hash (SHA-256), TTL, useCount, lastUsedAt, lastUsedFromIp, lastUsedUserAgent | indirekt (Token→Job→User) | `compliance-profiles.userRights.exportDownloadTtl` (default 7d) | Magic-Link-Auth + Multi-Use-Audit |
43
- | `read_download_attempts` | Invalid Download-Versuche: result, via, tokenHash, ip, userAgent, attemptedAt | indirekt (IP) | **90d hardDelete** (Entity-Default, Disk-Bomb-Schutz) | DPO-Brute-Force-Detection |
44
- | `read_tenant_compliance_profiles` | Per-Tenant Profile-Wahl + Override | nein | unbounded (Konfiguration) | Region-/Branchen-Defaults |
45
- | `read_tenant_retention_overrides` | Per-Tenant Retention-Override pro Entity | nein | unbounded (Konfiguration) | Aufbewahrungspflicht-Edge-Cases |
46
- | Storage-Provider (Local / S3) | Export-ZIPs + File-Binaries | indirekt (Inhalt) | Local: per `exportDownloadTtl` Cleanup. S3: lifecycle-Policy (App-Author-Verantwortung) | Daten-Export-Auslieferung |
36
+ | Tabelle | Inhalt | Personenbezug | Retention | Zweck |
37
+ | --------------------------------- | ----------------------------------------------------------------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
38
+ | `read_users` | User-Profil (email, displayName, passwordHash, locale, status, gracePeriodEnd, roles) | direkt | per Domain (typ. blockDelete bei Aufbewahrungspflicht, sonst hardDelete via Forget-Pipeline) | Authentifizierung, Nutzer-Profil |
39
+ | `read_tenant_memberships` | User↔Tenant-Verknüpfung + Rollen | direkt (via userId) | per Tenant-Lifecycle | Mehrmandant-Zuordnung |
40
+ | `kumiko_events` | Event-Store (alle write-Events) | direkt (`createdBy = userId`) | per Domain-Policy via `data-retention` | Audit-Trail (Art. 15-Selbstauskunft Quelle), Event-Sourcing |
41
+ | `read_export_jobs` | Async Export-Status (queued/running/done/failed), userId, requestedAt, doneAt, storageKey | direkt (userId) | per `compliance-profiles` Profil-Default | Idempotenz + Status-Polling |
42
+ | `read_export_download_tokens` | Magic-Link-Hash (SHA-256), TTL, useCount, lastUsedAt, lastUsedFromIp, lastUsedUserAgent | indirekt (Token→Job→User) | `compliance-profiles.userRights.exportDownloadTtl` (default 7d) | Magic-Link-Auth + Multi-Use-Audit |
43
+ | `read_download_attempts` | Invalid Download-Versuche: result, via, tokenHash, ip, userAgent, attemptedAt | indirekt (IP) | **90d hardDelete** (Entity-Default, Disk-Bomb-Schutz) | DPO-Brute-Force-Detection |
44
+ | `read_tenant_compliance_profiles` | Per-Tenant Profile-Wahl + Override | nein | unbounded (Konfiguration) | Region-/Branchen-Defaults |
45
+ | `read_tenant_retention_overrides` | Per-Tenant Retention-Override pro Entity | nein | unbounded (Konfiguration) | Aufbewahrungspflicht-Edge-Cases |
46
+ | Storage-Provider (Local / S3) | Export-ZIPs + File-Binaries | indirekt (Inhalt) | Local: per `exportDownloadTtl` Cleanup. S3: lifecycle-Policy (App-Author-Verantwortung) | Daten-Export-Auslieferung |
47
47
 
48
48
  ### Bekanntes Residuum: `createdBy` / `metadata.userId` in `kumiko_events`
49
49
 
@@ -69,12 +69,12 @@ Retention-Policy irgendwann das Event selbst löscht.
69
69
 
70
70
  ## 3. Compliance-Profile
71
71
 
72
- | Profil | gracePeriod | exportDownloadTtl | Stale-After | Sub-Processors |
73
- |--------|-------------|-------------------|-------------|----------------|
74
- | `eu-dsgvo` | 30d | 7d | 30d | per Tenant konfigurierbar |
75
- | `swiss-dsg` | 30d | 7d | 30d | + EDÖB-Meldepfad |
76
- | `de-hr-dsgvo-hgb` | 30d | 7d | 30d | + HR-Aufbewahrung 10y für HR-Entities (anonymize statt delete) |
77
- | `minimal-no-region` | 30d | 7d | 30d | Migration-Edge-Case ohne Region |
72
+ | Profil | gracePeriod | exportDownloadTtl | Stale-After | Sub-Processors |
73
+ | ------------------- | ----------- | ----------------- | ----------- | -------------------------------------------------------------- |
74
+ | `eu-dsgvo` | 30d | 7d | 30d | per Tenant konfigurierbar |
75
+ | `swiss-dsg` | 30d | 7d | 30d | + EDÖB-Meldepfad |
76
+ | `de-hr-dsgvo-hgb` | 30d | 7d | 30d | + HR-Aufbewahrung 10y für HR-Entities (anonymize statt delete) |
77
+ | `minimal-no-region` | 30d | 7d | 30d | Migration-Edge-Case ohne Region |
78
78
 
79
79
  Tenant kann via `compliance-profiles:write:set-profile` + Override (`override.userRights.gracePeriod={ days: N }` etc.) eigene Werte setzen.
80
80
 
@@ -128,13 +128,13 @@ und Organisatorische Maßnahmen" übernommen werden:
128
128
 
129
129
  ## 5. Cron-Jobs / Operationale Trigger
130
130
 
131
- | Job | Trigger | Was passiert | Operator-Sichtbarkeit |
132
- |-----|---------|--------------|------------------------|
133
- | `run-forget-cleanup` | Cron (per App-Author konfigurierbar, typisch täglich) | Findet User mit abgelaufener Grace, ruft `EXT_USER_DATA.delete` pro Provider, anonymisiert User, sendet `sendDeletionExecutedEmail`-Callback | `read_job_runs` (success/fail), Hook-Errors als `errors[]` im Result |
134
- | `run-export-jobs` | Cron + Event-getriggert | Findet pending Export-Jobs, ruft `runUserExport` → ZIP-Bau → Storage-Upload → Magic-Link-Token → `sendExportReadyEmail` | `read_job_runs`, `read_export_jobs.status` |
135
- | `data-retention-cleanup` | Cron (in `data-retention`) | Cleant abgelaufene Rows pro Entity per `retention.keepFor` | `read_job_runs` |
136
- | Token-Cleanup (implizit) | Per `compliance-profiles.userRights.exportDownloadTtl` | Worker-Job entfernt expired Magic-Link-Tokens + Storage-Binaries | `read_export_download_tokens` |
137
- | Download-Attempt-Cleanup | Per Entity-Default 90d | Cleant `read_download_attempts` | — |
131
+ | Job | Trigger | Was passiert | Operator-Sichtbarkeit |
132
+ | ------------------------ | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
133
+ | `run-forget-cleanup` | Cron (per App-Author konfigurierbar, typisch täglich) | Findet User mit abgelaufener Grace, ruft `EXT_USER_DATA.delete` pro Provider, anonymisiert User, sendet `sendDeletionExecutedEmail`-Callback | `read_job_runs` (success/fail), Hook-Errors als `errors[]` im Result |
134
+ | `run-export-jobs` | Cron + Event-getriggert | Findet pending Export-Jobs, ruft `runUserExport` → ZIP-Bau → Storage-Upload → Magic-Link-Token → `sendExportReadyEmail` | `read_job_runs`, `read_export_jobs.status` |
135
+ | `data-retention-cleanup` | Cron (in `data-retention`) | Cleant abgelaufene Rows pro Entity per `retention.keepFor` | `read_job_runs` |
136
+ | Token-Cleanup (implizit) | Per `compliance-profiles.userRights.exportDownloadTtl` | Worker-Job entfernt expired Magic-Link-Tokens + Storage-Binaries | `read_export_download_tokens` |
137
+ | Download-Attempt-Cleanup | Per Entity-Default 90d | Cleant `read_download_attempts` | — |
138
138
 
139
139
  App-Author registriert die Cron-Trigger im run-config; `jobs`-Feature
140
140
  persistiert Run-Tracking + Retry-Pfad.
@@ -18,16 +18,16 @@ defineFeature("tasks", (r) => {
18
18
  r.useExtension(EXT_USER_DATA, "task", {
19
19
  export: async (ctx) => ({
20
20
  entity: "task",
21
- rows: await ctx.db.select().from(tasksTable)
22
- .where(eq(tasksTable.authorId, ctx.userId)),
21
+ rows: await ctx.db.select().from(tasksTable).where(eq(tasksTable.authorId, ctx.userId)),
23
22
  }),
24
23
  delete: async (ctx, strategy) => {
25
24
  if (strategy === "anonymize") {
26
- await ctx.db.update(tasksTable).set({ authorId: null })
25
+ await ctx.db
26
+ .update(tasksTable)
27
+ .set({ authorId: null })
27
28
  .where(eq(tasksTable.authorId, ctx.userId));
28
29
  } else {
29
- await ctx.db.delete(tasksTable)
30
- .where(eq(tasksTable.authorId, ctx.userId));
30
+ await ctx.db.delete(tasksTable).where(eq(tasksTable.authorId, ctx.userId));
31
31
  }
32
32
  },
33
33
  });
@@ -42,15 +42,15 @@ Hook-Signaturen in `framework/src/engine/extensions/user-data.ts`:
42
42
 
43
43
  ## Endpoints
44
44
 
45
- | Article | Handler | Zweck |
46
- |---------|---------|-------|
47
- | Art. 15 | `user-data-rights:query:my-audit-log` | User sieht eigene Framework-Events (account-weit über alle Memberships). Domain-Entities ohne `ctx.events.append` erscheinen NICHT — nur im Export-Bundle. |
48
- | Art. 15+20 | `user-data-rights:write:request-export` | Async Job → ZIP mit user-Profil + fileRefs + alle EXT_USER_DATA-Provider-Daten + signed Magic-Link |
49
- | Art. 17 | `user-data-rights:write:request-deletion` | Soft-Delete mit Grace-Period, anschließend Cron anonymisiert User + cleant Domain-Entities |
50
- | Art. 17 | `user-data-rights:write:cancel-deletion` | User widerruft seinen Forget-Request während der Grace |
51
- | Art. 18 | `user-data-rights:write:restrict-account` | Auth-Middleware blockt Logins bis Lift |
52
- | Art. 18 | `user-data-rights:write:lift-restriction` | Admin/SystemAdmin hebt Restriction auf |
53
- | Operator | `user-data-rights:query:list-download-attempts` | DPO-Sicht auf invalid Download-Versuche (Brute-Force-Detection, Admin/SystemAdmin only) |
45
+ | Article | Handler | Zweck |
46
+ | ---------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
47
+ | Art. 15 | `user-data-rights:query:my-audit-log` | User sieht eigene Framework-Events (account-weit über alle Memberships). Domain-Entities ohne `ctx.events.append` erscheinen NICHT — nur im Export-Bundle. |
48
+ | Art. 15+20 | `user-data-rights:write:request-export` | Async Job → ZIP mit user-Profil + fileRefs + alle EXT_USER_DATA-Provider-Daten + signed Magic-Link |
49
+ | Art. 17 | `user-data-rights:write:request-deletion` | Soft-Delete mit Grace-Period, anschließend Cron anonymisiert User + cleant Domain-Entities |
50
+ | Art. 17 | `user-data-rights:write:cancel-deletion` | User widerruft seinen Forget-Request während der Grace |
51
+ | Art. 18 | `user-data-rights:write:restrict-account` | Auth-Middleware blockt Logins bis Lift |
52
+ | Art. 18 | `user-data-rights:write:lift-restriction` | Admin/SystemAdmin hebt Restriction auf |
53
+ | Operator | `user-data-rights:query:list-download-attempts` | DPO-Sicht auf invalid Download-Versuche (Brute-Force-Detection, Admin/SystemAdmin only) |
54
54
 
55
55
  Plus 2 anonyme HTTP-Routes für Export-Download (Magic-Link-Pfad +
56
56
  session-auth-Pfad), siehe `handlers/download-by-{token,job}.query.ts`.
@@ -77,30 +77,30 @@ weglassen wenn er Custom-Hooks registrieren will.
77
77
 
78
78
  ## Audit-Trail
79
79
 
80
- | Tabelle | Zweck | Retention |
81
- |---------|-------|-----------|
82
- | `kumiko_events` | Framework-Event-Store, Quelle für `my-audit-log` | per Domain-Policy |
83
- | `read_export_jobs` | Async Export-Status (queued / done / failed) | per `compliance-profiles` |
84
- | `read_export_download_tokens` | Magic-Link-Hash + TTL + lastUsed-Audit | per `compliance-profiles` (default `exportDownloadTtl`) |
85
- | `read_download_attempts` | Invalid-Download-Versuche für DPO-Brute-Force-Detection | **90d hardDelete** (Entity-Default — schützt vor Disk-Bomb bei aktiven Angriffen) |
80
+ | Tabelle | Zweck | Retention |
81
+ | ----------------------------- | ------------------------------------------------------- | --------------------------------------------------------------------------------- |
82
+ | `kumiko_events` | Framework-Event-Store, Quelle für `my-audit-log` | per Domain-Policy |
83
+ | `read_export_jobs` | Async Export-Status (queued / done / failed) | per `compliance-profiles` |
84
+ | `read_export_download_tokens` | Magic-Link-Hash + TTL + lastUsed-Audit | per `compliance-profiles` (default `exportDownloadTtl`) |
85
+ | `read_download_attempts` | Invalid-Download-Versuche für DPO-Brute-Force-Detection | **90d hardDelete** (Entity-Default — schützt vor Disk-Bomb bei aktiven Angriffen) |
86
86
 
87
87
  ## Tests
88
88
 
89
89
  18 Testdateien, 188 Tests, alle grün:
90
90
 
91
- | Datei | Pinst |
92
- |-------|-------|
93
- | `audit-log.integration.test.ts` | Cross-User-Isolation, Account-weite Sicht, eventType-Filter, Admin-only operator-query, download-attempt 90d-retention |
94
- | `cross-data-matrix.integration.test.ts` | 3-Provider-Pipeline (user + fileRef + custom-domain), Cross-Tenant Forget mit user-anonymize, Other-User-Isolation |
95
- | `download.integration.test.ts` | HTTP-e2e via `r.httpRoute`: Magic-Link, multi-use, expired, failed-job, storage-cleared, cross-tenant-same-user, malicious-filename |
96
- | `request-export.integration.test.ts` | Idempotency, active-job-constraint, cross-tenant-anyMember-userId-pattern |
97
- | `request-deletion-callback.integration.test.ts` + `request-cancel-deletion.integration.test.ts` | Grace + Cancel-Pfad + Email-Callback best-effort |
98
- | `restriction-flow.integration.test.ts` | Status-Flip + Auth-Middleware-Block + Lift |
99
- | `run-{export-jobs,forget-cleanup,user-export}.integration.test.ts` | Worker-Logic + Idempotency + Email-Callbacks |
100
- | `policy-to-strategy.test.ts` | Retention.strategy → UserDataDeleteStrategy mapping |
101
- | `user-data-rights.integration.test.ts` | Boot-Smoke + Feature-Meta |
102
- | `token-helpers.test.ts` + `zip-path.test.ts` | Token-Hashing + Path-Traversal-Schutz |
103
- | `export-job-{idempotency,schema}.test.ts` | Active-job-uniqueness + Schema-Constraints |
91
+ | Datei | Pinst |
92
+ | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
93
+ | `audit-log.integration.test.ts` | Cross-User-Isolation, Account-weite Sicht, eventType-Filter, Admin-only operator-query, download-attempt 90d-retention |
94
+ | `cross-data-matrix.integration.test.ts` | 3-Provider-Pipeline (user + fileRef + custom-domain), Cross-Tenant Forget mit user-anonymize, Other-User-Isolation |
95
+ | `download.integration.test.ts` | HTTP-e2e via `r.httpRoute`: Magic-Link, multi-use, expired, failed-job, storage-cleared, cross-tenant-same-user, malicious-filename |
96
+ | `request-export.integration.test.ts` | Idempotency, active-job-constraint, cross-tenant-anyMember-userId-pattern |
97
+ | `request-deletion-callback.integration.test.ts` + `request-cancel-deletion.integration.test.ts` | Grace + Cancel-Pfad + Email-Callback best-effort |
98
+ | `restriction-flow.integration.test.ts` | Status-Flip + Auth-Middleware-Block + Lift |
99
+ | `run-{export-jobs,forget-cleanup,user-export}.integration.test.ts` | Worker-Logic + Idempotency + Email-Callbacks |
100
+ | `policy-to-strategy.test.ts` | Retention.strategy → UserDataDeleteStrategy mapping |
101
+ | `user-data-rights.integration.test.ts` | Boot-Smoke + Feature-Meta |
102
+ | `token-helpers.test.ts` + `zip-path.test.ts` | Token-Hashing + Path-Traversal-Schutz |
103
+ | `export-job-{idempotency,schema}.test.ts` | Active-job-uniqueness + Schema-Constraints |
104
104
 
105
105
  ## Sample
106
106
 
@@ -600,14 +600,14 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
600
600
  type: "gauge",
601
601
  unit: "seconds",
602
602
  description:
603
- "Unix timestamp of the last export storage-cleanup pass that completed. Alert on time() minus this value exceeding the cron interval plus a buffer; the backlog-age gauge alone stays at its last value when the cron stops.",
603
+ "Unix timestamp of the last export storage-cleanup pass that completed. Alert on time() minus this value exceeding one day plus a buffer; the backlog-age gauge alone stays at its last value when the cron stops.",
604
604
  });
605
605
 
606
606
  // Worker for the asynchronous export pipeline, triggered by a cron job.
607
607
  const RUN_EXPORT_JOBS_REASON = "processes pending export jobs across every tenant";
608
608
  r.job({
609
609
  name: "run-export-jobs",
610
- trigger: { cron: "0 3 * * *" },
610
+ trigger: { maintenance: true },
611
611
  concurrency: "skip",
612
612
  escapeHatch: { reason: RUN_EXPORT_JOBS_REASON },
613
613
  handler: async (_payload, ctx) => {
@@ -705,7 +705,7 @@ export function createUserDataRightsFeature(opts: UserDataRightsOptions = {}): F
705
705
  const RUN_FORGET_CLEANUP_REASON = "executes overdue Art.17 forget requests across every tenant";
706
706
  r.job({
707
707
  name: "run-forget-cleanup",
708
- trigger: { cron: "0 3 * * *" },
708
+ trigger: { maintenance: true },
709
709
  concurrency: "skip",
710
710
  escapeHatch: { reason: RUN_FORGET_CLEANUP_REASON },
711
711
  handler: async (_payload, ctx) => {
@@ -32,6 +32,7 @@ export function makePublicDeletionGate(
32
32
  routes: PublicDeletionRoutes,
33
33
  ): ComponentType<{ children: ReactNode }> {
34
34
  const shell = routes.shell ?? centeredShell;
35
+ // biome-ignore lint/correctness/noComponentHookFactories: setup-time factory, called once per userDataRightsClient(); gates only receive children, so routes are bound here
35
36
  function PublicDeletionGate({ children }: { readonly children: ReactNode }): ReactNode {
36
37
  const path = window.location.pathname;
37
38
  if (path === routes.requestPath) return shell(<RequestAccountDeletionScreen />);
@@ -24,11 +24,11 @@ Port 4177 is hardcoded in the `dev` script. The server reads the root
24
24
  All three log into the dev tenant "Admin Console Demo" (tenant key
25
25
  `demo`). Passwords are in `src/app/auth-constants.ts`.
26
26
 
27
- | Email | Role | Sees |
28
- |---|---|---|
29
- | `sysadmin@admin-console.local` | global `SystemAdmin`, tenant `Admin` | tenant-admin and platform workspaces |
30
- | `tenant-admin@admin-console.local` | tenant `TenantAdmin` | tenant-admin workspace only |
31
- | `regular@admin-console.local` | tenant `User` | no admin workspace |
27
+ | Email | Role | Sees |
28
+ | ---------------------------------- | ------------------------------------ | ------------------------------------ |
29
+ | `sysadmin@admin-console.local` | global `SystemAdmin`, tenant `Admin` | tenant-admin and platform workspaces |
30
+ | `tenant-admin@admin-console.local` | tenant `TenantAdmin` | tenant-admin workspace only |
31
+ | `regular@admin-console.local` | tenant `User` | no admin workspace |
32
32
 
33
33
  The SystemAdmin comes from the `auth.admin` bootstrap in
34
34
  `src/app/server.ts`; the other two are created by `seedRoleUsers` in
@@ -11,9 +11,9 @@
11
11
  "@cosmicdrift/kumiko-dev-server": "workspace:*",
12
12
  "@cosmicdrift/kumiko-framework": "workspace:*",
13
13
  "@cosmicdrift/kumiko-renderer": "workspace:*",
14
- "lucide-react": "^1.14.0",
15
- "react": "^19.2.6",
16
- "react-dom": "^19.2.6",
14
+ "lucide-react": "^1.65.0",
15
+ "react": "^19.3.1",
16
+ "react-dom": "^19.3.1",
17
17
  "@cosmicdrift/kumiko-locale-de": "workspace:*"
18
18
  }
19
19
  }
@@ -9,9 +9,9 @@ The sample doubles as **living documentation** for the pattern: read the code to
9
9
  A tiny newsletter app with two tiers:
10
10
 
11
11
  | Tier | Newsletters per month | Soft warning at | Hard block at |
12
- |------|-----------|-------------------|-----------------|
13
- | free | 10 | 11 (110%) | 12 (120%) |
14
- | pro | 100 | 110 (110%) | 120 (120%) |
12
+ | ---- | --------------------- | --------------- | ------------- |
13
+ | free | 10 | 11 (110%) | 12 (120%) |
14
+ | pro | 100 | 110 (110%) | 120 (120%) |
15
15
 
16
16
  Mails land in an **in-memory transport** (`mail-transport-inmemory`).
17
17
  There's no real SMTP server — perfect for the demo, no Mailpit/Mailcrab
@@ -87,12 +87,12 @@ cd samples/apps/cap-billing-demo
87
87
  bun dev # → http://localhost:4290
88
88
  ```
89
89
 
90
- | Login | Value |
91
- |-------|------|
92
- | URL | `http://localhost:4290` |
93
- | Email | `admin@cap-demo.local` |
94
- | Password | `changeme` |
95
- | Tenant | "Cap-Billing-Demo" |
90
+ | Login | Value |
91
+ | -------- | ----------------------- |
92
+ | URL | `http://localhost:4290` |
93
+ | Email | `admin@cap-demo.local` |
94
+ | Password | `changeme` |
95
+ | Tenant | "Cap-Billing-Demo" |
96
96
 
97
97
  In the browser, use the Designer/Admin UI to set the config key
98
98
  `newsletter:config:tier` to `"free"` or `"pro"` and trigger the
@@ -110,13 +110,13 @@ omitted to keep the focus on cap+tier.
110
110
 
111
111
  The sample is intentionally minimal. For a production app, swap:
112
112
 
113
- | Demo component | Production replacement |
114
- |-----------------|-------------------|
115
- | `mail-transport-inmemory` | `mail-transport-smtp` (BYOK) or a custom plugin |
116
- | Hardcoded `DEMO_TIER_MAP` | stays — tier definitions are static, the subscription row only writes the `tier` key |
113
+ | Demo component | Production replacement |
114
+ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
115
+ | `mail-transport-inmemory` | `mail-transport-smtp` (BYOK) or a custom plugin |
116
+ | Hardcoded `DEMO_TIER_MAP` | stays — tier definitions are static, the subscription row only writes the `tier` key |
117
117
  | Tier switch via webhook (test-only) | mount a real plugin: `createSubscriptionStripeFeature(...)` and/or `createSubscriptionMollieFeature(...)` in the run config |
118
- | 2 tiers (free/pro) | any number, see `samples/apps/platform/src/tier-map.ts` for a 4-tier example |
119
- | Newsletter domain | your own feature with `withCapEnforcement(handler, capResolver)` |
118
+ | 2 tiers (free/pro) | any number, see `samples/apps/platform/src/tier-map.ts` for a 4-tier example |
119
+ | Newsletter domain | your own feature with `withCapEnforcement(handler, capResolver)` |
120
120
 
121
121
  The **plugin API switch** between demo and production is a single
122
122
  config value: `mail-foundation:config:provider` flips from
@@ -26,13 +26,13 @@ The server reads the root `.env` (`--env-file=../../../.env`).
26
26
 
27
27
  `src/features/demo/feature.ts` declares five keys with `r.config()`:
28
28
 
29
- | Key | Scope | Type | Default | Write access |
30
- |---|---|---|---|---|
31
- | `siteName` | tenant | text | `My Site` | all |
32
- | `themeColor` | tenant | text | `#000000` | all |
33
- | `maxUploadSize` | tenant | number, bounds 1 to 1000 | `10` | all |
34
- | `emailNotifications` | user | boolean | `true` | all |
35
- | `autoApprove` | system | boolean | `false` | `access.systemAdmin` |
29
+ | Key | Scope | Type | Default | Write access |
30
+ | -------------------- | ------ | ------------------------ | --------- | -------------------- |
31
+ | `siteName` | tenant | text | `My Site` | all |
32
+ | `themeColor` | tenant | text | `#000000` | all |
33
+ | `maxUploadSize` | tenant | number, bounds 1 to 1000 | `10` | all |
34
+ | `emailNotifications` | user | boolean | `true` | all |
35
+ | `autoApprove` | system | boolean | `false` | `access.systemAdmin` |
36
36
 
37
37
  ### Seeds
38
38
 
@@ -13,8 +13,8 @@
13
13
  "@cosmicdrift/kumiko-dev-server": "workspace:*",
14
14
  "@cosmicdrift/kumiko-renderer": "workspace:*",
15
15
  "@cosmicdrift/kumiko-framework": "workspace:*",
16
- "react": "^19.2.6",
17
- "react-dom": "^19.2.6",
16
+ "react": "^19.3.1",
17
+ "react-dom": "^19.3.1",
18
18
  "@cosmicdrift/kumiko-locale-de": "workspace:*"
19
19
  }
20
20
  }
@@ -2,8 +2,8 @@
2
2
 
3
3
  Sample app that produces marketing screenshots for
4
4
  [kumiko.rocks](https://kumiko.rocks). Two small internal tools (asset tracker
5
- + helpdesk) on a single Kumiko instance — a DACH-persona proof that the
6
- same framework stack covers two tools without separate codebases.
5
+ - helpdesk) on a single Kumiko instance — a DACH-persona proof that the
6
+ same framework stack covers two tools without separate codebases.
7
7
 
8
8
  **No auth** — like the showcase, in auto-mint-JWT mode. Straight to
9
9
  the edit/list screen without a login. To exercise auth paths, see
@@ -14,8 +14,8 @@
14
14
  "@cosmicdrift/kumiko-renderer": "workspace:*",
15
15
  "@cosmicdrift/kumiko-framework": "workspace:*",
16
16
  "@cosmicdrift/kumiko-types": "workspace:*",
17
- "react": "^19.2.6",
18
- "react-dom": "^19.2.6",
17
+ "react": "^19.3.1",
18
+ "react-dom": "^19.3.1",
19
19
  "@cosmicdrift/kumiko-locale-de": "workspace:*"
20
20
  }
21
21
  }
@@ -13,9 +13,9 @@
13
13
  "@cosmicdrift/kumiko-dev-server": "workspace:*",
14
14
  "@cosmicdrift/kumiko-renderer": "workspace:*",
15
15
  "@cosmicdrift/kumiko-framework": "workspace:*",
16
- "lucide-react": "^1.14.0",
17
- "react": "^19.2.6",
18
- "react-dom": "^19.2.6",
16
+ "lucide-react": "^1.65.0",
17
+ "react": "^19.3.1",
18
+ "react-dom": "^19.3.1",
19
19
  "@cosmicdrift/kumiko-locale-de": "workspace:*"
20
20
  },
21
21
  "devDependencies": {
@@ -5,8 +5,8 @@
5
5
  "@cosmicdrift/kumiko-dev-server": "workspace:*",
6
6
  "@cosmicdrift/kumiko-renderer": "workspace:*",
7
7
  "@cosmicdrift/kumiko-framework": "workspace:*",
8
- "react": "^19.2.6",
9
- "react-dom": "^19.2.6",
8
+ "react": "^19.3.1",
9
+ "react-dom": "^19.3.1",
10
10
  "@cosmicdrift/kumiko-locale-de": "workspace:*"
11
11
  },
12
12
  "description": "Styleguide-Gallery: rendert die Auto-UI-Bausteine, Screenshot-Runner schießt sie in 3 Themes für die Doku",
@@ -31,14 +31,14 @@ bun kumiko dev
31
31
  cd samples/apps/ui-walkthrough && bun dev # http://localhost:4173, admin@kumiko.dev / kumiko-admin
32
32
  ```
33
33
 
34
- | Mask | Route |
35
- | --- | --- |
36
- | Liste | `/lease-list` (pager check: `/lease-list-short`, 3 rows) |
37
- | Detail | click "Max Nachmieter" (`/lease-detail/00000000-0000-4000-8000-000000003103`), tab "Positionen" |
38
- | Drawer | Positionen tab, button "Miete anpassen" |
39
- | Formular | `/vehicle-list`, click "Octavia" (`/vehicle-edit`) |
40
- | Wizard | `/vehicle-list`, row menu "Schritt für Schritt" (`/vehicle-wizard`), 3x "Weiter" for step 4 |
41
- | Mobile | `/campaign-list` at 390 px width |
34
+ | Mask | Route |
35
+ | -------- | ----------------------------------------------------------------------------------------------- |
36
+ | Liste | `/lease-list` (pager check: `/lease-list-short`, 3 rows) |
37
+ | Detail | click "Max Nachmieter" (`/lease-detail/00000000-0000-4000-8000-000000003103`), tab "Positionen" |
38
+ | Drawer | Positionen tab, button "Miete anpassen" |
39
+ | Formular | `/vehicle-list`, click "Octavia" (`/vehicle-edit`) |
40
+ | Wizard | `/vehicle-list`, row menu "Schritt für Schritt" (`/vehicle-wizard`), 3x "Weiter" for step 4 |
41
+ | Mobile | `/campaign-list` at 390 px width |
42
42
 
43
43
  Screenshots (light 1440x900, mobile 390x844, dark for liste and detail):
44
44
 
@@ -102,4 +102,3 @@ bun kumiko test e2e samples/apps/ui-walkthrough
102
102
 
103
103
  Six Playwright specs: smoke + create flow + update flow + 4 generated
104
104
  specs (from the registry-driven E2E generator).
105
-
@@ -16,8 +16,8 @@
16
16
  "@cosmicdrift/kumiko-renderer-mail-html": "workspace:*",
17
17
  "@cosmicdrift/kumiko-renderer-puppeteer-client": "workspace:*",
18
18
  "@cosmicdrift/kumiko-server-runtime": "workspace:*",
19
- "react": "^19.2.6",
20
- "react-dom": "^19.2.6",
19
+ "react": "^19.3.1",
20
+ "react-dom": "^19.3.1",
21
21
  "zod": "^4.6.7",
22
22
  "@cosmicdrift/kumiko-locale-de": "workspace:*",
23
23
  "@cosmicdrift/kumiko-locale-es": "workspace:*"
@@ -12,8 +12,8 @@ from any real app. This sample is the gate.
12
12
 
13
13
  The `use-all-bundled-smoke` job guards `release`. It boots the sample with `KUMIKO_DRY_RUN_ENV=boot` (no DB) so feature-wiring bugs surface before publish.
14
14
 
15
- | Job | What it catches |
16
- |---|---|
15
+ | Job | What it catches |
16
+ | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
17
17
  | `use-all-bundled-smoke` | feature-wiring bugs: `Object.entries(undefined)`, self-extension, missing-requires, schema-Validators, `composeFeatures` failures. **No DB**. Boot exits after `createRegistry`. |
18
18
 
19
19
  DB-backed boot (schema apply for every bundled feature against real Postgres + Redis, dispatcher round-trip) is gated by `src/__tests__/full-stack-boot.integration.test.ts` in the non-blocking `integration` CI-job. The prod-only paths (`assertSchemaCurrent`, `GET /health`) run in `packages/dev-server`'s `run-prod-app.integration.test.ts` — not against this sample's feature set.
@@ -25,8 +25,8 @@
25
25
  "@cosmicdrift/kumiko-renderer-puppeteer-client": "workspace:*",
26
26
  "@cosmicdrift/kumiko-server-runtime": "workspace:*",
27
27
  "@cosmicdrift/kumiko-types": "workspace:*",
28
- "react": "^19.2.6",
29
- "react-dom": "^19.2.6",
28
+ "react": "^19.3.1",
29
+ "react-dom": "^19.3.1",
30
30
  "zod": "^4.6.7",
31
31
  "@cosmicdrift/kumiko-locale-de": "workspace:*",
32
32
  "@cosmicdrift/kumiko-locale-es": "workspace:*",
@@ -232,6 +232,19 @@ function scanSamples(): Map<string, MutableFeatureRow> {
232
232
  return byFeature;
233
233
  }
234
234
 
235
+ // The docs site builds sample pages from READMEs only; a primarySample without one
236
+ // makes feature-reference link to a page that does not exist.
237
+ export function assertPrimarySampleHasReadme(
238
+ feature: string,
239
+ slug: string,
240
+ readmeExists: boolean,
241
+ ): void {
242
+ if (readmeExists) return;
243
+ throw new Error(
244
+ `sample-index.overrides.json: feature "${feature}" has primarySample "${slug}" but ${slug}/README.md does not exist. Add a README.md to that sample (the docs site only generates sample pages from READMEs) or point primarySample at a sample that has one.`,
245
+ );
246
+ }
247
+
235
248
  export function loadOverrides(): SampleIndexOverrides {
236
249
  if (!existsSync(OVERRIDES_PATH)) return {};
237
250
  return JSON.parse(readFileSync(OVERRIDES_PATH, "utf-8")) as SampleIndexOverrides;
@@ -257,7 +270,11 @@ export function buildSampleIndex(): SampleIndex {
257
270
  const primarySample = override?.primarySample ?? dedicated[0];
258
271
  let readmeSummary: string | undefined;
259
272
  if (primarySample) {
260
- readmeSummary = extractReadmeSummary(join(sampleDirFromSlug(primarySample), "README.md"));
273
+ const readmePath = join(sampleDirFromSlug(primarySample), "README.md");
274
+ if (override?.primarySample) {
275
+ assertPrimarySampleHasReadme(name, primarySample, existsSync(readmePath));
276
+ }
277
+ readmeSummary = extractReadmeSummary(readmePath);
261
278
  }
262
279
 
263
280
  features[name] = {
@@ -14,12 +14,12 @@ the integration test top-to-bottom and you've understood the contract.
14
14
  A tiny todo app where each user has private todos. The demo proves that
15
15
  DSGVO requests work end-to-end:
16
16
 
17
- | Article | Endpoint / Runner | What it does |
18
- |---------|-----------|----------------|
19
- | **Art. 15** | `user-data-rights:query:my-audit-log` | User sees own framework events (auth, deletion-request, restriction). Domain entities like todos appear in the **export bundle** (Art. 20), not the audit-log — only handlers using `ctx.events.append` show up here. |
20
- | **Art. 15+20** | `user-data-rights:write:request-export` | ZIP with user-profile + fileRefs + todos + signed download magic-link |
21
- | **Art. 17** | `user-data-rights:write:request-deletion` | Soft-delete with grace period; cron anonymizes user + deletes todos |
22
- | **Art. 18** | `user-data-rights:write:restrict-account` | Auth-middleware blocks logins until lift-restriction |
17
+ | Article | Endpoint / Runner | What it does |
18
+ | -------------- | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | **Art. 15** | `user-data-rights:query:my-audit-log` | User sees own framework events (auth, deletion-request, restriction). Domain entities like todos appear in the **export bundle** (Art. 20), not the audit-log — only handlers using `ctx.events.append` show up here. |
20
+ | **Art. 15+20** | `user-data-rights:write:request-export` | ZIP with user-profile + fileRefs + todos + signed download magic-link |
21
+ | **Art. 17** | `user-data-rights:write:request-deletion` | Soft-delete with grace period; cron anonymizes user + deletes todos |
22
+ | **Art. 18** | `user-data-rights:write:restrict-account` | Auth-middleware blocks logins until lift-restriction |
23
23
 
24
24
  ## Architecture in 3 layers
25
25
 
@@ -80,12 +80,12 @@ cd samples/apps/user-data-rights-demo
80
80
  bun dev # → http://localhost:4291
81
81
  ```
82
82
 
83
- | Login | Value |
84
- |-------|------|
85
- | URL | `http://localhost:4291` |
86
- | Email | `admin@user-data-rights.local` |
87
- | Password | `changeme` |
88
- | Tenant | "User-Data-Rights Demo" |
83
+ | Login | Value |
84
+ | -------- | ------------------------------ |
85
+ | URL | `http://localhost:4291` |
86
+ | Email | `admin@user-data-rights.local` |
87
+ | Password | `changeme` |
88
+ | Tenant | "User-Data-Rights Demo" |
89
89
 
90
90
  In the browser, use the dispatcher to create a few todos, then call
91
91
  `user-data-rights:write:request-export` — the worker queues a job and
@@ -11,8 +11,8 @@
11
11
  "@cosmicdrift/kumiko-dev-server": "workspace:*",
12
12
  "@cosmicdrift/kumiko-renderer": "workspace:*",
13
13
  "@cosmicdrift/kumiko-framework": "workspace:*",
14
- "react": "^19.2.6",
15
- "react-dom": "^19.2.6",
14
+ "react": "^19.3.1",
15
+ "react-dom": "^19.3.1",
16
16
  "@cosmicdrift/kumiko-locale-de": "workspace:*"
17
17
  },
18
18
  "devDependencies": {
@@ -10,9 +10,9 @@ spec also paints a product-board mock (`hero-app.png`), uses it in the hero
10
10
 
11
11
  `buildLandingPage`'s `head.schemaJson` combines `organizationSchema` +
12
12
  `webPageSchema` (from the `seo` feature) into one `@graph` block — Organization
13
- + WebPage nodes in a single `<script type="application/ld+json">`, which
14
- `renderApexPage` already knows how to emit. This is the seam an app extends
15
- with `faqPageSchema` for an FAQ section, or its own custom schema.org type.
13
+ - WebPage nodes in a single `<script type="application/ld+json">`, which
14
+ `renderApexPage` already knows how to emit. This is the seam an app extends
15
+ with `faqPageSchema` for an FAQ section, or its own custom schema.org type.
16
16
 
17
17
  ## Site discovery: sitemap.xml / llms.txt
18
18