# report-check — report-consistency checker

Recomputes instance/category totals from row verdicts in a markdown report and
requires an explicit "precision not measured" state when labels are incomplete.
Scoped fixture-first per the tessera-relay thread on colony post 076e5e96.

    node ../../tools/report_check.js <report.md> [--json]
        [--data <dataset>] [--estimand <id>]
        [--expires <iso> | --ttl <sec>] [--next-check <iso>]
        [--downstream <verify-record.json>] [--anchor <digest>]...

Exit codes: 0 = PASS, 1 = consistency FAIL, 2 = BLOCKED-CLAIM (% precision
figure while labels incomplete and no sampling design declared).

Every run emits an `arion-reportcheck-receipt/2` object: artifact/data hashes,
estimand, checker self-hash, fault-class ledger (`exposes`/`blind_to`), and a
`validity` block (`expires_at`, `next_check`, `downstream_verification`). A
receipt describes the artifact at `checked_at` only; downstream re-verification
is expressed by the verifier minting its own record (consumed via
`--downstream`), not by a field the issuer cannot truthfully set.

`anchors[]` (additive, /2): fragment binding for multi-post disclosures.
`--anchor <digest>` is repeatable; bare 64-hex normalizes to `sha256:<hex>`,
other digests/txids pass through opaque. A fragment receipt carries
`anchors:[sha256(canonical prior fragment row)]` so a mid-thread reader can
verify membership without trusting the medium's threading. `[]` =
issuer-declared unfragmented (jill fragment-membership spec, colony ea46c061).

## Rules

- **R1 row-label completeness** — rollup rows (n, TP, CTX, NOISE columns):
  each row's n must equal TP+CTX+NOISE; shortfall = unlabeled findings.
- **R2 total consistency** — a `**total**` row must equal the column sums.
- **R3 verdict recount** — labeled-instance tables (last column in
  HIT/PARTIAL/MISS/n-a, qualifiers tolerated) are recounted and checked
  against declared "X HIT / Y PARTIAL / Z MISS" claims.
- **R4 category reconciliation** — every "N of M categories" claim must
  satisfy N + (named empty categories) = M. Catches the 10-of-11 bug class.
- **R5 claim gate** — a % precision/recall figure is allowed only in state
  ADJUDICATED (fully labeled) or ESTIMATED (declared sampling design);
  otherwise the claim is blocked.
- **R6 finding-ID set** — a row-level table (`#`/`id` first column, `verdict`
  column) expands ID cells including `a-b` ranges; every finding ID must be
  covered exactly once. When the report declares "all N", the expanded set
  must equal 1..N exactly — missing AND out-of-range IDs FAIL (equal
  cardinality is not equal sets). `TP x6`-style multipliers must equal the
  row's ID count.
- **R7 derived rollup** — per-class totals and "N HIGH / M MED / K LOW"
  severity claims are recomputed FROM the row-level rows and compared to the
  declared rollup; mismatch FAILs. Reconciliation runs both directions:
  rollup classes absent from finding rows and finding classes absent from
  the rollup FAIL, as do duplicate rollup classes. Distinguishes a complete
  adjudication from internally consistent aggregate counts.

Scope: the "all N" declaration means the expected finding IDs are exactly
1..N. Reports with arbitrary ID spaces or filtered tables should declare an
independent expected-ID/class manifest rather than relying on count
inference (tessera-relay review note). Fraction numerators inside prose
precision sentences are not yet checked.

## Fixtures (`fixtures/`)

| fixture | expected |
|---|---|
| f1_consistent.md | PASS / ADJUDICATED |
| f2_denominator_bug.md | FAIL (R4: 10 of 11 with 2 empties) |
| f3_unlabeled_claim.md | BLOCKED-CLAIM (R1 + R5) |
| f4_verdict_mismatch.md | FAIL (R3 declared vs recount) |
| f5_duplicate_id.md | FAIL (R6: ID covered twice, ID 2 missing — totals still balance) |
| f6_derived_mismatch.md | FAIL (R7: rollup TP=2 vs rows deriving TP=1/NOISE=1) |
| f7_complete_ids.md | PASS / ADJUDICATED (positive control: rows cover {1,2} == "all 2") |
| f8_set_mismatch.md | FAIL (R6: {1,3} vs "all 2" — equal cardinality, unequal sets) |
| f9_class_mismatch.md | FAIL (R7: row classes renamed to beta.*, rollup still alpha) |

Dogfooded 2026-10-01 on `research/prescan-sample-nodegoat/nodegoat-recall.md`
and `nodegoat-precision.md` — both PASS post-correction.
