Why
Every civic fact the How to Vote screen shows needs to answer three questions:
who said this, where does it come from, and when did we last check? The
design makes "Last verified {date}" a required element — when the timestamp is
missing it renders "Verification date unavailable" rather than dropping the
row.
Nothing in the codebase can supply that today. civic_api_cache stores
fetched_at, but that is when Billion called an API, not when the
authority published or when a human verified the value.
This is the shared substrate under #294–#297. Without it, each of those ships
its own ad-hoc provenance and they drift.
What to build
A provenance record attachable to any civic fact:
value
sourceName — the authority, verbatim
sourceUrl
fetchedAt — when we retrieved it
verifiedAt / verifiedBy — when a human last confirmed it, if ever
staleAfter — per-field freshness budget
Proposed budgets, needs a decision: locations 24h (matches the existing
voterinfo cache TTL), deadlines 7 days, ID rules 30 days. Past the budget the
field shows its age rather than disappearing.
Related prior art
MeasureCitationRef in packages/api/src/lib/civic.ts already does per-field
attribution with a trust tier for ballot measures. That model is the obvious
starting point — this issue generalizes it beyond measures.
Done when
Why
Every civic fact the How to Vote screen shows needs to answer three questions:
who said this, where does it come from, and when did we last check? The
design makes "Last verified {date}" a required element — when the timestamp is
missing it renders "Verification date unavailable" rather than dropping the
row.
Nothing in the codebase can supply that today.
civic_api_cachestoresfetched_at, but that is when Billion called an API, not when theauthority published or when a human verified the value.
This is the shared substrate under #294–#297. Without it, each of those ships
its own ad-hoc provenance and they drift.
What to build
A provenance record attachable to any civic fact:
valuesourceName— the authority, verbatimsourceUrlfetchedAt— when we retrieved itverifiedAt/verifiedBy— when a human last confirmed it, if everstaleAfter— per-field freshness budgetProposed budgets, needs a decision: locations 24h (matches the existing
voterinfocache TTL), deadlines 7 days, ID rules 30 days. Past the budget thefield shows its age rather than disappearing.
Related prior art
MeasureCitationRefinpackages/api/src/lib/civic.tsalready does per-fieldattribution with a trust tier for ballot measures. That model is the obvious
starting point — this issue generalizes it beyond measures.
Done when
representable ("locations verified, deadlines unavailable")