Story-2555 · functions lane
Table anchors now carry the grid, not a guess
The truthful path
verbatim Textract vault
↓
LAYOUT_TABLE → TABLE → every CELL (including empty cells)
↓
{row, col, rowSpan, colSpan, text, confidence}
↓
document_pages_v2.blocks JSONB
↓
AnchorBlockView.cells → app OpenAPI contract
Why the backfill creates a new version
Activated extraction pages are immutable, and the extraction-version UUID is the Anchor response ETag. Editing version 75b2b479… in place would let a browser reuse a stale grid after receiving 304 Not Modified. The staging replay therefore derives one stable replacement UUID from each source version, reuses the source's immutable vault, and runs the existing atomic projection transaction. The old version becomes superseded; the new current version changes the ETag and carries the cells.
Staging replay receipt
After the Story deploy, temporarily add TEXTRACT_FIXTURE_ENABLED=true and the seven document UUIDs in TEXTRACT_FIXTURE_DOCUMENT_IDS to the extraction worker configuration. Wait for the Lambda configuration update to finish, then invoke pramaan-document-extraction-worker without an alias so the request reaches the newly configured $LATEST. The immutable :live alias cannot receive transient environment changes.
{
"table_cell_backfill": {
"firm_id": "<firm-uuid>",
"document_id": "<allowlisted-document-uuid>",
"source_extraction_version": "<current-source-uuid>"
}
}
Invoke once for each of the seven allowlisted fixture documents. The path is disabled unless TEXTRACT_FIXTURE_ENABLED=true, and it rejects documents outside TEXTRACT_FIXTURE_DOCUMENT_IDS. Record each source-to-replacement UUID response, verify the Anchor view's explicit grid, then run the normal extraction-worker deploy so both temporary fixture variables are absent from $LATEST again. Confirm their absence with get-function-configuration; the :live alias remains on its immutable, clean version throughout.
Proof
- The pure projection test keeps empty cells and exact row, column, span, text, and normalized confidence.
- The real-Postgres replay test proves old pages remain untouched, one deterministic replacement becomes current, and retry creates no third version.
- The app-boundary test proves camel-case
rowSpan/colSpanreach the Anchor response. - The migration round-trip documents the JSONB contract and leaves one Alembic head.
Scope
No title/footer or cell-bounding-box metadata was added because the Story makes it optional and the explicit grid is already sufficient for truthful rendering. No serving query changed; only the Anchor payload gained table cells.