# Document review preview and original-access boundary

Use when a sensitive read-only dashboard must let an authenticated operator inspect **unreviewed** document originals or extracted text without accidentally admitting that content into reviewed search, reports, or medical summaries.

## Separate technical availability from trust

Model these as independent facts:

- original file technically available
- extracted text technically available
- content review status
- full-text-search eligibility
- report/summary eligibility

Do not make the low-level filesystem probe itself decide review policy. Prefer:

1. a review-neutral descriptor probe that performs root containment, component-by-component `openat`/`dir_fd`, `O_NOFOLLOW`, `fstat`, regular-file/size checks, magic-derived MIME, and generic download names;
2. a reviewed-policy wrapper retained for workflows such as laboratory reconciliation where review is a prerequisite;
3. explicit route/query policy at each caller.

This avoids weakening medical/reconciliation callers merely to support a review inbox.

## Recommended API split

- Document history may return safe metadata and independent technical/review/search statuses for reviewed and pending documents.
- Keep the normal extracted-content detail endpoint reviewed-only.
- Keep document-match and all FTS endpoints reviewed-only, with a current-state `dokumente.review_status='geprueft'` join even when the FTS index contains stale rows.
- Add a distinct authenticated extracted-preview endpoint for pending review. Resolve only an opaque ID and return bounded chunks from the shared normalization/chunk contract. Never return paths, stored filenames, Drive IDs/URLs, hashes, or numeric database IDs.
- If pending originals are intentionally reviewable, the original route may resolve both pending and reviewed opaque IDs, but it must retain authentication, same-origin checks, exact routing, descriptor pinning, magic/size checks, generic names, streaming, and `no-store`/`nosniff`/`no-referrer` headers.
- HEAD is not a capability grant: HEAD and later GET must independently resolve, open, validate, and close their descriptors.

## Report and overview pitfall

A report can violate a reviewed-only contract indirectly even when its main `documents` collection is filtered. Check nested overview/summary builders too. Give shared summary helpers an explicit `reviewed_documents_only` mode rather than filtering only the top-level report section. Test that unique pending sentinels are absent from the entire serialized report payload.

## UI contract

- Show separate cards/chips for original, extraction, review, and search states.
- Pending rows may offer `Original öffnen` and a clearly labeled unreviewed extracted preview.
- Pending rows must not offer reviewed viewer, FTS snippet, or match-navigation actions.
- Render preview and database values through text nodes only.
- Coverage should state whether `original_available` means technical availability or reviewed availability; do not silently mix the two.

## Legacy-route inventory

Before claiming the new API closes the document boundary, inventory older routes such as numeric document IDs or report-file routes. They may sit outside API authentication, review gates, opaque IDs, or descriptor-safe streaming. Either harden them, retire them, or document them as a separate legacy trust boundary. A test that only proves an opaque V5 ID is rejected by the legacy route does not prove numeric enumeration or pending-content access is blocked.

## Attack/regression matrix

1. Pending original: authenticated GET/HEAD success when intended; unauthenticated 401, cross-site 403, malformed/unknown opaque 404, unknown/duplicate query 400.
2. Repeat leaf and intermediate symlink, traversal, FIFO/directory, oversized file, wrong magic, and outside-root probes against a pending document—not only a reviewed fixture.
3. Open a safe descriptor, replace the path afterward, and prove bytes still come from the pinned descriptor. Also prove GET after HEAD revalidates.
4. Preview: malformed opaque/query rejection, bounded output, no forbidden identifiers, XSS-as-text, and proof that preview handling never opens the original file.
5. Review downgrade: original/preview behavior follows the explicit review-inbox policy while detail, matches, FTS/global search, report, and report overview disclose no reviewed content.
6. Browser: pending cards expose only intended actions; coverage semantics are accurate; visible DOM and URLs contain no path, stored filename, numeric ID, or visible opaque identifier.
7. Include a legacy-route boundary probe whenever older document/report routes remain live.

## Efficient read-only audit workflow

Trace one vertical path before proposing changes:

1. low-level original probe and its callers;
2. opaque ID generation and resolution;
3. HTTP GET/HEAD dispatch, auth/origin checks, and headers;
4. extracted detail, preview, FTS, global search, and report/overview queries;
5. UI status cards/actions/coverage;
6. synthetic fixtures and attack tests.

Do not treat a finding as out of scope merely because the unsafe caller predates the diff: if the diff changes a shared helper's status or availability semantics, retest every report/summary caller whose output changes. A deterministic counterexample is to insert one pending document with unique category/institution sentinels inside the requested report range, then assert those sentinels are absent from both top-level `documents` and nested `overview.documents` while remaining visible in the authenticated review inbox.

## Focused remediation re-review gate

When re-reviewing a medical/data-contract fix rather than the whole feature, supplement changed-line inspection with one disposable synthetic counterexample that proves all affected boundaries together:

1. Insert a pending document with unique category, institution, and extracted-text sentinels inside the report range.
2. If FTS exists, deliberately add a stale FTS row for that pending document; require global and document search to return no hit because current `review_status` still governs eligibility.
3. Request the doctor report with both `overview` and `documents`; search the entire sorted serialization for every pending sentinel, not only IDs in the obvious arrays.
4. Re-read the database review status after preview, report, and search calls and require it to remain unchanged.
5. For related review queues, change an otherwise matching row from `open` to a valid non-open state and require the day/detail payload to emit no deep-link key. Key generation must bind to the exact currently open row, not merely to an unmapped item or deterministic hash.
6. Scan added application lines for new diagnostic, therapeutic, or causality assertions; distinguish an explicit non-medical disclaimer from prohibited medical inference.

Keep the probe under a temporary synthetic fixture root and report each boundary result explicitly. A committed test that only deletes all queue rows or checks one pending ID is weaker: it does not prove stale-FTS exclusion, whole-payload sentinel absence, non-open-state behavior, and unchanged review state in one end-to-end path.

Run focused tests with bytecode/cache writes disabled where a clean-tree audit matters, then verify `git status` again. Avoid `py_compile` for a strict read-only review unless its cache output is redirected to a temporary directory; `PYTHONDONTWRITEBYTECODE` does not prevent `py_compile` from writing explicit `.pyc` files. Separate verified current behavior from proposed changes and explicitly call out any legacy boundary that prevents a global no-leak claim.
