Skip to content

Provenance and citations

Provenance is Futuros' competitive moat. Anyone can show figures; what is rare is that every figure traces back to a specific primary-source record in two clicks, and that the system fails at build time if it can't. This document explains the machinery behind that contract. It serves both Pillar 1 — Data Trust (provenance is what makes the corpus verifiable) and Pillar 2 — Sovereign Model (facts live in citations, never baked into weights).

The two-click contract

Every on-screen figure carries its citation on the figure itself: a light underline, not a [WB] chip after the number. One click opens the reference (title, publisher, year); a second click takes you to the exact record at the source — the precise country and indicator, not a generic landing page. That specificity is a build rule: the check-source-links gate fails the build if a link points to a generic source instead of the country + indicator record.

The exception is explicit, not tacit: the gate itself carries a LANDING_OK list of sources whose citations legitimately resolve to a landing or methodology page — curated and internal layers (gov-, event-, calc-, agg-, idx-), document layers (EXA, GDELT, RSS, gazettes, signals) and a few sources with no per-record URL (OECD benchmarks, IDEA, UNODC, V-Dem, FSI, INFORM, IMF PortWatch, FAOSTAT) — each with its justification commented in the code. It is a conscious, auditable opt-out, not a hole: anything not on that list must carry a deep link or the build does not compile. If a data point cannot be traced this way, it is not published as a value.

Two more nets close the gaps. GENERIC_URLS enumerates "generic dataset" pages that differ from the root only by a path or a fragment (the FSI dashboard, the empty USGS search form…): a non-exempt citation on one of them is a violation just like a bare landing page. And regional aggregates (-latam, -world, -subregion, -oecd in the id) are exempt because no country+indicator record exists for a supranational average. The design is fail-closed: a new source without a deep-link template falls back to its url_root and the build dies in prebuild; the error message prints the two valid ways out — write the url_template and run fix-source-deeplinks.ts, or document the exemption in LANDING_OK with a one-line justification.

citation_id conventions

The central citation registry is public/data/citations.json15,098 records of the form {id, source, title, url, retrieved_at, year}. Data cells and components point to a record by that id (the field cells call citation_id). The id carries a prefix that encodes its origin: wb- (World Bank, the most numerous), oecd-, who-, gov- (government source), event-, web- (external web search), etc.; corr- (correlation) exists as a reserved convention in the code, currently with no live citations. The prefix is not cosmetic: it is what makes it possible to resolve a citation to its source even when the citation is not in the in-memory registry (see synthetic reconstruction).

The id grammar is mechanical, and therefore parseable: <prefix>-<geo>-<year>, consumed right to left by parseMultiId() — if the last token is 4 digits, it is the year; if the next one is a lowercase ISO3 (or latam, the regional average derived by Futuros), it is the geography; whatever remains is the prefix. extractYear() applies the same convention with the regex -(\d{4})(?:[-_]|$). A real example from the registry: wb-si-pov-gini-arg-2024 → source WB, indicator SI.POV.GINI, Argentina, 2024.

The source registry

src/lib/source-registry.ts maps more than 200 SourceKeys (acronyms like WB, WHO, IMF) to their SourceMeta: acronym, full name, agency, jurisdiction, URL root, URL template, cadence, methodology (with _en / _pt variants), tier and license. The key pieces:

  • inferSourceKey() resolves a citation_id to its source by prefix.
  • deepLinkFor() builds the link to the specific record.
  • 43 url_template functions reconstruct the per-indicator, per-country deep link from the id alone — that is, given a citation_id the system can regenerate the exact source URL without depending on anyone having saved it by hand.

This is what guarantees the contract's second click: the deep link is not stored, it is derived, and therefore it cannot drift out of date relative to the id. Two details of the resolver matter in practice:

  • Prefix order is semantic. inferSourceKey() is a chain of startsWith rules where the first match wins, so longer prefixes go first (mepyd- must precede mep-; the comment in the code warns about it). Central banks illustrate the naming discipline: bcb- is Brazil, bcbol- Bolivia, bch- Honduras, bcch- Chile, bccr- Costa Rica — unique tokens ending in a hyphen so none shadows another. An unmapped prefix returns UNKNOWN, which is deliberately not exempt in the deep-link gate: a new unregistered prefix is a regression, not an allowed case.
  • The license travels with the source. SourceMeta.license (open, cc-by, non-commercial, share-alike, cite-only; absent ⇒ open/permissive) gates the bake: a cite-only source can be cited without redistributing its values.

CiteRef markers

src/components/citations/CiteRef.tsx is the citation anchor across the platform: the cited figure or phrase is the link, with a light underline. There are no [CEPAL] / [INEC] / [WB] chips after the number. Hover (or focus / tap) shows the same popover as before (title, publisher, year); click goes to the external source. If one span has several sources, the popover lists them. Resolution follows this order:

  1. In-memory citation registry (citations.json).
  2. If absent, synthetic reconstruction from the source registry.

There is a deliberate third state: strict mode. Surfaces that know their citations were registered (for example, an indicator's page) pass strict; there, an id that fails to resolve renders as a dangling, unlinked marker instead of fabricating a synthetic title, URL or vintage. It is the exact counterweight to syntheticCitation(): reconstruction exists for embeds that never loaded the registry, never to paper over a broken id where the registry was in fact available. The condition is double by design: a marker only counts as dangling when a registry is mounted on the page and the caller passed strict — a simple miss on a surface that never registers (embeds, chat, mini-cards) is normal and falls back to the synthetic. And when an id resolves to a source but not to a useful external URL, the click does not navigate anywhere dead: it opens the SourceTrace drawer with the methodology. Every click on a marker also emits the source_click telemetry event with {citation, source, year} — provenance usage is measured too.

Around CiteRef live CitationChip (a wrapper that requires the text to underline), CitationDrawer and SourceTrace (a fallback drawer with methodology and the falsifications log). The external-evidence mark is not a separate component: it lives inside CiteRef itself, which paints a gold dashed underline when the citation_id carries the web- prefix. The end-to-end journey is: baked value → citation_idCiteRef → registry → deep link.

Synthetic reconstruction for embeds

Embeddable surfaces (/embed/stat, /embed/chart, /embed/board) do not always load the full citation registry — it would weigh too much. That is what syntheticCitation() is for: it reconstructs the citation from the citation_id and the source registry (prefix → SourceKeyurl_template). Result: a card embedded on a third-party site keeps its citation and its deep link, without loading the registry's megabytes. Provenance travels with the data, not with the page.

Three honesty invariants in the synthetic: it returns null if the prefix resolves to UNKNOWN — it never invents an institution; retrieved_at stays empty — it does not fabricate a retrieval date that never happened; and the year is only included if it is derivable from the id itself.

Badging of external web citations

Not every fact comes from the baked corpus. When the assistant uses web search as a last resort (see The assistant), those citations carry the web- prefix and are visibly marked as external: the figure's underline is gold and dashed, and the hover card prefixes the unverified-data tag. The reason is honesty: a fact fetched from the open web must never be confused with a datum from the verified corpus. The badging separates the two classes of evidence in the interface itself, so the reader always knows how firm the ground is under each figure.

Falsifications log

public/data/falsifications.json is the record of claims that were put to the test — the reverse of "every citation is a source": each falsification is an auditable commitment that the platform corrects whatever turns out not to hold. SourceTrace exposes it alongside the source's methodology. It is part of the honesty contract detailed in Methodology and honesty.


In short, provenance in Futuros is derivable, not promised: the deep link is reconstructed from the id, the synthetic citation travels to the embed, and the build refuses to compile if any link outside the documented opt-out (LANDING_OK) points to a generic source. Continue with The assistant to see how the chat inherits this same discipline — every figure anchored to a [[cite:id]]. And for the next frontier — from derivable provenance to provenance cryptographically verifiable by third parties — see the technical optimization roadmap.

Every figure with its source — traceability is the contract.