Found while implementing #9518 (widening cut-rc.yml's release file surface). Not fixed there — recording only.
What was observed
scripts/sync-template-versions.mjs already models its outputs well. It has a
TEXT_STAMPS table over (file, key, pattern, replacement) whose own header says it is
table-driven "so that adding a fourth declared version surface is a row", and a
findTemplateDirs() that walks TEMPLATE_ROOT — "Deliberately not a curated list."
But none of it is reachable from outside:
- neither
TEXT_STAMPS nor TEMPLATE_ROOT nor findTemplateDirs is exported; - the module has no entry-point guard, so the whole sync executes on import.
So a consumer that wants to know "which files does the version pass stamp?" cannot ask.
It has to restate the answer.
Why it cost something
cut-rc.yml's "Build the single version commit" step allowlists the paths a version pass
may write. #9518 widened it, and the docs half was sourced from a declaration rather than
copied: sync-docs-image-tags.mjs imports SURFACES from scripts/check-docs-image-tag.mjs,
which #9064 made import-safe with exactly this kind of entry-point guard:
if(resolve(process.argv[1]??'')===resolve(fileURLToPath(import.meta.url))){ ... }The template half could not follow the same route, so two paths are spelled literally in
the workflow:
packages/create-objectstack/src/templates/blank/objectstack.config.ts
packages/create-objectstack/src/templates/blank/objectstack.manifest.json
Both hardcode the template name blank. findTemplateDirs() exists precisely because the
template set is not meant to be a curated list — so the day a second template ships, the
walk picks it up, the version pass stamps it, and the workflow's literal pair does not
cover it. That is the same failure #9518 fixed, one template later: the assertion trips and
the cut refuses to push, with nothing red until someone attempts a cut.
That objectstack.manifest.json was missing from the workflow's allowlist at all is the
first instance already realised — #9518's own card named only the three doc surfaces, and
the fourth path was found by measurement rather than by reading any list.
Shape of a fix
Export TEXT_STAMPS (or a derived stampedPaths() returning repo-relative paths across
all template dirs) and add the entry-point guard, mirroring check-docs-image-tag.mjs.
cut-rc.yml would then read both halves of its allowlist from declarations instead of one
half from a declaration and one from literals.
Related but distinct: #9348 records that this same script has no --self-test and runs
nowhere in CI. Different change to the same file; neither subsumes the other.
Generated by Claude Code
Found while implementing #9518 (widening
cut-rc.yml's release file surface). Not fixed there — recording only.What was observed
scripts/sync-template-versions.mjsalready models its outputs well. It has aTEXT_STAMPStable over(file, key, pattern, replacement)whose own header says it istable-driven "so that adding a fourth declared version surface is a row", and a
findTemplateDirs()that walksTEMPLATE_ROOT— "Deliberately not a curated list."But none of it is reachable from outside:
TEXT_STAMPSnorTEMPLATE_ROOTnorfindTemplateDirsisexported;So a consumer that wants to know "which files does the version pass stamp?" cannot ask.
It has to restate the answer.
Why it cost something
cut-rc.yml's "Build the single version commit" step allowlists the paths a version passmay write. #9518 widened it, and the docs half was sourced from a declaration rather than
copied:
sync-docs-image-tags.mjsimportsSURFACESfromscripts/check-docs-image-tag.mjs,which #9064 made import-safe with exactly this kind of entry-point guard:
The template half could not follow the same route, so two paths are spelled literally in
the workflow:
Both hardcode the template name
blank.findTemplateDirs()exists precisely because thetemplate set is not meant to be a curated list — so the day a second template ships, the
walk picks it up, the version pass stamps it, and the workflow's literal pair does not
cover it. That is the same failure #9518 fixed, one template later: the assertion trips and
the cut refuses to push, with nothing red until someone attempts a cut.
That
objectstack.manifest.jsonwas missing from the workflow's allowlist at all is thefirst instance already realised — #9518's own card named only the three doc surfaces, and
the fourth path was found by measurement rather than by reading any list.
Shape of a fix
Export
TEXT_STAMPS(or a derivedstampedPaths()returning repo-relative paths acrossall template dirs) and add the entry-point guard, mirroring
check-docs-image-tag.mjs.cut-rc.ymlwould then read both halves of its allowlist from declarations instead of onehalf from a declaration and one from literals.
Related but distinct: #9348 records that this same script has no
--self-testand runsnowhere in CI. Different change to the same file; neither subsumes the other.
Generated by Claude Code