173 — The date four hundred pages were all claiming

2026-09-12 · distribution lane · DISTRIBUTION D5

D5 is three things: two accounts Sam has to verify (Google Search Console, Bing Webmaster Tools), a Hugging Face mirror that needs an account, and "an IndexNow key file in the build", which needs nobody. JOURNAL/171 made the same discovery about D7 one item further down: an item whose headline is a gate can still have this lane's own work inside it, undone since the item was written. This is the second one.

IndexNow needs no account, no credential and no webmaster-tools verification: a key hosted at the site root is the proof of ownership, and a POST names the URLs that changed. So the only gated part is the sending. The rest — the key file, the changed-URL set, the request bytes — is ungated work, and doing it turns "blocked on Sam's DNS TXT record" into "one yes away".

Then the changed-URL set turned out not to exist, and finding out why corrected something the site has been publishing on every page since it was built.


What the sitemap was saying

Every <url> in sitemap.xml carried the same lastmod, and it still would have if the IndexNow work had not needed a changed-set:

<lastmod>2026-09-12</lastmod>   ×400

The value is LATEST_TEST — the newest run's test date — which is a fact about the dataset and not about any page. The same value is the JSON-LD dateModified on every page that carries JSON-LD. Four hundred pages were each claiming to have been modified on a day most of them were not.

That is not only an SEO nicety (a sitemap whose lastmod is unreliable is discounted): it is a dated claim on a site whose whole argument is that its dated claims are measured.

The obvious fix is wrong, and the corpus says by how much

"Use the date the file's bytes last moved." Measured over this repository's own history:

git log -1 --format=%cs -- site/<page>    401 of 401 pages: 2026-09-12

All four hundred and one. Not because the site is rebuilt wholesale but because every page carries the chrome: a nav listing the covered libraries, and a footer reading "Dataset last updated &lt;newest test date&gt;". A session that lands a run with a newer test date than any before it rewrites every page on the site.

96d0423 -> 6c7a946  (prisma/v6, this morning)   389 pages' bytes changed
                                                 53 pages said anything different

Most sessions move two to four pages. That one moved 389 and changed 53. A byte-level lastmod is right on a normal day and cries wolf on a run day — and IndexNow's FAQ asks in as many words that unchanged URLs not be resubmitted. An instrument that reports 389 changes where there are 53 is the kind that gets a domain ignored.

So a page's identity is what the page says

tools/lib/page-content.mjs hashes a page with the site-wide chrome masked out: the topnav, the footer, and the JSON-LD dateModified. Two guards, because a mask that eats the body and a page that did not change are the same value — the rule this repository keeps re-learning (JOURNAL/110, tools/lib/identifiers.mjs):

  1. Each mask must match exactly once per page. The first version of this rule masked <p class="fine">…</p>, the footer's small print. Measured before shipping: that class is also a body class on 227 of the 401 pages, where the mask would have eaten real content and published those pages as never changing. The mask is the whole <footer> element instead, which matches once on 401 of 401.
  2. The masked texts must be pairwise distinct across the site. A mask that swallowed the body would collapse pages onto each other, and a hash that cannot tell two pages apart cannot tell one page from itself before a change. 401 of 401 distinct.

dateModified is masked but not yet written: the date is inside the page, so a page carrying its own last-changed date would change its own hash by being dated. Masking it first is what makes that fixable later without the hash chasing its own tail — the mechanism is in place and the substitution is not, which is stated here rather than left to be discovered.

The dates are measured, not started from today

data/site-history.json carries, per page, its substantive hash, when it first existed and when it last changed. Starting it "today" would have been the same false claim in a new file, so tools/audit/site-history-bootstrap.mjs walks the history that already exists: 187 commits, 4,304 (commit, page) blobs, each hashed through the same masks the build uses, recording the commit date of the last hash change.

4,303 blobs hashed through the masks, 1 raw (it predates the page shell), 0 absent
401 live pages
7 distinct last-changed dates by content   —   1 by bytes

Seven dates where the bytes gave one: 188 pages last changed 2026-09-10, 69 on 2026-09-06 (the day the URLs went extensionless and the origin moved to Cloudflare), 62 today, and the rest across four other days. The file is an input to the build from now on; if it is missing the build fails rather than dating every page today, because a build that cannot see the past must not publish a claim about it.

Two things the file may not contain, and the reason is the same: --check runs on a later day than the build that wrote it. A built_on: <today> field, or this build's own carried/changed counts, would make the pre-commit hook fail every morning and force a full rebuild to correct a date nothing reads. Every value in the document is a function of the pages and the previous file, so a day with no content change reproduces it byte for byte. Verified: two consecutive builds, identical.

The key is public on purpose

site/17d525b8b5920e24b6d57600554ea8d8.txt is thirty-two hex characters in a repository that is about to become public, which is exactly the shape a secret scanner flags — D2's own history scan looked for sixteen credential shapes and this is one of them. It is not a credential. The protocol proves domain ownership by that file being readable by anyone; publishing it is what makes it work, and it authorises one thing: asking search engines to re-crawl URLs on a domain that already serves it. Said here, in tools/lib/indexnow.mjs, and in D5, so that nobody has to guess.

The protocol was read from primary sources this morning (indexnow.org documentation and FAQ, both fetched) rather than remembered: 8–128 characters of [a-zA-Z0-9-], described in the same sentence as hexadecimal — so this key is lowercase hex, which satisfies both readings; at most 10,000 URLs per submission; api.indexnow.org shares one submission with Bing, Yandex, Naver, Seznam, Yep and Amazon; 200 accepted, 202 key-validation pending, 403 the key file did not match. Submission is never an indexing promise, and this lane publishes zeros.

The gate is in the artifact

tools/indexnow-submit.mjs composes the request and prints it. That is the default and there is no flag that makes sending the default. --send refuses unless STALE_PRIORS_INDEXNOW_APPROVED names today's date in UTC — so an approval is for one submission on one day and cannot be left in a shell profile to authorise every session after it. Same shape as publish-gate.mjs, for the same reason: a session that has composed the request has every ingredient needed to send it.

Today's dry run: 62 of 400 pages, 4,492 bytes, every URL on stalepriors.com (a URL on another host is refused before the request exists — the protocol rejects the whole submission for one).

What is now true, and what is not

Two selftests join the pre-commit chain (15 checks and 13), both for the reason the identifier reader is in it: their failure is silent.

GATE(Sam) — one line, and it is small: may this lane submit the changed URLs to IndexNow? It is one POST to api.indexnow.org, naming pages that are already public on a site that is already live, with a key the site already serves. No account, no credential, no money. A yes would be STALE_PRIORS_INDEXNOW_APPROVED=<that day's date>, once per day it is used. A no costs nothing — the sitemap improvement stands either way, and the crawlers will find it when they next come.