claimset

specification

Rendered verbatim from SPEC.md in the reference implementation — the same bytes the verifier is built against. Raw copy: /spec.md. Structure is normatively defined by claims.schema.json.

claims.json — specification

Version: 1 Status: draft, stable enough to build against Schema: claims.schema.json (JSON Schema 2020-12) is normative for structure License: MIT (this document and the reference implementation)

A claimset says: this exact sentence appeared at this exact URL on this exact date, and after this date do not trust that it still does. A verifier re-fetches the source and tells you whether it is still true.

The difference from every "freshness" signal that came before: a claimset is falsifiable by a stranger. It does not assert that content is current. It publishes what would have to be checked to find out, and anyone can run the check without asking permission.


1. The file

{
  "claimset": "1",
  "publisher": "Denial Facts",
  "contact": "https://example.org/corrections",
  "claims": [
    {
      "claim_id": "external-review-deadline-four-months",
      "assertion": "you must file a written request for an external review within four months",
      "source_url": "https://www.healthcare.gov/appeal-insurance-company-decision/external-review/",
      "checked_on": "2026-08-14",
      "expires_on": "2027-02-14",
      "source_sha256": "149ad94e…",
      "superseded_by": null,
      "note": "Cited on /how-to-appeal and in the Kit."
    }
  ]
}

Conventional filename claims.json, committed to version control next to the content it backs. Larger publishers MAY split by topic (claims/appeals.json, claims/pricing.json).

1.1 Fields

FieldRequiredMeaning
claimsetno (default "1")format version; a verifier MUST refuse an unknown one
title, publisher, contactnowho stands behind the register, and where to report an error
claimsyesnon-empty array

Per claim:

FieldRequiredMeaning
claim_idyesstable, unique within the file; matches ^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$
assertionyesa verbatim span from the source (§2)
source_urlyeshttp, https, or file
checked_onyesYYYY-MM-DD, the date the assertion was last confirmed present
expires_onyesYYYY-MM-DD, strictly after checked_on
superseded_bynoa claim_id in this file that replaces this one
source_sha256no64 lowercase hex, SHA-256 of the raw response body at checked_on
note, tagsnofor humans

Unknown fields are rejected, not ignored. A typo in a field name must not silently disable a check.

1.2 Why expires_on is required

Because a claim with no expiry never becomes wrong — it just quietly stops being right, and nothing ever says so. Requiring it forces the publisher to answer "how long am I willing to vouch for this?" at the moment they know the answer. A regulation page might get 180 days; a price might get 30.

1.3 Supersede, do not delete

Content published elsewhere cites claim_ids. Deleting a claim breaks that citation silently; superseding it leaves a forwarding address. A superseded claim is reported, skipped, never fetched, and never fails a run. superseded_by MUST name a claim_id present in the same file, and MUST NOT be the claim's own id.


2. Assertions must be verbatim, and must be specific

The assertion is a span copied from the source. Not a paraphrase, not a summary, not a conclusion drawn from the source.

This is the single rule the format lives or dies on, and it is not stylistic. A paraphrase cannot be checked by string comparison, and a "verifier" that scores paraphrases with a similarity threshold is making a judgement call — at which point you are trusting the verifier instead of the source, and you are back where you started.

2.1 The falsifiability floor

Measured failure this exists to prevent. An early checker was given the assertion free and reported PASS, because the page contained the word "free" in an unrelated sentence. The claim was true, the check was worthless, and the green result was actively misleading — it consumed the reader's trust without earning it.

A conforming verifier MUST reject an assertion that fails any of:

RuleTest (on the normalised assertion, §3)
W1at least 12 characters
W2at least 2 word tokens
W3at least 4 significant words, or at least one number and at least 2 significant words

A significant word is a token that is not in the verifier's stopword list (common English function words: the, of, to, is, you, may, must, and so on). Numbers always count as significant.

Rejected assertions get status WEAK and fail the run. They are not fetched: an unfalsifiable claim has nothing to check.

W1–W3 are verifier rules, not schema rules. claims.schema.json deliberately accepts a weak assertion, so that a verifier can load the file and report exactly which claim is weak and why. A file that failed to parse would tell the author nothing useful.

Worked examples:

AssertionVerdictWhy
freeWEAKW1, W2
you have theWEAKW3: no numbers, 0 significant words
appeal itWEAKW1
within 180 days of the denialoknumber + 4 significant words
no later than 45 daysoknumber + 3 significant words
required by law to accept the reviewer's decisionok5 significant words

The exact stopword list is implementation-defined and MAY differ between verifiers; the thresholds MUST NOT. A verifier SHOULD publish its list.


3. Normalisation

The assertion and the fetched source are put through the same pipeline, in this order, before comparison. Order is normative.

  1. HTML extraction, if the response is HTML (by Content-Type, or by sniffing a leading HTML element). Remove comments; remove script, style, noscript, svg and template elements including their content; replace every remaining tag with a single space. Tags become spaces, never nothing — otherwise 180<b>days</b> becomes 180days and a true claim fails.
  2. Entity decoding (&nbsp;, &rsquo;, &mdash;, numeric references).
  3. Unicode NFKC normalisation.
  4. Typographic folding: curly quotes → straight, all dash variants → -, ..., every Unicode space → U+0020, zero-width and soft-hyphen characters → removed. These are CMS artefacts, not content.
  5. Case folding (Unicode casefold, not ASCII lower).
  6. Digit group separators: a comma between two digits is removed (1,0001000).
  7. Number words → digits (§3.1).
  8. Whitespace collapse: any run of whitespace → one space; trim.

Comparison is then plain substring containment: the normalised assertion must appear in the normalised source.

Substring containment, not fuzzy matching, on purpose. A fuzzy threshold is a knob, and a knob is an argument. Containment either holds or it does not, and two implementations cannot disagree about it.

3.1 Number words

Measured failure this exists to prevent. A source said "within four months"; the published guide said "within 4 months". A naive checker reported DRIFT. That is a false alarm, and false alarms are how a checker dies — the second time a green build turns red for nothing, people stop reading the output.

Written cardinals are rewritten as digits on both sides:

A run is not combined when the sequence is not a well-formed number: one two three1 2 3, not 6. Concretely, a run continues only when the next word is a scale, or the previous word was a scale, or a unit follows a ten.

Ordinals are deliberately excluded. second is also a unit of time, and rewriting "within 30 seconds" would be worse than not trying. If you need an ordinal in an assertion, quote it as the source spells it.

3.2 What normalisation deliberately does not do

No stemming, no synonym expansion, no punctuation stripping, no stopword removal. Each of those would turn near-misses into passes, which is the failure mode §2.1 exists to prevent. Normalisation only ever collapses distinctions that are presentational.


4. Statuses

Evaluated in this order; the first match wins.

#StatusConditionFetches?Fails the run?
1SUPERSEDEDsuperseded_by is setnono
2WEAKthe assertion fails §2.1noyes
3EXPIREDexpires_on < todayno by defaultyes
4UNREACHABLEthe source could not be fetched (§5)attemptedyes
5DRIFTfetched, but the assertion is not presentyesyes
6PASSfetched, and the assertion is presentyesno

Notes:

4.1 Exit codes

CodeMeaning
0every claim is PASS or SUPERSEDED
1at least one DRIFT, EXPIRED, UNREACHABLE or WEAK
2the run could not happen: bad arguments, missing file, schema violation, unknown format version

Schema violations are exit 2, not 1. A malformed register is not a failed check; it is an absent one.


5. Fetching

A conforming verifier:

A claimset verifier is a politeness-critical tool: it re-fetches other people's pages, forever, on a schedule, on behalf of people who did not write it. A verifier that behaves badly gets the whole format blocked.


6. Publishing a claimset

Nothing is required beyond committing the file. A publisher SHOULD additionally:

.well-known/claims.json is a plausible future home. It is not claimed here: registering a .well-known name before anyone uses the format would be standardising first and finding out later, which is the wrong order.


7. What this format is not


8. Versioning

claimset increments when the file shape changes. A verifier MUST refuse an unknown version rather than best-effort it: silently skipping a field it does not understand could turn a failing check into a passing one, which is the one outcome the format must never produce.

Changing the normalisation pipeline (§3) or the falsifiability floor (§2.1) in a way that changes any existing verdict is a breaking change and requires a version bump.