Verification
Every ranked paper is checked against the live record before it ships. What is checked, what the mismatch codes mean, and why a failure is flagged rather than repaired.
verify is the last stage before emit. It checks every ranked paper against the
live record and records what did not match.
It runs late on purpose. Verification is cheap relative to screening, and running it on the papers that will actually be delivered means the check covers exactly the set that matters.
What gets checked
For each ranked paper: the DOI resolves, and the title, year and first author on the live record agree with what the pipeline holds.
Verification is done against Crossref, with OpenAlex as the fallback if Crossref is unreachable. Crossref is never a retrieval source — it appears in the pipeline only here.
The verification record
Every EvidencePacket carries one, and emit refuses to run without it.
| Field | What it holds |
|---|---|
verified | Whether the record checked out. |
verified_by | Which sources confirmed it: crossref, openalex, arxiv, s2. |
verified_on | When the check ran. |
title_match_ratio | 0–100. Fuzzy match between the held title and the live one. |
mismatches | What disagreed, if anything. |
Mismatch codes
| Code | Meaning |
|---|---|
doi_unresolved | The DOI did not resolve. |
no_record | No record was found to compare against. |
title | The title did not match closely enough. |
year | The publication year disagreed. |
author | The first author disagreed. |
retracted | The live record marks the paper as retracted. |
The matching threshold
Titles are compared fuzzily, because punctuation, subtitles and capitalisation differ legitimately between sources. The default threshold is a 90 match ratio.
research-scan verify --strict--strict requires 95 instead of 90. Use it when a false positive is more
expensive than a manual check.
Flagged, not repaired
This is the rule that matters most, and it is worth being exact about.
A paper whose DOI did not resolve, or whose title, year or first author disagreed
with the live record, ships anyway — with its mismatches[] populated and an
[UNVERIFIED — check manually] marker in the rendered Markdown.
It is not silently corrected. It is not silently dropped.
Correcting it would mean writing metadata that came from somewhere other than a source of record, which is precisely the failure this pipeline exists to prevent. Dropping it would hide the fact that something in the chain produced a record that does not check out — information you want, especially if it recurs.
The one exception is retraction. A paper the live record marks as retracted is removed. Everything else is flagged and kept, and the reader decides.
Reading it
In evidence.json, filter on the verification record directly:
jq '.[] | select(.verification.verified | not) | {rank, title, mismatches: .verification.mismatches}' evidence.jsonIn evidence.md, unverified papers carry their marker inline, so a human reading
the deliverable sees the same caveat the JSON records.
Per-call detail is in verify.log.jsonl.
What verification is not
It confirms that a record exists and that the bibliographic details match. It says nothing about whether the paper is any good, whether its claims replicate, or whether the abstract accurately describes the work — no PDF is fetched, parsed or read anywhere in the pipeline.
Verification is a check against fabrication and drift, not a quality judgement. The quality judgement is the rerank, and it is the agent’s.
Last updated Aug 21, 2026