Story 1869: the quote that cannot lie — one canonical text + the V-CITE invariant

Why this exists (the catastrophe it prevents). The whole product is one promise: when PRAMAAN attributes a quote to a document, that quote is in the document. Break that once and a court strikes the evidence — and once one exhibit is struck for a fabricated quote, every other exhibit we filed is suspect. That is the strike-off vector, and it is self-inflicted in exactly two ways. (1) Two sources. If a document has two extracted texts — a page-anchored one for search and a second, separately-parsed one Blake reads — the two drift, and Blake quotes the second while we cite the first. (2) A re-paginate. If “page 6” is whatever the renderer produces today, upgrading LibreOffice silently slides a citation a lawyer already relied on onto different text. This Story closes both: one canonical text per document, and the V-CITE invariant — any string surfaced as a Blake quote must be byte-present on the cited segment of that one canonical artifact, enforced at the boundary, or the document is held for a human and the quote never becomes evidence.

The analogy to hold the whole way down: a court reporter’s certified transcript. Counsel may read from a marked-up working copy, a highlighted printout, a translation — but when they put a quote on the record, the clerk checks it against the one certified transcript, character for character. If the words are not there, it does not go on the record. V-CITE is that clerk: Blake may read a convenient rendering, but every quote is checked, byte for byte, against the one canonical text — and an unverifiable quote is held, never recorded.

🛰 Satellite view: remove a source, add a checkpoint

RFD-DOC-002’s panel locked two load-bearing decisions this Story implements (logic only — the schema landed in S1/#1866, single alembic head, no migration here):

DecisionMoveWhat it buys
DR-2 — one canonical textThe segment-anchored page text on documents.document_pages is the sole source for search, Blake, and contradiction. No second parser on the Office path (MarkItDown is absent). derived/document.md is a presentation synthesised from those same page rows, not an independent source.The two-source drift is gone — there is one text, and V-CITE anchors to it.
DR-1 — immutable render anchorA citation carries (render_id, segment_kind, segment_index) and resolves against the frozen segment map of that render.A re-paginate mints a new render and can never silently re-point a landed citation.
V-CITE — byte presenceAt the broker boundary, every quote-bearing citation is verified byte-present on its cited canonical segment; a failure holds the document registration_needs_review and records no effect.A paraphrased / hallucinated / off-segment quote can never become indexed evidence.

✈️ Airplane view: where the clerk stands

Blake (the Record Registrar) emits typed tool intents; the broker executor (execute_tool_intent) is the one path by which an intent touches business truth. That is where the clerk stands. A quote rides an intent as a span_ref carrying its citation anchor and the verbatim string. Before any effect is recorded:

  intent.span_refs ──▶ _enforce_vcite(document, pages)
      │                      │
      │   any ref with a     ▼
      │   "quote"?      verify_citations(span_refs, document.render_id, pages)
      │   no → skip          │
      │   (no DB load)       ├─ render_id != document.render_id   → render_id_mismatch
      ▼                      ├─ segment (kind,index) not found     → segment_not_found
   proceed to               ├─ quote bytes ∉ segment bytes        → quote_not_on_segment
   idempotency + effect      └─ quote but no anchor                → missing_citation_anchor
                                     │
                          any violation → hold registration_needs_review,
                                          record NO effect, return held_needs_review

The check runs before the idempotency claim, so a held substrate is never marked registered and a corrected re-run is not skipped as a duplicate. Refs that carry no quote (legacy page pointers) are untouched and skip the page load entirely — the common path pays nothing.

🔬 Microscopic view: the decisions that matter

1. Byte-presence, not “close enough”

A court reads bytes. So V-CITE is quote.encode("utf-8") in segment_text.encode("utf-8") — exact, no normalisation, no case-folding, no whitespace collapsing. The temptation is to be lenient so a near-match passes; that is exactly the leniency that lets “the defendant admitted liability” pass against text that says “the defendant denied liability.” An empty quote or an absent segment is a failure, not a vacuous pass: a quote with nothing to anchor to can never be evidence. The check is byte-exact across scripts — a Devanagari quote is matched on its UTF-8 bytes like any other (DR-7’s multilingual corpus).

2. Why the canonical text is the page rows, not document.md

Blake reads derived/document.md — a convenient markdown rendering. But markdown is a presentation: it prefixes headings with # , strips whitespace, joins blocks. A quote lifted verbatim from the markdown may not be byte-identical to the canonical text. That is the whole reason V-CITE anchors to the page rows (the one canonical text), not to the markdown Blake happened to read. The markdown is the working copy; the page text is the certified transcript. Even a quote Blake pulls from the working copy must trace back to the transcript’s bytes, or it is held. This is what makes “one canonical text” load-bearing rather than decorative: there is exactly one text a quote is allowed to be true against.

3. The render anchor makes “page N” immutable (DR-1)

Citations resolve against a segment map — (segment_kind, segment_index) → text — built purely from the page rows. The map keys on the coordinate, never on the rasterised image, so re-rendering with a different LibreOffice digest produces the identical map as long as the segment text is unchanged: a citation does not move because the image changed. And a citation carrying an old render_id against a document that has since been re-rendered does not silently re-point — it fails render_id_mismatch and is flagged, never moved. The golden-fixture test pins this: the same fixture yields a byte-identical segment map across builds.

4. Held, not dropped — and held safely

A V-CITE failure must do two things: never record the quote as an effect (so it cannot surface as indexed evidence), and hold the document for a human (so the failure is visible, not silent). The executor sets registration_status = 'registration_needs_review' directly — V-CITE is a safety override that outranks the normal registration lifecycle, so the held state wins from any pre-terminal status rather than threading through the transition validator. The returned outcome is the new held_needs_review — the one additive contract change (a ToolOutcome enum value; the app mirror consumes it). Violations log only low-cardinality coordinates and a reason code — never the quote or the document text (ADR-124).

How it was proven

One-sentence recall: there is exactly one canonical text per document, and a Blake quote becomes evidence only if its bytes are present on the cited segment of that text in its immutable render — otherwise it is held, never recorded.