Skip to content

claims

Check assertions written in Markdown against runs in the ledger.

A README says "MAE 0.353 eV vs experiment @ aDZ". That number was transcribed by hand from an artifact, and nothing checks it: re-run the benchmark, and the document keeps asserting 0.353 forever. This closes that loop.

The format is an HTML comment beside the prose it describes:

The cut leaves WER essentially unchanged (**0.053 vs. 0.043** baseline).
<!-- claim: ablation/whisper-small-ablated metric=wer value=0.053 tol=0.001 -->

An HTML comment because it is the only annotation that is invisible in every Markdown renderer, plain text so grep and git diff work on it, and adjacent to the claim rather than in a separate file that drifts. The prose is never touched; a claim is added beside an assertion that already exists, so annotating is incremental -- one claim is useful on its own.

Five verdicts, and the distinctions between them are the point:

supported     a run matches, within tolerance
contradicted  a run matches the reference but DISAGREES on the value
unsupported   no run matches -- the claim may be true, nothing backs it
ambiguous     the reference matched several runs; which is meant is unknown
stale         the artifact changed after the claim was last verified

unsupported and contradicted must never collapse together: one means "I never recorded this", the other means "this is wrong", and the response differs. ambiguous exists because silently taking the first of several matches is how a checker reports a confident wrong answer.

Claim dataclass

One <!-- claim: ... --> annotation, parsed but not yet checked.

raw keeps the annotation's original body so a malformed field can be reported with the text that failed to parse, not a reconstruction of it.

Verdict dataclass

One claim's outcome: which VerdictKind, why, and what it was checked against -- source_path names the run the ledger read, so an auditor's "from which file?" has an answer without opening the ledger separately.

matched_split/matched_step are the MATCHED RUN ROW's split/step -- what was actually found -- distinct from claim.split/claim.step, which are what the document asked for. The two are different quantities that happen to share a name one level down; the matched_ prefix here keeps a reader from having to guess which is which.

VerdictKind

Bases: StrEnum

The closed set of claim-check outcomes.

A StrEnum member IS a str -- every existing verdict.verdict == "supported" comparison, Counter tally, and dict(...) == {"supported": 1} assertion keeps working untouched -- but a misspelled verdict at a construction site is now a ty error instead of a silently-tallied new category in counts.

check(conn, root, *, resolver=None)

Verify every claim under root. Read-only; never edits a document.

resolver is optional and INJECTED. When one is passed, the citation lint runs too, so a claim naming a key no configured source has is reported here rather than only from cite.check -- a claim whose number agrees but whose citation does not resolve was reading as plain supported from the tool whose name says it checks claims.

Injected rather than imported: this module verifies numbers against the ledger and must not grow an import-time dependency on the bibliographic readers, which are an optional surface. No resolver means no lint, which is exactly the behaviour every caller had before.

The two concerns stay separate verdicts. A claim can be contradicted on its number AND uncited on its key, and folding one into the other would hide something the author has to fix.

check_citations(claims_, resolver)

Claims whose citation key no configured source can resolve.

A lint, not a verification: it says the key is unknown here, never that the cited work fails to support the claim. Claims with no cite are skipped -- every claim written before this field existed has none, and reporting them all would turn one new field into a document-wide false alarm.

check_claim(conn, claim)

Check one claim against the ledger and return its Verdict.

Deterministic throughout: no run, no matching metric, or more than one candidate row all become distinct VerdictKinds rather than a guess -- disambiguating by distance to the claimed value would let the claim select its own evidence, so "more than one row, no split=/step=" is AMBIGUOUS, never a silent pick of the closest.

coverage(root)

Numbers in prose that no claim annotation covers.

The inverse of check: that verifies claims that exist, this finds assertions that were never made checkable. A document with zero contradicted claims looks healthy while asserting a dozen unverifiable numbers, and nothing surfaces the difference.

Reports a decimal as uncovered when no claim in the same file states that value (within its own tolerance). Deliberately line-agnostic: a claim annotation usually sits a line or two from the prose it describes, and requiring adjacency would produce false alarms for correctly-annotated documents.

find_claims(root)

Every claim in every Markdown file under root.

parse_file(path)

Claims in one Markdown file, plus complaints about malformed ones.

A malformed annotation is reported, never skipped: silent skipping is how a claim disappears from review without anyone deciding to remove it.