From 14748055c1f7679a827d414ccce2cdc00627abbe Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 5 Sep 2026 23:30:27 +0000 Subject: [PATCH] docs(gate): two doc-gate workflow headers point at the gate's own reading instead of copying its count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `doc-fence-languages.yml` described its scan surface as "the same 222 documents `check-doc-snippet-types` covers" and `doc-component-types.yml` as "184 pages (144 `.mdx` + 40 `.md`)". Both had drifted, and no check went red while they did — a count copied into a comment rots by construction. Refreshing the literals would only have restarted that clock, so each header now states its POPULATION (which trees, which extensions, which walk derives it) and points at where the live figure is printed: the gate's own verdict line, on every run. A pin in each gate's existing test file makes the class fail loudly: the header may state the population, never count it. The regex is deliberately narrow — a numeral directly qualifying a document-population noun — so issue references, `node-version` and "the fifth instance of the shape" stay legal, since none of those rots when a document is added. No gate predicate, scan population or verdict changed; comments only. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01KbJQ1y1J12nZxYzFWhP8Q3 --- .github/workflows/doc-component-types.yml | 28 +++++++++-- .github/workflows/doc-fence-languages.yml | 26 ++++++++++- .../check-doc-component-types.test.ts | 43 +++++++++++++++++ .../check-doc-fence-languages.test.ts | 46 +++++++++++++++++++ 4 files changed, 139 insertions(+), 4 deletions(-) diff --git a/.github/workflows/doc-component-types.yml b/.github/workflows/doc-component-types.yml index d6414e906b..18e8bbdbb0 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 052e411739..179f1c9eb6 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 28d33e769e..acb7868fe1 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 15c45c9de4..a62c815107 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([]); + }); });