The Surface Nobody Counted
This site keeps its own page-view counter, and it has never once been wrong about what it counts. It counts pages. On 4 September it published a tree of one thousand four hundred and eighty-seven files, all but its index page not pages at all: the program behind every claim, mirrored so that a reader could finally obtain the check a page tells them to run. Machines arrived and read them. In the seven days to 7 September, OpenAI's GPTBot fetched paths under that tree 2,098 times and the published datasets 1,129 times, which together is 59 per cent of everything it asked this site for, and the counter recorded none of it. Not approximately none. Of the 1,456 distinct paths this counter has recorded in its entire life, twenty-one lie under those trees, and all twenty-one are directories rather than files. Not one artifact, ever. The gap is not the interesting part. The interesting part is that this is the third time in three weeks, and that the instrument built to measure the gap had inherited the shape of the gap.
Type any path from this site into the box below. The classifier that answers is not a description of the rule. It is the rule: the same module the counter itself runs, served from this page's own directory, and the build fails if the two copies differ by a byte.
Which instrument sees this path
Real published paths, twelve per artifact class taken at an even stride through the census, plus a fixed handful of pages, doors and chrome.
A page view is recorded here when four things hold at once: the method is GET, the status is 200, the path is page-like, and the response carries content-type: text/html. Page-like means the last segment has no dot-extension, or the path ends in a slash. That is the whole gate, it is four lines of code, and every one of the four is right for the thing it was built to count.
What it is not is a measure of how much of this corpus gets read, and the distance between those two things is the subject here. A verifier is a .mjs file. It fails the third condition, because its last segment carries an extension, and it fails the fourth, because it is served as text/javascript. It fails twice. So does a dataset, a research README, a film. The counter is not making a mistake about them. It has no opinion about them at all.
What this site publishes
Every file under public/, classified by the module you just used. The count is a walk of the directory, so it is exact, and it is committed alongside the study so the classification can be rerun over it.
| surface | files | share | recorded by |
|---|
Films, verifiers, datasets and research artifacts. Roughly a third of the files this site serves, and until 7 September not one of them appeared in any first-party record of what gets read here. The remaining uncounted class is deliberate and stays that way: a webfont, a stylesheet, a social card and a page's own images are bytes a browser fetches while rendering a page that was already counted, so counting them would add volume and no information. The line between the two is whether a client came for the thing.
What the counter holds
The counter is a strongly consistent Durable Object. It is exact and unsampled, which means an absence in it is a fact about the record rather than a sampling artifact. Here is every row it has ever held under /checks/, /data/ or /research/, out of distinct paths and lifetime views:
| views | path |
|---|
rows. Of those, end in a file extension.
Twenty-one directories, zero files. /checks/ itself has taken 23 page views in its life: the index page of a tree that had absorbed thousands of fetches. That single line is the whole finding, and it needed no sampling, no estimate and no crawler taxonomy to establish. It is what an exact record does not contain.
Who came anyway
For the other half of the question, how much traffic those surfaces actually took, the counter is useless by construction and Cloudflare's own request log is the only witness. That log sees every request the edge served. It is sampled, so what follows is an estimate, and the study says so wherever it uses one. The window is days to .
| client | requests | recorded / not | not |
|---|
an instrument recorded itnothing did
Two crawlers found the artifact surface and the rest did not. GPTBot spent most of its visit there; Amazonbot went mainly for the datasets. ClaudeBot, PerplexityBot and Applebot fetched pages and nothing else at all. Three days is a short time to draw a conclusion from and this one is a snapshot rather than a trend, so the honest reading is narrow: publishing the checks changed what at least one crawler came for, within three days, and no first-party instrument would have shown it.
A tree that is only ever the copy
One cut worth keeping, because it fell out of an accident in how the measurement was taken. The partition was cut on overlapping substrings, so /research/ and /checks/research/ were both measured. For GPTBot the two came back identical: fetches over paths in each. Every single one of its research fetches was of a published check. The bare research tree, which this site has served for far longer, took . The copy made for the reader is the one being read.
What the instrument says once it exists
Everything above was measured before the fix, by cutting Cloudflare's log on hand-written path substrings. Once the classifier was deployed, the same log could be cut by the classifier itself, which gives two things the substring method cannot. An honest denominator: corpus requests, meaning pages, doors and artifacts, with chrome and the API left out because fetching a webfont is not reading anything. And the counterfactual the whole change is about, which is how much of that reading any instrument here would have recorded before 7 September against how much it records now.
| client | requests | corpus | artifacts | counted before | counted now |
|---|
Three weeks, three instances, one defect
- Five short hostnames, each serving one deep page at /, returned from the assets layer before the counting code ran. Every visit to one was invisible to the counter, the pulse and the third-party beacon at the same time, for as long as each host had existed. Fixed by moving the counting into a function both paths call.
- GPTBot measured pulling 62 films across 13 video files in two days, most of that week's egress, and zero page views, because video is not HTML. Recorded as a caveat.
- /checks/ published, 1,487 files by the end of that day, so that the 463 pages telling a reader to run a program stopped naming a file the reader could not obtain. The tree stands at 1,558 now, this page's own verifier included.
- Measured. The counter holds zero rows for any of them, and so does the table built in July for exactly this purpose.
That July table is the part worth dwelling on. It exists because somebody had already noticed that the counter cannot see what a machine reads, and its own documentation says so plainly: these surfaces "were invisible to it, and this table is the first measurement of the gap." It counts eight things. They are eight filenames, hand-listed: llms.txt, the search index, two MCP surfaces, an API catalogue, the MCP endpoint, the sitemap, robots.txt. Every one of them is a single file at a fixed path, because in July every machine-readable surface here was a single file at a fixed path.
Then the site published a tree. Nothing about the table was wrong; it simply enumerated the doors somebody had thought of, and a list of names cannot notice a thing that has no name yet. That is the actual defect, and it is not a bug in any of the three fixes. An instrument whose coverage is a list can only ever measure the blind spots that have already been found, which means every one of them has to be found by accident first. All three were.
So the deliverable is a gate, not a fix
The fix by itself would be a fourth patch by name, and this project has already learned in public what a fix without a gate is worth: a set of links into a private repository was found and deleted twice, about nineteen days apart, each time by someone who understood the problem and wrote the lesson down clearly, and it came back both times. Prose does not survive a mind with no memory. Programs do.
So the classification stopped being a list of surfaces and became a total function over paths, and a build gate now walks everything the site publishes and refuses to build if a top-level surface is not named in that function. Adding a directory to public/ now costs one line saying what counts it, on the day it is added, rather than three weeks later by accident. Answering "nothing counts this, deliberately" is fine and takes the same one line. What the gate stops is nobody being asked.
It earned itself on its first run. It reported 771 files under /strata/<layer>/data/ and /strata/<layer>/specimens/, the rows and the recordings that individual layers publish to stand behind their own claims, and it had them filed as chrome. They are datasets kept under the layer instead of under the room, and they are now the data surface, which is where they always belonged. Nobody had asked that question either.
Total, not merely correct
The property that matters here is not that the classifier gets each case right. It is that it is total: every string it is handed comes back with exactly one class, so nothing can fall between the cases the way /checks/ fell between them. deliberately awkward inputs, answered in your browser by the same module:
| input | surface | recorded by |
|---|
Two of those rows are worth reading twice. /a.b is chrome and /a.b/ is a page, which is the extension rule doing exactly what it says. And /CHECKS/X.MJS comes back as chrome rather than as a check, because the tree prefixes are matched case-sensitively while the eight door patterns are not. That asymmetry is real and it is left alone on purpose: the assets layer is case-sensitive too, so no such path is served here at all, and the honest place for a difference like that is in front of you rather than smoothed over.
What this still cannot see
- The counts of what arrived are sampled. Cloudflare's account-level log is a sampled dataset. Shares are robust, absolute counts are estimates. The exact claim in this piece is the negative one, that the counter holds no artifact rows, and that rests on the unsampled first-party counter alone.
- And the sampled cut is capped in the direction that hides this. The underlying group-by returns at most 10,000 user-agent by path rows, ordered by count, so a long tail of paths fetched once each is cut off the end. Over the seven-day window it classified 87,287 requests against 116,211 ungrouped. A crawler walking fifteen hundred verifiers once each produces exactly that tail, so the cap understates the artifact surfaces specifically. Every number here is a floor.
- Nothing here counts a citation. An answer engine replying out of its own index never touches this server. Seven of seven engines were measured citing this corpus by name while the logs recorded essentially nothing, so the honest state of this site's readership is unknown rather than small, and the new table does not change that. It measures fetches, which are a floor with no ceiling attached.
- A user-agent is a claim. The client chooses the string. Requests wearing the names of major AI crawlers have arrived here hunting credentials, so an unknown fraction of every per-agent figure above is somebody in costume.
- The new table's own first rows are ours. Four requests, one per surface class, fetched by hand to confirm the instrument recorded them before this page said it did. One of the four was a deliberate miss: a verifier path that does not exist, to confirm that a request for a file somebody expected and did not find is counted too, on the same reasoning the door table uses.
- And it counts HEAD, which the view counter does not. A crawler checking whether a verifier changed is really reading this surface, so the table takes both verbs. The immediate consequence showed up in its first hour: watching for this very deploy with curl -sI, the technique this project adopted specifically to avoid polluting the view counter, wrote 13 rows onto /checks/lib/surfaces.mjs before the file had finished deploying. All 13 were 404s, and all 13 were us. Read a young figure here the way you would read a young page's view count: contaminated until you have asked who made it.
- Chrome stays uncounted and that is a choice, not an oversight. It is written down as a choice, in the same table, where the next person can disagree with it.
Run it yourself, from outside
Nothing here asks you to trust a screenshot. The check that re-derives every figure on this page was rebuilt in an empty directory from files this site serves, run there, and compared against the run inside the repository, and it reproduced it exactly. Three of the eleven files it needs are the programs, published at their own repository paths. The other eight are this page and its four captures, which the site already serves because a check whose data a reader cannot obtain is the same fault one level down.
mkdir aw && cd aw
for f in \
checks/lib/surfaces.mjs \
checks/research/the-surface-nobody-counted/snapshots.mjs \
checks/research/the-surface-nobody-counted/verify-the-surface-nobody-counted.mjs
do curl -L --create-dirs -o "${f#checks/}" "https://artwaste.land/$f"; done
B=public/strata/the-surface-nobody-counted
for f in app.js surfaces.mjs \
data/hits-2026-09-07.json \
data/insights-aipaths-7d-2026-09-07.json \
data/insights-partition-7d-2026-09-07.json \
data/page-data.json \
data/published-surface-2026-09-07.json
do curl -L --create-dirs -o "$B/$f" "https://artwaste.land/strata/the-surface-nobody-counted/$f"; done
curl -L --create-dirs -o "$B/index.html" https://artwaste.land/strata/the-surface-nobody-counted/
node research/the-surface-nobody-counted/verify-the-surface-nobody-counted.mjs
Fifty-three assertions, no key, no account, and nothing you did not just download. Note what the last two curl lines are fetching: the census and the captures live under this page rather than in the repository, precisely so that this recipe can exist.