diff --git a/.github/workflows/docs-drift-check.yml b/.github/workflows/docs-drift-check.yml index 271093aeb9..9257bebba4 100644 --- a/.github/workflows/docs-drift-check.yml +++ b/.github/workflows/docs-drift-check.yml @@ -260,6 +260,45 @@ jobs: if (overbroad.length) limits.push(`**${overbroad.length}** anchor(s) matched too much of the corpus to be a work list: \`${overbroad.join('`, `')}\``); if (weak.length) limits.push(`**${weak.length}** name(s) were too generic to anchor anything (single lowercase words)`); if (bridge && bridge.measured && bridge.unreachable > 0) limits.push(`the SDK route bridge reached **${bridge.reachable}** of **${bridge.clientRows}** client-bound route-ledger rows — the other **${bridge.unreachable}** have no registrar \`path:\` tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: \`node scripts/docs-audit/affected-docs.mjs --bridge-coverage\``); + // ── THE RULE-CARRYING PAGE AN EMITTER DIFF CANNOT REACH (#11434) ────── + // + // Every line above is a REPORT about this run: a count this run produced, a + // set this run derived. This one is not — it is a BOUNDARY of the anchor + // model, and it is why it is pushed unconditionally. + // + // The pair it describes: a page states a rule by the rule's INPUTS, and the + // code that carries the rule is its EMITTER. They share no identifier, so + // precision-first anchoring (#9192) has nothing to join them with, and no + // per-run signal can be computed — the absence of a shared token is the whole + // defect, and an absence anchors nothing. Reported here rather than detected + // because there is nothing to detect on. + // + // Measured, twice, on the specimen from #11434. On the recorded diff (#11430, + // `sql-driver.ts` `createColumn`, re-run through today's mapper): 9 anchors + // derived, 7 pages listed, and `content/docs/protocol/objectql/types.mdx` — + // the page that diff falsified in four places — listed by none of them. Not + // by the coarse fallback either: the page never names the changed package. On + // today's tree an emitter-only change to `MAX_KEYABLE_VARCHAR_CHARS` derives + // 1 anchor, lists 0 pages, and leaves `anchorlessChanges`, `crossCuttingSymbols`, + // `overbroadAnchors` and `weakAnchorsDropped` all EMPTY — so no line above + // fires, and the run reads as narrow rather than blind. Positive control on + // the same tree and the same page: a change to `FieldSchema`, which that page + // does name, lists it via `FieldSchema (symbol)`. The page is reachable; this + // pairing is not. + // + // ⛔ NOT a detector, and must not grow into one here. The routes that would + // buy real coverage — a curated code↔page ledger, a per-type anchor + // vocabulary, authored enumeration marks — are the open question escalated as + // #11817, and deciding the cheap half of it by implementation would pre-empt + // that ruling. This says the gap exists; it does not close it. + // + // ⛔ And it is deliberately NOT the `notCovered` headline's posture. That + // sentence is conditional because it reports what THIS diff left unanchored, + // and unconditional text there would report nothing (see + // `scripts/docs-audit/check-drift-comment.mjs`). This one is true on every + // run by construction, exactly like the SDK bridge line above, and stating it + // only sometimes would be the lie. + limits.push('a page that states a rule by its **inputs** shares no identifier with the **emitter** that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on #11430: `content/docs/protocol/objectql/types.mdx` documents the text-family column mapping by the ObjectQL type names it maps FROM (`text` / `textarea` / `html`) while the diff changed `createColumn`; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.'); // A broken scan is not a coverage number and must not read like one — see // `bridgeCoverageFrom`. Rendered outside the collapsed section below, because // "the bridge could not read the ledgers" is not a limit of this run, it is a @@ -277,6 +316,11 @@ jobs: // say is "the wide net exists and holds N pages". A short list is the right // answer here, but a reader must be able to tell a short list from a blind one // without leaving the PR. + // + // Since #11434 `limits` is never empty — the rule-carrying line above holds on + // every run — so this fold now renders on every run too. The guard stays: it is + // what keeps the fold from rendering empty if that line is ever moved out, and + // an empty "what this run could not see" is the reading it exists to refuse. const limitsBlock = (limits.length || coarse.length) ? ['', '
What this run could not see', '', ...limits.map(l => `- ${l}`), ...(limits.length ? [''] : []), `Coarse fallback — **${coarse.length}** page(s) merely *mention* a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): \`${rederive}\` → \`packageMentionDocs\`.`, '
'] : []; diff --git a/scripts/docs-audit/README.md b/scripts/docs-audit/README.md index 052cf0cd5d..fe73f40a8e 100644 --- a/scripts/docs-audit/README.md +++ b/scripts/docs-audit/README.md @@ -450,10 +450,52 @@ to re-verify. The comment also carries a collapsed **"What this run could not see"** section: anchorless files, cross-cutting symbols, over-broad anchors, the coarse package-mention -count, and the `sdk` bridge's reach over the client-bound ledger rows (#9572). That is -the point-of-use half of #9192 — every one of the three derived-list failures in that -shift was caught only because a dev widened the probe past what the tool offered, never -because the tool signalled its own limits where it was read. +count, the `sdk` bridge's reach over the client-bound ledger rows (#9572), and the +rule-carrying pairing no run can reach (#11434, below). That is the point-of-use half of +#9192 — every one of the three derived-list failures in that shift was caught only because +a dev widened the probe past what the tool offered, never because the tool signalled its +own limits where it was read. + +### The rule stated by its inputs, the diff touching its emitter (#11434) + +Every other entry in that fold is a **report about the run** — a count it produced, a set +it derived. One is not, and renders on every run: + +> a page that states a rule by its **inputs** shares no identifier with the **emitter** +> that implements the rule, so an emitter-only diff cannot list it — not on this run and +> not on any run. + +A page can document a rule in the rule's own **vocabulary** — the values it maps *from* — +while the code carrying the rule is named for what it *does*. The two share no token, so +precision-first anchoring has nothing to join them with. This is not a tuning miss with a +threshold behind it: the absence of a shared identifier **is** the defect, and an absence +anchors nothing, so no per-run signal exists to compute. It is reported because it cannot +be detected — the same posture as the `sdk` bridge line above. + +Measured on the specimen (#11434, from PR #11430 / card #11374), re-run through today's +mapper: + +| | | +|---|---| +| the diff | `packages/drivers/driver-sql/src/sql-driver.ts` — `createColumn`, the text family's column mapping | +| derived | 9 anchors (`SqlDriver`, `createColumn`, `keyableTextLength`, …) | +| listed | 7 pages, every one via a `SqlDriver`-family symbol it merely mentions | +| **not listed** | `content/docs/protocol/objectql/types.mdx` — **the page that diff falsified, in four places** | +| coarse fallback | misses it too: the page never mentions the changed package | +| existing limit lines | all silent — `anchorlessChanges`, `crossCuttingSymbols`, `overbroadAnchors`, `weakAnchorsDropped` all empty | + +The last row is the reason this is stated rather than left to the reader: with every limit +line silent, the run reads as *narrow*, not *blind*. Positive control on the same page and +the same tree — a change to `FieldSchema`, which the page **does** name, lists it via +`FieldSchema (symbol)`. The page is reachable; that pairing is not. + +⛔ **This states the gap; it does not close it.** Buying real coverage would take a curated +code↔page ledger, a per-type anchor vocabulary, or authored enumeration marks — the open +question escalated as **#11817**, whose ruling also owns the population question (how wide +this class is is deliberately **not measured here**; the sentence's truth does not depend +on the count). `check-drift-comment.mjs` pins the line byte-exact on **every** fixture +case, which is the only meaningful pin for a statement whose contract is that it is always +true — making it conditional, moving it to the headline, or dropping it all go red. ### The headline says what the run did not cover (#11357) diff --git a/scripts/docs-audit/check-drift-comment.mjs b/scripts/docs-audit/check-drift-comment.mjs index 2b3581b0bd..959fc85e58 100755 --- a/scripts/docs-audit/check-drift-comment.mjs +++ b/scripts/docs-audit/check-drift-comment.mjs @@ -31,6 +31,14 @@ * UNCONDITIONALLY, which is not a report at all. A blind-spot notice that renders on every * run says nothing about any run. * + * ⚠️ That reasoning is about REPORTS, and since #11434 the fold also carries one line that + * is not one: a BOUNDARY of the anchor model, true on every run by construction, because + * the pair it names (a page stating a rule by its INPUTS, a diff touching the rule's + * EMITTER) shares no identifier for any per-run signal to be computed from. Its + * unconditionality is the contract, so it is pinned the only way that is meaningful — on + * EVERY case, byte-exact, and in the fold rather than the headline. Removing it, making it + * conditional, or promoting it to the headline all go red. See `RULE_CARRYING_BLIND_SPOT`. + * * So both directions are measured end to end, from a git diff: * * diff → the real affected-docs.mjs → its real --json → the real comment script → bytes @@ -201,6 +209,27 @@ const ANCHORS_DERIVED_NONE_NAMED = /** The phrase that tells that verdict apart from every neighbouring one. */ const NARROWED_PHRASE = 'nothing to list'; +/** + * The #11434 boundary line, byte-exact — the one fold entry that is not a report about the + * run, and the only one asserted on every case. + * + * The class: `content/docs/protocol/objectql/types.mdx` documents the text-family column + * mapping by the ObjectQL type names it maps FROM, `sql-driver.ts` carries the mapping in + * `createColumn`, and the two share no token — so the advisory listed seven other pages on + * #11430 and not the one that diff falsified in four places. Nothing in the anchor model + * can join that pair (the routes that could are escalated as #11817), so the run says so + * instead of implying coverage by silence. + */ +const RULE_CARRYING_BLIND_SPOT = + 'a page that states a rule by its **inputs** shares no identifier with the **emitter**' + + ' that implements the rule, so an emitter-only diff cannot list it — not on this run and' + + ' not on any run. Measured on #11430: `content/docs/protocol/objectql/types.mdx`' + + ' documents the text-family column mapping by the ObjectQL type names it maps FROM' + + ' (`text` / `textarea` / `html`) while the diff changed `createColumn`; it went unlisted,' + + ' and it was the page that diff falsified, in four places. No shared token exists to' + + ' detect this on, so a rule your change carries has to be re-read by hand in the pages' + + ' that restate it.'; + /** * `want` is the mapper contract each case rides on — asserted before any text is, so a * case that silently stopped exercising its branch fails here instead of passing there. @@ -310,6 +339,15 @@ try { if (typeof c.narrowed !== 'boolean') throw new Error(`case ${c.id} does not declare \`narrowed\``); check(c.id, `the #11356 narrowed verdict ${c.narrowed ? 'renders here' : 'does NOT render here'}`, c.narrowed, headline.includes(NARROWED_PHRASE)); + // #11434 — the opposite pin to every other one in this file, and deliberately so. A + // model boundary that renders only sometimes is false on the runs it skips, so the + // assertion is "on every case", which is what fails if someone makes it conditional. + // It belongs in the fold: the headline is where a run says what IT did not cover, and + // a sentence true of all runs would dilute exactly that (#11357). + check(c.id, 'the rule-carrying blind spot is stated in the fold, byte-exact', + true, body.includes(`- ${RULE_CARRYING_BLIND_SPOT}`)); + check(c.id, 'and it stays out of the headline, which reports this run only', + false, headline.includes('shares no identifier with the **emitter**')); c.expect(headline, body); } } finally {