Skip to content

[finding] 39 scripts/ modules export bindings with no entry guard — the class has now been fixed one file at a time twice, and check:entry-guard accepts "no guard at all" by design #10631

Description

@claude

Filed unassigned from the #10610 dev seat (session session_01DdCnBGcHeufjrq7drTD3wt, PR #10628). This is the sweep that #10610's "Not claimed" section explicitly left undone — "I have not swept the other scripts/** gates for the same missing guard. If several are unguarded the shape of the fix changes."

Several are. 39.

The measurement

On origin/main at 5e50e2e3ba, files under scripts/ that export at least one binding and have noisEntrypoint(import.meta.url) guard — comments and string literals masked with check-entry-guard.mjs's own codeOnly(), so a prose mention of the idiom is not miscounted as a guard:

scripts/ files scanned : 123
with a real (code, not comment) entry guard : 41 (42 after PR #10628)
exporting bindings AND unguarded : 40 (39 after PR #10628)

The 39 that remain:

ablation-dist-preflight.mjs check-release-page-status.mjs
adr-anchors.mjs check-required-contexts.mjs
check-changeset-no-major.mjs check-route-envelope.mjs
check-dispatcher-error-vocabulary check-runtime-services-index.mjs
check-driver-memory-census.mjs check-shard-attestation.mjs
check-empty-changeset.mjs check-spec-parsed-alias.mjs
check-engine-split-ratio.mjs check-startup-registry-verdict.mjs
check-error-code-casing.mjs check-tenant-chokepoint.mjs
check-error-status-conformance.mjs check-test-completeness.mjs
check-examples-live-imports.mjs check-workflow-status-functions.mjs
check-filter-alias-parity.mjs checklist-select.mjs
check-nul-bytes.mjs cli-build-prerequisite.mjs
check-org-identifier.mjs console-spec-probes.mjs
check-query-options-erasure-ratchet.mjs docs-audit/check-audit-scope.mjs
check-quick-reference-counts.mjs eslint-fatal-guard.mjs
check-ratchet-remedy-authority.mjs eslint-stack-headroom.mjs
i18n-bundle-surface.mjs
measure-test-shard-timings.mjs
pm/check-governed-prose.mjs
pm/check-label-desc-cap.mjs
pm/check-skill-id-lint.mjs
pm/check-skill-line-ratchet.mjs
regen-artifacts.mjs

Reproduce:

import{readdirSync,readFileSync,statSync}from'node:fs';import{join}from'node:path';import{codeOnly}from'./scripts/check-entry-guard.mjs';// walk scripts/, skipping node_modules and dotfiles, for .mjs/.js/.cjsconstguarded=/isEntrypoint\s*\(\s*import\.meta\.url\s*\)/.test(codeOnly(src));constexports=/^export\s/m.test(codeOnly(src));

Why this is a class, not 39 tickets

It has now been fixed twice, one file at a time:

Both were found by someone tripping over one instance. Nothing gates the next one, and there are 39 more sitting there. check-entry-guard.mjs exists precisely because a one-time sweep is worth little on its own — its own header says so about the spelling problem — but it currently enforces only that a guard, if present, is spelled the one right way. A file with no guard at all is accepted; that case is an explicit self-test assertion:

t('a file with no guard at all is accepted',n("console.log('hello');\n")===0);

which is correct for a script that exports nothing, and is exactly the hole for one that does.

Suggested shape (not claimed)

Extend check-entry-guard.mjs with a second finding kind: a scripts/** file that both exports a binding and performs top-level side-effectful dispatch must carry the guard. That closes the class the way the spelling half was closed, instead of a 40th ticket later.

The hard part is deciding "top-level side-effectful dispatch" precisely enough to avoid false positives — a module of pure export functions with no top-level statements is fine unguarded, and several of the 39 may be exactly that. Worth measuring before choosing between:

  • A. Require the guard whenever the file exports AND has any top-level call expression. Strongest; needs a real parse, and check-entry-guard.mjs is deliberately a masked-source scanner rather than a parser.
  • B. Require it only when the file exports AND reads process.argv. Cheap, no parser, catches the CLI-dispatch shape all three known instances had — including this one. Misses a module whose top-level side effect is not argv-driven.
  • C. Behavioural: import each candidate in a child and assert silence + exit 0. Directly measures the property. check-entry-guard.mjs's header already rejected running scripts wholesale, but importing them is a much weaker action than invoking them — though not a safe one for the modules whose top level has real side effects, which is the same objection in a smaller form.

B looks like the best value for the risk; A is the one that actually closes the class. Someone should measure how many of the 39 have top-level statements at all before picking — that number decides whether this is a 5-file fix or a 39-file one.

Not claimed


Generated by Claude Code

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions