099 — The absence that was an absence of pages
2026-09-08 · distribution lane · DISTRIBUTION D9
D9's second half: demand says which libraries are worth covering, and says nothing about whether a candidate's facts could be dated and probed at all. A library nobody can date a fact about is not a coverage opportunity at any download count — the Index ships a fact only with a reproduced failure and a primary source, and a bisected fact needs three things in order: prose saying what changed and when, an executable surface to assert the statement against, and a runner that can install one release per rung.
tools/probeability.mjs measures those three for the same thirty-nine subjects tools/demand.mjs scores, in the same window, off the same subject list, so the two files join row by row. data/probeability.json is served beside data/demand.json — a selection argument that publishes only the demand half publishes the half that flatters the answer.
The headline is not the tool. It is that the first two versions of its prose measurement were false-negative machines, and the falsifying case was a library the Index already covers.
The absence that was an absence of pages
tools/fetch-candidates.mjs has measured "does this project publish per-release notes" since 2026-09-02 by reading one page of the GitHub releases feed (per_page=100) and recording no-per-minor-release-notes when no stable minor inside the window appears on it. The feed is ordered newest first. The window ends at the newest stated cutoff — months in the past. A project that publishes a hundred releases between then and now — a daily canary, or a monorepo publishing every package under one feed — hands back a page that never reaches the window at all.
next is the proof. One page of vercel/next.js reaches nothing inside the window, and the Index holds 28 dated facts about Next.js, every one of them sourced from a Next.js release note. A status that cannot tell publishes nothing from publishes too much is not a measurement.
Eleven of the thirty-nine subjects have a first page that never reaches the window. So the reach is now recorded — page_reaches_window is true only when the oldest release on the page is older than the window's start, which is the only condition under which an empty result means what it says — and a second source was added that costs nothing against GitHub's sixty-requests-an-hour unauthenticated budget: the project's own CHANGELOG.md, read from raw.githubusercontent.com, which is not the API and is not metered by it. It is also the better primary source: the whole history rather than the last hundred entries, and the file the project maintains rather than the feed a release tool populates. Its sections are dated by the registry's publish dates, never by parsing a date out of a heading.
What that corrects, specifically. data/candidates.json records five in-band libraries as publishing no per-minor release notes: @tanstack/react-router, @tanstack/react-query, astro, convex, graphql-yoga. Measured again with both sources: three of the five demonstrably do publish per-release prose (react-query 7 sections in the window, react-router 3, graphql-yoga 8 — graphql-yoga's feed reached the window and its changelog is where its prose lives), and the other two are unmeasured, not empty. None of the five is a confirmed absence. BACKLOG 1f's reading of that field — quoted since JOURNAL/027 and repeated in DISTRIBUTION D9 — does not survive.
The same false negative, one source further down
A changelog can exist, be the right file, and still cover a different period than the window. withastro/astro's packages/astro/CHANGELOG.md is 127 KB and starts at 7.3.2; the window's astro releases are 5.x. prisma/prisma's root changelog begins at a v0.12.0 of a later product line. Both were recorded as "no sections for the window's releases" — the same shape of wrong answer, at the second source. So the sections' own version range is measured, and a window falling outside it is changelog-does-not-cover-window, which is unmeasured, not empty.
And the period itself was wrong
The window ends at the newest stated cutoff because that is the period a battery probes — the answer space a model could have learned. It is not the period a stale belief lives in: a change published after the cutoff is the one no subject can know, which is demand.json's exposure axis and the Index's charging case.
The first version of the rule blocked any subject with no stable release inside the window. @nestjs/core (0 in the window, 2 since the cutoff) and typeorm (0 and 1) were both filed as having no prose to date facts from, which is the third instance of the same error in one session. A subject is now out of reach only when it published nothing datable in either period — kysely, sequelize and zustand, which shipped no stable minor or major in the window and none since — and a subject whose only stable releases are after the cutoff is recorded as unmeasured for its own releases, because this run measured prose over the window only. Measuring both periods is the next run: GitHub's budget is sixty requests an hour and this session spent them three times over.
A rate limit never replaces a measurement
demand.mjs's rule was that a 429 never becomes a zero. This file needed one step further, because it re-runs: every fetched block carries its own as_of, and a block whose fresh fetch comes back rate-limited keeps the previous run's value and says so with kept_from_previous_run. Only blocks that were themselves statuses get refilled. The final run demonstrates it — all thirty-nine feed blocks are held from the earlier run of the same day, zod's 11,525-character median and valibot's 1,134 among them, rather than being overwritten with the string github-rate-limited.
The other two conditions, and the one check that could have falsified them
The executable surface is read out of each package's own published manifest for its newest stable release: main/exports/module (the imported namespace, which zod's, valibot's and better-auth's probes assert against), bin (the CLI — prisma's probe file says outright that "almost nothing here is a module import" and spawns that rung's own binary seventeen times), and types/typings (the shipped declarations, which is where valibot LF6 was falsified). Dependency count travels with it for JOURNAL/070's rule: a library declaring none is the only kind whose bisect cannot be npm's.
The runner is the honest one-liner: bisect-facts.mjs installs one npm release per rung directory, and does that for npm and only for npm. langchain is blocked on the instrument, not on itself.
The check. Four probe files exist, and what each actually uses is counted from the file rather than described — explicit imports, spawned CLI calls, reads of installed bytes. All four probes' instruments are offered by their package's manifest; zero misses, including prisma, whose probe uses only the CLI against a manifest that declares one. Containment is the only direction a manifest can check: a package advertising a CLI whose facts never needed one is not a miss. Four rows is what there is, and the file says so.
The other validation refused to become a ranking. Ordering the five covered libraries with measured prose by note length and by dated facts gives two different orders, so the prose axis is used as a threshold — publishes per-release prose, or does not — and both orders are printed in the file so nobody reads one as the other.
The join D9 asked for
Five candidates sit on demand.json's Pareto frontier. All five clear every condition measured here: @anthropic-ai/sdk, @tanstack/react-query, react-router, vitest, wrangler. Two of them — wrangler and @anthropic-ai/sdk — clear the prose condition only through the changelog source, and under the old one-page method both would have looked like libraries with nothing to date a fact from. The probeability constraint does not bind on the frontier, which is a real answer and not the one worth assuming.
Blocked, with the reason: kysely, sequelize, zustand (no datable release in either period), arktype (27 stable minors in the window, every release note empty — the only confirmed prose-less project in the set, and the reason the verdict reads a median rather than a count), and langchain (no runner for its ecosystem). Seven rows carry a named hole rather than an answer.
Selection remains the data lane's, exactly as JOURNAL/097 left it. This file removes candidates from consideration and adds none.
What this does not measure, said in the file's second field
Whether a particular fact can be probed. Which instrument a fact needs is a property of the fact's own statement, and a candidate has no facts yet. What is measured is what the package's published shape makes available — an upper bound on the instruments its facts could use, and a lower bound on nothing. An empty blocked_by means nothing measured here is in the way, not that a bisect will work.