Chapter 1 of 7 · 15 min read
A document link names a source; only a span names the evidence
On this page
A citation is a reference a system attaches to something it has just asserted, naming the source that is supposed to establish it. The wider practice of producing those references, so that any sentence in an answer can be traced to the text it came from, is attribution. It exists because a language model writes fluent, confident sentences whether or not anything it was given supports them, and because the retrieval step that put documents into the context window keeps no record of which words in those documents the answer actually leaned on. Most readers have met attribution without naming it: the numbered markers under an answer in a chat assistant, the Sources strip beneath a search summary, the panel of quoted text that a support tool opens when an agent asks where a policy came from. Inside a company, attribution is the line under the answer that somebody clicks before repeating a number to a paying customer.
A citation can name its evidence at more than one resolution, and which resolution it uses is the subject of this topic. Naming the document is the coarse end. The fine end is a span: one continuous run of characters inside one retrieved chunk, fixed by the position where it starts and the position where it ends, and kept together with the text that sat at those positions when the answer was written. Attaching a span to each individual claim, rather than a document to the answer as a whole, is span-level grounding, and this topic is what it takes to produce those spans, to test them, to put them in front of a reader, and to find out when they stop being true. The choice between the two resolutions decides what the citation is able to assert, and that is a narrower and more useful question than whether the citation is good.
Two different assertions wearing the same badge
There are two claims a system can make when it puts a reference under a sentence, and they are not the same claim at two resolutions.
The first is this document was consulted. It is a claim about the system's own behaviour: these bytes were in the context window when the answer was generated. It is cheap to produce, it is almost always true, and it is nearly worthless to a reader trying to check a number, because a fourteen-page rate card is consistent with every possible overage figure the model could have emitted. A reader who follows such a link is not verifying the answer. She is redoing the retrieval by hand, with a worse retriever than the one the system has, and then reading the passage the system already read.
The second is this text supports this claim. It is a claim about a relation between two strings: the sentence the answer asserted, and a specific run of characters in a specific version of a specific document. That assertion can be false while the document is entirely relevant, and this is the case that matters. A rate card is unambiguously the right document for a pricing question. It can still contain nothing that establishes "$0.12 for Growth" — if, for instance, the model read the Starter row and mislabelled it, or read a superseded table on page three, or produced the number from its own training data and the retrieval had no influence on it at all.
Those three states, plus the correct one, are worth counting rather than listing, because the counting is what settles the argument. Under a document-level citation, all four are byte-identical in the output: the same sentence, the same single hyperlink, the same document identifier. A test is a function of the output. If the output is identical across four states, then every function of it returns the same value on all four, and no test anybody writes can separate them. This is not a statement about how difficult the test would be to build. It is a statement that there is nothing for a test to read: the output carries no information at all about which of the four occurred. There is nothing to compare, so there is nothing to check, so nothing can be reported wrong.
This is where a common argument goes astray. Retrieval evaluation, the reasoning goes, already measures whether the retrieved documents are relevant, so a document-level citation is a lighter-weight version of the same guarantee — if retrieval is good, the links are good. The step that fails is the assumption that the two evaluations are about the same thing, and the quickest way to see it is to write down what each one takes as its arguments. Relevance holds between a query and a document, and says the document is about the right subject. Support holds between a passage and a specific proposition, and says the passage makes that proposition true. The query is not the proposition — the proposition is a sentence the model wrote afterwards, which did not exist when retrieval ran. The document is not the passage — it is a container that may hold thousands of passages, only one of which was leaned on. Neither argument of the first predicate is an argument of the second, so a measurement of one places no bound on the other. A document can be perfectly relevant to the question and contain no passage that supports the sentence pointing at it, and the document-level format makes that state indistinguishable from the correct one.
The belief worth naming here is the one about the link itself, because it is the belief that keeps document-level citations in production. A document link never breaks. That is true, and it is true in the way that matters least: the HTTP status is a quantity that stays constant while the dangerous property varies freely underneath it. A link returns 200 whether the cited sentence is still in the document, has been reworded, or has been deleted outright. The status code is a fact about the server having a file; the citation is a claim about text inside it. Nothing keeps the two aligned, and nothing reports when they come apart.
What has to be in the record
To assert support you have to be able to name the evidence precisely enough that a machine can fetch it back and a human can look at it. That takes more than a document identifier. The minimum record, emitted per claim rather than per answer, looks like this. The document in it is a pricing rate card, and the claim is a sentence about what one plan charges for overage.
{
"answer_id": "a91f3c",
"claim_id": "c3",
"claim_text": "Growth plan overage is billed at $0.12 per 1,000 API calls above the included volume.",
"citations": [
{
"doc_id": "pricing/2025-q3-rate-card.pdf",
"doc_version": "sha256:9f2c41ab…",
"chunk_id": "pricing/2025-q3-rate-card.pdf#tok:1200",
"char_start": 412,
"char_end": 477,
"quote": "Growth — included 500,000 calls/mo; overage $0.12 per 1,000 calls",
"page": 7,
"retrieved_rank": 2,
"retrieved_score": 0.71
}
]
}Each field is there because something breaks without it.
The chunk_id says which retrieval unit the evidence came from, and it is the same identifier the retrieval log already carries — the document-plus-offset form from topic 9.2 rather than an opaque UUID, so that a citation and a retrieval trace can be joined by eye. Without it, a character range is meaningless, because you do not know what string the offsets index into.
The char_start and char_end pair is the span itself, expressed as a half-open range over the chunk's text as stored, meaning that the character at char_start is inside the span and the one at char_end is the first character outside it. Half-open is what makes the width a subtraction: end minus start, with no adjustment for whether both ends are inside. Chunk-relative offsets are the right base because the chunk text is the thing the model actually saw, and the thing you can compare against later without re-parsing the source. Document-relative offsets and a page number are derived from the chunk's own position in the document, and they exist in the record because the interface needs them to open a viewer at the right place. Storing only document-relative offsets is a common shortcut and it couples every citation to the parser: change the PDF extractor and every offset in the archive shifts.
The doc_version is a content hash of the parsed document at answer time — a short fingerprint computed from the text itself, which comes out different if any character of it changes. Documents change. A rate card gets a new footnote in November, and a citation emitted in September pointed at text that is no longer there. Without a version, a citation that resolves and a citation that resolves to different text than it cited are the same thing.
The quote is the field people leave out, and it is the one that does the most work. It is the text as it read at answer time, copied into the record. It is redundant while everything is stable, since you could recompute it from the chunk and the offsets. It stops being redundant the moment the chunking changes, the parser is upgraded, or the document is edited, because then the stored quote is the only evidence of what the system claimed to be looking at, and comparing it against what the offsets now resolve to is a cheap, decisive check. It is also what the interface renders, so a reader sees the same string the checker compares.
There is a small piece of arithmetic in that record which is worth doing by hand, because it is the whole reason the redundancy pays. Subtract the two offsets: four hundred and seventy-seven minus four hundred and twelve is sixty-five, so the range is sixty-five characters wide. Now count the stored quote, from the G of Growth to the final s of calls, and it is sixty-five characters long as well. The two agree, and the agreement is not built in anywhere. The offsets were produced by a matcher searching the chunk text, and the quote was produced by the generator copying characters out of what it was shown; the two came down different paths and were written into the same object. Nothing in the schema forces them to match.
That is why the equality licenses something. A stored quote whose length differs from the width of its own range is a failure that has already been detected, by one subtraction and one call to len, before any document has been fetched, any text compared, or any model consulted. It is the cheapest check in this topic and it catches the case where a matcher returned a range into a normalised string rather than the raw one, which is the defect the next chapter spends a section avoiding. The check is weak in the obvious direction: two strings of the same length can be entirely different text, so equal widths prove nothing on their own. It is the inequality that is decisive, and cheap detectors that can only ever say no are worth having when they cost a subtraction.
The retrieval rank and score are diagnostic rather than load-bearing. When a citation turns out to be wrong they tell you immediately whether the problem was that the right passage never got retrieved or that it got retrieved and the generator picked a different one, and those two failures have entirely different fixes.
What each format lets you check
The practical difference between the two citation styles is best seen as a list of questions and whether the format can answer them.
| Question about an answer | Document link | Span citation |
|---|---|---|
| Which passage supports this sentence? | Not expressible | chunk_id plus character range |
| Did the cited text change since the answer was written? | Undetectable; the link still resolves | Stored quote versus current text at the offsets |
| Does the cited text actually entail the sentence? | Nothing to feed a checker | Span and claim are both strings a checker can take |
| Which sentences in the answer carry no evidence at all? | Every sentence shares one footer link | Claims without citations are visible per claim |
| How long does a reviewer take to confirm one claim? | Minutes: open, find, read | Seconds, if the interface resolves the span |
| What breaks on a reindex? | Nothing visibly | Offsets shift; detectable via the stored quote |
Read that table by its gaps rather than by its cells, because the gaps are all one gap until the bottom. In the first five rows the difference between the columns has the same shape every time: the question is expressible in one format and not in the other. It is never a matter of the document link answering worse. There is no answer to give, because the format holds no object the question is about — no passage, no range, no per-claim structure. A format that cannot express a question cannot be improved at answering it, which is why the first five rows are not five arguments but one argument restated on five subjects.
The last row is the only one where the gap points the other way, and that is where the argument for document links makes its stand: a document link never breaks, while offsets rot on every reindex, so the sturdier format is the coarser one. Offsets do rot, and a reindex that changes chunk boundaries will invalidate a large fraction of stored ranges in a single job. The conclusion does not follow, because "never breaks" and "never reports a problem" are the same property here, which is the point the HTTP status made above arriving now as a row in a table. A document link that still returns 200 after the document has been rewritten is not intact; it is silent. The span citation with a stored quote fails loudly, and the failure is repairable by mechanical means — you still have the quote, so you can search for it in the current text and rewrite the offsets. A format that degrades visibly and can be re-derived is in a better position than one that cannot express the failure at all. The infrastructure required to notice, sweep and repair that drift is real work, and chapter 6 is about it.
Provenance, truth and retrieval quality
Span citation is not provenance in the data-lineage sense. It does not tell you where the document came from, who approved it, which ingest run loaded it, or whether it should have been in the corpus at all. Those are useful and they are a different record, attached to the document rather than to the claim, and a system can have either without the other.
It is not a correctness guarantee for the answer either, and the gap between the two is where a reader is most likely to over-read a green check. A citation asserts that a passage supports a sentence; it says nothing about whether the passage is true, current, or authoritative. Citing the superseded 2024 rate card correctly — right span, right quote, genuine support — still yields a wrong answer, and no citation machinery detects that, because every component in the chain is comparing the answer against the retrieved text rather than against the world. Corpus hygiene and recency filtering are the controls for that failure, and they operate on the index rather than on the output.
It does not repair retrieval. If the passage that would answer the question never entered the context window, the honest outcome is an answer that declines or an answer whose claims carry no citations. The value of the format in that case is not that it fixes anything; it is that the second case becomes visible instead of being hidden behind a plausible document link, so a missing passage shows up as a gap in the output rather than as a confident sentence with a source under it.
And it is not free at the interface. A span citation the user cannot resolve in one action is, from the reader's point of view, roughly as expensive as the document link it replaced — the four minutes above do not shrink because a character range exists in a database somewhere. The record specified here is what makes a good interface possible rather than what makes it exist, and chapters 4 and 5 are about the difference between those two.
What remains open is where the record comes from. Nothing in a retrieval pipeline produces a character range on its own: the retriever hands back chunks and does not know which words mattered, and only the component that writes the sentence knows which part of which chunk it was leaning on. That component emits text, not offsets. Getting a usable span therefore means getting the generator to commit to a verbatim quote rather than a paraphrase, and then matching that quote back into the chunk text to recover the positions — which is a string-matching problem with more edge cases than it first appears to have, and the subject of the next chapter.
Citations and attribution — read the current version at https://daviddominguez.dev/learn/en/citations-and-attribution/a-document-link-names-a-source-only-a-span-names-the-evidence