Skip to content

[finding] scripts/js-comment-mask.d.mts is a hand-maintained type mirror with nothing gating it in step — if it drifts, every consumer type-checks clean against a wrong signature #10549

Description

@os-elon

Filed unassigned by the domain:cli seat (session session_019bmVFqoQPq63zhKrxdYG1r), as a PM judgment call on an observation a dev deliberately left unfiled during #10453. Recording only; not graded by the filing seat.

⚠️This is not an oversight report.scripts/js-comment-mask.d.mts was landed deliberately by #10398 three days ago, and the hand-maintenance is its own stated design — the file says "Keep this file in step with the module by hand." The trade was made knowingly. What this card adds is that the cost is now measured rather than hypothetical, and that a cheap gate would remove it.

The shape

scripts/js-comment-mask.mjs is untyped. scripts/js-comment-mask.d.mts hand-declares its surface (maskComments(source: string): string). Nothing checks that the declaration still matches the module — not a test, not a gate, not a build step. TypeScript consumers see only the declaration, so if the two drift, every consumer type-checks green against a signature the module does not implement, and the first symptom is a runtime failure somewhere downstream.

That is the same shape this lane has hit three times today in unrelated code — a declaration and the value disagreeing, with only the declaration visible in the file being read (#9901's options type, #10078's /meta scope maps, #10063's publishMetaItem declared type). In each of those the disagreement was caught only by executing something. Here there is nothing to execute: the .d.mts has no runtime existence at all.

Why it is worth a card now — the cost stopped being hypothetical

PR #10513 is a live demonstration that this file's existence silently changes typecheck outcomes across trees:

  • The branch forked at 5c3faa70d. js-comment-mask.d.mts landed on main in 0681a76b8after that.
  • On the branch's tree the .mjs had no types, so an @ts-expect-error on the import was load-bearing and tsc was clean.
  • On CI's merged tree the import resolves with types, so the same directive was unused — TS2578, two typecheck lanes red.

Neither tsc was wrong; they read different trees. That is base drift, not the drift this card is about — but it is the same file quietly deciding a typecheck result, and it shows the blast radius is real: @objectstack/cli's ledger entry has surplus: none, so a single error from this file's influence was enough to turn typecheck-debt red too.

A signature drift would be strictly worse than that episode, because it fails green instead of red.

What is NOT claimed

⛔ No drift exists today — I have not compared the declaration against the module, and I am not asserting they disagree. This is about the absence of anything that would tell us, not about a present defect.

⛔ I have not swept for other hand-maintained .d.mts mirrors in scripts/. If this is the only one, the cheapest fix is narrow; if there are several, that changes the shape of the answer. That sweep is one git ls-files 'scripts/**/*.d.mts' and I deliberately did not run it in place of the person who will own this.

Leads, not decisions

  • A conformance test: import both the module and its declaration in one test file and assert the real export's arity/behaviour against the declared shape. Cheapest, no new tooling, and it fails loudly at the right moment.
  • Generate the declaration (tsc --allowJs --declaration --emitDeclarationOnly, or JSDoc types in the .mjs) so there is nothing to keep in step by hand. Removes the class rather than guarding it, at the cost of changing how the script is authored.
  • Accept and mark it: if hand-maintenance is genuinely preferred, the file could at least carry the enforce-or-mark treatment this repo uses elsewhere, so the unguarded state is a recorded decision rather than an implicit one.

⛔ Which of these is right — including "none, the trade stands" — belongs to whoever owns scripts/ tooling, not to this seat.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions