Skip to content

v0.13.0

Release Date: 2026-08-10

Highlights

  • claims.Lint: evidence-integrity linting for claims reports — a verified claim must have a resolving URL and quote, sufficient independent corroboration, and a statistic that isn't stale
  • SourceRole: distinguishes primary/secondary-relay/secondary-analysis/self-reported sources; secondary-analysis and self-reported now require a corroborating relatedClaimId
  • ClaimsCriteria.MinCorroboratingSources / MaxClaimAge: opt-in, configurable corroboration and staleness thresholds, enforced by both claims.Lint and EvaluateClaims
  • BREAKING: ExternalValidation.AccessedAt and InternalValidation.ValidatedAt are now *time.Time so omitempty actually omits an unset value

Overview

v0.13.0 closes the gap that let a claim marked verified cite a figure synthesized from secondary reporting, when a primary-source check later put the real number substantially lower. DetermineVerdict computes a verdict from a Validation, but a verdict can also be hand-authored directly on a Claim — bypassing that computation entirely. This release adds a re-checking layer, claims.Lint, that verifies a stated verified is actually earned, plus three specific checks targeting the failure mode that motivated it: missing quotes, insufficiently corroborated secondary sources, and stale statistics presented as current.

New Features

claims.Lint and sevaluation lint <claims.json>

Lint gates every claim whose Verdict is VerdictVerified:

Rule Level Description
claim-missing-id / claim-duplicate-id Error Every claim has a unique id
verified-requires-validation Error A verified claim has a validation object
verified-requires-url / verified-requires-quote Error A verified external claim has a source URL and a verbatim quote
verified-value-in-quote Warning The claim's statistical value appears in the quoted text (advisory — a legitimate quote can state a rule or a range instead of the literal number)
verified-derived-needs-sources Error A verified derived claim lists source claim ids
verified-internal-needs-evidence Error A verified internal claim has an evidence path or output
verified-subjective Warning A subjective estimate is published as verified
verified-role-needs-corroboration Error A secondary-analysis/self-reported source has a corroborating relatedClaimId
verified-insufficient-corroboration Error Opt-in — fewer independent sources than criteria.minCorroboratingSources requires
verified-stale-as-of-date Error Opt-in — asOfDate older than criteria.maxClaimAge

Lint never mutates the report. It's wired into sevaluation lint (auto-detected from the claims top-level key) and sevaluation render --format=html/--format=json and sevaluation check also gained claims- report support. See Evidence-Integrity Linting for the full walkthrough.

SourceRole

ExternalSourceType categorizes a source's general authority (NVD, reputable vendor, community, aggregator, ...). SourceRole is a separate axis: how directly this citation speaks for the claim.

type SourceRole string

const (
    SourceRolePrimary           SourceRole = "primary"
    SourceRoleSecondaryRelay    SourceRole = "secondary-relay"
    SourceRoleSecondaryAnalysis SourceRole = "secondary-analysis"
    SourceRoleSelfReported      SourceRole = "self-reported"
)

Two "reputable vendor" citations can carry very different trust: a wire report relaying an earnings call is near-primary, but an outlet's own synthesized estimate across other reporting is secondary-analysis — exactly what a direct primary-source check can contradict. SourceRole.RequiresCorroboration() is true for secondary-analysis and self-reported; Lint then requires at least one RelatedClaimIDs entry. Role is optional and empty by default, so existing reports are unaffected.

ClaimsCriteria.MinCorroboratingSources / MaxClaimAge

Two new, opt-in criteria knobs, both disabled by default:

report.Criteria = claims.ClaimsCriteria{
    MinCorroboratingSources: 2,                                    // a general threshold, independent of SourceRole
    CorroborationCategories: []claims.ClaimCategory{claims.ClaimStatistical},
    MaxClaimAge:             365 * 24 * time.Hour,                 // flags a stale Statistical.AsOfDate
}

IsSufficientlyCorroborated and IsFresh are the pure checks; both claims.Lint and EvaluateClaims use them. In EvaluateClaims, a verified claim failing either check is treated the same way as needs-review for the report-level Decision — a conditional pass if AllowNeedsReview, otherwise a fail — without changing the claim's own stored Verdict or the Counts breakdown. The combined rationale text now lists up to three reasons, e.g. "Passed with 1 claim needing review, 1 claim lacking sufficient corroboration, and 1 claim with a stale statistic"; the existing single- and two-reason wording is unchanged.

Bug Fix: AccessedAt/ValidatedAt omitempty

ExternalValidation.AccessedAt and InternalValidation.ValidatedAt were plain time.Time with json:",omitempty"omitempty never applies to a non-pointer time.Time (its zero value isn't Go's "empty" value for structs), so an unset field silently serialized as "0001-01-01T00:00:00Z" instead of being omitted. Both are now *time.Time; NewExternalValidation/NewInternalValidation still default them to time.Now().UTC(), and new WithAccessedAt/WithValidatedAt chainable setters (mirroring StatisticalDetail.WithAsOfDate) cover hand-authored reports. No JSON schema change — the wire shape was already string/format: date-time; only the Go-side representation was wrong.

Backward Compatibility

claims.Lint, SourceRole, MinCorroboratingSources, and MaxClaimAge are all additive and opt-in — disabled/unset by default, verified to produce byte-identical lint output against existing case-study ledgers that don't set the new fields. The one breaking change is the AccessedAt/ ValidatedAt field-type fix: code that assigned these fields a time.Time value directly (rather than via the constructors or new With* setters) needs updating to take an address.

Commits

  • 7709583 feat(render/html): add HTML renderer for claims reports
  • 06b9fba feat(cli): render and check claims reports
  • d53be38 feat(claims): add Lint and wire sevaluation lint for claims reports
  • 5ff6086 feat(claims): add SourceRole and gate secondary-analysis/self-reported on corroboration
  • 18a5a0b feat(claims): add MinCorroboratingSources criteria + report-level enforcement
  • c01aa66 feat(claims): add MaxClaimAge criteria + staleness enforcement
  • 4ee701b fix(claims): make AccessedAt/ValidatedAt *time.Time so omitempty works
  • 3869045 docs: add v0.13.0 changelog entry