# VERIFY — what each check asserts

`node verify.mjs` runs 17 MUST_PASS checks against the delivered dataset. It is
deterministic and offline; the proof that the source is live and real lives in the
provenance manifest + retained raw evidence captured by `src/collect.mjs`.

| # | Check | What it asserts |
|---|-------|-----------------|
| 1 | Totals agree | `contacts.json` count equals `summary.json` and `manifest.json` totals |
| 2 | All 50 states + DC | Exactly 51 jurisdictions present, none missing, no extras |
| 3 | No empty jurisdiction | Every state has ≥ 1 organization |
| 4 | Real fields populated | name, type, phone, state, source, sourceUrl, year present on every record |
| 5 | Phone coverage ≥ 99% | Fraction of records with a phone number |
| 6 | **No fabrication** | `contactName`, `contactTitle`, `email`, `domain` are EMPTY on every record |
| 7 | Source authenticity | Every record names the NCES/Urban source and a URL on that host |
| 8 | JSON integrity | sha256 of `contacts.json` equals the value recorded in the manifest |
| 9 | CSV integrity | sha256 of `contacts.csv` equals the value recorded in the manifest |
| 10 | Source requests | Fetch log has one HTTP 200 request per jurisdiction (51/51) |
| 11 | Grouping partition | Per-state groups sum to exactly the total record count |
| 12 | Per-state files | A `by-state/<ST>.json` exists for all 51, counts matching the groups |
| 13 | CSV consistency | CSV header equals the schema; row count equals record count |
| 14 | Determinism | Grouping is stable and the dataset is canonically sorted |
| 15 | Raw evidence | Retained raw API responses (AK, DC) match delivered counts |
| 16 | Site builds | `public/index.html` is produced by `build-site.mjs` |
| 17 | Site embeds directory | The page contains the record count, state grouping, and a known org |

Check 6 is the keystone: it makes shipping a fabricated name, title, email, or
domain mechanically impossible without failing verification.
