Story-2487 · functions lane
One extraction record, three useful views
The request path
matter-team read permission
↓
document + current extraction under RLS
↓
ordered document_pages_v2 rows
├── Text: page text joined in order
├── Anchor: IDs + kinds + geometry + offsets
└── Model: compact, fenced JSONL + anchor records
↓
immutable render cache
Why Anchor uses an ETag
An extraction version never changes. Its Anchor response therefore gets that version as its cache validator. The browser keeps the body and asks whether the validator still matches; the API returns 304 without resending it.
Why only Model is materialized
Text and Anchor are already persisted as page rows, so another cache would duplicate truth. Model rendering is a potentially large wire format used repeatedly by model workflows. Its cache key includes document, extraction version, renderer version, and a bounded budget profile. A new extraction or renderer version misses naturally; no invalidation machinery is needed.
The Model view wraps compact JSONL in a named Markdown fence. Document text is JSON-escaped inside text fields, so hostile PDF text cannot counterfeit server page or anchor records.
Trust boundary
All three endpoints require matters:read and join through the RLS-protected document row before reading child pages. A missing, unassigned, or cross-firm document has the same typed 404. Raw JSON blocks are validated through response DTOs before leaving the API.
Scope boundary
This Story exposes the new fixture-backed views. It does not move the app, registrar, or Blake off their current serving paths; those cutovers remain with T2b and T2d.