## Ergebnis ab Base `95c9c03` ### Befund - **Reviewstatus:** Dokumentlisten projizieren aktuell interne DB-Werte teilweise direkt (`read_api.py:1669–1671`). Unbekannte Pipeline-Zustände werden zwar zu `unknown`, aber der öffentliche Vertrag ist nicht sauber vom internen Workflow getrennt. - **Inhaltsstatus:** Im Detailvertrag fehlt ein expliziter `content_status`. Leere `sections` müssen clientseitig interpretiert werden. - **Originalstatus:** `original_available` (`read_api.py:1773`) prüft nur, ob ein Pfadfeld nicht leer ist. Nicht geprüft werden dabei Existenz, Root-Grenze, Symlinks, Dateityp, Größe und MIME-Magic. Dadurch kann die API Verfügbarkeit melden, obwohl HEAD/GET anschließend `404 original_not_available` liefert. - **Descriptor-Schutz:** Der eigentliche Originalabruf ist bereits gut gehärtet: komponentenweises `openat`/`O_NOFOLLOW`, `fstat`, Größen- und Magic-Prüfung sowie gepinnter Descriptor. HEAD und GET durchlaufen derzeit denselben Abrufzweig (`health_dashboard_server.py:844–879`). Diese Schutzmechanismen sollten zentralisiert, nicht ersetzt werden. - **Review-Gate:** Detail, Treffer und Original sind bereits auf geprüfte Dokumente begrenzt. Das darf nicht gelockert werden. ## Empfohlener minimaler Vertrag Öffentliche, fail-closed Enums: ```json { "review_status": "reviewed | pending_review", "content_status": "available | unavailable", "original_status": "available | unavailable" } ``` Alle unbekannten oder neuen internen Reviewwerte werden öffentlich zu `pending_review`; niemals optimistisch zu `reviewed`. ### `read_api.py` 1. Kleine zentrale Mapper ergänzen: - `public_review_status(raw)` → nur bei exakt `geprueft` = `reviewed`, sonst `pending_review`. - `content_status(row)` → nur bei geprüftem Dokument und mindestens einem normalisierten `document_chunks()`-Abschnitt = `available`. 2. In Listen- und Detailantworten die drei Statusfelder stets ausgeben. 3. `original_available` entfernen oder höchstens für eine kurze additive Übergangsphase aus `original_status == "available"` ableiten; nicht mehr aus einem Pfadstring. 4. Die vorhandene descriptorbasierte Originalprüfung aus dem Server in eine generische Funktion im Read-API-Modul ziehen, etwa: ```python open_record_original( connection_or_database, opaque_id, *, allowed_roots, max_bytes, ) -> OpenedOriginal | None ``` `OpenedOriginal` enthält ausschließlich Descriptor, Größe, MIME und generischen Downloadnamen. Sowohl Detail-Statusprüfung als auch Originalroute verwenden exakt diese Funktion. Für die Statusprüfung Descriptor sofort wieder schließen. 5. Keine Originalprüfung für jede Listenzeile durchführen; nur beim Dokumentdetail bzw. direkten Originalabruf. So bleibt Pagination günstig. ### `health_dashboard_server.py` - `_open_regular_beneath()` und `open_verified_original()` nicht dupliziert weiterführen, sondern die gemeinsame Funktion aus `read_api.py` importieren. - Für `/original` bei HEAD und GET: 1. authentisieren und Same-Origin prüfen, 2. dieselbe Descriptorfunktion aufrufen, 3. identische Status-/MIME-/Längen-/Security-Header senden, 4. nur bei GET den bereits geprüften Descriptor streamen, 5. Descriptor in jedem Pfad schließen. - Keine `Path.resolve()`, `is_file()` oder Pfadstring-Prüfung als Ersatz einführen. ## Tests ### `test_dashboard_v5_sprint6e_record.py` Additive Statusmatrix mit rein synthetischen Zeilen: - geprüft + normalisierter Inhalt → `reviewed / available` - geprüft + leerer Inhalt → `reviewed / unavailable` - ungeprüft + vorhandener Text → `pending_review / unavailable` - unbekannter interner Reviewwert → `pending_review`, nicht `unknown` oder `reviewed` - Statusfelder in Liste und Detail immer vorhanden - keine Pfade, Dateinamen oder internen Reviewwerte im Payload - ungeprüfte Details und Treffer bleiben `404` ### `test_dashboard_v5_sprint6e1_original.py` - Gültiges Original: Detail meldet `original_status == "available"`. - Fehlender Pfad, falsche Magic, zu groß, Verzeichnis, FIFO sowie Blatt-/Zwischen-Symlink: `unavailable`. - Ein bloß nichtleeres DB-Pfadfeld darf nie `available` erzeugen. - Parametrisierter HEAD/GET-Test: - gleicher HTTP-Status, - gleiche `Content-Type`, `Content-Length`, `Content-Disposition` und Schutzheader, - HEAD ohne Body, - GET streamt vom geprüften Descriptor. - Spy/Monkeypatch auf die gemeinsame Open-Funktion: genau ein descriptorbasierter Check pro HEAD bzw. GET; kein alternativer HEAD-Sonderpfad. - Austausch der Datei zwischen vorherigem Detailstatus und Abruf: HEAD/GET prüfen erneut und lehnen fail-closed ab. ## Verifikation - Bestehende fokussierte Tests: **13 bestanden**. - Arbeitsbaum und die untersuchten Dateien blieben **unverändert**. - Keine Dateien erstellt oder geändert; keine Schutzmechanismen gelockert.