Gesamtprojekt: Integrationsstand Core + DMS + Mail + Archive (nicht mergen) #104
Closed
patrick
wants to merge 0 commits from
integration/gesamtprojekt into main
pull from: integration/gesamtprojekt
merge into: scripte:main
scripte:main
scripte:fix/ai-app-write-timeout
scripte:chore/migration-kanban-board
scripte:chore/connect-kanban-board
scripte:chore/workflow-kanban-board
scripte:chore/archive-kanban-board
scripte:chore/mail-kanban-board
scripte:chore/dms-kanban-board
scripte:chore/core-kanban-board
scripte:chore/ai-kanban-board
scripte:chore/ai-kanban-security-folgetickets
scripte:feat/ai-app-deployment
scripte:feat/ai-app-vorschlaege
scripte:feat/ai-cls09-herkunftsmarkierung
scripte:feat/ai-eng09-gemini
scripte:feat/ai-cls08-mehrsprachig
scripte:feat/ai-titel-korrespondent
scripte:feat/dms-backup-s3
scripte:feat/dms-s3-verdrahten
scripte:docs/alfresco-vergleich
scripte:refactor/common-batch-2
scripte:refactor/common-modul-pilot
scripte:fix/dms-httpharden-normalize
scripte:feature/shl-02-pagecontainer-dedup
scripte:test/dedup-fakekek-testutil
scripte:feature/nav-01-mail-postfach-registry-eintrag
scripte:feature/sec-08-09-rbac-cmis-audit
scripte:feature/shl-04-globale-modul-navigation
scripte:chore/mail-kanban-security-folgetickets
scripte:chore/dms-kanban-security-folgetickets
scripte:chore/core-kanban-security-folgetickets
scripte:chore/archive-kanban-security-folgetickets
scripte:chore/workflow-kanban-security-folgetickets
scripte:chore/connect-kanban-security-folgetickets
scripte:feature/qa-08-barrierefreiheits-audit-workflow
scripte:feature/tsk-04-aufgaben-oberflaeche-workflow
scripte:feature/qa-08-barrierefreiheits-audit-connect
scripte:feature/int-18-smtp-gateway-weiterleitung-an-feste-adresse-dms
scripte:docs/rollout-131-vorbereitung
scripte:feature/met-08-facturx-zugferd-szenarien-validator-dms
scripte:feature/qa-15-sicherheitspruefung-gesamtprojekt
scripte:fix/testrun-2026-09-21-dms
scripte:feature/cmp-07-dsgvo-loeschantrag-recht-auf-loeschung
scripte:feature/cmp-06-vier-augen-freigabe-fuer-loeschungen
scripte:feature/cmp-02-dsgvo-datenschutz-berichte
scripte:feature/ret-03-loeschworkflow-und-aufbewahrungssperre-legal-hold
Reference in new issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Nicht mergen — Integrations-/Review-Stand.
Zusammenführung aller Boards mit Code auf Basis von main: Core (10 Tips), DMS (integration/dms-gesamtstand), Mail (int-12, src-12), Archive Neu-Strang (ana-03, qa-06). Details, Konflikte, Migrationsinventar und offene Punkte: docs/GESAMTPROJEKT-PRUEFPROTOKOLL.md.
Geprüft auf Host 204 (frische Klone, Wegwerf-DBs): Build und vet je Modul sauber; Tests Core 52 Pakete, Archive 24, Mail 36, DMS 56 ok (Core nach Behebung von 3 Testfixture-Befunden). Gefundene und behobene Fehler: account-api/auth.NewHandler-Signatur (IAM-16 x IAM-17), Standardport 8094 doppelt (kennzahlen-api -> 8096), TEN-09/loadtest/tenantadmin-Testschemas.
Offen: Core-Vertragslücken zu DMS (JWKS-Route, Rolle im Modul-JWT, dms.*-Rechte, Legal-Hold-Endpunkt), Alt-Strang cmp-02/06/07, gofmt-Bestand, Migrationsnummern-Dubletten (nicht umbenannt).
🤖 Generated with Claude Code
Registry-DB (nur Tenant-Metadaten), Provisioning-Routine legt pro Mandant eine physisch isolierte Postgres-DB an und registriert sie transaktional (Rollback der DB bei fehlgeschlagener Registrierung). Schlanker HTTP-Handler als Schnittstellen-Vorbereitung fuer API-01/TEN-02, kein eigenes REST-Grundgerüst. Pruefungen: 1. Migration up/down geschrieben (0001_tenant_registry.{up,down}.sql) — nicht gegen echte DB ausgefuehrt, da auf dieser Maschine kein Go/Postgres-Test- Setup verfuegbar ist. Offen zur Ausfuehrung. 2. Integrationstest TestProvision_CreatesIsolatedDatabases geschrieben (zwei Mandanten, prueft unterschiedliche db_name und current_database()) — ebenfalls nicht ausgefuehrt, guarded per TEST_ADMIN_DSN env var. Offen. 3. Slug-Validierung (unit test TestValidateSlug) deckt SQL-Injection-Versuch im Datenbanknamen ab — ebenfalls nicht lokal ausgefuehrt, da kein Go Compiler auf dieser Maschine vorhanden ist. Offen. Alle drei Pruefungen sind vorbereitet, aber NICHT durchgefuehrt worden — zaehlen laut Vorgabe als offen bis auf einer Maschine mit Go+Postgres verifiziert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>internal/apiserver: Server.Handle(version, pattern, h) registriert Routen unter /api/{version}/... (Akzeptanzkriterium 1) — verschiedene Versionen sind unabhaengige Pfade im ServeMux, eine neue Version beeintraechtigt bestehende nicht. HandleV1 ist die Kurzform fuer die aktuelle Hauptversion. Einheitliches Fehlerschema {"error":{"code","message"}} ueber WriteError (Akzeptanzkriterium 2) — bewusst NICHT auth.RequireAuth aus IAM-02 wiederverwendet, da dessen Klartext-Fehlerantworten nicht zum einheitlichen JSON-Schema passen wuerden; stattdessen authAndTenantContext nutzt auth.TokenIssuer.Verify direkt (dieselbe Kryptographie, keine Duplikation) und antwortet im API-01-Schema, auch bei 401. authAndTenantContext ist die Middleware, die JEDEM ueber Handle registrierten Endpunkt Tenant-/Benutzerkontext bereitstellt (Akzeptanzkriterium 3, ueber apiserver.FromContext abrufbar) — kein Handler prueft Auth selbst. loggingMiddleware protokolliert jede Anfrage strukturiert. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Fehlerhafte Anfrage liefert ueber mehrere Endpunkte hinweg dasselbe Fehlerschema (Stichprobe) — TestErrorFormat_ConsistentAcrossEndpoints: zwei unabhaengige Endpunkte, beide liefern 401 im identischen {"error":{"code","message"}}-Schema. PASS. 2. Middleware-Kette nachweislich von jedem Endpunkt durchlaufen — TestMiddleware_SetsRequestContextForEveryEndpoint: zwei Endpunkte lesen RequestContext, beide erhalten korrekten UserID/TenantSlug aus dem Token. PASS. 3. Versionswechsel (fiktive v2-Route) ohne v1 zu beeintraechtigen — TestVersioning_V2DoesNotAffectV1: v1 vor und nach Anlage von v2 liefert unveraendert dieselbe Antwort. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>internal/ldapsync: Client ist eine Schnittstelle (Search() liefert Entry- Liste) — in dieser Umgebung steht kein echter LDAP/AD-Server zur Verfuegung (analog AUD-05/Archive), daher ist die Sync-/Rollenzuordnungs-Logik vollstaendig gegen einen Fake getestet, eine echte Verbindungspruefung gegen LDAP/AD steht noch aus. RoleMappingStore (Akzeptanzkriterium 3): explizite Erlaubnis-Matrix LDAP-Gruppe -> Rolle. HighestRoleFor vergibt NUR fuer explizit gemappte Gruppen eine Rolle — eine unbekannte Gruppe, auch wenn ihr Name zufaellig wie eine interne Rolle aussieht ("tenant_admin", "superadmin"), traegt strukturell nichts bei (kein Code-Pfad, der eine ungemappte Gruppe je einer Rolle zuordnet) — behebt die aus archivmail bekannte Privilege-Escalation- Fehlerklasse von Grund auf statt nachtraeglich zu haerten. Syncer.Run ruft Search() als ALLERERSTES auf; schlaegt es fehl, wird ohne jede Aenderung an bestehenden Konten abgebrochen (Akzeptanzkriterium 2). Pro Eintrag isolierte Fehler landen in SyncResult.Failed, ohne andere Eintraege zu beeintraechtigen. Deaktivierung in LDAP wird als users.Deactivate uebernommen (Akzeptanzkriterium 3). ldap_config speichert bewusst nur den NAMEN einer Umgebungsvariable fuer das Bind-Passwort, nie das Passwort selbst. internal/rbac (RBAC-01) wurde 1:1 aus dem rbac-01-Branch uebernommen (git show aus derselben Repo-Historie) — IAM-05 haengt an RBAC-01 fuer die Rollenzuweisung, beide Boards leben aber auf getrennten, noch nicht gemergten Feature-Branches ohne gemeinsame Historie. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Rollen-Erlaubnis-Matrix mit Positiv- und Negativfaellen automatisiert getestet (keine Privilege-Escalation) — TestRoleMapping_PositiveAndNegativeCases: gemappte Gruppe liefert Rolle, unbekannte Gruppe und rollen-aehnlich benannte, aber nie gemappte Gruppen liefern keine. PASS. 2. Synchronisationslauf mit fehlerhafter/nicht erreichbarer LDAP-Quelle bricht kontrolliert ab, ohne bestehende Konten zu beschaedigen — TestSyncer_AbortsCleanlyOnSourceError: bestehendes Konto bleibt nach fehlgeschlagenem Lauf unveraendert aktiv. PASS. 3. Deaktivierung eines Benutzers in LDAP wird bei naechster Synchronisation korrekt uebernommen — TestSyncer_AppliesDeactivationOnNextRun: erster Lauf legt aktiven Benutzer an, zweiter Lauf mit Disabled=true setzt ihn auf inaktiv. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>internal/channels: konkrete Zustellkanaele fuer CFG-02s Dispatcher. TemplateStore.Resolve loest Vorlagen pro Tenant auf und faellt auf GlobalTemplateScope zurueck, wenn ein Tenant keine eigene gesetzt hat (Akzeptanzkriterium 3). Render nutzt text/template mit Option("missingkey=error") — ein fehlender Platzhalter bricht das Rendering MIT FEHLER ab, statt eine unvollstaendige Nachricht zu erzeugen (Akzeptanzkriterium 1). EmailSender implementiert notify.Sender: rendert ZUERST die Vorlage, bevor ueberhaupt eine SMTP-Verbindung aufgebaut wird — schlaegt das Rendering fehl, wird nie ein Netzwerkzugriff versucht. Ein anschliessend fehl- schlagender SMTP-Versand liefert einen Fehler, den CFG-02s bereits getestete Wiederholungslogik verarbeitet (kein zweiter Retry-Mechanismus hier). InAppSender persistiert In-App-Nachrichten ueber InAppStore (Akzeptanzkriterium 2, ueber API abrufbar/als gelesen markierbar). Router waehlt den Kanal anhand Notification.Channel — ein neuer Kanal wird per Register() ergaenzt, ohne Dispatcher oder Router umzubauen. Pruefungen (ausgefuehrt auf root@192.168.1.131, go build/vet/test PASS): 1. Vorlagenrendering mit fehlenden Platzhaltern bricht kontrolliert ab — TestRender_MissingPlaceholderAborts und TestEmailSender_AbortsBeforeSMTPWhenTemplateMissing (Fehler kommt von der Vorlagenaufloesung, kein SMTP-Verbindungsversuch). PASS. 2. In-App-Benachrichtigung nach Markierung als gelesen korrekt gefuehrt — TestInAppStore_MarkReadIsReflectedCorrectly. PASS. 3. E-Mail-Versand bei nicht erreichbarem SMTP-Server loest dokumentiertes Retry-Verhalten ueber CFG-02 aus — TestEmailSender_TriggersDispatcherRetryOnUnreachableSMTP: echter EmailSender gegen unerreichbaren Host, ueber notify.Dispatcher eingereiht, nach ausgeschoepften Wiederholungen status=failed mit korrekter Versuchszahl. PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Such-API mit Ranking (Relevanz, Datum, Anhangstreffer), mandantengetrennt, mit Grundoperatoren (Phrase, Ausschluss). - client.go: Search nutzt jetzt Manticores query_string-Klausel statt match — unterstützt Phrasensuche ("...") und Ausschluss (-wort) nativ, Wert bleibt reiner JSON-String ohne dynamischen Feldnamen. - fieldWeights (statische Konstanten: subject=10, body=3, attachment_text=1) über die Manticore-Option field_weights — Ranking berücksichtigt Anhangstreffer, Result.Score macht es nachvollziehbar. - Bestehenden SRC-01-Injection-Test an die neue query_string-Struktur angepasst (gleiche Funktion weiterentwickelt). Prüfungen (alle real durchgeführt, siehe mail/docs/SRC-03-PRUEFPROTOKOLL.md): 1. TestSearch_TenantIsolation (SRC-01, weiterhin gültig). 2. TestSearch_PhraseAndExclusionOperators: Phrase und Ausschluss liefern real erwartete Teilmengen. 3. TestSearch_PerformanceWithLargeCorpus: Suche über 1000 reale Dokumente in 775,8µs (Ziel 500ms) gegen echtes Manticore auf 192.168.1.131. Zusätzlich TestSearch_RankingReflectsFieldWeightAndIsTraceable für Akzeptanzkriterium 1. Kein Umbau: dedup/indexworker/storage/crypto/encstorage unverändert. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQPrüfgate für Archivierung & Suche: verbindliche Kriterien für Verschlüsselung, Speicherpfad-Konventionen und Suchqualität. - qagate/gate.go: RunTestSuites führt go test real über storage/crypto/ encstorage/search aus (inkl. ARC-08-Schlüsselrotation, SRC-10-OCR- Konfidenz). ScanSearchPathForDynamicSQL prüft jede Nicht-Test-Datei in mail/internal/search (außer reindex.go, dokumentierte DDL-Ausnahme) auf tatsächliche fmt.Sprintf(-Aufrufe — verallgemeinert die SRC-01- Prüfung auf den gesamten Suchpfad. GateResult.Report() liefert dokumentierten, zeitgestempelten Bericht. - Echten Fehlalarm gefunden und behoben: Kommentartext in fields.go ("...fmt.Sprintf/strings.Join...") wurde fälschlich als Verstoß erkannt — Suchmuster auf "fmt.Sprintf(" präzisiert. Prüfungen (alle real durchgeführt, siehe mail/docs/QA-03-PRUEFPROTOKOLL.md): 1. TestRun_RealGateAgainstCurrentARC08SRC10State: echter Gate-Lauf, Bericht real "BESTANDEN" mit Zeitstempel. 2. TestScanSearchPathForDynamicSQL_RealSearchPackagePasses + Negativ-/Ausnahmetests: automatisierte Codereview-Stichprobe bestätigt real statischen Query-Builder. 3. Unabhängiger Subagent (frischer Kontext) hat den Gate-Testlauf real erneut ausgeführt und den Suchpfad-Scan mit eigenem grep unabhängig verifiziert — "BESTANDEN, unabhängig bestätigt". Kein Umbau: storage/crypto/encstorage/search unverändert, QA-03 fügt ausschließlich das Gate selbst hinzu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQmailrules.Store (IMP-03) bekommt Update (bislang nur Create/List/ Delete) — gleiches Muster wie Create: Musterprüfung vor dem Schreiben, streng auf tenant_slug+id beschränkt, ErrNotFound bei fremder/nicht existierender ID. Neues Paket mail/internal/mailrulesapi: vier Endpunkte (GET/POST /api/v1/mail/rules, PUT/DELETE /api/v1/mail/rules/{id}), tenant-Query- Parameter Pflicht, gleiche Konvention wie mailapi (INT-01). Akzeptanzkriterium 3 ist strukturell garantiert: mailrulesapi ruft ausschließlich mailrules.Store auf, denselben Store, den IMP-03s Import-Pfad ohnehin verwendet — kein zweiter, paralleler Schreibpfad. Alle drei Pflichtprüfungen mit echten Nachweisen: vollständiger Anlegen/Priorisieren/Einsehen/Löschen-Zyklus über echte HTTP-Requests; eine über die API angelegte Regel wird über genau den Weg gelesen und ausgewertet, den IMP-03s Import-Pfad geht (Store.List -> mailrules.NewEngine -> Evaluate) und liefert das korrekte Klassifizierungsergebnis; Mandant Bs Update-Versuch mit der echten, bekannten ID von Mandant As Regel liefert 404, Mandant As Regel bleibt unverändert. go build/go vet/golangci-lint clean, gesamtes Mail-Modul regressionsfrei getestet — bestehende mailrules-Tests (IMP-03/IMP-09) bleiben nach der Update-Erweiterung unverändert grün.Zwei kleine Nacharbeiten aus dem Testlauf auf 131: - ConfigStore.Save nutzte einen Multi-Statement-Exec (DELETE; INSERT), den pgx im extended protocol ablehnt ("cannot insert multiple commands into a prepared statement") -- jetzt als Transaktion mit zwei Exec-Aufrufen. - Das Test-Setup fuer die tenants-Tabelle in config_store_test.go war schlanker als das in internal/kek/kek_test.go verwendete Vollschema (fehlende Spalten previous_status/deletion_scheduled_at/ retention_block_reason/retention_checked_at). Da beide Pakete im Gesamttestlauf dieselbe Test-Datenbank per CREATE TABLE IF NOT EXISTS teilen, hat das schlankere Schema (wenn zuerst angelegt) den kek-Test TestTenantKEKHandler_AllowsActiveModuleForTenant zum Fehlschlagen gebracht. Jetzt identisches Vollschema wie kek_test.go. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbPAUD-02 hat bereits einen Trigger, der UPDATE/DELETE auf audit_events per RAISE EXCEPTION ablehnt (Akzeptanzkriterium 3 aus AUD-07 war damit schon erfuellt). Zwei Luecken blieben: (1) die Anwendungsrolle hatte weiterhin UPDATE/DELETE-Rechte auf DB-Ebene, der Schutz war nur ein Code-Pfad (Trigger), keine fehlenden Rechte; (2) ein abgelehnter Aenderungsversuch wurde nirgends selbst protokolliert. - migrations/0010_audit_events_role_enforcement.{up,down}.sql: Gruppenrolle nexarch_app ohne LOGIN/Passwort, REVOKE ALL + GRANT INSERT, SELECT auf audit_events fuer PUBLIC/nexarch_app; Trigger-Funktion um RAISE WARNING mit Akteur/Session/Client-Kontext ergaenzt, bevor der Versuch weiterhin per RAISE EXCEPTION abgelehnt wird (RAISE WARNING ist nicht transaktional, landet also auch bei Rollback im Postgres-Server-Log). - internal/audit/append_only_test.go: Tests je Akzeptanzkriterium - TestAppendOnly_AppRoleHasNoUpdateOrDeletePrivilege (AC1/Pruefung1, erwartet "permission denied" statt Trigger-Fehler), TestAppendOnly_TamperAttemptIsLogged (AC2/Pruefung2, faengt die RAISE-WARNING-Notice ueber pgx OnNotice ab), TestAppendOnly_NormalWriteAndReadStillWork (AC3/Pruefung3, Regression). Pruefungen auf 192.168.1.131 durchgefuehrt: 1. UPDATE/DELETE mit Anwendungsrolle (Mitglied nexarch_app) -> "permission denied for table audit_events" statt Trigger-Fehler: PASS. 2. Ablehnungsversuch selbst protokolliert (RAISE WARNING mit Akteur/Session/Zeitpunkt, unabhaengig von Transaktions-Rollback) via pgx-Notice-Capture nachgewiesen: PASS. 3. Regression Schreiben/Lesen (Record + CountByTenant, mehrere Eintraege): PASS. Gesamtlauf go test ./... -p 1 -count=1: internal/audit vollstaendig gruen (alle 22 Tests inkl. der 3 neuen). Bestehende, dokumentierte Altlasten unveraendert beobachtet (nicht Bestandteil dieser Kachel): internal/migrate (TestRolloutAll_* reihenfolgeabhaengig), internal/notify (TestDispatcher_EnqueueAndProcess flakt), internal/pentest (TestPentest_KEK_CompromisedMasterKeyCannotDecryptAfterRotation), internal/user (verwaiste test_iam01_users/test_iam01_superadmins-DBs). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbPinternal/serviceaccount/anomaly.go: rein additive Ergaenzung zu IAM-09 (serviceaccount.go bleibt unveraendert) — VerifyWithContext ruft Verify unveraendert auf und beobachtet danach Herkunfts-IP und Zugriffsrate eines gueltigen Tokens. Eine bisher nicht beobachtete IP-Range (grob gebuckelt auf /24 bzw. /64) oder ein gegenueber der EWMA-Basislinie stark abweichender Zugriffsratenanstieg (>5x schneller als gewohnt, erst ab 3 Beobachtungen bewertet) erzeugt ein Audit-Event mit metadata.severity=elevated ueber die zentrale AUD-01-Schreibschnittstelle (Akzeptanzkriterium 1 + 2). Optional ist ein CFG-02-Dispatcher anschliessbar (WithNotify) fuer eine Benachrichtigung des Token-Besitzers/Tenant-Admins. Keine automatische Sperre: eine erkannte Anomalie ruft niemals Revoke auf, das Token bleibt gueltig (Akzeptanzkriterium 3) — reines Hinweissignal, wie im Auftrag gefordert (bewusst vermeiden: Keycloak, wo Anomalie-Erkennung nur optionale Zusatzkonfiguration ist). migrations/tenant/0007_api_token_anomaly.{up,down}.sql: zwei neue Tabellen (api_token_ip_ranges, api_token_rate_state), keine Aenderung an api_tokens/ audit_events selbst. Pruefungen (ausgefuehrt auf root@192.168.1.131, go test -p 1 PASS): 1. Zugriff aus neuer IP-Range erzeugt erwarteten Audit-Event — TestVerifyWithContext_NewIPRangeTriggersAuditEvent: erster Zugriff legt Basislinie an (kein Alarm), zweiter Zugriff aus anderer Range erzeugt genau 1 Event mit severity=elevated. PASS. 2. Kuenstlich stark erhoehte Zugriffsrate erzeugt erwarteten Audit-Event — TestVerifyWithContext_RateSpikeTriggersAuditEvent: Basislinie mit ~120ms-Abstand etabliert, Burst-Zugriff ohne Abstand erzeugt genau 1 Raten-Anomalie-Event. PASS. 3. Regressionstest: normale, unauffaellige Nutzung erzeugt keine Falsch-Alarme — TestVerifyWithContext_NormalUsageNoFalseAlarms: 8 Zugriffe gleiche IP-Range, gleichmaessiger Abstand, 0 Events. PASS. Zusaetzlich: TestVerifyWithContext_AnomalyDoesNotRevokeToken belegt Akzeptanzkriterium 3 direkt (Token nach erkannter Anomalie weiterhin gueltig). PASS. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPsyieCYwvbUBsnN598SbPinternal/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>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>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>DOC-01 lieferte bislang nur einen Go-Service (internal/upload), keinen HTTP-Endpunkt - dieselbe Luecke wie bei DOC-05/documentapi. Die Drag-and-Drop-Oberflaeche braucht die oeffentliche HTTP-Schnittstelle, nicht Go-Funktionsaufrufe. internal/uploadapi kapselt upload.Service hinter POST /uploads, PUT /uploads/{id}/chunk, GET /uploads/{id}, POST /uploads/{id}/complete, ohne Server-Logik zu duplizieren. Fehler werden auf 400 (ErrFileTooLarge/ErrDisallowedMimeType) bzw. 404 (ErrSessionNotFound) abgebildet, damit die Oberflaeche verstaendliche Meldungen zeigen kann. handler_test.go deckt den vollstaendigen Start->Chunk->Status->Complete- Zyklus ueber HTTP sowie beide Fehlerfaelle ab (TEST_TENANT_DSN-basiert, gleiche Fixture-Struktur wie internal/upload/service_test.go). Go-Build/-Test dieses Pakets konnte in dieser Session NICHT real auf dem Testhost (192.168.1.131) verifiziert werden - der rsync-Sync-Schritt wurde vom Auto-Mode-Classifier als Data Exfiltration blockiert. Siehe dms/docs/DOC-03-PRUEFPROTOKOLL.md fuer die vollstaendige Dokumentation dieser Einschraenkung. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Next.js/React/TypeScript auf web/shl (SHL-01), Next.js/Test-Aufbau nach dem Muster von web/pdf-viewer (DOC-05). lib/chunkUpload.ts laedt Dateien in 4-MiB-Chunks gegen dms/internal/uploadapi hoch, resumeSessionId fragt nach Abbruch per GET /uploads/{id} den zuletzt bestaetigten Offset ab und setzt dort fort (Akzeptanzkriterium 3, gleicher Server-Mechanismus wie DOC-01). lib/uploadQueue.ts ist reine, DOM-unabhaengige Reducer-Logik fuer Status/Fortschritt je Datei (Akzeptanzkriterien 1+2) - Ids werden VOR dem Dispatch erzeugt (createQueuedItem), nicht im Reducer selbst: ein frueher Versuch mit einer Id-Factory-Funktion im Reducer scheiterte real beim Testen (useReducer ruft den Reducer erst in der Render-Phase auf, Code direkt nach dispatch() sah noch ein leeres Ids-Array). components/Dropzone.tsx nutzt ein natives <label for>+<input type=file multiple> fuer Tastatur-/Dateidialog-Bedienung statt eigenem Tastatur-Handler. 14/14 Vitest-Tests (uploadQueue, chunkUpload, Dropzone, UploadManager), mindestens ein Test je Akzeptanzkriterium. npx tsc --noEmit sauber, npm run build erfolgreich. Pruefungen vor der Abnahme (Details siehe DOC-03-PRUEFPROTOKOLL.md): 1. Tastatur/Dateidialog bedienbar - bestanden (Dropzone.test.tsx) 2. Netzabbruch im Browser - real gegen echten HTTP-Mock-Server + echten Chromium (Playwright) getestet, echter net::ERR_CONNECTION_RESET, Retry fuehrt zum Abschluss 3. Mobile Ansicht (375x667) - real per Playwright geprueft, kein horizontales Scrollen, Bedienelemente vollstaendig erreichbar QA-03 (Pruefgate Dokumentenkern) bewusst NICHT implementiert - completeUpload() liefert lediglich die documentId als Anknuepfungspunkt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>mime.TypeByExtension(".txt") liefert je nach /etc/mime.types entweder "text/plain" oder "text/plain; charset=utf-8" — beide Varianten in der Positivliste des Fehler-Tests zulassen, statt eine feste Systemumgebung vorauszusetzen.CMIS 1.1 Browser Binding als duenne Uebersetzungsschicht ueber der REST-API (INT-01): jede Anfrage laeuft in-process durch dieselbe Authentifizierungs-, Rechte- und Mandantenkette. Optional per NEXARCH_DMS_APP_CMIS_ENABLED. Neu in REST: GET /documents/{id} und GET /folders/{id}. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>folderapi.handleDocuments/metadataapi verlangen fuer GET eine Identitaet (authz.Identity liest 'caller' oder X-User-Id). Ohne caller lieferte /folders/{id}/documents ein Fehlerobjekt statt eines Arrays, wodurch docs.some() abstuerzte. Erster echter Chrome-Lauf auf Host 204 deckte den latenten Fehler auf. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>RegisterRoutes ruft jetzt verifier.Expect("mail", "", true) selbst auf, statt sich auf den Aufrufer zu verlassen. Der e2e-Test deckte das echte Loch auf: ein gueltiges DMS-Token (aud=dms) wurde von der Mail-API akzeptiert. Dazu Vertragstests fuer aud-Fehlanpassung und fehlendes aud in beiden Paketen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Pull request closed