diff --git a/.github/workflows/doc-component-types.yml b/.github/workflows/doc-component-types.yml index d6414e906..18e8bbdbb 100644 --- a/.github/workflows/doc-component-types.yml +++ b/.github/workflows/doc-component-types.yml @@ -24,12 +24,34 @@ name: Doc Component Types # gate, one home. # # It needs no install and no build. The script reads the checkout with `node:fs` -# only: 184 pages (144 `.mdx` + 40 `.md`, objectui#5342) for the fenced snippets -# AND for the plugin key tables (objectui#5106), and the `packages/` + `apps/` -# sources for the registered-key universe it compares them against. A few +# only: every `.mdx` AND `.md` page under `content/docs/**` and `apps/*/docs/**`, +# plus the root pages in `ROOT_PAGES` — `.md` has been in that population since +# objectui#5342, which is the extension `ci.yml`'s diff excludes — for the fenced +# snippets AND for the plugin key tables (objectui#5106), and the `packages/` + +# `apps/` sources for the registered-key universe it compares them against. A few # seconds. Keep it that way if you add checks to it — the moment this needs # `pnpm install` it stops being cheap enough to run unfiltered, and the filter is # the hole. +# +# ⛔ Deliberately NO page count is written here, and none may be added +# (objectui#7448). The sentence above used to carry one — a total and its +# per-extension split — and every part of it had drifted, because nothing fails +# on a stale number in a comment, which is precisely why it rots. Refreshing the +# literal only restarts that clock; the rule above has no clock to restart, since +# it states the population instead of counting it. This is the fix +# `check-doc-snippet-types.mjs`'s `UNGATED_DOCS` header records being applied to +# itself once both halves of its own copied count had drifted ("a pointer to the +# list now rather than a copy of its length"). Two durable readings, neither a +# copy: +# +# * HOW MANY — the run below prints it, in the gate's own verdict line: +# "Scanned N doc file(s) (.mdx + .md), …". `pnpm check:doc-types` reprints it +# on demand. +# * WHICH — `scanDocs` in `scripts/check-doc-component-types.mjs`, which +# derives the population from the tree on every run. +# +# `scripts/__tests__/check-doc-component-types.test.ts` fails if a population +# count reappears here, so the rule above is a gate rather than an intention. on: pull_request: diff --git a/.github/workflows/doc-fence-languages.yml b/.github/workflows/doc-fence-languages.yml index 052e41173..179f1c9eb 100644 --- a/.github/workflows/doc-fence-languages.yml +++ b/.github/workflows/doc-fence-languages.yml @@ -15,13 +15,37 @@ name: Doc Fence Languages # gate, one home. # # It needs no install and no build. The script reads the checkout with `node:fs` -# only — the same 222 documents `check-doc-snippet-types` covers — and it +# only — exactly the documents `check-doc-snippet-types` covers — and it # deliberately re-implements that gate's document walk rather than importing it, # because that gate imports `typescript` and an install-gated docs check is one # a docs-only pull request skips. The copy is held to the original by the pin # test above, which imports BOTH walks and compares them. Keep this job # install-free: the moment it needs `pnpm install` it stops being cheap enough # to run unfiltered, and the filter is the hole. +# +# ⛔ Deliberately NO document count is written here, and none may be added +# (objectui#7448). The sentence above used to state one as a literal, and it was +# already wrong by the time anyone read it back — nothing fails on a stale number +# in a comment, which is precisely why it rots. Refreshing the literal only +# restarts that clock; a pointer has no clock to restart. This is the fix +# `check-doc-snippet-types.mjs`'s `UNGATED_DOCS` header records being applied to +# itself once both halves of its own copied count had drifted ("a pointer to the +# list now rather than a copy of its length"), and it is why the pin test +# deliberately does not assert the size of the baseline either. Two durable +# readings, neither of them a copy: +# +# * HOW MANY — the run below prints it, in the gate's own verdict line: "every +# TypeScript block in N document(s) is fenced ts/tsx/typescript". `pnpm +# check:doc-fences` reprints it on demand. +# * WHICH — `listDocuments` in `scripts/check-doc-fence-languages.mjs`, which +# derives the population from the tree on every run. +# +# What IS pinned is the claim that survives a document being added or deleted: +# that the two populations are the same SET. The pin test compares the walks +# themselves rather than a copy of their length — the only number it holds is a +# non-vacuity FLOOR, which no addition or deletion can falsify — and it also +# fails if a population count reappears in this header, so the paragraph above +# is a gate rather than an intention. on: pull_request: diff --git a/scripts/__tests__/check-doc-component-types.test.ts b/scripts/__tests__/check-doc-component-types.test.ts index 28d33e769..acb7868fe 100644 --- a/scripts/__tests__/check-doc-component-types.test.ts +++ b/scripts/__tests__/check-doc-component-types.test.ts @@ -874,6 +874,49 @@ describe('wiring — the gate is reachable and a docs-only PR starts it', () => expect(seen.size, 'the import walk read only the gate itself — it followed nothing').toBeGreaterThan(1); expect(external, `the gate's import graph reaches a package, so it needs an install: ${external}`).toEqual([]); }); + + /** + * objectui#7448. The header of this workflow described its scan surface as + * "184 pages (144 `.mdx` + 40 `.md`)". Every part of that had drifted by the + * time the card was worked — the gate's own verdict line reported 188 doc + * files, and the `.md` half of the split was four short — and NO check went + * red over the whole distance, because nothing fails on a stale number written + * in a comment. That is the same lesson `UNGATED_DOCS`'s header in + * `check-doc-snippet-types.mjs` records after both halves of its own copied + * count went stale ("a pointer to the list now rather than a copy of its + * length"). + * + * Changing 184 to 188 would only have restarted that clock. This is what makes + * the class fail loudly instead: the header may state the population — which + * trees, which extensions — but never count it. `doc-fence-languages.yml` + * carries the twin of this pin in its own test file; one gate, one home, so + * each workflow's header is asserted beside its own gate rather than in a + * shared sweep that would own neither. + * + * Deliberately narrow: a numeral DIRECTLY qualifying a document-population + * noun. Issue references, `node-version`, `timeout-minutes` and "the fifth + * instance of the shape" are all numbers this header legitimately carries, and + * none of them rots when a page is added or deleted. It also means the header + * must not quote another header's stale literal verbatim — this pin cannot + * tell a quotation from a claim, and refusing both is the safe direction for a + * check on prose accuracy. + */ + it('its header states the population and never counts it — no count can rot here', () => { + const header = fs + .readFileSync(workflowPath, 'utf8') + .split('\n') + .filter((line) => /^\s*#/.test(line)) + .join('\n'); + const counts = [...header.matchAll(/\b\d+\s+`?(?:\.mdx|\.md|documents?|pages?|docs?|files?)\b/gi)].map( + (m) => m[0], + ); + expect( + counts, + `doc-component-types.yml's header states a page count (${counts.join(', ')}). Nothing fails when ` + + 'it drifts, so it will. State the population — or point at the gate\u2019s own verdict line, which ' + + 'prints the live figure on every run — instead of copying a number into a comment (objectui#7448).', + ).toEqual([]); + }); }); /** diff --git a/scripts/__tests__/check-doc-fence-languages.test.ts b/scripts/__tests__/check-doc-fence-languages.test.ts index 15c45c9de..a62c81510 100644 --- a/scripts/__tests__/check-doc-fence-languages.test.ts +++ b/scripts/__tests__/check-doc-fence-languages.test.ts @@ -216,4 +216,50 @@ describe('check-doc-fence-languages is wired, not merely present', () => { const out = execFileSync('node', [GUARD, '--self-test'], { cwd: ROOT, encoding: 'utf8' }); expect(out).toMatch(/check-doc-fence-languages self-test: \d+ cases pass/); }); + + /** + * objectui#7448. The header of this workflow described its scan surface as + * "the same 222 documents `check-doc-snippet-types` covers". The walk held 227 + * when this pin was written, and NO check went red over the whole distance + * between the two — a count copied into a comment drifts by + * construction, which is the same reason the file header above declines to + * assert the size of the baseline, and the same fix `UNGATED_DOCS`'s header + * records being applied to itself ("a pointer to the list now rather than a + * copy of its length"). + * + * Changing 222 to 227 would only have restarted that clock. This is what makes + * the class fail loudly instead: the header may state the population, never + * count it. `doc-component-types.yml` carries the twin of this pin in + * `check-doc-component-types.test.ts` — one gate, one home, so each workflow's + * header is asserted beside its own gate rather than in a shared sweep. + * + * Deliberately narrow: a numeral DIRECTLY qualifying a document-population + * noun. Issue references, `node-version`, `timeout-minutes` and "the sixth + * instance of the same shape" are all numbers this header legitimately + * carries, and none of them rots when a document is added or deleted. That is + * also why the header must not quote another header's stale literal verbatim + * — this pin cannot tell a quotation from a claim, and the safe direction for + * a check on prose accuracy is to refuse both. + * + * Only the negative half is asserted. A positive assertion ("the header names + * the verdict line") would pin a wording, and pinned wording is the thing this + * file already refuses to do elsewhere; what has to stay true is that no + * number is written here that a document being added would falsify. + */ + it('its header states the population and never counts it — no count can rot here', () => { + const header = fs + .readFileSync(path.join(ROOT, '.github/workflows', WORKFLOW), 'utf8') + .split('\n') + .filter((line) => /^\s*#/.test(line)) + .join('\n'); + const counts = [...header.matchAll(/\b\d+\s+`?(?:\.mdx|\.md|documents?|pages?|docs?|files?)\b/gi)].map( + (m) => m[0], + ); + expect( + counts, + `${WORKFLOW}'s header states a document count (${counts.join(', ')}). Nothing fails when it ` + + 'drifts, so it will. State the population — or point at the gate\u2019s own verdict line, which ' + + 'prints the live figure on every run — instead of copying a number into a comment (objectui#7448).', + ).toEqual([]); + }); });