Skip to content

A nested template literal desynchronises the vocabulary gate's shared textual scanners, and every code: position after it in that file goes unplaced #14626

Description

@claude

Found while widening scripts/check-dispatcher-error-vocabulary.mjs for #13790 (PR #14625). Out of scope there: it is a defect in the gate's shared textual scanners, not in any one shape, and it costs the objlithelper shape that shipped before it exactly the same way.

The defect

enclosingOpeners, sliceBalanced and scanTopLevel all skip a string the same way — on seeing a quote character, advance to the next occurrence of that same character:

else if (c === "'" || c === '"' || c === '`') {
  const quote = c;
  i += 1;
  while (i < src.length && src[i] !== quote) i += src[i] === '\\' ? 2 : 1;
}

A nested template literal breaks that. In

`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\``).join(', ')} — the toggle body is { enabled?: boolean }`

the scanner enters at the outer backtick and leaves at the first inner backtick, so from there on it is parsing string bytes as code and code bytes as string. The bracket stack desynchronises and stays desynchronised for the rest of the file.

What it costs, measured

packages/runtime/src/domains/automation.ts carries such a template at line 1285. Downstream of it, enclosingOpeners returns no enclosing bracket for code: positions it should answer for, so any shape guarded by that primitive silently declines them:

code: positions in that file after line 1285 that enclosingOpeners cannot place 2
of those, real stamps of the identical validation-catalog ternary 2 (:1384, :1399)

Both are code: <x> === undefined ? 'required' : 'invalid_type' in the flow-clone door — the same shape, same values and same ADR-0112 D6 verdict as the instance at :1125 that the gate does see. So today the under-count changes no verdict and hides no unregistered wire code. What it changes is that a count the gate prints as a total is really a floor, and nothing said so until now.

Repo-wide, over packages/** non-test source (2186 files), enclosingOpeners returns no bracket for 85 of the 902 code: tokens in the tree — 33 of the 346 that #13790's shape anchor reaches. Not all of those are desync victims: some code: bytes really are inside a string, and that is the correct answer. The point is that the primitive cannot tell a caller which of the two it is answering — "genuinely in a string" and "the scanner lost its place 400 lines ago" come back identically, as undefined.

Why it is not a one-shape problem

What a fix would owe

The scanners would need a template-literal mode that tracks ${} nesting rather than seeking a matching backtick — a real change to three shared primitives that every shape and every census figure in the gate depends on. Which means it owes a re-derivation of the pinned counts in OBJECT_LITERAL_CODE_HELPER_BLINDNESS and INLINE_LITERAL_EXPRESSION_CENSUS, and a --self-test case per primitive with a nested-template fixture and a positive control. That is a card, not a rider on a shape widening.

Unassigned, for triage. Filed under Prime Directive #10 rather than folded into PR #14625.


Generated by Claude Code

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions