Numbers from the people who keep the records, with the method attached.
A small series of counts taken from primary records — most often a regulator's own published case files, sometimes a published survey. Each entry answers one question, from one public source, with one committed script, and states what it cannot answer.
Published 2026-08-23 at github.com/raviaxo/primary-record.
This directory is the source. The public repo is built from it by tools/build_public.py, which
flattens series/ to the root, copies the seven referenced probe directories to scripts/, strips
internal decision codes, refuses to ship a link that points outside the published tree, and compiles
every script before it will report success.
Licence: prose and figures CC BY 4.0, scripts MIT. Upstream terms are in LICENSE.md
— the UK ICO data is OGL v3.0; the Sophos and IBM reports are cited and never redistributed.
The figures this field runs on mostly have no method attached. "60% of small businesses close within six months of a breach." "95% of breaches are human error." "40–96% framework overlap." You cannot check any of them, and the paid apparatus around compliance is not pointed at them — it is pointed at producing evidence for auditors, which is a different job.
Meanwhile regulators publish their own case records, free, in bulk, with the decision attached. Almost
nobody counts them, and counting them correctly is harder than it looks — which is the second
reason this series exists. See METHOD.md rule 5, and CORRECTIONS.md entry 1, which is our own.
Every entry declares its source kind, and the two are never combined in a figure.
| Kind | What it is | Entries |
|---|---|---|
| regulator record | adjudicated outcomes, published because a statutory duty makes them public | 01–05 |
| vendor survey / report | a commissioned survey or a vendor study, and the way it travels | 06, 07 |
A survey of victims and a regulator's caseload are different populations answering different
questions. Dividing one into the other is the single error this series most reliably finds in other
people's figures, so it does not appear in ours — not in an entry, not across entries, not in
figures.csv, where vendor rows carry a source_kind column so no downstream reader can do it by
accident.
Why admit vendor sources at all? Because the same question — what can this number actually carry? — is worth asking of both, and the answers differ in an instructive way. A regulator's record is usually shaped so that counting it correctly is hard. A vendor's survey is usually counted correctly and published in cells too small to carry the comparison its format invites.
| One question | Stated in a sentence, answerable from the source |
| One source | Public, free, named with a URL and an access date |
| One script | Committed here. Every figure comes from running it |
| The limits | First, not in a footnote |
| A kill condition | Written before the run, so the entry can be retired honestly |
The contribution is the counting rule, not the number. Anyone can re-run it and get the same answer, or show that we got it wrong.
| # | Entry | Source | Question | Refresh |
|---|---|---|---|---|
| 01 | The audit evidence gap | PCAOB Part I.A inspection findings | How often do inspectors find that an auditor didn't check whether the company's own data was complete? | quarterly (source), annual (us) |
| 02 | What actually happens after you report a breach | UK ICO — seven quarterly exports, 130 files | You report a breach to the regulator. What happens next? | quarterly |
| 03 | How many controls are in NIST 800-53? | NIST SP 800-53 Rev 5 OSCAL catalogue | Published answers range 1,014–1,196 for the same catalogue. Which is each one counting? | on revision |
| 04 | What Canada's privacy regulator decided | OPC Canada — 172 business investigations, 2001–2026 | When the OPC investigates, what does it find? | irregular |
| 05 | There is no "most-cited" NYDFS section | NYDFS enforcement orders citing 23 NYCRR Part 500 | Three ways to count 34 orders give three different winners. Which does your source use? | on new enforcement |
| 06 | What a vendor's country reports can carry | Sophos State of Ransomware 2026 — 16 national cuts (vendor survey) | A US-vs-Singapore gap needs ~22 points before it means anything. Almost every published gap is smaller | annual |
| 07 | Two currencies, one dollar sign | IBM Cost of a Data Breach + newsrooms + trade press (vendor report) | Canada's 2025 figure is CA$6.98M and $4.84M. Both correct. Nothing on the page tells them apart | annual |
Every figure is disputable at the finest grain its source allows. You do not need to be able to open a pull request:
- Disagree with a number — open an issue naming the entry, the figure and what you think it should
be. Every figure in
figures.csvcarries the script, the source and the run date needed to check it. - Disagree with the method — the counting rule is in each entry's Unit section, stated so it can be attacked directly.
- Know of prior work — say so. Each entry lists a kill condition, and "someone already publishes this" fires it. That is a useful outcome, not a hostile one.
Corrections go in CORRECTIONS.md, dated, with what changed and why. Nothing is fixed silently.
Alonso, S. Primary Record. Zenodo. https://doi.org/10.5281/zenodo.22072578
Figure from entry NN, run YYYY-MM-DD. CC BY 4.0.
10.5281/zenodo.22072578 always resolves to the latest version. Each release also gets its own — v1.0 is 10.5281/zenodo.22072579 — so cite the versioned one if you need the exact snapshot a figure came from.
Carry the run date. Every row of every figures.csv has one, and these corpora grow — the PCAOB
dataset gains rows each quarter, the ICO adds files, NIST revises the catalogue. A figure without its
date is not checkable, which is the thing this series is about.
Machine-readable metadata is in CITATION.cff.
python3 verify.py # re-fetch every source, re-run every figure, diff against committed
python3 verify.py --offline # same, from cache, no network
python3 verify.py --strict # any movement is a failure
python3 verify.py --update # accept the fresh run and rewrite figures.csv
These corpora grow, so movement is expected. Silent movement is not. verify.py fails when a
committed figure is no longer produced, when a fetch or script errors, or when two figures
share an id — and merely reports movement, with its delta, for a human to accept with --update.
It never rewrites anything implicitly: a tool that silently updates the thing it is checking is not a
check.
Otherwise deliberately small. One script per entry, standard library only, no dependencies, no build step, no framework. An entry that cannot be refreshed in under an hour does not belong here.