Zum Hauptinhalt springen

Mandantenfähigkeit & Tenant-Isolation (OP-TENANT)

Status: Design festgezurrt (kein Code) — Entscheidungen 2026-06-27, alle E-TENANT-Sign-offs ✅ (§7); Umsetzung (Slices T-1…T-7, §6) offen · Bezug: OP-TENANT (Owning-OP) · OP-DATA-1 (docs/architektur/Persistenz.md) · OP-AUDIT-1 (docs/architektur/Audit-Log.md) · OP-BACKUP-1 (docs/architektur/Backup-Restore.md) · OP-AUTH-1 (docs/architektur/Billing-Zugang.md) · Zielgruppe: IT-Dev/Architektur · CISO/Security · True North: nur indirekt — die Tenant-Isolation ist das Fundament dafür, dass jeder Betrieb die drei Leitfragen auf seinen eigenen Daten beantwortet bekommt (beantwortet selbst keine).

Kernaussage. Architektur der Mandantentrennung: Daten pro Tenant komplett (physisch) getrennt (Entscheidung des Auftraggebers, 2026-06-27) — DO-SQLite je Tenant, inklusive Audit-Log. Dieses Dokument zurrt die Datenhaltung je Datenklasse fest und benennt die zwei bewussten zentralen Ausnahmen: Identität/Registry via Better Auth und der zentrale Hash-Anker für Audit-Manipulationsnachweise (E-TENANT-1 = C, keine Events/PII zentral). Fotos in R2 sind logisch per Tenant-Prefix isoliert (nicht physisch wie die DO-SQLite-Daten).

Verhältnis zu Seed-Ablöse-Plan: dieses Doc = Zielarchitektur der Tenant-Trennung; der Seed-Ablöse-Plan = Migrationsweg dorthin (Seed aus dem Laufzeitpfad → Tenant-Vorlage + Purge). Beide teilen OP-TENANT.

1. Getroffene Entscheidungen (2026-06-27)

  1. Mandantendaten (heißer Arbeitssatz + Historie): Isolations-Option A — DO-SQLite je Tenant („Room = Tenant"). Physische Trennung durch die Durable-Object-Grenze; keine geteilte Abfrageoberfläche. → löst zugleich E-DATA-1 = B (DO-SQLite, docs/architektur/Persistenz.md): Option A der Isolation ist die DO-SQLite-Persistenz. Beide Fragen damit beantwortet.
  2. Audit-Log: ebenfalls Option A — je Tenant in dessen DO-SQLite (statt geteilter D1). Maximale Isolation + Audit-PITR je Tenant. Mitigation der Compliance-Folge: siehe §4.
  3. Tenant-Registry: zentral — über Better Auth (Organizations) und dessen EU-D1 (OP-AUTH-1, docs/architektur/Billing-Zugang.md). Die einzige bewusst zentrale Datenklasse, weil sie die Identität→Tenant- Zuordnung ist (Enforcement-Quelle, §5) und naturgemäß mandantenübergreifend.

2. Leitprinzip

Physische Trennung je Tenant ist der Default; Zentralität ist die begründete Ausnahme. Alles, was zu einem Mandanten gehört, lebt in dessen DO (DO-SQLite). Zentral bleibt nur, was die Mandanten erst zuordnet (Identität/Registry) — und, als bewusste minimale Ausnahme (E-TENANT-1=C, §4), ein zentraler Hash-Anker (nur der Chain-Kopf, keine Daten) für die off-box-Manipulationssicherheit des Audits.

3. Datenhaltung je Datenklasse

DatenklasseHeimatTrennungBegründung
Heißer Arbeitssatz (auftraege aktiv, belegung, Stammdaten)DO-SQLite je TenantphysischSingle-Writer-DO, kein Cross-Query-Surface
Wachsende Historie (leistungsnachweise, Abwesenheits-/Beförderungs-Historie, archivierte Aufträge)DO-SQLite je Tenant (Tabellen, on-demand gefiltert)physischbeseitigt Blob-Wachstum, abfragbar, PITR je Tenant
Audit-LogDO-SQLite je Tenant (primär, append-only + Hash-Chain) + zentraler Hash-Anker (nur Chain-Kopf, keine Events/PII, §4)physischvolle Isolation + off-box-Tamper-Nachweis ohne zentrale Daten (E-TENANT-1=C)
Fotos/Blobs (R2)ein EU-Bucket, Key-Prefix tenant/<id>/…, gekapselt hinter tenantBucket()/tenantKey() (§5)logisch (E-TENANT-2=3)opake Bytes; die Foto↔Tenant↔Auftrag-Relation liegt bereits physisch in der DO-SQLite; Prefix skaliert auf tausende kleine Mandanten (Bucket-Limit), Bucket-je-Tenant = Opt-in-Upgrade
Tenant-Registry / Identität / Seatszentral (Better Auth, EU-D1)zentral (gewollt)ordnet Identität→Tenant zu; Enforcement-Quelle
Cross-Tenant-Reporting (Audit-Aggregat, Betriebs-Analytics)Fan-out über die Tenant-DOs (Sink = Upgrade C→A)abgeleitetkeine eigene geteilte Live-DB für Mandantendaten

4. Audit-Log: Compliance-Folge der „Option A"-Entscheidung (proaktiv geflaggt)

Spannungsfeld. Die bisherige OP-AUDIT-1-Architektur legt das Audit-Log bewusst in eine separate D1 (taktano-audit, heute live, Dual-Write Blob+D1). Das gab zwei Eigenschaften: (a) der Live-Writer kann das Log physisch nicht ändern (Gewaltenteilung → „nicht reverse-engineerbar") und (b) mühelose Cross-Tenant-Compliance-Sicht. Audit in die per-Tenant-DO-SQLite zu verlegen, gibt volle Isolation + PITR, verliert aber zunächst (a) und (b).

E-TENANT-1 ✅ ENTSCHIEDEN 2026-06-27: (C) Zentraler Hash-Anker (kein voller Sink)

Erwogen wurden drei Stufen: (A) voller zentraler Audit-Sink (alle Events PII-gescrubbt in D1) · (B) kein Zentrales (rein per-Tenant + Fan-out) · (C) nur ein zentraler Hash-Anker je Tenant. Gewählt: (C) — ehrt „komplett getrennt" am stärksten und schließt trotzdem die Manipulationssicherheits- Lücke, ohne zentrale Events/PII.

Mechanik (C):

  • Primär (je Tenant): Audit als append-only Tabelle in der Tenant-DO-SQLite — im Code kein UPDATE/DELETE (nur Retention-Purge-Job), plus Hash-Chain: eintrag_n.hash = H(payload_n ‖ eintrag_{n-1}.hash). Jede nachträgliche Einfügung/Löschung/Änderung bricht die nachgerechnete Kette.
  • Zentral (off-box, minimal): periodisch/forward-only wird nur der Chain-Kopf je Tenant verankert — { tenant_id, seq, head_hash, ts } (optional signiert / mit externem Zeitstempel) in der umgewidmeten taktano-audit-D1. Keine Events, keine PII zentral. Verifikation = Tenant-Log neu durchhashen und gegen die verankerten Köpfe prüfen → off-box-Manipulationsnachweis ohne zentrale Datenhaltung.
  • Cross-Tenant-Reporting bleibt Fan-out über die Tenant-DOs (bewusst akzeptiert). Upgrade-Pfad C→A: falls mandantenübergreifendes Audit-Reporting später ein echter Betreiber-Bedarf wird, kann der Anker zum vollen Sink erweitert werden — der Hash-Anker ist die saubere Zwischenstufe.

Folge für den bestehenden Code: Der heute live geschaltete D1-Dual-Write (#186) schreibt aktuell volle Events nach D1 — das ist Interim und nutzbar. End-Zustand (C) reduziert die zentrale Schreibmenge auf den Hash-Kopf (Events bleiben nur per-Tenant). taktano-audit wird also vom „Audit-Primärspeicher" zum zentralen Tamper-Anker umgewidmet (Tabelle audit_anchors neben/statt audit_entries). Migration = OP-AUDIT-1/OP-TENANT-Folge-Slice (T-3/T-4), kein Sofort-Rip-out.

5. Enforcement — physische Trennung nützt nur mit vertrauenswürdigem Routing

  • Tenant-Id aus der verifizierten Identität ableiten (Better-Auth-Organizations-Claim / Access-JWT) → daraus den DO-Roomnamen bestimmen. Nie client-gesetzt (Muster wie heute beim kostenAdmin-Gate: server-seitig aus der vom Worker injizierten, verifizierten Identität).
  • Heute: fixer Room leitstand-vest. OP-TENANT macht den Room identitätsabgeleitet.
  • Weitere Leck-Flächen mit tenant_id scopen: Logs (trace_id+tenant_id, OP-LOG-1), Backups (Snapshots/PITR je Tenant, OP-BACKUP-1). In-Memory unkritisch (ein DO = ein Tenant).
  • R2 (E-TENANT-2=3): ausschließlich über tenantBucket()/tenantKey(tenantId, …) (Prefix tenant/<id>/…) zugreifen — nie rohe Keys; unrätbare Foto-Ids + signierte URLs zur Auslieferung; tenant_id immer aus der verifizierten Identität. Die Kapselung macht den späteren Bucket-je-Tenant-Split (Opt-in für High-Compliance-Tenants) zu einer mechanischen Änderung an einer Stelle.

6. Slices (OP-TENANT)

  • T-1 · Tenant-Auflösung im Worker. Identität→tenant_id→DO-Room (statt fix leitstand-vest); Registry-Lookup gegen Better Auth. Kein Datenschema-Change.
  • T-2 · DO-SQLite je Tenant (= OP-DATA-1 Slice D5-3): Live-State + Historie von Blob → DO-SQLite (Drizzle), Provisionierung = neuer Room + baueState(gewählte Vorlage) (S-SEED-4-Vorlage).
  • T-3 · Audit → DO-SQLite je Tenant + Hash-Chain; T-4 · zentraler Hash-Anker (E-TENANT-1=C): forward-only Push des Chain-Kopfs {tenant_id, seq, head_hash, ts} in taktano-audit (umgewidmet zu audit_anchors, keine Events/PII) + Verifikations-Pfad (Tenant-Log neu durchhashen vs. Anker).
  • T-5 · Cross-Tenant-Reporting per Fan-out (Sink = Upgrade C→A nur bei echtem Bedarf) + Betreiber-Sicht.
  • T-6 · Isolations-Tests (CI: kein Cross-Tenant-Read möglich) + Audit-Tamper-Evidenz-Test.
  • T-7 · R2-Kapselung (E-TENANT-2=3): tenantBucket()/tenantKey() + signierte URLs + unrätbare Ids; bestehende tenant/<id>/-Prefix-Konvention durchsetzen. Bucket-je-Tenant = späteres Opt-in.

7. Sign-offs

  • E-TENANT-1entschieden 06-27 = (C) zentraler Hash-Anker (kein voller Sink) — §4.
  • E-TENANT-2entschieden 06-27 = (3) ein Bucket + Prefix je Tenant, gekapselt (Bucket-je-Tenant = Opt-in-Upgrade) — Begründung: relationale Isolation liegt schon in der DO-SQLite; Prefix skaliert auf tausende kleine Mandanten (R2-Bucket-Limit); §3/§5.
  • E-TENANT-3entschieden 06-27 = zurückgestellt („später, auf Anforderung"): per-Tenant-Ver- schlüsselung at-rest (Envelope, eigene Keys) wird nicht vorab gebaut — erst wenn ein konkreter Compliance-/Vertragsbedarf sie fordert. Bis dahin gilt die Plattform-Verschlüsselung at-rest (DO/D1/R2 in EU) als ausreichend. Re-evaluieren bei Enterprise-/regulatorischen Anforderungen (OP-COMPLIANCE-1).

➡ Alle E-TENANT-Sign-offs abgeschlossen (E-DATA-1=B · E-TENANT-1=C · E-TENANT-2=3 · E-TENANT-3=zurück- gestellt). Offen bleiben nur die OP-BACKUP-1-Detailfragen (E-BACKUP-1/2/3, docs/architektur/Backup-Restore.md).

8. Bezüge

OP-DATA-1 (docs/architektur/Persistenz.md — E-DATA-1 = B/DO-SQLite hiermit entschieden) · OP-AUDIT-1 (docs/architektur/Audit-Log.md — Speicher-Entscheidung wird per §4 verfeinert) · OP-BACKUP-1 (docs/architektur/Backup-Restore.md — PITR je Tenant) · OP-AUTH-1/2 (docs/architektur/Billing-Zugang.md — Better Auth Organizations = Registry/Enforcement) · OP-SEED-1/OP-TENANT-Vorlage (docs/architektur/Seed-Abloese-Plan.mdbaueState + benannte Vorlage = Tenant-Provisionierung) · OP-COMPLIANCE-1 (DSGVO/EU-Residenz).


↩ Zurück zur Doku-Landkarte · Lesepfade · Register (alle OPs)