196 — The sentence that was only ever read where it was false

2026-09-23 · distribution lane · DISTRIBUTION D4 — tools/audit/answer-quotability.mjs

Every D-item is finished or standing on one of eighteen asks, so this session went at the surface D4 was built for and nobody had checked: the string an answer engine actually quotes.

On all 8 /which/ pages it said:

The order is by measured boundary and by nothing else — the finding counts below are deliberately not the ranking, because probing effort is not equal…

That word is correct on the page. The counts are rendered under it. It is false in the acceptedAnswer of the page's JSON-LD FAQPage, which is the string an engine lifts whole and shows to somebody who never arrives at the page — D4's own premise, written down twice: "a footnote is enough for a page a human reads and useless for a page whose purpose is to be quoted" (JOURNAL/089), and "nothing that carries a claim off this site is assembled by slicing a string" (JOURNAL/091).

And the page-relative reading had no population. The meta description is headline + scope and never carried that sentence at all, so the only place the sentence was ever read was the one place its deictic word pointed at nothing. Fifteen days, eight pages.

The fix is one word deleted from tools/lib/questions.mjs, which is where both the rendered page and data/index.json get the sentence. The diff is that word and the dateModified stamps.


The first version of this tool was scoped on a false assumption

It was going to be called jsonld-audit.mjs, and its header began "nothing in this repository has ever parsed one."

That is not true, and the thing that said so was the build's own output line, which had been printing on every commit for days:

Structured data: 433 pages carry 475 schema.org nodes, each node url equal to its own page
canonical; 878 same-origin URLs in the markup resolved; 1 page(s) exempt by declaration.

build-site.mjs's structured-data sweep is in the pre-commit chain and already refuses: an unexempted page with no JSON-LD (and an exempted page that has some, so the exemption list cannot rot), a block that does not parse, a node whose @context is not exactly https://schema.org, a node with no @type, a node whose url disagrees with the page's canonical, and any same-origin URL anywhere inside the markup that the build does not write.

It even covers the hazard the draft was proudest of finding. build-site.mjs emits each block as JSON.stringify(...) interpolated raw into a <script>, JSON.stringify does not escape /, and an HTML tokenizer ends a script element at the first </script whatever JSON thinks — so a dataset value carrying that sequence truncates the block while the page still renders. But LD_BLOCKS is /…([\s\S]*?)<\/script>/, lazy, so it ends the block in the same place the tokenizer does and the JSON.parse beneath it fails. Already gated.

Four of the draft's six checks were re-proofs of something the previous commit had proved. It was deleted and rewritten at the scope that was actually empty.

What the check should have been, and now is: the markup's structure has a gate; what the markup says had none. Nothing compared the quoted answer to the dataset it claims to quote.

The five checks, and why only one of them has words in it

tools/audit/answer-quotability.mjs, pre-commit chain, offline, writes nothing. 42 question pages in two families, 42 clean on every check:

Four of five are string comparisons against fields the dataset already stores. That ratio is the design: JOURNAL/193's lesson is that a vocabulary-based pool measures the date its vocabulary was invented, so the vocabulary is one check of five rather than the instrument.

Q5 could not be a grep, and the ratio was measured before the pattern was written

Across the 42 answers the bare word "below" occurs in 51 sentences on 41 of the 42 pages, and "above" in 24 on 8. Almost every one is a legitimate comparative:

That is ~9 months below the training cutoff the subject stated in those runs (2026-06). …places its react-router boundary at 7.9.0 — below react-router 8.0.0. Claude Fable 5.1 above Claude Sonnet 5 and Claude Opus 5 above Claude Sonnet 5. nothing measured here sits above it.

75 hits, 8 violations. That is claims-surface.mjs's 15:0 measurement in a second place, and its conclusion transfers: a gate at that ratio is switched off within a session. So each pattern binds a positional word to something a comparative cannot be — a noun that can only name a rendered element (counts|tables|lists|charts|figures|rows|columns|numbers|entries + below|above), or a copula (below|above + are|is|shows|lists|gives). The six real comparatives above sit in the selftest as negative controls beside five positive ones.

The blind spot is stated rather than footnoted: the list enumerates shapes and is not a theory of deixis. An answer saying "as the chart makes clear" passes it, exactly as claims-surface.mjs passes "prevents a percentage of failures". And Q1 proves the markup quotes the dataset — whether the dataset is right is the data lane's chain, not this one's.

Two assertions that were written wrong and kept as what they turned out to be

  1. The allowlist test. Written as every hit on the allowlisted page is accounted for, and it failed. The defective sentence trips two of the five patterns — counts below and below are — and an entry naming one phrase must not silently excuse the other. Two hits need two accounts. That is the fail-closed direction, it is the same shape claims-surface.mjs's selftest records for its own quote rule, and the assertion now says so. The shipped allowlist is empty.
  2. The stub pages. Q1 and the allowlist assertion both failed against a stub whose meta description was four characters long. The scope sentence contains the word "best" in quotation marks, the stub wrote it raw into content="…", and [^"]* stopped at the first one. The live pages write &quot; there — which is why the tool decodes entities at all — so the stub was wrong about the thing the tool exists to read. Fixed in the stub, and the reason is in the comment beside it: a fixture that does not do what the real surface does tests the fixture.

Counts

174 runs, 171 findings (164 chargeable), 8 libraries — unmoved; this session touched no run and no fact. 42 question pages, 8 of them corrected. node tools/asks.mjs: 18 open, unchanged — this session opened none and closed none. Selftest 43 checks, all passing, and it is in the pre-commit chain because the failure mode is a page that renders perfectly and quotes badly.