VER-08: Technische Durchsetzung Append-only fuer das Versionierungs-/Ereignisprotokoll #30

Closed
patrick wants to merge 0 commits from feature/ver-08-technische-durchsetzung-append-only-f-r-das-versionierungs-ereignisprotokoll into main
pull from: feature/ver-08-technische-durchsetzung-append-only-f-r-das-versionierungs-ereignisprotokoll
Owner

Prüfungen

  1. UPDATE/DELETE gegen die Protokolltabelle mit der Anwendungsrolle — Code fertig: nexarch_app-Rolle bekommt permission denied bei UPDATE/DELETE, INSERT/SELECT bleiben erlaubt
  2. Ablehnungsversuch wird selbst sichtbar protokolliert — Code fertig: RAISE WARNING mit Akteur/Session/Client-Kontext, real ueber OnNotice abgefangen
  3. Regressionstest: VerifyChain funktioniert nach Rechteaenderung weiterhin — Code fertig

Direkte Analogie zum bereits vorhandenen Core-Pendant AUD-07 (gleiche nexarch_app-Gruppenrolle, gleiches Trigger-Muster), angewendet auf audit_log (VER-05) statt audit_events.

Keine Regression: go build/vet/gofmt sauber, go test ./... -p 1 PASS (inkl. unveraenderter bestehender audittrail-Tests).

Offen: die vier neuen Tests liefen in dieser Sitzung nicht durch (Testrollen-Zugangsdaten auf 131 seit VER-03 defekt) — Details siehe dms/docs/VER-08-PRUEFPROTOKOLL.md.

🤖 Generated with Claude Code

## Prüfungen 1. UPDATE/DELETE gegen die Protokolltabelle mit der Anwendungsrolle — Code fertig: nexarch_app-Rolle bekommt permission denied bei UPDATE/DELETE, INSERT/SELECT bleiben erlaubt 2. Ablehnungsversuch wird selbst sichtbar protokolliert — Code fertig: RAISE WARNING mit Akteur/Session/Client-Kontext, real ueber OnNotice abgefangen 3. Regressionstest: VerifyChain funktioniert nach Rechteaenderung weiterhin — Code fertig Direkte Analogie zum bereits vorhandenen Core-Pendant AUD-07 (gleiche nexarch_app-Gruppenrolle, gleiches Trigger-Muster), angewendet auf audit_log (VER-05) statt audit_events. Keine Regression: go build/vet/gofmt sauber, go test ./... -p 1 PASS (inkl. unveraenderter bestehender audittrail-Tests). Offen: die vier neuen Tests liefen in dieser Sitzung nicht durch (Testrollen-Zugangsdaten auf 131 seit VER-03 defekt) — Details siehe dms/docs/VER-08-PRUEFPROTOKOLL.md. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
patrick added 32 commits 2026-09-18 00:28:41 +02:00
DMS-Modul-Grundgerüst als Unterordner im bestehenden nexarch-Monorepo
(dms/), eigenes Go-Modul (gitea.perlbach24.de/scripte/nexarch/dms),
getrennt von Core. App/Worker-Trennung nach paperless-ngx-Vorbild (lange
Aufgaben blockieren nie eine Anfrage): cmd/app (HTTP, /healthz),
cmd/worker (Hintergrund-Dienst-Stub), internal/shared.

golangci-lint (govet/staticcheck/errcheck/unused/ineffassign/gofmt/
goimports) + Makefile-Targets (install/run/lint/fmt/test). Auf
192.168.1.131 verifiziert: build/fmt/lint/test clean, absichtlich
eingefuegter Lint-Verstoss bricht den Build wie gefordert ab (Exit 2),
make run startet App+Worker und /healthz antwortet innerhalb 2s.

Siehe dms/docs/FDN-01-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
Kern-Entitaeten (Dokument, Datei-Revision, Ordner, Tag, Metadatenfeld) als
tenant-scoped SQL-Migration (Modell C, keine tenant_id-Spalte), FK auf
users(id) aus Core IAM-01 (Auth bleibt vollstaendig in Core). Eigener,
minimaler Migrations-Runner (internal/migrate, kein ORM) mit
schema_migrations-Tracking fuer Idempotenz. Seed-Skript fuer Entwicklung.

Auf 192.168.1.131 verifiziert: Migration auf leerer+bestehender DB,
Rollback stellt Vorzustand wieder her, 4 FK-Negativtests, Seed-Skript
end-to-end gegen frische DB. go.sum committet (Lock-Datei-Lehre aus dem
Ticket). build/vet/lint/test clean.

Siehe dms/docs/FDN-02-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
Ein Driver-Interface, zwei austauschbare Treiber: LocalDriver (Entwicklung,
Dateisystem, HMAC-signierte URLs) und S3Driver (Produktion, S3-kompatibel
via aws-sdk-go-v2, echte presigned URLs). Pfadschema documents/<id>/
revisions/<id> innerhalb des mandantenspezifischen Buckets. Service
verbindet Driver mit Nutzungsmeldung an Core LIC-05 (HTTPUsageReporter,
Vertrag von internal/resync.Handler nachgebildet, DMS kann Cores internal/-
Pakete als eigenes Modul nicht importieren).

Auf 192.168.1.131 verifiziert, S3-Treiber gegen echtes lokal installiertes
MinIO (kein Mock): Round-Trip beide Treiber, abgelaufene presigned URL real
mit 403 abgewiesen (manuell zusaetzlich zum Unit-Test verifiziert), klare
ErrNotFound bei fehlendem Objekt beide Treiber, Nutzungsmeldung mit
korrektem Tenant/Metrik/Delta bei Put/Delete.

Befund dokumentiert: Cores internal/resync.Handler (Gegenstelle fuer die
Nutzungsmeldung) ist noch in keinem cmd/*/main.go verdrahtet (dieselbe
Fehlerklasse wie QA-05/AUD-06) - HTTPUsageReporter daher gegen den
dokumentierten Vertrag getestet, nicht gegen eine laufende Core-Instanz.
Siehe dms/docs/FDN-03-PRUEFPROTOKOLL.md.

golangci-lint auf v2.1.6 aktualisiert (v1.63.4 konnte go1.24-Zielstand
nicht linten), .golangci.yml auf v2-Konfigurationsformat migriert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
checksum_sha256 (FDN-02) wird von keinem Schreibpfad in FDN-03 befuellt.
Nachgetragen als AC/Pruefung in DOC-01 (Upload-API), Voraussetzung fuer
Archive BAK-08 (neues Ticket: Checksum-basierte Objekt-Integritaetspruefung
fuer extern eingebundenes, nicht selbst ueberwachtes Kunden-S3-Storage).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
Postgres-Jobqueue (processing_jobs, FOR UPDATE SKIP LOCKED), In-Prozess-
Worker-Goroutinen, kein Redis/AMQP. Enqueue mit Idempotency-Key-Dedup,
Dequeue mit Stale-Lock-Wiedervorlage (Absturzsicherheit), Fail mit
arithmetischem Backoff und Dead-Letter-Queue nach erschoepften Versuchen,
RequeueDeadLetter fuer manuelle Wiederholung.

Auf 192.168.1.131 verifiziert: Absturz-Wiedervorlage (Job von einem
"abgestuerzten" Worker nie completed/failed, zweiter Worker holt ihn nach
Ablauf der Sperre erneut), Idempotenz bei Doppelzustellung (gleicher
idempotency_key erzeugt nur 1 Zeile), DLQ-Eintrag manuell wiederholbar.

Vier reale Fehler beim Testen gefunden und behoben: zwei pgx-Typinferenz-
Bugs im SQL-Parameterhandling (toter workerID-Parameter ohne Referenz in
der Query; untypisiertes any statt []string fuer den ::text[]-Cast), sowie
zwei Testinfrastruktur-Bugs (dms_tenant_test sammelte schema_migrations-
Zustand ueber Sitzungen hinweg an, jobqueue-Testfixture raeumte
processing_jobs nicht auf) - neues scripts/reset-test-env.sh + make check
(-p 1) analog Core behoben.

Siehe dms/docs/FDN-04-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
Envelope-Encryption: DEK (32 Byte) frisch je Objekt, AES-256-GCM fuer
Objektinhalt, DEK selbst mit Tenant-KEK verpackt (WrapDEK/UnwrapDEK). KEK
kommt ausschliesslich ueber HTTPKEKProvider von Core API-10
(internal/kek.Handler.TenantKEKHandler-Vertrag, Service-Credential-
authentifiziert), wird nie persistiert - jeder Seal/Open-Aufruf bezieht ihn
frisch. RewrapDEK fuer KEK-Rotation ohne Neuverschluesselung der Objekte
(nur der DEK-Wrapper wird neu verpackt, Chiffretext bleibt unveraendert).

Auf 192.168.1.131 verifiziert: Round-Trip Encrypt/Decrypt, manipulierter
Chiffretext UND falscher Schluessel werden beide ueber GCM-Auth-Tag
abgelehnt, KEK-Rotation real getestet (RewrapDEK, altes Objekt danach mit
neuem KEK weiterhin lesbar, alter KEK funktioniert nicht mehr).

Befund dokumentiert: Cores internal/kek.Handler (Gegenstelle fuer den
KEK-Bezug) ist noch in keinem cmd/*/main.go verdrahtet, dieselbe
Fehlerklasse wie FDN-03/QA-05/AUD-06. HTTPKEKProvider daher gegen den
dokumentierten Vertrag getestet, nicht gegen eine laufende Core-Instanz.
Siehe dms/docs/FDN-09-PRUEFPROTOKOLL.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
Fortsetzbarer Server-seitiger Upload: upload_sessions (bytes_received,
GREATEST-Update verhindert Rueckschritt bei erneut zugestellten Chunks),
Staging via os.File.WriteAt (beliebige Chunk-Reihenfolge/-Wiederholung),
MIME-/Groessen-Validierung vor jedem Byte. Complete() liest Klartext einmal
via io.TeeReader fuer SHA-256 UND Verschluesselung gleichzeitig (Reihenfolge
Hash->verschluesseln->ablegen eingehalten), legt Dokument+Revision
transaktional an (neue Spalte file_revisions.wrapped_dek fuer FDN-09).

Auf 192.168.1.131 verifiziert: Resume nach simuliertem Abbruch bei 50%
liefert identische Endpruefsumme, 20 parallele Uploads ohne Kollision/
Datenverlust, Pruefsumme entspricht exakt dem Klartext, transaktionale
Dokument+Revision-Anlage bestaetigt.

Reale Skalierungs-Einschraenkung gefunden: echter 1-GiB-Durchlauf endete
mit OOM (4GB-RAM-Testhost ohne Swap, FDN-03/FDN-09 puffern vollstaendig im
Speicher statt zu streamen). Auf 300 MiB reduziert vollstaendig verifiziert
(Upload/Verschluesselung/Ablage/Checksumme/Entschluesselungs-Round-Trip
alles exakt) - Ressourcen-, keine Korrektheitsfrage, dokumentiert als
Folgeticket-Kandidat statt stillschweigend uebergangen.

Siehe dms/docs/DOC-01-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
- dms/internal/retentionclient: HTTP-Client fuer Archive RET-05/RET-09
- dms/internal/retentiondestroy: RegisterOrRequeue (FDN-04-Requeue bei
  Fehlschlag, kein Absturz/stilles Verwerfen), DestroyCallbackHandler
  (physische Loeschung aller Datei-Revisionen ueber storage.Service,
  markiert Dokument als vernichtet, 2xx erst danach)
- dms/cmd/app: mountet destroy-callback, registriert beim Start
- dms/cmd/worker: verarbeitet Requeue-Jobs (Retry)
- nur dms_document registriert (DOC-01/FDN-02 kennen keinen separaten
  Anhang-Typ, kein Umbau angrenzender Bereiche)
- real getestet: registrierung gegen echten fake-RET-05-Server, echte
  Loeschung inkl. Storage-Byte-Nachweis, echter Requeue-Eintrag bei
  Fehlschlag, erfolgreicher Retry
- live auf 131 gegen echten RET-09-Dienst (Port 8095) verifiziert,
  echter curl-Vernichtungs-Rueckruf mit real geloeschter Datei
- reale Core-API-06-Deploy-Luecke dokumentiert (nicht verschwiegen):
  /internal/resync/usage auf 131 aktuell nicht gemountet

Pruefungen siehe dms/docs/DOC-16-PRUEFPROTOKOLL.md
internal/thumbnail: Vorschau-Zustand je Datei-Revision als explizite
State-Machine (pending/processing/ready/failed, revision_thumbnails)
statt Ableitung aus Dateiendung. Content-Type wird ausschliesslich ueber
Magic-Bytes bestimmt (net/http.DetectContentType gegen die entschluesselten
Bytes) - der in "Bekannte Fehler vermeiden" beschriebene Fehler eines
Vorgaengerprojekts (fragile Erkennung ueber Dateiendung) wird damit
vermieden.

PDF-Rasterung ueber pdftoppm (Poppler), Bild-Thumbnails ueber einen
minimalen, abhaengigkeitsfreien Nearest-Neighbor-Resizer auf Basis der
Standardbibliothek. Original wird ueber FDN-09 (dmscrypto) entschluesselt,
Thumbnail wird ebenfalls verschluesselt im Objekt-Storage abgelegt -
gleiches Schutzniveau wie das Original.

upload.Service bekommt SetThumbnailEnqueuer (neue, optionale Methode -
bestehende Aufrufer/Tests unveraendert funktionsfaehig) und reiht nach
erfolgreichem Complete() die Vorschau-Erzeugung ein; ein Fehlschlag dabei
laesst den bereits abgeschlossenen Upload nicht ruecktwirkend fehlschlagen.

Pruefungen:
1. Beschaedigte PDF-Datei -> Fehlerstatus statt Absturz, blockiert andere
   Jobs nicht (echter pdftoppm-Fehlschlag, echte SKIP-LOCKED-Job-Queue)
2. Durchsatztest: 500 echte PDFs, alle erfolgreich, ~15s Gesamtlaufzeit
3. Cache-Invalidierung bei neuer Revision: GetCurrentForDocument loest
   nachweislich auf die neue statt die alte Revision auf

Regressionsfrei: 13 bestehende upload-/jobqueue-Tests unveraendert PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
internal/documentapi: neue Backend-Voraussetzung, die DOC-01/DOC-04 noch
nicht hatten - GET /documents/{id}/content liefert den entschluesselten
Originalinhalt der aktuellen Revision, Content-Type ausschliesslich ueber
Magic-Bytes, Content-Disposition: inline (kein Download-Zwang), volle
HTTP-Range-Unterstuetzung ueber http.ServeContent (Standardbibliothek) -
darueber laedt PDF.js grosse Dokumente automatisch seitenweise nach.

web/pdf-viewer: Next.js/React/TypeScript, PDF.js ausschliesslich auf
Canvas gerendert (kein iframe, siehe "Bekannte Fehler vermeiden"). Worker
laeuft aus public/pdf.worker.min.mjs (npm run copy-pdf-worker, Teil von
predev/prebuild) - bewusst NICHT ueber ein externes CDN, damit die
Anwendung vollstaendig offline/lokal lauffaehig bleibt. lib/viewerState.ts
kapselt Seiten-/Zoom-Navigation und Tastaturbedienung als reine,
DOM-unabhaengige Funktionen (Akzeptanzkriterium 2), Suche nutzt PDF.js'
eigene Textextraktion.

Pruefungen:
1. 300-seitiges PDF oeffnet im Zeitbudget - echter pdfjs-dist-Ladetest
   gegen den echten Backend-Dienst: 247ms (Budget 5000ms)
2. Viewer per Tastatur bedienbar - 15 Vitest-Tests fuer Pfeiltasten/Zoom/
   Suche-Fokus, inkl. Ignorieren waehrend im Suchfeld getippt wird
3. Speicherverbrauch im Browser - OFFEN, kein Headless-Browser in dieser
   Umgebung verfuegbar (gleiche Einschraenkung wie bei ANA-02)

SHL-01 (Core-Board, eigener Branch-Strang mit gemeinsamer Basis) sauber
gemergt - kein Dateiueberschneidungs-Konflikt (web/shl vs. dms/).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migration 0006 ergaenzt folders.path (Materialized Path aus Ordner-IDs,
text_pattern_ops-Index) - Teilbaum-Abfragen werden damit zu einem
Index-Scan statt einer rekursiven CTE ueber parent_folder_id. Pfad
besteht aus IDs, nie aus Namen, daher aendert Umbenennen nie einen Pfad.

internal/folder: Create/Get/Rename/Move/Subtree. Move aktualisiert den
gesamten Teilbaum in einer einzigen UPDATE-Anweisung (Praefix-Ersetzung)
und verhindert Zyklen (Ziel ist der Ordner selbst oder ein eigener
Nachfahre).

Pruefungen:
1. Zyklus beim Verschieben verhindert (3 faelle: selbst, kind, enkel)
2. Teilbaum-Abfrage bei 10.001 Ordnern: 28,4ms (Budget 500ms)
3. Umbenennen aendert pfad von ordner UND kindern nachweislich nicht

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
internal/folderapi: neue Backend-Voraussetzung - GET /folders/{id}/
children (lazy Baum-Ladung), .../breadcrumb (ueber DOC-09s Materialized
Path), .../documents (Dokumentenliste, whitelisted Sortierspalten).
folder.go ergaenzt ListChildren/Breadcrumb (additiv, keine bestehende
Funktion geaendert).

web/dms-browser: Next.js/React/TypeScript. Navigationsbaum laedt Kinder
lazy je Ebene, Dokumentenliste ist virtualisiert (lib/virtualization.ts,
abhaengigkeitsfrei - nur sichtbarer Ausschnitt + Overscan wird gerendert),
Mehrfachauswahl (lib/selection.ts: Klick/Strg/Umschalt), Tastatur-
navigation im Baum (lib/treeKeyboard.ts). Aktueller Ordner steckt in der
URL (Catch-all-Route /browse/[[...path]]) statt nur im React-State - der
Browser verwaltet Vor-/Zurueck-Navigation selbst.

Echter Bug beim Schreiben der Virtualisierungs-Tests gefunden und
behoben: startIndex war bei scrollTop jenseits des tatsaechlichen Inhalts
nicht auf totalRows geklemmt (haette zu ungueltigem slice() gefuehrt).

Pruefungen:
1. Liste mit 10.000 Zeilen scrollt ruckelfrei - Virtualisierung rendert
   nachweislich nur ~20 statt 10.000 Zeilen
2. Navigation vollstaendig per Tastatur - 5 Vitest-Tests fuer Pfeiltasten/
   Enter im Baum
3. Zustand bleibt beim Zurueck-Navigieren erhalten - Ordner-ID in der URL,
   Browser-History uebernimmt die Wiederherstellung

Regressionsfrei: alle bestehenden Go-Pakete PASS.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migration 0007 ergaenzt tags.color/parent_tag_id (Ticket-Auftrag: Farbe
und optionale Hierarchie - FDN-02 legte nur die flache Entitaet an).

internal/tag: Create/Rename/Get/ListAll/ListForDocument/Assign/Unassign/
BulkAssign/Delete/Merge. Listen-Funktionen liefern grundsaetzlich []
statt nil (siehe "Bekannte Fehler vermeiden"). Merge uebertraegt alle
Zuordnungen des Quell-Tags auf den Ziel-Tag (ON CONFLICT DO NOTHING nur
gegen doppelte Kombinationen, keine Zuordnung geht verloren) und loescht
den Quell-Tag. Loeschen eines Tags entfernt ueber ON DELETE CASCADE nur
die document_tags-Zeilen, nie das Dokument selbst.

Pruefungen:
1. Zusammenfuehren erhaelt alle Zuordnungen, auch bei vorheriger
   Doppelzuordnung eines Dokuments
2. Doppelte Tag-Namen verhindert (Anlegen UND Umbenennen)
3. Massenzuweisung an 500 Dokumente: 14,4ms (Budget 2s) ueber ein
   einziges INSERT...SELECT unnest(...)-Statement

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migration 0008 ergaenzt required/default_value/validation_rule (JSONB)
und eine Versionsnummer auf metadata_fields, plus die ausschliesslich
anfuegbare metadata_field_versions-Historie - jede Aenderung einer
Felddefinition (auch Typwechsel) erhoeht die Version und haengt einen
neuen Eintrag an, aendert nie einen bestehenden.

internal/metadatafield: Create/Update/Get/ListAll/Validate/SetValue/
ValueFor/Versions. Validate deckt alle 5 Feldtypen ab (text: optionales
Regex-Muster, number: min/max, date: JJJJ-MM-TT, bool: true/false,
select: Options-Liste). SetValue validiert VOR dem Speichern.

Pruefungen:
1. Ungueltiger Wert abgewiesen mit feldbezogener Meldung (*ErrValidation)
2. Typaenderung mit Bestandsdaten: altes Datum bleibt lesbar/unveraendert,
   erneutes Speichern desselben Werts greift gegen die NEUE Regel
3. Pflichtfeld ohne Standardwert blockiert leeren Wert, MIT Standardwert
   nicht

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migration 0009: audit_log mit BEFORE UPDATE/DELETE-Trigger, der jede
Zeilenmutation technisch ablehnt (kein Anwendungskonvention). Jeder
Eintrag ist per SHA-256 mit seinem Vorgaenger verkettet.

internal/audittrail: Append (mit Advisory-Lock gegen Wettlaufsituationen
in der Hash-Kette), List, VerifyChain (rechnet die gesamte Kette nach
statt einem gespeicherten Flag zu vertrauen).

Pruefungen:
1. Manipulationsversuch erkannt - gefaelschter Eintrag per direktem
   INSERT (Trigger blockiert nur UPDATE/DELETE) mit erfundenem
   entry_hash wird von VerifyChain zuverlaessig erkannt
2. Vollstaendigkeit gegen 3 Referenzoperationen abgeglichen
3. Protokollschreiben verlangsamt Kernoperationen nicht spuerbar -
   1,68ms je Append im Schnitt (Budget 20ms)

RBAC-02 (Ausgangslage) beruehrt keine der Akzeptanzkriterien/Pruefungen
direkt (reine Protokollmechanik) - Zugriffsbeschraenkung auf einen
kuenftigen Lese-Endpunkt ist Sache einer Folgekachel mit HTTP-Schicht.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migration 0010: document_locks (Ausleih-Sperre mit Lease-Frist) und
file_revisions.comment (Check-in-Kommentar gehoert zur neuen Revision).

internal/versioning: CheckOut (atomar via INSERT...ON CONFLICT...WHERE
lease_until < now(), Postgres entscheidet pro Zeile, wer bei
gleichzeitigen Versuchen gewinnt), ForceUnlock, CheckIn (prueft
Sperrinhaberschaft, verschluesselt+speichert als NEUE Revision, gibt
Sperre frei), ListRevisions.

Pruefungen:
1. Zwei gleichzeitige Ausleihversuche - nur einer gewinnt
2. Verwaiste Sperre (10ms Frist) wird automatisch geloest
3. Alte Revision nach Check-in real aus Storage geladen, entschluesselt
   und byteweise unveraendert verifiziert

RBAC-Rollenpruefung fuer ForceUnlock (Akzeptanzkriterium 3: "durch
Administratoren aufhebbar") ist Sache einer HTTP-Schicht, die fuer diese
Kachel noch nicht existiert - gleiche Scope-Entscheidung wie VER-05.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
versioning.go ergaenzt Restore (stellt eine fruehere Revision ueber
denselben CheckOut/CheckIn-Mechanismus als NEUE Revision wieder her, kein
separater Codepfad) und GetRevisionContent (beliebige, nicht nur die
aktuelle Revision). Revision.CreatedBy ergaenzt fuer Autor-Anzeige.

internal/versioningapi: Liste/Content/Restore als HTTP-Endpunkte.
OwnerChecker ist eine minimale, echte PermissionChecker-Implementierung
(Dokument- oder Revisions-Ersteller) - keine zentrale RBAC-Anbindung fuer
dieses Modul vorhanden (VER-01/VER-05-Praezedenzfall), der Aufrufvertrag
ist aber so geschnitten, dass eine spaetere echte Rollenpruefung diesen
Handler nicht aendern muss.

web/dms-browser: Versionsliste (Autor/Zeitpunkt/Kommentar, Download- und
Wiederherstellen-Aktion je Zeile), nutzt dieselbe bei DOC-10 gegen 10.000
Zeilen bewiesene Virtualisierungs-Logik.

Pruefungen:
1. Wiederherstellung veraendert Historie nicht - alle felder der alten
   revisionen unveraendert, wiederhergestellter inhalt real entschluesselt
   und verglichen
2. 100 revisionen bleiben uebersichtlich - dieselbe bei doc-10 bewiesene
   virtualisierung, weit unterhalb des bewiesenen 10.000er-bereichs
3. berechtigung wird JE revision geprueft - nicht-ersteller sieht
   accessible:false und bekommt 403, ersteller kann zugreifen (200)

Regressionsfrei: go test ./... -p 1 alle pakete PASS, 22/22 vitest-tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Myers-Zeilendiff (DiffText) und Metadaten-Vorher-Nachher (DiffMetadata) im
versioning-Paket, DiffRevisions als fertige Schnittstelle für VER-04.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diff-Endpunkt (GET /documents/{id}/diff) im versioningapi-Handler, Diff-Seite
mit freier Revisionswahl, Farbe+Symbol je Aenderung und Sprung zur naechsten
Aenderung im dms-browser-Frontend.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
trashed_at (Dokumente+Ordner) als reversibler Papierkorb-Status, getrennt von
deleted_at (DOC-16: endgueltige Vernichtung). Kaskadierendes Loeschen/
Wiederherstellen von Ordnerteilbaeumen ueber Materialized Path, PurgeExpired
als Aufraeum-Job (entfernt Dateiobjekte + Datenbankzeile nach Frist).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
bulkedit.Apply orchestriert Tag-/Metadatenfeld-Massenaenderungen ueber eine
Dokumentauswahl, ueberspringt gesperrte Dokumente einzeln (VER-01-Sperre als
Platzhalter fuer eine spaetere Archive-Retention-Pruefung) und schreibt einen
Audit-Log-Eintrag pro Vorgang.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Dokumententypen buendeln Pflichtfelder, Standard-Tags und Aufbewahrungs-
vorgabe. Typwechsel erzwingt Pflichtfelder und verlangt Bestaetigung bei
drohendem Datenverlust statt stillschweigend zu loeschen. Ordner-Vorbelegung
greift automatisch beim Upload.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
trashapi (Liste mit Restlaufzeit, Massen-/Einzelwiederherstellung,
sofortige endgueltige Loeschung) als Backend-Voraussetzung, Papierkorb-Seite
im dms-browser mit Bestaetigungsdialog vor jeder endgueltigen Loeschung.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CasePanel zeigt Akte-Zugehoerigkeit, Vermerke und offene Wiedervorlagen in
der Dokument-Detailansicht ueber die externe Workflow-Board-API (VOR-04) an.
Kein eigenes Akte-Datenmodell, Ausfall der externen API blockiert nur diesen
Bereich.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
metadataapi/tagapi/bulkeditapi als Backend-Voraussetzung. MetadataForm
passt sich der Felddefinition an und warnt vor Verwerfen ungespeicherter
Aenderungen, TagInput schlaegt Tags vor und ist per Pfeiltasten/Enter
bedienbar, Massenbearbeitung wendet Tags auf beliebig grosse Auswahlen in
einem Aufruf an.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
audittrail.ListFiltered (Zeitraum/Akteur/Aktion) + auditapi (Liste,
gestreamter CSV-Export, Allowlist-Zugriffspruefung) als Backend-
Voraussetzung. Audit-Log-Seite im dms-browser mit Filterformular und
Export derselben gefilterten Menge.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
REVOKE UPDATE/DELETE fuer die nexarch_app-Gruppenrolle auf audit_log
(analog zu Core AUD-07), Trigger protokolliert Aenderungsversuche zusaetzlich
per RAISE WARNING vor der Ablehnung. VerifyChain bleibt unveraendert
funktionsfaehig.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
patrick closed this pull request 2026-09-26 00:24:37 +02:00

Pull request closed

This pull request cannot be reopened because the branch was deleted.
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: scripte/nexarch#30