Story 2486: zero-silent-move anchor remaps

Why this exists

Extraction anchors are version-scoped. A later extraction or DOCX conversion may move the same passage to a different block or page. Counsel's pin must therefore either follow the exact same words with a durable ledger row or remain visibly unresolved. It must never land on different words by accident.

The fixture-only transition

old current extraction + pinned anchors
  → project the new extraction pages
  → exact text occurs once: mapped (confidence 1.0)
  → exact text absent or repeated: unresolved (reason recorded)
  → persist one outcome per old pinned anchor
  → mark downstream artifacts stale
  → atomically make the new extraction current
No serving-path change. The v1 page surface, endpoints, DTOs, and automatic render worker remain unchanged. These tables and jobs are exercised only by the Extraction Record fixture rail.

Version and ledger contract

SurfaceRule
documents.document_versions Append-only rendition history with a server-derived storage key, converter identity/version, and an authenticated matters:write actor required for every re-conversion after version 1.
documents.extraction_versions.document_version_id Pins each extraction to the exact Original or converted rendition it observed.
documents.anchor_remaps Append-only mapped, unresolved, or retired outcomes. Automated migration treats every old anchor as pinned, so it emits only mapped or unresolved.
Fail closed. Matching is byte-exact and unique. No fuzzy text, normalization, page proximity, or provider block identity can move a pin. Duplicate boilerplate is unresolved. Both tables inherit document-level matter visibility under forced RLS; the app role cannot update or delete ledger history.

Executable proof

Recall

A pin never guesses. One exact destination earns a mapped row; every unsafe case stays unresolved and visible.