Suche nach einem chattr-faehigen Host im Projektkontext (131, 204
bereits bekannt unprivilegiert; zusaetzlich Host 236 geprueft) ergibt:
kein privilegierter Host verfuegbar. Host 236 ist ebenfalls ein
unprivilegierter LXC-Container (uid_map 0->100000, ZFS-Root), realer
chattr +i-Testversuch auf synthetischer Testdatei schlaegt mit EPERM
fehl, identisch zu 204/131. AC1/AC2 aus RET-15 bleiben damit offen,
dokumentiert als zweiter Nachtrag-Abschnitt in der bestehenden
RET-11-NACHTRAG-CHATTR-UNTERSUCHUNG.md (kein neues Paralleldokument).
Keine Aenderung an wormstore.go.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reale Reproduktion auf Host 204 (chattr, roher FS_IOC_SETFLAGS-Ioctl,
Gegenprobe auf tmpfs im selben Container) zeigt: Ursache ist der
unprivilegierte LXC-Container (capable(CAP_LINUX_IMMUTABLE) prueft
immer gegen den Host-Namespace, nicht gegen den Container), nicht ZFS
wie bisher in RET-11-PRUEFPROTOKOLL.md vermutet - betrifft jedes
Dateisystem im Container. Acht geprueften Alternativen (ZFS readonly/
hold, ACL/chmod, Proxmox-Features, Capabilities, verschachtelte
User-Namespaces, FUSE, externes Object-Lock-Backend) sind im
Container-Scope entweder nicht verfuegbar oder strukturell keine
vollwertige Entsprechung. Keine Aenderung an wormstore.go, da der
bestehende Fail-Closed-Mechanismus (ImmutableLockSupported) bereits
korrekt reagiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Reale Reproduktion auf Host 204 (chattr, roher FS_IOC_SETFLAGS-Ioctl,
Gegenprobe auf tmpfs im selben Container) zeigt: Ursache ist der
unprivilegierte LXC-Container (capable(CAP_LINUX_IMMUTABLE) prueft
immer gegen den Host-Namespace, nicht gegen den Container), nicht ZFS
wie bisher in RET-11-PRUEFPROTOKOLL.md vermutet - betrifft jedes
Dateisystem im Container. Acht geprueften Alternativen (ZFS readonly/
hold, ACL/chmod, Proxmox-Features, Capabilities, verschachtelte
User-Namespaces, FUSE, externes Object-Lock-Backend) sind im
Container-Scope entweder nicht verfuegbar oder strukturell keine
vollwertige Entsprechung. Keine Aenderung an wormstore.go, da der
bestehende Fail-Closed-Mechanismus (ImmutableLockSupported) bereits
korrekt reagiert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Konsolidiert QA-02 (Retention & WORM), QA-03 (Compliance &
eDiscovery) und QA-04 (Backup & Restore) sowie ergänzend QA-06/QA-07
zu einer Gesamtfreigabeentscheidung für das Archive-Board. Bedingte
Freigabe: WORM-Storage-Object-Lock (chattr +i) bleibt auf allen
bisher geprüften Hosts (131, 204) strukturell nicht nachweisbar
(LXC/ZFS-Infrastrukturgrenze), erneut unabhängig reproduziert;
Anwendung reagiert fail-closed, kein fabriziertes PASS. Keine
Änderung an Produktivcode oder den Einzelprotokollen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RET-03/RET-04 Pflichtpruefungen gegen den Referenzdatensatz
(internal/referenceset) wiederholt: Legal-Hold-Widerstand gegen
Loeschung fuer alle drei Referenzobjekttypen (PASS, 7/7), WORM-
Ueberschreib-/Manipulationspruefung fuer alle drei Groessenklassen
(Code strukturell korrekt, Ausfuehrung auf Host 204 durch bekannte
chattr+i-Infra-Grenze blockiert, siehe RET-11-PRUEFPROTOKOLL.md -
Schreibschutz stattdessen manuell als echter nexarch-Betriebsnutzer
nachgewiesen). Ergebnisse mit Zielwert/Messwert/Bewertung sowie
offene Restrisiken in archive/docs/QA-02-PRUEFPROTOKOLL.md
festgehalten.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
retain_until aus DOK-01/MAI-02-migrierten Kernobjekten 1:1 in das
generische Retention-Modell (RET-01) uebernehmen, ohne Neuberechnung
aus dem Migrationsdatum.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- migrations/0010_deletion_requests: speichert nur den Token-Hash,
nie das Token selbst
- archive/internal/deletionapproval: RequestDeletion (einmaliges
Klartext-Token), ConfirmAndExecute (FOR UPDATE-Lock, andere Person
als requester, zeitkonstanter Tokenvergleich via subtle.ConstantTimeCompare,
ruft danach genau einmal deletionworkflow.Destroy (RET-03) auf)
- 4 Tests real bestanden, inkl. echtem Nebenlaeufigkeits-Test (zwei
echte Goroutinen, genau 1 Erfolg + 1 ErrAlreadyExecuted, genau ein
destruction_log-Eintrag)
- Migration real auf dms_tenant_test angewendet
Pruefungen siehe archive/docs/CMP-06-PRUEFPROTOKOLL.md
- migrations/0009_data_subject_ref: additive nullable Spalte auf
retention_objects, schliesst die Luecke fuer 'betroffene Person' in
RET-01
- retention.RegisterObjectForSubject: NEUE additive Funktion, RegisterObject
selbst unveraendert (kein Diff, keine Produktionsaufrufer betroffen)
- archive/internal/dpreport: SubjectReport (Auskunftsbericht),
ProcessingOverview (Verarbeitungsuebersicht, statisch gepflegte
Zweck/Rechtsgrundlage je Objekttyp), WriteSubjectReportCSV
- Mandantentrennung strukturell durch Modell C (ein Pool pro Tenant),
real mit zwei physisch getrennten Tenant-DBs bewiesen (tenant_acme/
tenant_globex), nicht nur behauptet
- 4 Tests, alle Pflichtpruefungen real bestanden
- Migration real auf dms_tenant_test angewendet
Pruefungen siehe archive/docs/CMP-02-PRUEFPROTOKOLL.md
- migrations/0008_legal_hold_destruction: legal_holds (historisiert,
Partial-Unique-Index gegen doppelte aktive Sperre), destruction_log
(append-only, per Postgres-Trigger gegen UPDATE/DELETE geschuetzt)
- archive/internal/deletionworkflow: SetLegalHold (Begruendungspflicht),
ReleaseLegalHold (Aufheben selbst protokolliert, keine Loeschung der
Zeile), ReleaseExpired (Freigabeprozess active->expired, keine
Sofortloeschung, Sperre wird respektiert), Destroy (verlangt
vorherigen expired-Status, prueft Sperre erneut, transaktional mit
Protokolleintrag)
- 6 Tests, alle Pflichtpruefungen real bestanden (Sperre widersteht
Loeschversuch, Protokoll real unveraenderlich per Trigger, Aufheben
real protokolliert)
- Migration real auf dms_tenant_test angewendet
Pruefungen siehe archive/docs/RET-03-PRUEFPROTOKOLL.md
lizenzreport.Build nutzt ausschliesslich ANA-01s bereits gespeicherte
Kennzahlen (kennzahlen.SnapshotsInRange) - je Modul der juengste Snapshot
im gewaehlten Zeitraum (Zustandswert, kein Aufsummieren mehrerer
Erhebungszeitpunkte), Gesamtsumme ueber kennzahlen.CombinedTotals. Ein
Mandant ohne Aktivitaet im Zeitraum liefert Null-Werte, keinen Fehler.
ToCSV/ToPDF lesen beide aus derselben Report-Struktur - kein zweiter
Berechnungspfad, der auseinanderlaufen koennte. gobddoc.RenderTextPDF
als neue exportierte Funktion (bisherige interne Implementierung
unveraendert) statt den abhaengigkeitsfreien PDF-Writer zu duplizieren.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
internal/kennzahlenapi: liest ausschliesslich ueber ANA-01 (internal/
kennzahlen) bereits aggregierte, gespeicherte Kennzahlen - keine eigene
Aggregationslogik. Hinter RBAC-06 (retentionapi.RequireRBAC wiederverwendet),
Tenant-Kontext als Pflicht-Query-Parameter (gleiche Konvention wie Mail
INT-01/mailapi) - der Handler liefert strukturell nie Daten eines anderen
Mandanten. kennzahlen.SnapshotsInRange ergaenzt ANA-01 um kombinierbare
Modul-/Zeitraum-Filter, dieselbe Abfrage fuer API und direkten Aufruf.
web/kennzahlen-dashboard: Next.js/React/TypeScript, gespiegelt von
web/retention-admin (RET-06) auf @nexarch/shl (SHL-01). Filter nach Modul
und Zeitraum, modulübergreifende Summenanzeige, expliziter
Zugriff-verweigert-Zustand statt leerer Tabelle.
Echter End-to-End-Smoketest gegen den Backend-Stack auf 192.168.1.131
(echter Postgres, Fake-RBAC auf separatem Port ohne den laufenden echten
policy-api-Dienst zu stoeren, next dev) - Details und eine ehrlich
benannte Pruefluecke (kein Headless-Browser fuer den Rendering-Nachweis
der Client-Komponente) in docs/ANA-02-PRUEFPROTOKOLL.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Testumgebung enthaelt bei jedem BAK-06-Lauf echte wiederhergestellte
Mandantendaten - Schutzniveau war bisher nur implizit im Code vorhanden,
nicht explizit festgelegt/dokumentiert.
- Zugriffskontrolle: User=nexarch identisch zu den produktiven
Sicherungsdiensten (unveraendert, jetzt explizit verglichen/dokumentiert).
- Netzwerksegmentierung: testPostgresqlConf/testPgHbaConf als benannte
Konstanten extrahiert und direkt testbar gemacht (kein Netzwerkzugriff
moeglich); PrivateNetwork=true/NoNewPrivileges=true/ProtectHome=true
zusaetzlich in nexarch-archive-restoretest.service.tmpl erzwungen.
- Loeschfrist: bestehendes defer-os.RemoveAll-Verhalten real verifiziert,
auch im Fehlerfall - keine Aenderung noetig, nur Nachweis erbracht.
Schutzniveau jetzt explizit in docs/BAK-06-PRUEFPROTOKOLL.md benannt statt
nur implizit im Code zu stecken.
Fund waehrend Recherche: es existiert ein zweiter, aelterer Archive-
Branch-Strang (z.B. feature/cmp-02-dsgvo-datenschutz-berichte) mit
eigener Migrationsnummerierung (0001-0009) ohne RET-11/RET-12/RBAC-06 -
nie mit dem hier fortgefuehrten Strang zusammengefuehrt. CMP-05 (naechste
Kachel im Backlog) haengt von CMP-01 (dieser Strang) UND CMP-02 (anderer
Strang) ab und ist damit aktuell nicht sauber umsetzbar, ohne beide
Straenge zusammenzufuehren - das ist kein Auftrag dieses Tickets.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Gespiegelt von Core QA-07 (internal/contracttest): jeder Vertragstest
prueft die TATSAECHLICHE Antwort einer echten Archive-Komponente gegen
ihr dokumentiertes Schema, nicht nur eine Annahme darueber.
Zwei Vertraege abgedeckt:
- RET-05 Registrierungs-Antwort (POST /register) - RegisterHandler echt
aufgerufen, Antwortfelder (ID/TenantSlug/ModuleName/ObjectType/
RetentionClass/CallbackURL) geprueft.
- RET-05 Vernichtungs-Rueckruf-Payload (NotifyDestruction) - echter
HTTP-Request an Testserver, Payload-Felder (object_type/
object_reference/destroyed_at) geprueft.
Je Vertrag ein Breaking-Change-Erkennungstest (entferntes bzw.
umbenanntes Pflichtfeld in einer handgebauten Fixture) - beide werden
zuverlaessig erkannt.
.gitea/workflows/archive-contract-tests.yml fuehrt die Tests bei jedem
Push/PR aus. Gitea-Actions-Runner auf 192.168.1.131 laeuft (nach
Neustart wiederbelebt) und verarbeitet bereits den Ruecklauf aus den
vorherigen Sessions.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kennzahlen.Aggregate ermittelt Module AUSSCHLIESSLICH aus RET-05s
module_registrations-Tabelle statt einer im Code hinterlegten Liste -
ein neu registriertes Modul erscheint dadurch ohne Codeaenderung im
naechsten Aggregationslauf. Je (Mandant, Modul) wird die Basis-URL aus
der bereits vorhandenen callback_url abgeleitet (kein neues
Registrierungsfeld, kein Umbau von RET-05). Fetcher ist eine
Schnittstelle - Tests laufen gegen fiktive Testmodule (Ticket-Vorgabe),
reale Module implementieren spaeter GET /archive-metrics ueber
HTTPFetcher.
Jeder Lauf persistiert einen Snapshot pro Modul (archive_metrics_
snapshots) - Zeitverlauf ueber erhoben_am, LatestSnapshots liefert den
juengsten Stand je Modul als Grundlage fuer ANA-02 (Dashboard). Ein
einzelnes fehlschlagendes Modul blockiert nicht die Snapshots der
uebrigen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ediscovery.ModuleAdapter ist die schmale Schnittstelle, ueber die ein
Modul (DMS, Mail, ...) Treffer liefert und Originalinhalte herausgibt -
dieselbe Adapter-Disziplin wie RET-01/RET-05, Archive kennt die
Fachobjekte anderer Module nicht. Reale Module binden das spaeter per
HTTP-Client an ihre eigene REST-API (Mail hat mit INT-01/mailapi bereits
einen Endpunkt dafuer, DMS noch nicht) - diese Kachel prueft die
Aggregations-/Exportlogik gegen zwei fiktive Testmodule (Ticket-Vorgabe).
Search fragt alle Module ab und liefert eine gemeinsame Trefferliste;
ein einzelnes fehlschlagendes Modul verschluckt nicht die Treffer der
uebrigen. Export baut ein ZIP (Standardbibliothek archive/zip, keine
neue Abhaengigkeit) mit Originaldateien, manifest.json und
checksums.txt - Pruefsummen nach echtem Entpacken verifiziert.
Suchlauf und Export werden ueber audittrail.Recorder (CMP-04) an Core
AUD-08 gemeldet - die Unveraenderlichkeit des Protokolleintrags nutzt
den bereits in AUD-07 verifizierten Append-only-Schutz, statt ein
eigenes Protokoll zu bauen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
gobddoc.Build liest die AKTUELLE Retention-Konfiguration (RET-01/RET-02
ListClassRules) und erzeugt daraus eine Verfahrensdokumentation
(Aufbewahrungs-, Zugriffs- und Loeschverfahren) - nicht manuell gepflegt.
Version ist ein Inhalts-Hash der aktiven Aufbewahrungsklassen, aendert
sich also GENAU dann, wenn sich das Verfahren tatsaechlich aendert.
generatedAt wird vom Aufrufer uebergeben, damit zwei Exporte ohne
Konfigurationsaenderung byte-identisch sind.
PDF-Erzeugung ueber einen minimalen, abhaengigkeitsfreien Writer
(internal/gobddoc/pdf.go) statt einer Drittanbieter-Bibliothek - mehrseitig,
Helvetica, gegen echtes pdfinfo/pdftotext auf dem Testhost verifiziert
(siehe docs/CMP-01-PRUEFPROTOKOLL.md).
cmd/gobddoc-sample ist reines Verifikationswerkzeug (kein Teil der
Anwendung) fuer den pdfinfo/pdftotext-Nachweis.
Offen: Gegenlesen durch zweite Person (Pruefung 3) - wie bei QA-01 im PR
nachzutragen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Gespiegelt von Core QA-01: Testpyramide (Unit/Integration/Wiederherstellungs-
test/Extern), Pflichttests fuer sicherheitskritischen Code als technisch
erzwungenes Merge-Gate statt Dokumentationsvorgabe.
Sicherheitskritische Bereiche fuer Archive (docs/TESTSTRATEGIE-ARCHIVE.md
Abschnitt 3): wormstore (WORM-Durchsetzung selbst), rbacclient/retentionapi
(Zugriffspruefung), tenantregistryclient/kekclient/backupcrypto (Tenant-/
Schluessel-Bezug). internal/pflichttestgate + cmd/pflichttestgate pruefen
das automatisiert gegen den PR-Diff, .gitea/workflows/archive-pflichttest-
gate.yml fuehrt es in CI aus (Pfad-Filter auf archive/**, da Monorepo mit
mehreren physisch getrennten Go-Modulen).
internal/referenceset liefert den in Abschnitt 4 definierten, synthetischen
Referenzdatensatz (Retention-Objekte je Fristklasse, WORM-Nutzdaten in drei
Groessenklassen, Backup-Generationen mit geteilten/eindeutigen Bloecken) -
keine echten Kunden-/Mandantendaten (DSGVO-Vorgabe), reproduzierbar per
festem Seed, Nachweis in referenceset_test.go.
Offen: Gegenlesen durch zweite Person (Akzeptanzkriterium 3) - wie bei
Core QA-01 im PR nachzutragen, blockiert nicht das technische Inkrafttreten
des Gates.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Build/vet clean, Modul-Testsuite gruen. wormstore-Tests fuer AC1/AC2
skippen ehrlich auf 131: unprivilegiertes LXC-Root auf ZFS erzwingt
chattr +i nicht (EPERM trotz cap_linux_immutable im Namespace-
Boundingset - Pruefung laeuft gegen die gemappte UID). Kein Codefehler,
sondern eine Infra-Grenze dieses Testhosts, dokumentiert als offen statt
erfunden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbP
Tests je Akzeptanzkriterium: direkter Loesch-/Ueberschreibversuch am
Dateisystem scheitert an chattr +i (auch als root), Release verweigert
Freigabe vor Fristablauf, Release+Destroy loescht nach Fristablauf
real, Destroy ohne Release verweigert. Kapazitaets-Selbsttest
ImmutableLockSupported plus TEST_WORMSTORE_LOCK_DIR, damit der Test auf
tmpfs ehrlich uebersprungen statt falsch bestanden wird.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbP
- archive/internal/wormstore.Store: Put schreibt einmalig (chmod 0400
danach, ErrAlreadyExists bei Ueberschreibversuch inkl. DB-seitiger
Sperre gegen Wettlaufsituationen), GetVerified prueft SHA-256 bei
jedem Zugriff, KEINE Delete-Funktion (strukturelle API-Grenze)
- migrations/0007_worm_objects: append-only Metadatentabelle
- AC2/Pruefung 3 vor Umsetzung praezisiert: Schutz ueber Go-API,
kein absoluter Schutz gegen root (Nutzerentscheidung: kein chattr +i,
nicht portabel/nicht ehrlich als absolut behauptbar)
- Reflection-Test beweist strukturell: keine Loesch-Methode vorhanden
- zusaetzlich echter Nachweis auf 131 als Nicht-Root-Betriebsnutzer
(sudo -u nexarch): direkter Ueberschreibversuch scheitert real,
Automatiktest selbst laeuft als root und uebersprang diesen Teil
bewusst
Pruefungen siehe archive/docs/RET-04-PRUEFPROTOKOLL.md
- archive/internal/retentionapi.CORS: erlaubt genau einen konfigurierten
Origin (kein Wildcard), beantwortet OPTIONS-Preflights direkt
- cmd/retention-api: neue Pflicht-Env NEXARCH_RETENTION_CORS_ALLOWED_ORIGIN,
mux mit CORS umschlossen
- gefunden durch Sichtpruefung des laufenden RET-06-Frontends: curl
umgeht CORS, ein echter Browser haette den Fetch blockiert - weder
Go- noch Vitest-Tests konnten das strukturell erfassen
- 3 Tests: erlaubter Origin bekommt Header, Preflight korrekt
beantwortet, fremder Origin bekommt keinen Header
- real deployed auf 131, genau der bei der Sichtpruefung fehlgeschlagene
Aufruf (Origin http://127.0.0.1:3099) liefert jetzt 200 mit korrektem
Access-Control-Allow-Origin
Pruefungen siehe archive/docs/RET-10-PRUEFPROTOKOLL.md
- archive/cmd/moduleadapter-api: startet den fertigen RET-05
RegisterHandler als eigenstaendigen HTTP-Dienst, Port 8095
- reines Wiring, kein Diff an internal/moduleadapter/ (verifiziert)
- real deployed auf 131, end-zu-ende per curl: Registrierung +
Idempotenz-Nachweis (widerspruechliche zweite Werte werden ignoriert,
urspruengliche Registrierung bleibt bestehen)
Pruefungen siehe archive/docs/RET-09-PRUEFPROTOKOLL.md
- internal/rbacclient: HTTP-Client fuer Core RBAC-06 (POST /authorize)
- retentionapi.RequireRole (Header-Provisorium) ersetzt durch
RequireRBAC, echter Aufruf gegen RBAC-06, fail-closed bei Fehlern
- Mount nimmt jetzt rbacclient.Client entgegen
- alle bestehenden RET-06-API-Tests weiterhin gruen
- neue Tests: verweigerte Rolle (403 gegen echte RBAC-06-Antwort),
erlaubte Rolle (200), RBAC-06 nicht erreichbar -> fail-closed (403)
- real deployed auf 131, end-zu-ende per curl nachgewiesen (403/403/200/403)
Pruefungen siehe archive/docs/RET-08-PRUEFPROTOKOLL.md
Board-Entscheidung: Backend-API zuerst, echtes Next.js-Frontend als
separates Folgeticket - vermeidet Pseudo-Frontend-Protokoll.
internal/retentionapi: 4 Endpunkte (anlegen/aendern, deaktivieren,
liste, vorschau), Vorschau nutzt dieselbe ListExpiringObjects-Funktion
wie RET-02s periodischer Job (keine Doppel-Implementierung).
RequireRole ist AUSDRUECKLICH kein RBAC-02-Ersatz, sondern ein
dokumentiertes Provisorium (Header-Check) - RBAC-02 ist reiner
Core-interner Go-Code ohne HTTP-Schnittstelle fuer andere Module,
derselbe Befund wie FDN-03/FDN-09. Provisorium real getestet inkl.
Negativfall (403 ohne/mit falscher Rolle). retention_class_rules um
active-Flag erweitert (deaktivieren ohne Historienverlust). Real auf
131 deployed und per curl end-to-end verifiziert.
internal/retentionengine: Frist je Aufbewahrungsklasse als natives
Postgres-INTERVAL, Stichtagsberechnung an Postgres delegiert statt
eigener Kalenderrechnung (Schaltjahr/Monatsende-Referenzwerte real
verifiziert: 2024-02-29+1y=2025-02-28, 2026-01-31+1mo=2026-02-28).
Periodischer Job (ListExpiringObjects) beschraenkt sich per DISTINCT ON
auf die juengste Klassenzuordnung je Objekt - sonst wuerden Objekte mit
mehrfach geaenderter Klasse (RET-01-Historisierung) doppelt auftauchen,
real mit einem Zwei-Zuordnungen-Testobjekt bewiesen. Scope bewusst eng
gehalten: keine RET-05-Anbindung, keine Vernichtungslogik - das ist
Ticket-Scope, dependsOn ist nur RET-01.
Vor Board-Flip bemerkt: Akzeptanzkriterium 1 fordert Objekttyp MIT
Aufbewahrungsklasse UND Rueckruf-Adresse, retention_class fehlte im
ersten Entwurf komplett. Migration, Registration-Struct, Register,
ListRegistrations und RegisterHandler ergaenzt, Tests angepasst
(Idempotenz jetzt auch fuer retention_class geprueft, nicht nur
callback_url). Real auf 131 gedroppt und neu angewendet.
internal/moduleadapter: Registrierungs-API + Rueckruf-Ausloeser fuer
DMS/Mail, bewusst NUR Archives eigene Seite - keine Modul-Empfaenger-
Implementierung (Nutzervorgabe: Interface zuerst festlegen, damit
DMS/Mail spaeter nicht gegen ein sich noch aenderndes Interface bauen).
Register ist ON-CONFLICT-DO-NOTHING (erneute Registrierung aendert nie
bestehende callback_url), NotifyDestruction echter HTTP-POST mit festem
DestructionNotice-Vertrag. Idempotenz sowohl auf Go- als auch HTTP-
Ebene bewiesen, Rueckruf gegen echten Testendpunkt verifiziert.
internal/retention: object_type/object_reference als reine Textfelder
(Adapter-Muster, keine Fremdschluessel auf DMS-/Mail-Tabellen).
Aufbewahrungsklassen-Zuordnung historisiert (jede Zuordnung eigene,
unveraenderliche Zeile). Migration real vorwaerts+rueckwaerts gegen
die tatsaechlichen .sql-Dateien getestet, Mandantentrennung gegen
echtes zweites Tenant-DB bewiesen. Grundlage fuer RET-05 (Adapter-
Interface) und RET-02.
Realer Restore-Testlauf (DB+Objekt) und Reconciliation frisch auf 131
ausgeloest, beide sauber (kein Fund). Fuenf offene Restrisiken
schriftlich benannt (Rotations-Kohaerenz, BAK-04-Rollenrechte fuer
produktive Mandanten, Storage-Provider-Grenze aus BAK-08, Core
FDN-03/FDN-09-Wiring-Luecke, leerer Testbestand). Unterschrift steht
aus - kann nicht durch das System selbst erfolgen.
Nutzerfrage: DB- und Objekt-Rotation laufen unabhaengig, kein Test/
Invariant stellt sicher, dass aeltester erreichbarer Snapshot und
aelteste erreichbare DB-Generation zeitlich zusammenpassen. Aktuell
identische keep-Werte sind Zufall, kein erzwungenes Verhalten - als
Folgepunkt dokumentiert, nicht Teil des Tickets.
internal/backup.PruneTiered: reine GFS-Funktion (Tag/Woche/Monat) fuer
Datenbank-Generationen, behaelt strukturell immer die neueste Generation
(Sicherheitsnetz gegen Legal-Hold-Kollision). internal/objectbackup.
PruneTiered: duenner Wrapper um restics native --keep-daily/-weekly/
-monthly-Staffelung. Beide CLIs nutzen die Staffelung, wenn konfiguriert,
bleiben sonst abwaertskompatibel zur flachen "letzte N"-Regel. Real
gegen zeitversetzt erzeugte restic-Snapshots getestet (Fund: restics
--time-Flag erwartet eigenes Format, nicht RFC3339), Pruning-Sicherheit
nach dem Loeschen alter Snapshots ueber vollstaendigen Restore +
restic check --read-data bewiesen. Beide Rotationswege real ueber
systemd auf 131 ausgeloest.
internal/tenantbackup: datenbank-scharfes pg_dump/pg_restore statt
BAK-01s Cluster-weitem pg_basebackup - bei Modell C (TEN-01, physisch
isolierte DB je Mandant) wuerde ein Cluster-Restore zwangslaeufig ALLE
Mandanten ueberschreiben. Objekt-Seite nutzt BAK-02 direkt (Mandanten
haben eigene Buckets/Pfad-Roots). Eigene Postgres-Rolle
nexarch_tenantbackup (CREATEDB, kein Superuser, getrennt von
nexarch_backup). Zwei-Tenant-Isolation real in beide Richtungen
bewiesen (Markerwert-Nachweis), JSONL-Protokoll fuer Sicherung UND
Restore. Realer End-zu-Ende-Lauf ueber tenantbackup-cli auf 131.