# Corrections
Every claim this index has published and then withdrawn, what was wrong with it,
and what changed so it does not happen again.
The log exists because the alternative is worse. This index publishes statements
of the form *"company X changed Y on date Z"* to an audience that often knows the
answer independently, and a single false claim of that shape costs more
credibility than a hundred true ones earn. When one gets out, the useful response
is not to quietly delete it — it is to say what happened in enough detail that a
reader can judge whether the fix is any good.
The same standard applies to claims this index makes about **itself**. A wrong
number in the README is a published claim, it is read by more people than any
individual event, and it is not exempt because it was prose rather than data.
The 2026-08-08 entry below is the first of that kind.
**Nothing here is deleted.** A retracted event stays in `data/events.ndjson`
exactly as it was published, with a later line recording that it was withdrawn,
why, and when. The public feed excludes retracted events; `docs/api/retractions.json`
lists them. Both halves of the story stay in the git history, which is the whole
premise of the project.
---
## 2026-08-08 — The README described an eight-month archive. There was one day of it. {#2026-08-08-archive-age}
**Retracted:** three claims in `README.md`, and one in this file.
| Where | What was published |
|---|---|
| `README.md`, crawling gates | *"…also protects the **eight months of archive** recorded before any origin was written down."* |
| `README.md`, cost table | *"`docs/` is **under 1MB**"* |
| `README.md`, cost table | *"the append-only NDJSON grows by roughly **a megabyte a year**"* |
| `CORRECTIONS.md`, Notion entry | *"The archive **also contains a genuinely mixed-origin stretch**: everything recorded before 2026-08-07…"* |
### What actually happened
There is no eight-month archive. There is no mixed-origin stretch. Every
`observed_at` in `data/companies/` reads `2026-08-07`, the first archive commit
landed the same day, and the corpus at the time of writing is 126 observations
across 60 companies, 6 runs and 9 events.
The other two are measurements that were true when written and were never
re-checked:
```
docs/ claimed under 1MB measured 1,068 kB
data/ growth claimed ~1 MB/year measured ~400 bytes per target
crawled, so ~50 kB per
full sweep of the seed
```
The growth figure was not merely stale, it was the wrong shape. `data/` does not
grow at a rate per year; it grows at a rate per target crawled, because
`data/runs.ndjson` records one result per target on every run while the
per-company series only appends when a value actually changes. Stated as an
annual figure it hid the variable that matters, which is how many pages the seed
holds.
### Why the existing gates did not catch it
Because there are none. Every gate in this project guards the path from a fetched
page to a published event. Nothing at all guards the path from a fact about the
project to a sentence in the README.
That is the whole finding, and it is uncomfortable in proportion to how careful
the rest of the pipeline is. The extractor will not attribute its own version
bump to a company. The diff engine will not call a null a removal. The run ledger
gets a line whether or not anything happened, specifically so that silence cannot
be mistaken for calm. And a paragraph three screens above all of that described
an archive that did not exist, in a document whose argument is *the record is
this repository's git history* — a record which, at the time, said plainly that
it was one day old.
The eight-month sentence appears to be a forward-looking description written
while the origin rules were being designed, left in the present tense. Nobody
re-read it against `git log`. The two cost-table numbers were measured once and
then the thing they measured grew.
### What changed
1. **The archive-age claim is gone.** The currency rule (S9) is now described as
protecting readings recorded before the origin gate existed *should the
archive ever be extended backwards*, which is what it actually does. The same
correction is applied to the "What is still not solved" section of the
2026-08-07 Notion entry above, which asserted the same non-existent stretch.
2. **The cost table quotes measurements with their scale attached.** `docs/` is
1.0MB at 60 companies, and the note says most of it is one generated JSON
file, so the number moves for a legible reason. Repository growth is stated
per target crawled rather than per year, with the ledger named as the part
that grows on every run.
3. **This log now covers claims about the index itself**, stated in the preamble.
The distinction between "a false event" and "a false sentence" was never
argued for; it was just never considered.
### What is not retracted
Nothing in `data/`. No observation, event or run record was wrong, and the
crawling and diff rules the README describes are all implemented as described.
What was wrong was the prose reporting on the state of the archive, in three
places, and this is not a data correction.
### What is still not solved
The generated pages cannot drift from the data, because `bin/build-site.js`
computes them from it. `README.md`, `METHODOLOGY.md` and this file are written by
hand and can say anything. 87kB of hand-written prose currently documents 126
observations, and every claim in it about corpus size, file size or growth rate
is a number a human typed and no test reads.
The honest fix is to generate the numbers rather than type them — a small set of
build-time placeholders in the README, filled from the same fold the site uses,
so a stale figure becomes impossible rather than merely embarrassing. That is not
done. Until it is, the cost table is accurate as of 2026-08-08 and carries the
same guarantee as any other sentence somebody typed once.
---
## 2026-08-07 — Airtable did not add a logo wall. Our extractor found one. {#2026-08-07-airtable-acquisition}
**Retracted:** 1 change event, `airtable` / `home`, detected at
`2026-08-07T15:53:34Z`.
| Signal | What was published |
|---|---|
| `customer_logos` | *Customer logos first observed: "Azure, Box, ChatGPT, Claude, Dropbox, Ebay, GitHub, Google, Jira, …"* |
### What actually happened
Two observations of `airtable.com`, four hours and forty-three minutes apart, in
the index's first full sweep of all 120 targets:
```
2026-08-07T11:10:09Z customer_logos = null
2026-08-07T15:53:34Z customer_logos = "Azure, Box, ChatGPT, Claude, Dropbox,
Ebay, GitHub, Google, Jira, Openai,
Salesforce, Slack, Snowflake, Tableau,
Virgin Voyages"
```
We do not know that Airtable added a logo wall in those four hours, and neither
does anything in the archive. What we know is that we could not read one at
11:10 and could at 15:53. Those are different statements, and the index
published the stronger one.
Nothing distinguishes them from the transition alone. A logo wall rendered from
CSS sprites, or from a lazy-loaded component, or from `` tags whose `alt`
text was added later, produces exactly this: a null, then fifteen names, with no
announcement in between. "Our extractor finally succeeded" and "the company
finally shipped it" are one transition wearing two hats.
### Why the existing gates did not catch it
Because there was no gate. The engine had been suspicious of the *opposite*
transition since its first commit — rule S2, `value → null`, is a parser fault
and never a change event, and it is the property the whole test suite is built
around. It was never suspicious of `null → value` at all.
That asymmetry had no argument behind it. It was simply never written. The two
directions carry the same ambiguity for the same reason: a null means *we have
no value*, which is a statement about our reading, not about their page. S2
takes that seriously in one direction. Nothing took it seriously in the other,
and the first sweep large enough to contain a recovering selector published the
result as news.
The event's own summary gave the game away and nobody read it closely enough:
*"Customer logos **first observed**"*. That is an accurate description of what
happened to us. It was published under a heading that says what happened to
them.
### What changed
1. **`null → value` is a signal acquisition, not an addition.** Rule S10 in
`src/diff.js`, the sibling of S2 and built to the same shape: the observation
is recorded in full, the value is not published, and nothing is silently
dropped. The outcome is classified `acquisition`, beside the existing
`parser-fault` and `origin-shift`, and it is counted in the run ledger and in
the commit subject, because a suppression nobody can see is indistinguishable
from a crawler that found nothing.
2. **The value must be corroborated before it becomes a baseline.** The same
three consecutive readings the currency rule (S9) requires, from a healthy
page, and adopted **silently** when they arrive. A read that returns
byte-identical content counts, because identical bytes cannot hold a
different value — without that, a value on a page that never changes again
could never be adopted and the site would report a signal as unextracted
after extracting it every day for a week.
3. **Where there is evidence, the reason string says so.** If the previous read
of the page was classified `changed-structure`, had a signal in a parser
fault, yielded materially less than this one, or ran a different extractor
version, the acquisition is recorded as extractor recovery with high
confidence and names the evidence. None of it changes what is published — S10
publishes nothing either way — but *"we could not tell"* and *"we could tell,
and it was us"* are different findings and must not be written down the same
way.
4. **The engine no longer emits `added` events at all.** There is no longer a
code path that produces one. A consequence worth stating plainly: **this
index will not report the moment a company first publishes a logo wall, a
proof point or a seat minimum.** It cannot tell that moment apart from the
moment our extractor first managed to read one. A false *"company X added Y"*
costs more than a missed one, so the asymmetry resolves toward silence, the
same way §4.3 and §4.10 resolve it.
5. **Tests.** `tests/diff.test.js` replays this exact case from the two lines
still in `data/companies/airtable.ndjson` and asserts zero events for
`customer_logos` while the three genuine changes on the same page still
publish. Alongside it: a first observation of a target publishes nothing at
all, an acquisition after a parser fault or a structure change is recognised
as recovery, an acquired value is adopted only after corroboration, an
unhealthy read never corroborates, a value that keeps moving never becomes a
baseline, and a genuine value → value change still publishes normally.
### What is not retracted
The three other events from the same observation — `headline`, `subhead` and
`proof_points` — stand. Each is a `value → value` transition between two strings
this index actually read, and S10 has nothing to say about them.
They carry a limitation of their own, and it is now stated on the site rather
than only here. Airtable is documented in this repository as A/B-testing its
`