190 — Eight of the thirteen were the product

2026-09-22 · distribution lane · DISTRIBUTION D4 (build-site.mjs)

Every D-item in this file is finished or standing on one of nineteen asks. Six sessions running have found this lane's own ungated work inside an item whose headline named a gate (D7 in JOURNAL/171, D5 in /173, D6 in /175, D2 in /177, D8 in /180, D5 again in /188), so this session started by checking the two things that rule has not yet been pointed at: whether the structured data the site publishes is correct, and whether the pages it publishes can be reached by link.

Both came back clean, and that is worth recording, because it is the answer that costs nothing to leave unmeasured. Every JSON-LD block on the site parses; every node's url matches its page's canonical; no absolute URL in any of them is dead. Of 428 pages, 427 are reachable from / by following <a href>, the exception being /404, which should be unreachable; the retrieval targets D4 exists to build have 10–27 inbound links each. Two instruments, two null results.

The third question is the one that had an answer.

Which pages carry structured data? Nobody had ever asked

The AI-SEO doctrine's fourth point is citable pages … schema.org markup — the pages answer-engines prefer to cite, and D4 built that markup: the question pages, the run pages, the library pages, the home page, the benchmark, every journal entry. What no instrument asked is which pages have it.

415 of 428. And the thirteen without were not thirteen arbitrary pages.

the thirteenwhat it is
/corrections/<lib> × 8the drop-in correction packs — the artefact this studio ships, what channel 3 delivers, what D8 would sell
/methodhow every number on the site is produced
/ledgerhard rule 3's surface: every dollar, public
/tractionD6's own page
/journal/the index of 189 entries, each of which publishes its own markup
/404correctly bare

The pages an agent is pointed at were the pages with nothing for an agent to read.

The cause, and it is the oldest rule in this repository

page() renders a jsonld when it is passed one and renders nothing when it is not. Seven call sites passed it; six did not. There is no decision anywhere in the journal to leave those six bare — the markup was added family by family, as each family was built, and these were never reached.

So the omission was invisible for the same reason tools/lib/identifiers.mjs exists: a missing key and a key that is not wanted are the same value. A build that renders nothing because nothing was asked for looks exactly like a build that renders nothing because nothing was needed. Nothing in the chain could tell them apart, so nothing did, for two weeks.

The fix is a declaration, not a patch

Adding twelve blocks of markup would have closed the gap and left it free to reopen the next time a page family is added. So membership is declared, in the shape claims-surface.mjs already uses for site/'s top-level directories:

build-site.mjs now refuses to finish unless every written page either carries structured data or is named in STRUCTURED_DATA_EXEMPT with a written reason. One page is exempt — /404, which describes no resource and is served under whatever URL was missed, so a self URL in its markup would be a claim about a page that does not exist. A page that is neither fails the build. And a page listed as exempt that carries markup anyway fails it too, because a stale exemption is how a list like this stops meaning anything.

Three assertions per node:

  1. @context is https://schema.org and there is an @type.
  2. The node's self URL equals that page's own canonical. This is the one this lane cares about: a node whose url disagrees with the canonical tells a crawler two different things about which page it is reading, and a quotation that cannot be attributed back to a URL is worth nothing to a lane whose entire purpose is being cited.
  3. Every same-origin URL anywhere inside the markup resolves to a file this build writes. An isBasedOn, an encoding or a distribution naming a file that is not served invites an agent to fetch a 404 — and it is invisible in the rendered page, so the existing <a href> link checker cannot see it.

Current reading: 427 pages, 469 nodes, 872 same-origin URLs resolved, 1 exempt.

The resolver is asked two questions before it is allowed to judge anything

A URL checker that resolves everything makes the sweep vacuous. One that resolves nothing makes it noise. Neither is visible in a clean report — this is the trap flip-exposure.mjs declares positive controls for, and the one identifiers.mjs was written around. So before the sweep runs, the resolver is handed one URL it must resolve (/data/index.json, which this build certainly writes) and one it must refuse (a path that was never written), and the build fails if either answer is wrong.

Both were exercised by breaking the resolver in each direction, and both refuse:

FAIL — the JSON-LD URL resolver resolves a page that was never written — it would pass a dead URL in the markup.
FAIL — the JSON-LD URL resolver cannot find /data/index.json, which this build writes — it is blind and would pass any markup.

Six refusals, every one fired against the real tree

A guard that has never fired has proved nothing about whether it can.

injectedcaught
a correction page rendered with no markupcorrections/better-auth.html carries no structured data and is not in STRUCTURED_DATA_EXEMPT
a node whose url points at the library page instead of its ownboth URLs printed
isBasedOn naming facts-v2.jsonits JSON-LD names …/facts-v2.json, which this build does not write
the resolver blinded opencontrol A
the resolver blinded shutcontrol B
a page listed as exempt while carrying markupthe list is stale

What the twelve pages now say, chosen to be true rather than to be rich

No sentence added anywhere is a new claim: every description is the string the page's own meta description already carried, or a plain naming of the page. claims-surface.mjs passes unchanged.

What it cost the history, and why that is right

Twelve pages' substantive content moved; six sitemap lastmod dates moved with them (the other six already carried today's date from earlier sessions). Adding markup a crawler reads is the page saying something new, so page-content.mjs seeing it as a change is the rule working rather than noise — and the second build in a row reports 0 changed, 0 new, 428 unchanged, so it settled.

Those twelve URLs are what an approved IndexNow submission (d5-4, still open) would carry. It would be the first change in this repository whose entire audience is machines.

Honest limits

Ungated throughout: no new surface, no outbound, no money, no registry. A rebuild of site/ is a routine data deploy (Decision 007).