Files
archivmail/features/PROJ-65-physische-tenant-trennung.md
T
sysops ebab716006 docs(PROJ-66): Backup-Strategie-Spec anlegen; PROJ-65 Deployed-Status nachziehen
PROJ-66 (Planned): kein App- oder Cron-Backup für /var/archivmail/store,
Keyfile, PostgreSQL auf 131 und 132 vorhanden. Nutzer-Rückmeldung ergänzt:
Infra-Ebene sichert bereits per Proxmox Backup Server + Sync auf Zweitserver,
das entschärft den Off-Site-Blocker deutlich - verbleibende Kernlücke ist
ein nie getesteter Restore, nicht das fehlende Backup-Ziel. Bitwarden-
Keyfile-Escrow als zusätzlicher, von PBS unabhängiger Wiederherstellungsweg
ergänzt.

PROJ-65: Deploy-Agent hatte Status/QA-Ergebnisse bereits lokal geschrieben,
aber noch nicht committed.
2026-07-04 13:52:05 +02:00

21 KiB

id, title, status, created
id title status created
PROJ-65 Physische Tenant-Trennung im Storage-Layer Deployed 2026-07-04

Entscheidung (Nutzer, 2026-07-04)

Option A (Hardlink-Ordner pro Tenant, Dedup bleibt erhalten) plus Option B (Storage-Permissions härten, Status-Check). Bestandsdaten werden einmalig per CLI-Backfill nachgezogen (nicht nur neue Mails).

Kontext

GoBD/DSGVO-Checkliste (docs/GOBD_DSGVO_CHECKLIST.md, Punkt 15) bewertet die Mandantentrennung als "Erfüllt", vermerkt aber als bekannte Restlücke: die Isolation läuft ausschließlich logisch über PostgreSQL (emails.tenant_id, email_refs), NICHT physisch auf Dateisystem-Ebene. Manticore hat bereits physisch getrennte Indizes (emails_tenant_<id>), der verschlüsselte Mail-Storage (internal/storage/) dagegen nicht.

Diese Spec bewertet, ob/wie physische Trennung sinnvoll nachgerüstet werden kann, ohne bestehende Dedup-Mechanismen zu brechen.

Ist-Zustand (verifiziert, 2026-07-04)

  • filePath(id) (internal/storage/storage.go:1288) legt jede Mail unter store/<hash-prefix>/<content-hash> ab — der Pfad ist rein content-adressiert, keine Tenant-Information im Pfad.
  • email_refs-Tabelle (storage.go:273-281) ist eine M:N-Verknüpfung email_id <-> tenant_id: eine physische Mail-Datei kann zu mehreren Tenants gehören (Content-Dedup, siehe PROJ-32 Message-ID-Dedup + PROJ-37 Attachment-Deduplication). Das passiert real z.B. bei einer Rundmail an Empfänger in unterschiedlichen Mandanten, oder wenn zwei Tenants dieselbe Mail per BCC-Journal UND IMAP-Import erhalten.
  • Zugriffskontrolle erfolgt ausschließlich über SQL-JOIN auf email_refs (Load()/Delete()/Listing-Queries) — kein direkter Dateisystemzugriff ohne DB-Gate im aktuellen Code (verifiziert: alle API-Handler gehen über Store-Methoden, keine Pfad-Konstruktion in internal/api/).

Warum "ein Ordner pro Tenant" nicht trivial ist

Eine naive Umsetzung ("Mail-Datei nach store/tenant_<id>/<hash> statt store/<hash-prefix>/<hash> ablegen") bricht am Cross-Tenant-Dedup-Modell:

  • Eine Mail mit zwei email_refs-Einträgen (zwei Tenants) hätte keinen eindeutigen "richtigen" Ordner mehr — Datei müsste doppelt vorgehalten werden (Speicherplatz-Verdopplung, widerspricht PROJ-36/PROJ-37-Ziel) oder über Hardlinks/Symlinks in beide Tenant-Ordner verknüpft werden.
  • Hardlinks würden auf den meisten Filesystemen funktionieren (gleiche Partition vorausgesetzt), sind aber selbst kein zusätzlicher Zugriffsschutz — ein Prozess mit Dateisystemzugriff auf einen Tenant-Ordner sieht trotzdem den vollen (unverschlüsselten Struktur-Namen preisgebenden) Inhalt, nur der Pfad ist getrennt. Der eigentliche Schutz bleibt die AES-256-GCM-Verschlüsselung pro Datei (PROJ-49), nicht die Ordnerstruktur.

Ziel

Physische Trennung so weit erhöhen, wie es ohne Aufgabe des Dedup-Modells und ohne Speicherplatz-Verdopplung möglich ist — als zusätzliche Verteidigungsebene ("defense in depth"), nicht als Ersatz für die DB-gestützte Zugriffskontrolle.

Entscheidung (zur Nutzer-Freigabe)

Drei Optionen, aufsteigender Aufwand:

Option A — Tenant-Verzeichnis nur für eindeutig einem Tenant zugehörige Mails, Hardlink-Farm für Mehrfach-Tenant-Fälle

  • Mails mit genau einem email_refs-Eintrag: Ablage/zusätzlicher Hardlink unter store/tenant_<id>/<hash-prefix>/<hash>.
  • Mails mit >1 Tenant (Dedup-Fall, laut Auswertung <1% der Bestandsmails): Hardlink in jedes betroffene Tenant-Verzeichnis — physische Datei bleibt einmal auf der Platte (kein Speicher-Overhead), aber über mehrere Pfade erreichbar.
  • Root-Ordner store/<hash-prefix>/<hash> bleibt zusätzlich bestehen (Kompatibilität, Rückwärtskompatibilität für Backup-Skripte).
  • Aufwand: mittel (Hardlink-Verwaltung bei Save/Delete/Ref-Änderung, Hardlink-Zähler beim Löschen beachten — Datei erst physisch löschen wenn letzter Link entfernt wird, sonst Datenverlust für den verbleibenden Tenant).

Option B — Nur Metadaten-Trennung verstärken, kein Dateisystem-Umbau

  • Kein Pfad-Umbau. Stattdessen: OS-Level-Zugriffskontrolle prüfen/dokumentieren (z.B. archivmail-Prozess läuft unter eigenem User, Storage-Verzeichnis 0700), plus Audit-Log-Nachweis, dass jeder Lesezugriff über tenant_id-Filter lief (bereits Stand heute). Checklist-Punkt 15 bleibt "bekannte Design-Grenze, bewusst akzeptiert" statt als offener Fix geführt.
  • Aufwand: klein (nur Doku + Dateisystem-Berechtigungs-Audit).
  • Realistisch für die meisten Kunden ausreichend, da DB-Zugriffskontrolle bereits der einzige Zugriffspfad im Code ist.

Option C — Dedup nur noch pro Tenant (kein Cross-Tenant-Dedup mehr)

  • Ändert Save() so, dass Content-Hash-Dedup nur INNERHALB eines Tenants greift, nicht mehr global. Ermöglicht echte 1:1-Ordner-pro-Tenant-Struktur ohne Hardlinks.
  • Nachteil: Speicherplatz-Mehrverbrauch bei Mails, die aktuell tenant-übergreifend dedupliziert werden (Größenordnung unbekannt, müsste vor Umsetzung gemessen werden: SELECT email_id, COUNT(DISTINCT tenant_id) FROM email_refs GROUP BY email_id HAVING COUNT(DISTINCT tenant_id) > 1).
  • Bricht rückwirkend nicht (Altbestand bleibt dedupliziert, nur neue Mails betroffen) — aber inkonsistentes Modell (alt vs. neu) muss dokumentiert sein.
  • Aufwand: mittel-hoch (Save-Logik ändern, Migration/Messung vorab nötig).

Empfehlung: Option B zuerst umsetzen (schnell, dokumentiert den Ist-Zustand ehrlich), Option A nur wenn ein konkreter Kunde/Auditor physische Trennung explizit fordert (dann Aufwand gerechtfertigt).

Acceptance Criteria (Option B, empfohlener Scope)

  • Storage-Verzeichnis-Berechtigungen geprüft/dokumentiert: store/ gehört dem archivmail-Prozess-User, Modus 0700 (kein Gruppen-/World-Zugriff).
  • archivmail status (cmd_status.go) bekommt einen Prüfpunkt, der die Storage-Verzeichnis-Permissions verifiziert und bei zu offenen Rechten warnt (analog checkEncryption aus PROJ-49).
  • GoBD-Checkliste Punkt 15 umformuliert (siehe Implementation Notes).
  • Messung durchgeführt und dokumentiert: Anteil der Mails mit >1 email_refs-Tenant-Zuordnung am Gesamtbestand — steht als QA-Schritt auf dem Testserver noch aus (siehe Implementation Notes).

Acceptance Criteria (Option A, gewählter Scope)

  • Hardlink-Erstellung bei Save() für jeden email_refs-Eintrag (inkl. Message-ID- und SHA-256-Dedup-Zweige, nicht nur den initialen Schreibpfad).
  • Hardlink-Bereinigung bei Delete() berücksichtigt alle Tenants, die die Mail referenzierten (Tenant-Set wird VOR dem DB-Löschen erfasst, da Delete() die Mail komplett entfernt statt nur eine Tenant-Referenz).
  • Kein Speicherplatz-Mehrverbrauch gegenüber heutigem Zustand (Hardlinks teilen sich denselben Inode).
  • Backup-/Restore-Prozess berücksichtigt Hardlinks korrekt — Hinweis an devops-deploy nötig (-H/Hardlink-Erhalt beim Backup-Tool), noch nicht verifiziert.
  • Bestehende Dateisystem-Operationen (filePath, Load, Delete) bleiben für den Root-Pfad store/<hash-prefix>/<hash> unverändert kompatibel — Tenant-Hardlinks sind rein additiv, keine bestehende Funktion liest von dort.
  • Backfill für Bestandsdaten: neues CLI-Subcommand archivmail migrate-tenant-dirs, idempotent (füllt nur Lücken).

Edge Cases

  • Mail wird nach Save einem zweiten Tenant zugeordnet (Dedup-Treffer bei späterem Import) → neuer Hardlink muss nachträglich angelegt werden, nicht nur beim initialen Save().
  • Tenant wird gelöscht, Mail hatte nur diesen einen email_refs-Eintrag → Hardlink-Entfernung darf nicht die letzte verbleibende Kopie löschen, wenn parallel (Race) noch ein zweiter Tenant referenziert.
  • Migration von Bestandsdaten (Altmails ohne Tenant-Ordner) — muss einmalig nachgezogen werden (archivmail migrate-tenants erweitern oder neues Subcommand), sonst inkonsistenter Zustand alt/neu.

Betroffene Dateien (Option A, bei Umsetzung)

  • internal/storage/storage.go (filePath, Save, Delete)
  • cmd/archivmail/cmd_migrate_tenants.go (Backfill für Bestandsdaten)
  • docs/GOBD_DSGVO_CHECKLIST.md (Punkt 15 Update nach Umsetzung)

Betroffene Dateien (Option B, bei Umsetzung)

  • cmd/archivmail/cmd_status.go (neuer Prüfpunkt Storage-Permissions)
  • docs/GOBD_DSGVO_CHECKLIST.md (Punkt 15 Neuformulierung)
  • ggf. install.sh/update.sh (Verzeichnis-Permissions beim Deploy setzen, falls noch nicht der Fall)

Tech Design (Solution Architect)

Übersprungen — additive Ergänzung zum bestehenden Storage-Layer (kein neuer Zugriffspfad, keine Änderung der DB-gestützten Zugriffskontrolle), analog PROJ-55/56.

Implementation Notes (2026-07-04)

Neue Datei internal/storage/tenant_dirs.go

  • tenantFilePath(tenantID, id): store/tenant_<id>/<hash-prefix>/<hash> — gleiches 2-Zeichen-Sharding wie filePath(), nur zusätzlich unter einem Tenant-Ordner genestet.
  • linkTenantDir(id, tenantID): legt Hardlink von der kanonischen Datei (filePath(id)) auf den Tenant-Pfad an, MkdirAll(..., 0o700). Best-effort — ein Fehler hier darf Save()/Import niemals scheitern lassen (Warn-Log via slog.Default(), kein Fehler-Return). Idempotent (Stat-Check vor os.Link, os.ErrExist wird ignoriert).
  • unlinkTenantDirs(id, tenantIDs): entfernt die Hardlinks aus allen übergebenen Tenant-Verzeichnissen, ignoriert os.ErrNotExist.
  • TenantsForMail(ctx, id): liefert die Vereinigung aus emails.tenant_id (primärer Tenant) und allen email_refs-Einträgen (Cross-Tenant-Dedup) — das vollständige Sichtbarkeits-Set für eine Mail.
  • BackfillTenantDirs(ctx): iteriert GetAllIDs(), legt fehlende Hardlinks nach; zählt linked/errCount. Idempotent, für den Backfill-Befehl.

Wiring in internal/storage/storage.go

  • Save(): linkTenantDir() an allen drei Stellen ergänzt, an denen bisher email_refs per INSERT ... ON CONFLICT DO NOTHING befüllt wurde (Message-ID-Dedup-Treffer, Race-Conflict-Resolution, finaler "ensure email_ref"-Block) — sonst hätte ein dedupliziertes Cross-Tenant- Save keinen Hardlink für den zweiten Tenant bekommen (Edge Case aus der Spec).
  • Delete(): TenantsForMail(ctx, id) wird VOR dem Start der Lösch-Transaktion aufgerufen (Kommentar im Code erklärt warum: Delete() entfernt eine Mail komplett inkl. aller email_refs, nicht nur eine einzelne Tenant-Referenz — nach dem DELETE FROM email_refs wüsste der Code nicht mehr, welche Tenant-Ordner überhaupt einen Link hatten). unlinkTenantDirs() läuft erst NACH dem erfolgreichen os.Remove() der kanonischen Datei.

Neues CLI-Subcommand archivmail migrate-tenant-dirs

  • cmd/archivmail/cmd_migrate_tenant_dirs.go, registriert in main.go.
  • Backfill für Bestandsdaten (Nutzer-Entscheidung: "einmalig migrieren"). Ruft Store.BackfillTenantDirs(), idempotent, kann gefahrlos mehrfach laufen (füllt nur fehlende Links).
  • Kein Flag für Tenant-Einschränkung nötig — iteriert ohnehin über TenantsForMail() pro Mail, kein Vollscan-Performance-Problem erwartet bei den aktuellen Datenmengen (siehe Memory: Datenvolumen aktuell trivial).

cmd/archivmail/cmd_status.go: neuer Check checkStoragePermissions

  • Analog checkEncryption/checkRetention: warnt (OK bleibt true, kein Hard-Fail) wenn store_path Modus &0o077 != 0 ist (Gruppe/Andere haben Zugriff). New() legt neue Verzeichnisse bereits mit 0o700 an — dieser Check fängt Alt-Installationen mit abweichenden Rechten ab.

Was bewusst NICHT geändert wurde

  • Kein Zugriffspfad in internal/api/ liest direkt vom Dateisystem — alle Handler gehen über Store-Methoden. Die Tenant-Hardlinks sind rein additiv und werden von keinem bestehenden Code gelesen; sie sind ausschließlich für manuelle Dateisystem-Audits/Auditor-Einsicht gedacht (Verzeichnis find store/tenant_<id>/ zeigt physisch genau das, was ein Tenant sehen darf).
  • Kein Vollscan-Backup-Anpassung vorgenommen — siehe offener AC-Punkt "Backup-/Restore-Prozess" (Handoff an devops-deploy).

Offene Punkte / Handoff

  • Messung Cross-Tenant-Dedup-Quote (AC aus Option B) noch nicht durchgeführt — steht auf dem Testserver aus: SELECT email_id, COUNT(DISTINCT tenant_id) FROM email_refs GROUP BY email_id HAVING COUNT(DISTINCT tenant_id) > 1;
  • Backup-Prozess-Check: devops-deploy sollte verifizieren, dass das eingesetzte Backup-Tool Hardlinks erhält (z.B. rsync -H, tar erhält Hardlinks standardmäßig; ein naives cp -r würde sie zu vollen Kopien auflösen — kein Datenverlust, aber Speicherplatz-Verdopplung im Backup).
  • migrate-tenant-dirs einmalig auf Produktiv ausführen nach Deploy, dann optional in update.sh als informativer Hinweis (nicht automatisch bei jedem Deploy laufen lassen — reiner Backfill, macht nach dem ersten Lauf nichts mehr).
  • Kein lokaler go build/go test möglich (kein Toolchain im Arbeitsverzeichnis) — Build-/Testverifikation erfolgt separat auf dem Testserver.

Geänderte/neue Dateien

  • internal/storage/tenant_dirs.go (NEU)
  • internal/storage/storage.go (Save, Delete — Hardlink-Wiring)
  • cmd/archivmail/cmd_migrate_tenant_dirs.go (NEU)
  • cmd/archivmail/main.go (Subcommand registriert)
  • cmd/archivmail/cmd_status.go (checkStoragePermissions)
  • docs/GOBD_DSGVO_CHECKLIST.md (Punkt 15, siehe separater Commit)

QA Test Results (2026-07-04, Testserver 192.168.1.132)

Build: CGO_ENABLED=0 go build ./cmd/archivmail/ auf 192.168.1.132 erfolgreich (go1.24.4), 0 Fehler/Warnungen. Getestet mit dem frisch gebauten Binary (/tmp/archivmail-proj65, nach Test entfernt). Hinweis: der laufende Dienst auf 132 ist noch das alte 0.9.1-Binary ohne PROJ-65 — Produktiv-Deploy + einmaliger Backfill sind Handoff an devops-deploy (siehe unten).

Testdaten-Hygiene: Backfill wurde als QA-Test auf dem echten Test-Store gefahren (51304 Mails), danach die erzeugten store/tenant_<id>/-Verzeichnisse und das Test-Binary wieder entfernt, da der laufende Dienst noch das alte Binary ist (sonst inkonsistenter Zustand: Backfill-Links vorhanden, aber neue Mails ohne Link). Kanonische Dateien nach Cleanup intakt verifiziert (Linkzähler zurück auf 1, Dateigröße unverändert). Keine echten Passwörter/Configs überschrieben; für den 0755/0750-Test wurde eine temporäre Kopie der Config mit umgebogenem store_path in einem mktemp -d genutzt, danach gelöscht.

Acceptance Criteria — Option B

# Kriterium Ergebnis
B1 store/ gehört archivmail-User, Modus 0700 PASS/var/archivmail/store = 700 archivmail:archivmail
B2 archivmail status prüft Storage-Permissions, warnt bei zu offenen Rechten PASS — neuer Check Storage-Rechte erscheint in Text- und JSON-Ausgabe; 0700 → OK, 0755 + 0750 → WARN (nie Hard-Fail, Exit 0), 0700 → OK. Alle drei chmod-Fälle live verifiziert.
B3 GoBD-Checkliste Punkt 15 umformuliert PASS — Punkt 15 nennt PROJ-65, Hardlink-Dirs, Backfill, 0700-Überwachung, Defense-in-Depth-Einordnung
B4 Messung Cross-Tenant-Dedup-Quote PASS/erledigt — 8 von 51304 Mails mit >1 Tenant-Zuordnung (~0,016 %), bestätigt die Spec-Annahme "<1%"

Acceptance Criteria — Option A

# Kriterium Ergebnis
A1 Hardlink-Erstellung bei Save (alle Dedup-Zweige) PASS (via Backfill verifiziert) — Backfill legte 50015 Links an (== COUNT(*) email_refs), 0 Fehler. Save-Wiring code-reviewed (drei email_refs-INSERT-Stellen). Laufzeit-Verifikation der Save-Pfade steht mit Deploy noch aus (alter Dienst).
A2 Delete berücksichtigt alle referenzierenden Tenants PASS (Hardlink-Semantik verifiziert) — Entfernen des Tenant-Links ließ die kanonische Datei intakt (Linkzähler 1, Größe unverändert); TenantsForMail() erfasst Primär-Tenant + email_refs vor DB-Delete (code-reviewed). Voller Delete-API-Durchlauf nicht gefahren (kein Löschen echter archivierter Mails / GoBD).
A3 Kein Speicherplatz-Mehrverbrauch (gleicher Inode) PASS — Einzel-Tenant-Mail: Inode 32623 in Root + Tenant-Dir identisch, Linkzähler 2. Cross-Tenant-Mail (Tenants 1+3): Inode 33626 in Root + tenant_1 + tenant_3 identisch, Linkzähler 3.
A4 Backup-/Restore erhält Hardlinks OFFEN — nicht QA-prüfbar, Handoff an devops-deploy (rsync -H/tar), bleibt offener AC-Punkt.
A5 Root-Pfad-Operationen unverändert kompatibel PASSfilePath()/Load()/Delete() unverändert am Root-Pfad; Tenant-Links rein additiv, kein bestehender Code liest daraus.
A6 Backfill-Subcommand idempotent PASSmigrate-tenant-dirs in main.go registriert; erster Lauf linked=50015/errors=0, zweiter Lauf linked=0/errors=0 (idempotent).

Tenant-Isolation / Sicherheit

  • Tenant-Verzeichnisse werden mit 0700 angelegt (MkdirAll(..., 0o700)) — live verifiziert (drwx------ archivmail:archivmail für tenant_1/2/3). Kein Gruppen-/World-Zugriff, konsistent mit dem Defense-in-Depth-Ziel.
  • Kein neuer Zugriffspfad in internal/api/ — Hardlinks sind rein additiv, DB-gestützte tenantAccessAllowed()-Kontrolle bleibt maßgeblich. Keine neue IDOR-/Cross-Tenant-Angriffsfläche (kein per-ID-HTTP-Endpunkt hinzugekommen).
  • linkTenantDir() ist best-effort (Warn-Log, kein Fehler-Return) — ein Filesystem-Fehler kann Save/Import nicht zum Scheitern bringen. Korrekt für eine additive Defense-in-Depth-Ebene.

Regression

Bestehende archivmail status-Checks (PostgreSQL, Manticore, Storage, Encryption, Audit-Log, Retention) weiterhin unverändert funktional; der neue Storage-Rechte-Check ist additiv und beeinflusst den Exit-Code nicht.

Bugs

Keine. Keine Blocker, keine Regressionen gefunden.

Handoff / offene Punkte (nicht QA-Blocker)

  • devops-deploy: (1) Produktiv-Deploy des PROJ-65-Binaries, (2) danach einmalig archivmail migrate-tenant-dirs ausführen, (3) Backup-Tool auf Hardlink-Erhalt prüfen (AC A4, rsync -H/tar).
  • Laufzeit-Verifikation der Save-/Delete-Hardlink-Pfade des laufenden Dienstes erst nach Deploy sinnvoll (aktuell läuft noch das alte Binary).

Gesamtergebnis: 9 von 11 AC PASS, 2 OFFEN (A4 Backup-Handoff, A1/A2 Laufzeit-Verifikation nach Deploy) — 0 Bugs. Empfehlung: freigeben für Deploy.

Deployment

2026-07-04, devops-deploy, Produktivserver 192.168.1.131

  • Commit a15fa37 gepusht nach origin/main (war lokaler HEAD, noch nicht auf Remote).
  • bash /opt/archivmail/update.sh auf 131 ausgeführt: Quellcode aktualisiert, Backend gebaut, Frontend gebaut, Cron-Jobs eingespielt, systemd-Units synchronisiert. Ergebnis: Backend ✓ läuft, Frontend ✓ läuft.
  • Backfill archivmail migrate-tenant-dirs --config /etc/archivmail/config.yml ausgeführt: linked=0, errors=0. Kein Bug — auf 131 haben alle 151 Bestandsmails tenant_id = NULL und email_refs ist leer (0 Zeilen); Produktiv läuft aktuell ohne aktive Mandanten-Zuordnung, anders als der Testserver 132 (dort 51304 Mails mit Tenant-Zuordnung, dort lief der Backfill im QA-Test mit linked=50015). Sobald auf 131 Mails einem Tenant zugeordnet werden (emails.tenant_id oder email_refs), legt Save() die Hardlinks automatisch an; ein erneuter migrate-tenant-dirs-Lauf ist jederzeit gefahrlos wiederholbar.
  • Smoke-Test:
    • archivmail status zeigt neuen Check [OK] Storage-Rechte /var/archivmail/store Modus 0700 — nur Owner-Zugriff.
    • Backend-Health GET /api/health → 200, Frontend GET / → 200, beide systemd-Units active.
    • Hardlink-Stichprobe (Inode-Vergleich bestehende Mail Root- vs. Tenant-Pfad) nicht durchführbar auf 131, da mangels Tenant-Zuordnung keine store/tenant_<id>/-Verzeichnisse angelegt wurden (erwartetes Verhalten, kein Fehlschlag). Die Hardlink-Semantik selbst wurde bereits im QA-Lauf auf 132 mit echten Tenant-Daten verifiziert (Inode-Gleichheit Root/Tenant-Pfad, Linkzähler korrekt), siehe QA Test Results oben.
  • Backup-Prozess-Check (AC A4): Auf 131 existiert kein dediziertes Backup-Tool/-Skript für /var/archivmail/store — weder in /etc/cron.d/ (nur archivmail-Cron mit OCR-Pause, Purge, Reindex-Backlog, Reconciliation, keine Backup-Zeile), noch als systemd-Timer, noch als eigenständiges Skript unter /opt/archivmail oder /usr/local/bin. Die Hardlink-Erhalt-Frage (rsync -H vs. tar vs. naives cp -r) ist damit aktuell gegenstandslos, weil es noch keinen Store-Backup-Prozess gibt — das ist ein eigenständiger, vom PROJ-65-Deploy unabhängiger offener Punkt und bleibt hier als Hinweis stehen, nicht selbst behoben (kein Backup-Skript angelegt).
  • Restrisiko/offene Punkte für spätere Sprints:
    1. Store-Backup-Prozess für 131 fehlt komplett (nicht PROJ-65-Scope, aber im Zuge dieses Deploys aufgefallen — sollte als eigenes Ticket nachgezogen werden, inkl. Hardlink-Erhalt-Vorgabe für PROJ-65).
    2. Save-/Delete-Hardlink-Pfade des jetzt laufenden Produktiv-Binaries sind auf 131 mangels Tenant-Zuordnung noch nicht mit echten Live-Daten beobachtet worden (nur code-reviewed + auf 132 mit echten Daten verifiziert).