# ADR 0005: Citation edges live in their own table

**Status:** accepted

## Context

Wave 2 adds a citation graph: when document A's reference list names
document B and both are in the corpus, retrieval should be able to walk
from A to B. The knowledge graph already stores edges, so the cheap move
would be to reuse it.

It does not fit. `kg_relationships` is entity-to-entity by contract:
`source_entity_id` and `target_entity_id` are foreign keys into
`kg_entities`, the relation type must be one of the domain ontology's
declared types, and each row carries the chunk and the quoted phrase that
states it. A citation has none of those properties. It is
document-to-document, its type is always the same, and its evidence is a
DOI match in a reference list rather than a sentence someone wrote.

The alternative that would preserve the table is a synthetic entity per
document. That buys the reuse, and it pays for it three times over.
`search_entities` starts returning paper titles alongside concepts.
Community detection clusters documents with concepts and writes summaries
about the mixture. And `graph gc` has to learn that some entities may be
evidence-less, because they were never evidence-bearing to begin with.

## Decision

Citations get their own table, `document_citations`:

* `citing_document_id` and `cited_document_id`, both foreign keys into
  `documents` with `ON DELETE CASCADE`
* `cited_doi`, the DOI as printed in the reference list, kept so an
  unresolved reference can be re-resolved later when its target is
  ingested
* `source`, the provenance of the edge (`crossref` today; a reference
  parser later)
* a unique constraint on the pair, and indexes on both directions

Only edges **between documents present in the corpus** are stored. A
reference to a paper nobody ingested is not a graph edge, it is a dangling
pointer, and the corpus should not carry pointers it cannot resolve.

Citation traversal joins the existing graph stage as an optional
expansion rather than becoming a sixth retrieval layer. Two reasons. The
fusion weights, the router, and the public trace contract all enumerate
five layers. And a citation walk is a variant of "walk from here to
related evidence", not a new kind of evidence.

## Consequences

* `corpus delete` and `graph gc` must clean this table up. The FK cascade
  handles deletion of either endpoint; GC sweeps rows whose endpoints
  vanished by other means. The every-layer unreachability regression test
  in `tests/integration/test_corpus_delete.py` grows a citation case.
* MCP gains `get_citations(document_id)`, which reads this table
  directly in both directions (cites out, cited by).
* The reference list of a document not yet fully resolved is not lost:
  `cited_doi` rows can be re-resolved against the corpus after a later
  ingest, without re-fetching Crossref.
* The knowledge graph stays a graph of concepts. That was the point of
  ADR 0001, and this keeps it true.
