CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
Fable-Review auf Code-Dopplung/interne Links (siehe Analyse). P0-Funde: - Dangling Prosa-Verweise auf die per PR #367 geloeschten *-kanban/-Ordner in SKALIERUNGSKONZEPT.md, STORAGE-KONZEPT.md, MIGRATIONSKONZEPT.md, SICHERHEITSKONZEPT.md (7x "siehe X-kanban/README.md"), PRODUKTARCHITEKTUR.md um Hinweis ergaenzt, dass Boards jetzt in main-Historie/Gitea-PRs liegen. - dms/internal/searchsync/searchsync_test.go:19: falscher Pfad im Kommentar ("internal/bulkedit"/"internal/jobqueue" statt "dms/internal/..."). - web/shl/components/ModuleNavShell.tsx: resolveHrefClientOnly war inhaltlich identisch zu resolveModuleHref in web/portal/app/ModuleGrid.tsx (zwei unabhaengig gepflegte Kopien derselben Hostname-Ersetzung). Als resolveModuleHref aus shl exportiert, ModuleGrid.tsx nutzt jetzt die gemeinsame Funktion, Test-Import entsprechend umgestellt. - web/portal/lib/modules.test.ts: KNOWN_REAL_PERMISSIONS-Whitelist hatte kennzahlen.read (migrations/0019_kennzahlen_policy_rule.up.sql, real existierend seit ANA-02/PR #362) nie nachgetragen, Test lief seitdem unbemerkt im Fehler. Ergaenzt. go test/vitest: web/portal (15/15) und web/shl (61/61) gruen auf Testhost 131. Verbleibender P0-Punkt (14 Web-Apps ohne ModuleNavShell) braucht Produktentscheidung -- mehrere Registry-Keys (ai/mail/workflow/connect) sind bereits durch eine jeweils ANDERE App belegt als die ohne Navigation (z.B. mail-search hat "mail", mail-postfach nicht), daher keine mechanische Zuordnung moeglich ohne zu raten. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
166 lines
15 KiB
Markdown
166 lines
15 KiB
Markdown
# NEXARCH 2.0 – Storage-Konzept
|
||
|
||
Stand: 2026-08-26, Abschnitt 9 aktualisiert + Abschnitt 10 neu (Arbeitsverzeichnisse) 2026-08-28. Grundlage: `dms-kanban/tickets/FDN-03.md`, `mail-kanban/tickets/ARC-01.md`, `archive-kanban/tickets/RET-*.md`, `archive-kanban/tickets/BAK-*.md`, `SKALIERUNGSKONZEPT.md` (Kanban-Boards inzwischen aus dem Repo entfernt, Ticket-Historie liegt in der Gitea-PR-Historie).
|
||
|
||
## 1. Grundprinzip
|
||
|
||
Strikte Trennung, bereits in FDN-03 (DMS) und ARC-01 (Mail) unabhängig, aber konsistent so entschieden:
|
||
|
||
- **PostgreSQL**: ausschließlich Metadaten (wer, was, wann, Tenant, Beziehungen, Status, Fristen).
|
||
- **Objekt-Storage**: ausschließlich Binärinhalte (Dokumente, E-Mails, Anhänge, Thumbnails, Backups).
|
||
|
||
Kein Binärinhalt landet je in einer Datenbankspalte (kein `bytea` für Dateien) — das ist Bedingung für alles Weitere in diesem Dokument (Skalierung, Backup-Strategie, S3-Austauschbarkeit).
|
||
|
||
## 2. Backend-Optionen je Betriebsstufe
|
||
|
||
Deckt sich mit `SKALIERUNGSKONZEPT.md` Abschnitt 3:
|
||
|
||
| Betriebsstufe | Backend | Treiber |
|
||
|---|---|---|
|
||
| Stufe 1 (Einzel-LXC) | lokales Dateisystem oder ZFS | lokaler Treiber (FDN-03/ARC-01 "ein Interface, zwei austauschbare Treiber") |
|
||
| Stufe 2 (verteilt) | S3-kompatibel: MinIO (selbst betrieben) oder Ceph (RADOS Gateway) | S3-Treiber, identische API wie Stufe 3 |
|
||
| Stufe 3 (Private Cloud / SaaS) | S3-kompatibel: MinIO/Ceph selbst betrieben oder Managed-Object-Storage beim Provider | S3-Treiber, kein Unterschied im Code |
|
||
|
||
Der Übergang Stufe 1 → 2/3 ist ein reiner Infrastruktur-Schritt (Treiber-Konfiguration ändern, nicht Code), das war der explizite Zweck der Abstraktion. Datenübernahme beim Wechsel: einmaliger Kopiervorgang (z.B. `mc mirror` bei MinIO) mit anschließendem Prüfsummen-Abgleich (Abschnitt 6) — kein Big-Bang-Risiko wie bei der Systemmigration aus `MIGRATIONSKONZEPT.md`, da es dieselbe NEXARCH-Instanz ist, nur das Backend wechselt.
|
||
|
||
**Bewusst nicht vorgesehen:** NFS als produktives Ziel für Stufe 2/3 — das S3-Protokoll ist die gemeinsame Klammer, NFS wäre ein dritter, inkompatibler Zugriffspfad. NFS bleibt als Option für den lokalen Stufe-1-Treiber denkbar (Mount-Punkt statt echtes lokales Dateisystem), ändert aber nichts am Treiber-Interface.
|
||
|
||
## 3. Pfad-/Namenskonvention (modulübergreifend vereinheitlicht)
|
||
|
||
FDN-03 und ARC-01 legen je ihre eigene Pfadkonvention fest (Akzeptanzkriterium in beiden Tickets), bisher ohne geteilten Rahmen. Damit Backup (Archive `BAK-02`), Export (Archive `DMS2-01` im Connect-Board, DMS `DOC-08`) und Betrieb nicht drei verschiedene Konventionen lernen müssen, gilt projektweit ein gemeinsames Schema — und, analog zur Datenbank (Modell C, eigene Datenbank pro Mandant), jetzt auch eine physische Trennung im Objekt-Storage: **jeder Mandant erhält einen eigenen Bucket bzw. Pfad-Root**, statt eines gemeinsamen Buckets mit `tenant_id`-Pfadpräfix.
|
||
|
||
Begründung: physische Trennung schließt bei Objekt-Storage dieselbe Fehlerklasse aus wie bei der Datenbank — ein Codefehler ohne Tenant-Kontext kann kein fremdes Bucket erreichen, nicht nur einen falschen Pfad innerhalb eines gemeinsamen Buckets. Ein vergessener Pfadfilter/Präfix-Check in einem gemeinsamen Bucket führt zum Cross-Tenant-Leck; ein fehlendes Tenant-Bucket-Mapping führt dagegen nur dazu, dass gar kein Bucket referenzierbar ist.
|
||
|
||
Die Provisionierung des Tenant-Buckets ist Teil desselben Provisioning-Schritts wie die Anlage der Tenant-Datenbank in Core `TEN-01` — ein neuer Mandant erhält Datenbank und Bucket in einem automatisierten Schritt.
|
||
|
||
Innerhalb des Tenant-Buckets entfällt das führende `tenant_id`-Segment, das Schema wird zu:
|
||
|
||
```
|
||
{modul}/{objekttyp}/{objekt_id}/{revision-oder-teil}
|
||
```
|
||
|
||
Beispiele (innerhalb des Buckets von Mandant `t-4471`):
|
||
- `dms/documents/doc-9021/rev-3.bin`
|
||
- `mail/messages/msg-88213/part-2-attachment.bin`
|
||
- `archive/backups/2026-08-26/snapshot.tar.zst`
|
||
|
||
Innerhalb des Pfads bleibt jedes Modul für sein `{objekttyp}`-Vokabular selbst zuständig (DMS: `documents`, `thumbnails`; Mail: `messages`, `attachments`) — nur die äußeren zwei Ebenen (`modul`/Rest) sind projektweit fix, damit ein Werkzeug (Backup, Export, Kapazitätsauswertung) modulübergreifend funktioniert, ohne pro Modul eine Sonderbehandlung zu brauchen. Die Mandantentrennung selbst (Bucket-Auswahl) ist damit kein Bestandteil des Pfadschemas mehr, sondern eine reine Provisioning-/Zugriffsschicht-Frage (siehe Ticket ARC-06, Mandantentrennung im Objekt-Storage; Kanban-Board inzwischen aus dem Repo entfernt, Historie in Gitea).
|
||
|
||
## 4. Verarbeitungsreihenfolge (Lebenszyklus einer Datei)
|
||
|
||
Zusammengeführt aus den bereits getroffenen Einzelentscheidungen (FDN-01/DOC-01/DOC-02/FDN-09 im DMS-Board, ARC-01/ARC-02/ARC-03 im Mail-Board):
|
||
|
||
```
|
||
1. Empfang (Klartext, im Speicher/temporär)
|
||
2. SHA-256-Hash auf Klartext
|
||
3. Duplikatsprüfung (DOC-02 / ARC-03) — Gate: nur neue oder bewusste Duplikate gehen weiter
|
||
4. Verschlüsselung at rest (FDN-09 / ARC-02, AES-256-GCM, Envelope Encryption)
|
||
5. Ablage im Objekt-Storage nach Pfadkonvention (Abschnitt 3)
|
||
6. Prüfsummenvergleich nach dem Schreiben (Akzeptanzkriterium in FDN-03/ARC-01)
|
||
7. Registrierung bei Archive-Retention-Engine (RET-05 Modul-Adapter) — Aufbewahrungsfrist beginnt zu laufen
|
||
8. OCR (SRC-05 / SRC-07) und Indexierung (SRC-02 / SRC-01+SRC-02) — läuft NACH Schritt 3, nicht parallel dazu
|
||
```
|
||
|
||
Löschung läuft ausschließlich über die Archive-Engine (Retention/Legal-Hold-geprüft), niemals direkt durch DMS oder Mail — das ist bereits Konsequenz der Archive-Konsolidierung, hier nur noch einmal aus Storage-Sicht festgehalten: kein Modul löscht Objekte eigenständig aus dem Storage.
|
||
|
||
## 5. Zugriffsmodell
|
||
|
||
- **Kein direkter Client-Zugriff auf den Objekt-Storage.** Up-/Downloads laufen über zeitlich begrenzte signierte URLs (FDN-03 Akzeptanzkriterium 2, Standard-Pattern für S3-kompatible Backends), die das Backend ausstellt — Storage-Zugangsdaten selbst erreichen nie den Browser.
|
||
- Presigned-URL-Gültigkeit kurz und konfigurierbar halten (Minuten, nicht Stunden) — lange gültige URLs sind ein unnötig großes Zeitfenster für Linkweitergabe.
|
||
- Storage-Zugangsdaten (S3-Credentials) sind Secrets wie jedes andere — ausschließlich über Umgebungsvariablen, nie im Code (bereits projektweite Regel aus jedem Ticket-Abschluss-Abschnitt).
|
||
|
||
## 6. Konsistenzprüfung
|
||
|
||
Jeder Schreibvorgang wird durch Prüfsummenvergleich verifiziert (bestehende Akzeptanzkriterien in FDN-03 und ARC-01). Für den Bestand insgesamt zusätzlich:
|
||
|
||
- **Reconciliation** (Archive `BAK-05`): periodischer Abgleich zwischen den in Postgres referenzierten Objekten und dem tatsächlichen Storage-Inhalt — erkennt verwaiste Metadaten-Einträge (Objekt fehlt im Storage) und verwaiste Storage-Objekte (kein Metadaten-Eintrag mehr, z.B. nach einem Fehler bei Schritt 5/6 aus Abschnitt 4).
|
||
- Reale Lehre aus archivmail: Reconciliation-Reports existierten dort bereits als Soll/Ist-Abgleich (siehe `datenmodell-altsysteme.md`) — Muster wird über Archive `BAK-05` fortgeführt, nicht neu erfunden.
|
||
- **Integritätsprüfung per Hash** (Archive `BAK-08`): periodischer Scrub-Job, der Stichproben liest, SHA-256 neu berechnet und gegen `file_revisions.checksum_sha256` vergleicht — erkennt stille Inhaltsveränderung/Bit-Rot bei Objekten, die noch existieren.
|
||
|
||
### 6.1 Grenzen der Prüfung bei extern eingebundenem Kunden-Storage
|
||
|
||
Bindet ein Kunde einen eigenen S3-Bucket ein, überwachen wir weder dessen Hardware noch dessen Betreiber. Was NEXARCH technisch leisten kann, hängt davon ab, was wir dürfen:
|
||
|
||
- **Kein Lesezugriff, nur Schreiben:** keine Verifikation möglich. Wir können höchstens den Upload-Beleg und den Hash zum Upload-Zeitpunkt festhalten — das ist kein Monitoring, sondern ein Nachweis für später.
|
||
- **Lese-/API-Zugriff vorhanden:** Existenz- (`BAK-05`) und Inhaltsprüfung (`BAK-08`) sind möglich, aber periodisch/stichprobenhaft, nicht in Echtzeit, und nur für das, was wir selbst wieder lesen können.
|
||
|
||
Was diese beiden Prüfungen NICHT leisten:
|
||
|
||
- Sie erkennen nicht lückenlos, ob ein Objekt unbefugt gelöscht oder durch ein neues ersetzt wurde, wenn uns keine Inventar-/Versionsdaten vom Storage vorliegen (ETag/LastModified/VersionId — sofern verfügbar, nicht überall ein einfacher Hash, besonders bei Multipart-Uploads oder providerseitiger Verschlüsselung, daher nicht als alleinige Wahrheitsquelle nehmen).
|
||
- Sie ersetzen keine storage-seitige WORM-/Versionierungsstrategie und keine Zugriffs-/Audit-Logs des Storage-Providers — nur Object Lock, Versioning, Retention Policies und providerseitige Audit-Logs verhindern Veränderungen oder machen sie lückenlos nachweisbar, ein nachträglicher Scrub kann sie nur *bemerken*, nicht verhindern.
|
||
|
||
### 6.2 Drei Betriebsmodi für Objekt-Storage
|
||
|
||
1. **NEXARCH-managed Storage** — wir kontrollieren Policies, Versioning, Object Lock, Backup vollständig.
|
||
2. **Customer-managed, compliant Storage** — Kunde stellt den S3-Bucket, aber vertraglich/beim Onboarding verbindlich verlangt: Versioning aktiviert, Object Lock/Retention bei revisionssicherer Nutzung, Audit-Logs aktiviert, eingeschränkte Löschrechte, idealerweise eigener Backup-/Replikationspfad.
|
||
3. **Customer-managed, non-compliant Storage** — nur für nicht revisionssichere Nutzung zulässig; muss im System klar markiert sein ("keine belastbare Unveränderbarkeitsgarantie"), damit keine falsche GoBD-Erwartung entsteht.
|
||
|
||
## 7. Backup vs. Storage-Redundanz — zwei verschiedene Schichten
|
||
|
||
Häufige Verwechslung, deshalb hier explizit getrennt:
|
||
|
||
- **Storage-Redundanz** (diese Datei, Abschnitt 2): wie das Backend selbst Datenverlust durch Hardware-Ausfall verhindert (S3-Provider-Redundanz, MinIO-Distributed-Mode, Ceph-Replikation). Schützt vor Festplatten-/Knotenausfall, NICHT vor Anwendungsfehlern oder böswilligem/versehentlichem Löschen.
|
||
- **Backup** (Archive `BAK-01`/`BAK-02`/`BAK-03`): eigenständige, deduplizierende Sicherung nach restic/Borg-Konzept, zeitversetzt, wiederherstellbar auch wenn die Primärkopie durch einen Anwendungsfehler beschädigt wurde. Das ist die Antwort auf "ein Bug löscht fälschlich Dateien" — Storage-Redundanz allein hilft dabei nicht, weil sie den fehlerhaften Löschvorgang originalgetreu mit-repliziert.
|
||
|
||
Beide Schichten sind nötig, keine ersetzt die andere.
|
||
|
||
## 8. Export & Portabilität
|
||
|
||
Aus der DMS-Produkt-DNA fortgeführt (ecoDMS-Lehre: "was die Oberfläche kann, kann auch die offene Schnittstelle", keine Herstellerbindung):
|
||
|
||
- Vollständiger Export von Dateiinhalt + Metadaten ohne NEXARCH-eigenes Werkzeug muss jederzeit möglich sein (DMS `DOC-08` Massenexport, Mail `ARC-07` Export/Portabilität, Connect `DMS2-01` offenes Austauschformat für Fremd-DMS-Interoperabilität).
|
||
- Objekt-Storage-Ablage selbst nutzt offene Formate (Original-Dateiformat + PDF/A für OCR-Ergebnisse, kein proprietäres Container-Format) — keine Vendor-Lock-Kodierung, die einen Export nachträglich erschweren würde.
|
||
|
||
## 9. Speicherverbrauch-Messung für Quota/Lizenzierung
|
||
|
||
~~Offene Lücke~~ — **geklärt (2026-08-28)**: Core `LIC-05` "Speicherverbrauch-Metrik je Tenant" setzt genau den hier vorgeschlagenen Mechanismus um — Objekt-Storage-Treiber der Module (FDN-03/ARC-01) melden bei jedem Schreib-/Löschvorgang die Objektgröße, ein leichtgewichtiger Postgres-Zähler pro Tenant wird geführt, `LIC-03` fragt ihn zur Quota-Durchsetzung ab. `LIC-05` hängt im Core-Abschluss-Gate `QA-05`.
|
||
|
||
## 10. Arbeitsverzeichnisse (Consume, Quarantäne, Export-Staging, Modell-Cache)
|
||
|
||
Ergänzung 2026-08-28, angestoßen durch den Vergleich mit paperless-ngx (`consume/`, `data/`, `media/`, `export/`, `trash/`) — mehrere Tickets definieren Funktionalität, die einen lokalen Arbeitsordner braucht, legen aber bisher nicht fest, WO dieser liegt. Papierkorb (DMS `DOC-11`/`DOC-12`, Mail `ARC-05`) braucht dagegen **keinen** eigenen Pfad — das ist ein reines DB-Status-Flag, das Objekt bleibt physisch am gewohnten Platz im Objekt-Storage, bis Archive es nach Fristablauf endgültig entfernt.
|
||
|
||
### 10.1 Consume-/Hot-Folder (DMS `INT-06`/`INT-08`, Mail `IMP-05`)
|
||
|
||
Anders als der reguläre Objekt-Storage-Zugriff (Abschnitt 2, ausschließlich über signierte URLs, Abschnitt 5) braucht ein Datei-Watch zwingend ein echtes lokales Dateisystemverzeichnis — S3-kompatible Backends unterstützen kein Datei-Watch. Konsequenz: Consume-Ordner sind **kein Teil des Tenant-Objekt-Storage-Buckets**, sondern ein separater, lokaler Zwischenspeicher je Tenant auf dem Modul-Host:
|
||
|
||
```
|
||
{consume-root}/{tenant_id}/dms/inbox/
|
||
{consume-root}/{tenant_id}/mail/inbox/
|
||
```
|
||
|
||
Nach erfolgreicher Verarbeitung (Hash, Duplikatsprüfung, Verschlüsselung, Ablage im Objekt-Storage — Abschnitt 4) wird die Datei aus dem Consume-Ordner entfernt, nicht dauerhaft dort belassen. Consume-Root ist damit strukturell näher an einem Cache als an Aufbewahrung und braucht keine Backup-Einbindung (Abschnitt 7) — im Fehlerfall wird die Quelle erneut abgelegt/gescannt.
|
||
|
||
### 10.2 Quarantäne (Mail `IMP-06` Virenscan-Fund)
|
||
|
||
Bei Virenscan-Fund wird die Anhang-Datei nicht gelöscht, sondern in einen isolierten Quarantäne-Bereich verschoben (Nachvollziehbarkeit, siehe Compliance-Anforderung) — ebenfalls lokal, ebenfalls je Tenant:
|
||
|
||
```
|
||
{consume-root}/{tenant_id}/mail/quarantine/
|
||
```
|
||
|
||
Quarantäne-Objekte werden nicht automatisch gelöscht; Aufbewahrungsdauer und Zugriff (nur Admin/Security-Rolle) sind Akzeptanzkriterium von `IMP-06`, nicht Gegenstand dieses Dokuments.
|
||
|
||
### 10.3 Export-Staging (DMS `DOC-08`, Mail `ARC-07`)
|
||
|
||
Massenexport (ZIP) erzeugt eine temporäre Datei zwischen "Export angestoßen" und "Download abgeschlossen". Diese Staging-Fläche ist rein ephemer (Lebensdauer im Bereich von Minuten bis wenigen Stunden), braucht keine Tenant-Bucket-Struktur und keine Backup-Einbindung:
|
||
|
||
```
|
||
{export-staging-root}/{tenant_id}/{export_job_id}.zip
|
||
```
|
||
|
||
Aufräumung nach Ablauf einer kurzen Frist (Vorschlag: 24 Stunden) oder nach bestätigtem Download ist Akzeptanzkriterium der jeweiligen Export-Kachel.
|
||
|
||
### 10.4 Ollama-Modell-Cache (AI `ENG-02`)
|
||
|
||
Bisher nicht adressierte Lücke: `ENG-02` (Ollama-Adapter) lädt und hält LLM-Modellgewichte lokal vor — je nach Modellklasse mehrere GB bis über 40 GB (siehe `PROXMOX-DEPLOYMENT.md` Abschnitt 6a, dort nur RAM-Sizing, keine Disk-Planung). Anders als Consume/Quarantäne/Export ist das **kein Zwischenspeicher, sondern persistenter, potenziell großer Datenbestand**, der eine eigene Kapazitätsplanung braucht, getrennt vom restlichen Objekt-Storage:
|
||
|
||
```
|
||
{ollama-root}/models/
|
||
```
|
||
|
||
Nicht tenant-partitioniert (Modelle werden über Tenants hinweg geteilt, sofern keine Tenant-spezifische Modellwahl vorliegt — siehe AI `ENG-04`). Bei Stufe-1-Einzel-LXC (siehe `PROXMOX-DEPLOYMENT.md` Abschnitt 2) sollte dieser Pfad auf ein eigenes Volume/Dataset gelegt werden, damit ein wachsender Modellbestand nicht den restlichen Systemspeicher verdrängt — gleiches Isolationsprinzip wie bei Tenant-Buckets (Abschnitt 3), nur nicht tenant-, sondern funktionsbezogen.
|
||
|
||
### 10.5 Warum eigene Datasets/Mountpoints sinnvoll sind
|
||
|
||
Alle vier Arbeitsverzeichnisse (10.1–10.4) profitieren aus demselben Grund von eigenen ZFS-Datasets/Mountpoints statt Unterordnern im Root-Dateisystem: unabhängige Quotas (ein vollaufender Consume-Ordner darf nicht das Systemvolume fluten), unabhängige Snapshots/Löschbarkeit (siehe Erfahrung mit `archivmail`-Alt-Deinstallation, `nexarch-state.json` Entscheidungsverlauf 2026-08-28), und die Möglichkeit, unterschiedliche Backup-/Retention-Policies zuzuweisen (Consume/Export brauchen keine, Modell-Cache profitiert von keiner inkrementellen Sicherung großer Binärdateien, sondern von einfacher Neubeschaffung bei Bedarf).
|