Skip to content

Commit e569cac

Browse files
feat(scripts): sync docs image tags at version time so check:docs-image-tag cannot misfire (#9102)
* feat(scripts): sync docs image tags at version time so the gate cannot misfire `check:docs-image-tag` (#9018) pins the docs' concrete image/CLI tags to `packages/cli`'s version, so a bump reddens it until the doc surfaces move. The red could not land on the change that caused it: `changeset version` bumps the version on a release PR that gets no CI (changesets/action opens it with the default GITHUB_TOKEN, per sync-template-versions.mjs's own header), so the bump merged green and the gate reddened on the next ordinary PR, naming files that author never touched. Adds scripts/sync-docs-image-tags.mjs to the root `version` chain, beside the two sync scripts already there -- the shape this repo chose for the identical drift class in #2907. It imports SURFACES, PATTERNS, VERSION_SOURCE, extractOccurrences and checkSurfaces from the gate, so the rewriter and the gate read one list and its verdict IS the gate's. Version-time sync only: no publish, no tag, no Version Packages PR (#6170). check-docs-image-tag.mjs gains an entry-point guard and exports VERSION_SOURCE. Both additive: importing it previously ran main() and exited the process, which made its exports unusable by the one consumer they were added for. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm * test(scripts): name the inert-rewriter failure instead of crashing on it An ablation that made rewriteText inert died of a TypeError indexing an empty `changed` array, two limbs before the byte-identity control ran. Optional chaining so the self-test REPORTS the inert case by name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011RB4waLuNbdruCo6X9oobm --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7677f57 commit e569cac

4 files changed

Lines changed: 645 additions & 8 deletions

File tree

.github/workflows/lint.yml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,29 @@ jobs:
348348
- name: Docs image tags track packages/cli's version
349349
run: pnpm check:docs-image-tag
350350

351+
# #9064 — the self-test of the VERSION-TIME REWRITER that keeps the gate
352+
# above from ever having to fire. The gate alone could not: `changeset
353+
# version` bumps packages/cli on a release PR that (per
354+
# sync-template-versions.mjs's own header) gets NO CI because
355+
# changesets/action opens it with the default GITHUB_TOKEN — so the bump
356+
# merged green and the gate reddened on the NEXT ordinary PR, naming files
357+
# that author never touched. scripts/sync-docs-image-tags.mjs runs in the
358+
# root `version` script, alongside the two sync scripts already there.
359+
#
360+
# Only the --self-test runs here, and that is the whole point: the rewriter
361+
# itself has nothing to do on a green corpus, so CI can never observe it
362+
# working. The self-test is where a STALE fixture is observed going green
363+
# through the gate's own checkSurfaces, and — the control that matters just
364+
# as much — where a CLEAN fixture is observed left byte-identical and
365+
# unwritten, because an over-eager rewriter would silently corrupt the
366+
# documented tag scheme and historical version prose across three files.
367+
#
368+
# Same job as the gate deliberately: they share one SURFACES/PATTERNS list,
369+
# so the change that breaks one breaks the other, and both reds should land
370+
# in the same place on the same PR.
371+
- name: Docs image-tag version-time rewriter self-test
372+
run: pnpm check:docs-image-tag-sync
373+
351374
# #4851: the docs-accuracy-audit workflow carries its default scope inline
352375
# (a workflow script runs in a vm with no filesystem, so it cannot enumerate
353376
# content/docs/ itself). Hand-kept, that list rotted in BOTH directions —

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"setup": "pnpm install && pnpm --filter @objectstack/spec build",
1818
"prepare": "node scripts/setup-git-hooks.mjs",
1919
"check:merge-driver": "node scripts/git-merge-regen.mjs --self-test && node scripts/check-regen-pending.mjs --self-test",
20-
"version": "changeset version && node scripts/sync-protocol-version.mjs && node scripts/sync-template-versions.mjs",
20+
"version": "changeset version && node scripts/sync-protocol-version.mjs && node scripts/sync-template-versions.mjs && node scripts/sync-docs-image-tags.mjs",
2121
"release": "pnpm run build && bash scripts/build-console.sh && bash scripts/release-publish.sh",
2222
"docs:dev": "pnpm --filter @objectstack/docs dev",
2323
"docs:build": "pnpm --filter @objectstack/docs build",
@@ -40,6 +40,7 @@
4040
"check:docs-audit-scope": "node scripts/docs-audit/affected-docs.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs",
4141
"check:docs-redirects": "node scripts/check-docs-redirects.mjs --self-test && node scripts/check-docs-redirects.mjs",
4242
"check:docs-image-tag": "node scripts/check-docs-image-tag.mjs --self-test && node scripts/check-docs-image-tag.mjs",
43+
"check:docs-image-tag-sync": "node scripts/sync-docs-image-tags.mjs --self-test",
4344
"check:role-word": "node scripts/check-role-word.mjs --self-test && node scripts/check-role-word.mjs",
4445
"check:quick-reference-counts": "node scripts/check-quick-reference-counts.mjs --self-test && node scripts/check-quick-reference-counts.mjs",
4546
"check:skill-frame-sync": "node scripts/check-skill-frame-sync.mjs --self-test && node scripts/check-skill-frame-sync.mjs",

scripts/check-docs-image-tag.mjs

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,18 @@
115115

116116
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
117117
import { tmpdir } from 'node:os';
118-
import { dirname, join } from 'node:path';
118+
import { dirname, join, resolve } from 'node:path';
119119
import { fileURLToPath } from 'node:url';
120120

121-
/** The package whose `version` every concrete pin below must equal. */
122-
const VERSION_SOURCE = 'packages/cli/package.json';
121+
/**
122+
* The package whose `version` every concrete pin below must equal.
123+
*
124+
* Exported for the same reason SURFACES and PATTERNS are (#9064): the version-time
125+
* rewriter `sync-docs-image-tags.mjs` must resolve the SAME source of truth this gate
126+
* compares against. A second literal in the rewriter would be a second contract, and
127+
* the two drifting apart reproduces one layer up the very defect this gate exists for.
128+
*/
129+
export const VERSION_SOURCE = 'packages/cli/package.json';
123130

124131
/**
125132
* The doc surfaces scanned, enumerated explicitly (#9018).
@@ -718,8 +725,21 @@ function main() {
718725
process.exit(report(findings, stats, expected));
719726
}
720727

721-
if (process.argv.includes('--self-test')) {
722-
await selfTest();
723-
} else {
724-
main();
728+
// Entry-point guard (#9064). Without it, importing this module RUNS the check and
729+
// calls `process.exit()` as an import side effect -- measured: a probe importing
730+
// SURFACES never reached its own next line, because `main()` had already exited the
731+
// process for it. That makes the exports unusable by the one consumer they were added
732+
// for, and the failure is silent in the worst way: the exit code is the CORPUS's
733+
// verdict, so an importer looks fine while the corpus is green and dies with an
734+
// unrelated exit 1 the day a pin goes stale. The idiom is the repo's own, and the
735+
// sibling gates state the same rationale (check-adr-links, check-doc-anchors,
736+
// check-kernel-hook-pairs). Nothing about what this gate ASSERTS changes: both
737+
// `check:docs-image-tag` invocations run this file directly, where argv[1] is this
738+
// file and the branch is taken exactly as before.
739+
if (resolve(process.argv[1] ?? '') === resolve(fileURLToPath(import.meta.url))) {
740+
if (process.argv.includes('--self-test')) {
741+
await selfTest();
742+
} else {
743+
main();
744+
}
725745
}

0 commit comments

Comments
 (0)