## Outcome Mapped the document-original and extracted-text surfaces at base `039bbeaca97770af9f650754efd80d1977fd49d4`. The tree remained clean and unchanged. ### Current contracts and boundaries - **Document list:** `GET/HEAD /api/v1/documents` - Returns opaque `api-document-<24 hex>` IDs, metadata, four independent statuses, coverage, snippets, and signed keyset cursors. - IDs are deterministic SHA-256 projections of DB ID/date/category/institution; they expose no raw ID/path but change if those metadata change. - Location: `dashboard_v5/read_api.py:141-149,1835-2077`. - **Reviewed extracted content:** `GET/HEAD /api/v1/documents/{opaque}` - `_resolve_record_document(..., reviewed=True)` makes full extracted sections reviewed-only. - Shared chunking and bounded pagination are used. - Location: `read_api.py:2095-2161`. - **Reviewed FTS matches:** `GET/HEAD /api/v1/documents/{opaque}/matches` - Resolves reviewed-only and joins current `dokumente.review_status='geprueft'`, so stale indexed rows stop disclosing after downgrade. - Main and global FTS use the same current-status join and literal token quoting. - Location: `read_api.py:1928-1945,2164-2208`; migration indexes only reviewed rows at `document_fts_migrate.py:52-73`. - **Original:** `GET/HEAD /api/v1/documents/{opaque}/original` - Bearer/browser-session authentication, same-origin/fetch-site checks, exact route regex, no query parameters, `no-store`, `nosniff`, `no-referrer`, generic filename. - Opens every path component with `openat`/`dir_fd + O_NOFOLLOW`, validates the pinned descriptor with `fstat`, checks size and magic, and streams from that descriptor. - HEAD opens and validates independently; later GET reopens/revalidates. - Location: `health_dashboard_server.py:310-336,688-704,920-982`; primitive at `document_originals.py:51-115`. - **UI:** history cards show separate original/text/review/search statuses and five coverage cards. Reviewed extracted content and available originals receive actions. - Location: `dashboard-v5-record.js:248-298`. - **Synthetic coverage:** pending/reviewed/missing/unsupported/PDF/PNG fixtures plus HTTP auth, HEAD, symlink, traversal, magic, size, FIFO, replacement, no-leak, mobile, and browser-flow tests. - Main files: `test_dashboard_v5_sprint6e1_original.py`, `test_dashboard_v5_sprint6e4b.py`, `dashboard_v5_record_original_6e4b.spec.js`, `dashboard_v5_original_fixture.py`. ### Important gaps for Sprint A.3 1. **Technical original probing is coupled to content review.** `probe_original(..., reviewed=False)` returns `not_reviewed` without probing. Consequently pending documents report `not_checked`, even when the file is technically available, and cannot be opened for review. 2. **There is no pending extracted-preview API.** The only extracted-content endpoint is reviewed-only. The existing pending synthetic fixture already contains suitable preview text but has no safe UI action. 3. **Doctor-report documents are not currently reviewed-only.** `_doctor_report()` calls `_record_document_rows()` without `review_status=geprueft`; report documents and report overview can therefore include pending metadata despite UI copy claiming otherwise (`read_api.py:2561-2574,2589`). FTS is correctly reviewed-only. 4. **Legacy routes are a separate weaker boundary.** `/health-doc/` and `/health-report/` are outside `/api/v1`, lack API authentication/review gating, and use resolve-then-read rather than descriptor streaming (`health_dashboard_server.py:238-278,844-861,1038-1055`). A.3 must not claim global original protection unless these are separately hardened; if V4 is frozen, document this as an explicit legacy trust boundary. ## Exact minimal change set - **`document_originals.py`** - Extract the current filesystem body into a review-neutral `probe_original_file(...)`. - Keep `probe_original(..., reviewed=...)` as the reviewed-policy wrapper so laboratory reconciliation remains fail-closed. - Do not alter `_open_regular_beneath`, `O_NOFOLLOW`, `fstat`, magic/size validation, or descriptor ownership. - **`read_api.py`** - Use `probe_original_file()` for document-history technical availability and coverage. - Add exact route: - `GET/HEAD /api/v1/documents/{opaque}/extracted-preview` - Resolve any opaque document, return only bounded shared chunks plus review status and safe metadata; no paths, names, URLs, hashes, raw IDs, or FTS. - Keep existing document detail and `/matches` reviewed-only. - Pass `review_status=geprueft` to doctor-report document queries. - Add a `reviewed_documents_only` option to `_record_summary()` so report overview is reviewed-only while the normal record overview may still show pending workflow metadata. - **`health_dashboard_server.py`** - Change the V5 original opener to resolve any valid opaque document and call `probe_original_file()`. - Preserve exact authenticated GET/HEAD routing and the existing descriptor-streaming implementation unchanged. - **`dashboard-v5-record.js`** - Pending rows with extracted text: show **“Extraktvorschau prüfen”**, calling only the new preview endpoint. - Technically available pending originals: show **“Original öffnen”**. - Keep **“Inhalt anzeigen”**, FTS snippets, and match controls reviewed-only. - Label preview content explicitly as unreviewed and render exclusively with text nodes. - Coverage `original_available` becomes technical availability independent of review. ## Required new attack/regression tests - Pending original: authenticated GET/HEAD succeeds; unauthenticated 401, cross-site 403, malformed/unknown opaque 404, unknown/duplicate query 400. - Repeat leaf/parent symlink, traversal, FIFO/directory, oversized file, wrong magic, and outside-root attacks using a **pending** document. - Open safe descriptor, replace leaf/parent afterward, and prove streamed bytes remain from the pinned descriptor; GET after HEAD must revalidate. - Preview endpoint: malformed opaque/query attacks, bounded output, no filename/path/Drive/raw-ID fields, XSS payload rendered as text, and proof it never opens the original file. - Downgrade reviewed→pending: original and preview remain available, while detail, matches, FTS/global search, and doctor report disclose no reviewed content. - Doctor report: unique pending category/institution/content sentinels absent from both `documents` and `overview`. - Browser: pending card shows original + preview actions but no reviewed viewer/match action; coverage counts technical original availability; DOM/URL contain no stored filename, path, numeric DB ID, or opaque ID as visible text. - Add an explicit legacy-route boundary test or hardening test so `/health-doc/` cannot silently invalidate the claimed A.3 threat model. ### Verification - Focused baseline: **12 passed** across original, record, and 6E4B contract tests. - Files created/modified: **none**. - Final Git tree: **clean**.