fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners - #14739

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode
Sep 3, 2026
Merged

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners#14739
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14626

A nested template literal desynchronised the vocabulary gate's shared textual
scanners, and every code: position after it in that file went unplaced. This
adds a template-literal mode to all four primitives through ONE shared skipper,
makes the two undefined answers distinguishable, and re-derives both pinned
censuses on this tree.

The primitive population, re-derived by grep (not from the card's list)

On the branch base 4d0d9445a, grep -n "c === \"'\" || c === '\"' || c === ' + "" + '"finds the quote-skip at exactly FOUR sites, and the seek idiomwhile (i < src.length && src[i] !== quote) i += ... ? 2 : 1` at the four lines
beside them — the four the triage named, at this base's line numbers:

functionif (c === quote)seek
scanTopLevel11401143
enclosingOpeners15481551
sliceBalanced15661569
splitTopLevel15841587

After the change, grep -c "!== quote) i +=" over the file returns 1, and
that one is legacySeekQuote inside --self-test — the positive control.

The helper's state machine, in five lines

skipStringLiteral(src, at) returns { end, closed }, where end is the index
of the CLOSING quote (the same post-condition the four seek loops had, so each
caller still advances past it with its own i += 1).

  1. A STACK of frames, not a single mode: quote and template are string
    modes, interp is a code mode.
  2. ' / " — walk to the matching quote; \ escapes the next byte.
  3. ` — TEMPLATE: \ escapes, a bare ` closes, ${ pushes an
    interp frame with brace depth 1.
  4. ${ … } — CODE: {/} move the brace depth (so `${ {a: 1}.a }`
    closes where it should), and a quote there pushes a NEW literal frame, which
    recurses through the same stack to any depth.
  5. Running off the end with a frame still open returns closed: false — the
    only self-evident signature of a scan that lost its place.

The two undefineds, told apart — and CERTIFIED

enclosingOpeners now answers EVERY requested index with a tagged value:
{kind:'bracket', ch, at} · {kind:'top-level'} · {kind:'inside-string'} ·
{kind:'desync'}. The other three primitives take an optional scanReport()
(desynchronised, unterminatedAt, underflow, unbalanced), because their
return values have no room for a per-position tag.

inside-string is asserted only for a walk that ends health-certified: no
unterminated literal, no bracket underflow, an empty stack at EOF. Comments are
masked and brackets inside real literals are skipped, so a correct walk of
well-formed source ends empty; not ending empty is proof it did not. In a walk
that is not certified, every unvisited index is tagged desync — "cannot place
this, and cannot certify why" — rather than being called inside-string, which
would be the same over-claim one layer up.

--self-test: a fixture AND a positive control per primitive

The pre-fix legacySeekQuote is kept in test scope, with all four primitives
re-implemented around it differing in exactly the skip. Fixture:
`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\).join(', ')} …` ``

primitivefixture assertscontrol (pre-fix algorithm) asserts
scanTopLevel2 top-level ; past the templatelegacy sees 0
enclosingOpenersthe code: after it is {-enclosedlegacy answers undefined
sliceBalancedthe whole argument list comes backlegacy truncates at the inner backtick
splitTopLevel2 partslegacy answers 1

Plus: inside-string on a code: written inside a string; desync on a
position behind an unterminated template; the skipper's two interpolation cases
(braces and quotes inside ${ … }); and the scanReport on all three.

Mutation on the committed tree. Deleting the ${-tracking branch from
skipStringLiteral (the skipper falls back to seek-to-matching-backtick)
reddens exactly the four primitive cases:

check-dispatcher-error-vocabulary --self-test FAILED:
- scanTopLevel saw 0 top-level `;` past a nested template, not 2 …
- enclosingOpeners answered {"kind":"desync"} for a `code:` after a nested template …
- sliceBalanced returned "`a${xs.map((k) => `\`${k}\`` " for an argument list …
- splitTopLevel cut a nested-template argument list into 1 parts, not 2 …

Mutation confirmed on disk before the run (grep -c on the deleted text: 1 → 0;
on the injected marker: 0 → 1; blob hash 9d4b84921dce95d3). Restored by
git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap and proven back:
blob hash 9d4b8492 again, git diff HEAD --name-only empty.

Censuses re-derived — tree drift separated from what the fix moved

Both readings come from the same sweep, run with the PRE-fix primitives and the
POST-fix ones over today's packages/** non-test source.

INLINE_LITERAL_EXPRESSION_CENSUS

fieldpinned (#13790)pre-fix, todayafterwhat moved it
filesScanned218621872187drift
objectLiteralCodeTokens902906906drift
anchorHits346349349drift
notObjectLiteral747470fix
parameterList414141
insideAString333318fix (split)
behindScannerDesync11fix (new class)
ownedBySibling.objlitconst404041fix
candidatesReachingReduction216219222drift + fix
declinedByReduction214217218drift + fix
declinedTypeAnnotation150155155drift
declinedRuntimeValue646263drift + fix
livePositions224fix
distinctValues559fix
newVerdictRows222
unregisteredWireCodesHiding000
positionsBehindScannerDesync2211re-attributed (below)
positionsRecoveredByTemplateMode2new

Newly PLACED positions, classified

Over the whole 906-token code: population, 19 positions go from unplaced
to placed. Two of them reach objlitexpr — exactly the two the card predicted:

positionclassification
domains/automation.ts:1384code: targetName === undefined ? 'required' : 'invalid_type'ADR-0112 D6 field-addressed validation catalog. Same file, same shape, same two values as the :1125 instance the gate already saw ⇒ derives the site keys that already exist ⇒ 0 new verdict rows.
domains/automation.ts:1399code: targetLabel === undefined ? 'required' : 'invalid_type'Same class, same verdict, 0 new verdict rows.

The other 17 are objlit/objlitconst tokens (those shapes are not guarded by
enclosingOpeners, so they were never blind there) or type annotations. Two
enter objlithelper's candidate population, which is why localTwinCensus
moves by 2.

No new value surfaced anywhere.deriveSites diffed key-by-key across the
change: sites 59 → 59, unresolved 5 → 5, added [], removed [], per-shape
counts identical. So the ⛔ escalation clause (an unregistered value at a newly
placed position ⇒ report blocked) was not reached, and
packages/runtime/src/dispatcher-error-vocabulary.ts is untouched.

OBJECT_LITERAL_CODE_HELPER_BLINDNESS

measured.* and declarationFormClosure.* are historical deltas of #13233 /
#13226, not censuses of today's tree; re-checked rather than assumed —
objlithelper derives the same 29 sites and the same 5 unresolved before and
after — and left as they were, with that check written into the block.

localTwinCensus IS a census of this tree, and it had already drifted:

fieldpinned (#13478)pre-fix, todayafter
candidatePositions125134136
alreadyCoveredByObjlitconst364041
newlyReached899495
newlyReachedDistinctFileIdent697273
newlyReachedDistinctFiles454747
reduceToEmptyByClass.typeKeywordPosition495353
reduceToEmptyByClass.namedTypePosition161616
reduceToEmptyByClass.runtimeValueLocal151617
reduceToEmptyByClass.bindingWithoutDeclarator999
reduce000
newVerdictRows / unregisteredWireCodesHiding0 / 00 / 00 / 0

The headline zero survives at both ends — the reducible subclass is still
empty — so no verdict row is owed. The instrument was the #13478 replica: the
real objlithelper branch with the resolveConstant fallback switched on,
driven through the real deriveSites; its zero is backed by the positive
control already in --self-test (resolveConstant still reduces
const c = cond ? A : B to both branches).

SCANNER_LITERAL_BLIND_SPOTS — the class this card did NOT close

The sweep found a second desync class and it is declared with numbers rather
than left to be rediscovered: a regex literal whose character class carries a
quote or a backtick
. The live one is
packages/rest/src/error-response.ts's RELATION_DOES_NOT_EXIST, whose class
contains ", ' and a backtick; a textual walk opens a string the source does
not have.

files swept2187
whole-file walks health-certified, pre-fix → post-fix2108 → 2119 (+11)
files still desynchronised68
code: positions unplaced pre-fix86
↳ newly placed by the template mode19
↳ certified genuinely inside a string21
↳ still behind a desync46
unregistered wire codes surfaced0

⛔ Deliberately not fixed here: telling /re/ from division needs the preceding
token's grammatical class, which is lexer state this scan does not carry — a
heuristic would trade a blind spot that is now COUNTED for one that is not.
Filed as its own card. --self-test pins the declaration in the direction it
can fail: a regex-literal fixture must still come back desync, with the same
source minus the regex as the positive control, so the block cannot quietly
outlive the blind spot it declares.

Scope

  • git diff --stat: 1 file changed, scripts/check-dispatcher-error-vocabulary.mjs.
  • scripts/check-error-code-casing.mjsuntouched. Confirmed by the diff
    and by pnpm check:error-code-casing in the gate family below.
  • packages/runtime/src/dispatcher-error-vocabulary.tsuntouched. No
    ledger row added or moved; no site or unresolved entry changed.
  • ⛔ No shape grammar changed: objlit, objlitconst, objlittemplate,
    objlithelper and objlitexpr regexes are byte-identical.

Verification

The union below ran at HEAD 160c99d5 (git rev-parse --short HEAD), the
final commit on this branch — a merge of origin/main after the fix commit
0dc603cd2. The whole union was run again on that merged head rather than
carried over from the pre-merge run. Each exit code was captured by redirect
BEFORE any pipe.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-dispatcher-error-vocabulary.mjs derives 18 commands from this
tree (objectstack-ai/objectstack at 160c99d51, --repo asserted and held —
the derivation is re-run from the merged head, never a git diff list fed in by
hand). All 18 run, all green except one NOT MEASURED:

node scripts/check-ci-filter-parity.mjs exit=0
node scripts/check-cross-package-test-inputs.mjs exit=0
node scripts/check-self-test-wired.mjs exit=0
node scripts/check-shard-attestation.mjs exit=0
node scripts/check-test-completeness.mjs exit=3 NOT MEASURED
node scripts/check-whole-set-label-write.mjs exit=0
node scripts/pm/bare-root-worklist.mjs --self-test exit=0
pnpm check:agent-test-spelling exit=0
pnpm check:bash32-floor exit=0
pnpm check:cli-command-ids exit=0
pnpm check:cross-package-test-inputs exit=0
pnpm check:dispatcher-error-vocabulary exit=0
pnpm check:entry-guard exit=0
pnpm check:parse-guard exit=0
pnpm check:pm-dispatch-gates exit=0
pnpm check:pnpm-filter-targets exit=0
pnpm check:ratchet-remedy-authority exit=0
pnpm check:watch-hint-literal exit=0

node scripts/pm/dispatch-gates.mjs --ran RECORD_FILE on the --commands output
reconciles the run: "Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED,
0 UNRUN"
, exit 0.

check-test-completeness exit 3 is its own declared NOT-MEASURED branch, in
its own words: "Arrived here from the gate family scripts/pm/dispatch-gates.mjs
derives? … There is no local log to hand it, so the local reading for this gate
is NOT MEASURED. ⛔ It is not a red."
CI tees a real turbo run test log into
it, so CI measures it.

Named by the dispatch and outside the derived family, run anyway:

pnpm check:error-code-casing exit=0 (the ⛔ untouched gate — still green)
pnpm check:nul-bytes exit=0 "OK (scanned 8042 text file(s) … no raw ASCII control bytes)"

The gate's own verdict lines:

check-dispatcher-error-vocabulary --self-test: 10 shapes + 282 assertions OK
(vocabulary + #9098 door typing) [was 277 before this card]
check-dispatcher-error-vocabulary: OK — 59 unregistered code-stamping site(s),
all classified; 1 awaiting a ledger entry (#8846).

Lint, narrowed and the narrowing measured (not "not run"):

  • Population comes from eslint's own config, not from a guess about which files
    count; the diff is 1 file, so npx eslint --no-inline-config --format json scripts/check-dispatcher-error-vocabulary.mjs is the whole of it.
  • File count read from that JSON: 1 file linted, 0 errors, 0 warnings,
    exit 0.
  • Invariance: eslint.config.* states it runs with no parserOptions.project
    and no typed @typescript-eslint rules, so type-aware linting is off and a
    one-file diff cannot move the verdict on any untouched file. Repo-wide
    pnpm lint is CI's run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

…shared textual scanners (#14626)
`scanTopLevel`, `enclosingOpeners`, `sliceBalanced` and `splitTopLevel` each
carried the same seek-to-matching-quote skip, which a NESTED template literal
walks straight out of: the outer template closed at the first inner backtick
and every `code:` position after it in that file went unplaced.
All four now call ONE shared `skipStringLiteral`, a small state machine that
tracks `${ … }` nesting and recurses into literals opened inside an
interpolation. `enclosingOpeners` answers every requested index with a TAGGED
result, so "genuinely inside a string" and "the scanner lost its place" stop
arriving as the same `undefined`; the other three take an optional
`scanReport()`. The tag is certified — `inside-string` is asserted only for a
walk that ends with no unterminated literal, no bracket underflow and an empty
stack.
`--self-test` gains a nested-template fixture per primitive, each PAIRED with a
positive control run through the pre-fix `legacySeekQuote` kept in test scope.
`OBJECT_LITERAL_CODE_HELPER_BLINDNESS` and `INLINE_LITERAL_EXPRESSION_CENSUS`
are re-derived on this tree, with tree drift separated from what the fix moved,
and `SCANNER_LITERAL_BLIND_SPOTS` records the residual class the sweep found
(a regex literal carrying a quote) rather than leaving it to be rediscovered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 23:25
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33696604625 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 30 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 41978ecSep 3, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14626-scanner-nested-template-mode branch September 3, 2026 00:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners - #14739

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode
Sep 3, 2026
Merged

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners#14739
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14626

A nested template literal desynchronised the vocabulary gate's shared textual
scanners, and every code: position after it in that file went unplaced. This
adds a template-literal mode to all four primitives through ONE shared skipper,
makes the two undefined answers distinguishable, and re-derives both pinned
censuses on this tree.

The primitive population, re-derived by grep (not from the card's list)

On the branch base 4d0d9445a, grep -n "c === \"'\" || c === '\"' || c === ' + "" + '"finds the quote-skip at exactly FOUR sites, and the seek idiomwhile (i < src.length && src[i] !== quote) i += ... ? 2 : 1` at the four lines
beside them — the four the triage named, at this base's line numbers:

functionif (c === quote)seek
scanTopLevel11401143
enclosingOpeners15481551
sliceBalanced15661569
splitTopLevel15841587

After the change, grep -c "!== quote) i +=" over the file returns 1, and
that one is legacySeekQuote inside --self-test — the positive control.

The helper's state machine, in five lines

skipStringLiteral(src, at) returns { end, closed }, where end is the index
of the CLOSING quote (the same post-condition the four seek loops had, so each
caller still advances past it with its own i += 1).

  1. A STACK of frames, not a single mode: quote and template are string
    modes, interp is a code mode.
  2. ' / " — walk to the matching quote; \ escapes the next byte.
  3. ` — TEMPLATE: \ escapes, a bare ` closes, ${ pushes an
    interp frame with brace depth 1.
  4. ${ … } — CODE: {/} move the brace depth (so `${ {a: 1}.a }`
    closes where it should), and a quote there pushes a NEW literal frame, which
    recurses through the same stack to any depth.
  5. Running off the end with a frame still open returns closed: false — the
    only self-evident signature of a scan that lost its place.

The two undefineds, told apart — and CERTIFIED

enclosingOpeners now answers EVERY requested index with a tagged value:
{kind:'bracket', ch, at} · {kind:'top-level'} · {kind:'inside-string'} ·
{kind:'desync'}. The other three primitives take an optional scanReport()
(desynchronised, unterminatedAt, underflow, unbalanced), because their
return values have no room for a per-position tag.

inside-string is asserted only for a walk that ends health-certified: no
unterminated literal, no bracket underflow, an empty stack at EOF. Comments are
masked and brackets inside real literals are skipped, so a correct walk of
well-formed source ends empty; not ending empty is proof it did not. In a walk
that is not certified, every unvisited index is tagged desync — "cannot place
this, and cannot certify why" — rather than being called inside-string, which
would be the same over-claim one layer up.

--self-test: a fixture AND a positive control per primitive

The pre-fix legacySeekQuote is kept in test scope, with all four primitives
re-implemented around it differing in exactly the skip. Fixture:
`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\).join(', ')} …` ``

primitivefixture assertscontrol (pre-fix algorithm) asserts
scanTopLevel2 top-level ; past the templatelegacy sees 0
enclosingOpenersthe code: after it is {-enclosedlegacy answers undefined
sliceBalancedthe whole argument list comes backlegacy truncates at the inner backtick
splitTopLevel2 partslegacy answers 1

Plus: inside-string on a code: written inside a string; desync on a
position behind an unterminated template; the skipper's two interpolation cases
(braces and quotes inside ${ … }); and the scanReport on all three.

Mutation on the committed tree. Deleting the ${-tracking branch from
skipStringLiteral (the skipper falls back to seek-to-matching-backtick)
reddens exactly the four primitive cases:

check-dispatcher-error-vocabulary --self-test FAILED:
- scanTopLevel saw 0 top-level `;` past a nested template, not 2 …
- enclosingOpeners answered {"kind":"desync"} for a `code:` after a nested template …
- sliceBalanced returned "`a${xs.map((k) => `\`${k}\`` " for an argument list …
- splitTopLevel cut a nested-template argument list into 1 parts, not 2 …

Mutation confirmed on disk before the run (grep -c on the deleted text: 1 → 0;
on the injected marker: 0 → 1; blob hash 9d4b84921dce95d3). Restored by
git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap and proven back:
blob hash 9d4b8492 again, git diff HEAD --name-only empty.

Censuses re-derived — tree drift separated from what the fix moved

Both readings come from the same sweep, run with the PRE-fix primitives and the
POST-fix ones over today's packages/** non-test source.

INLINE_LITERAL_EXPRESSION_CENSUS

fieldpinned (#13790)pre-fix, todayafterwhat moved it
filesScanned218621872187drift
objectLiteralCodeTokens902906906drift
anchorHits346349349drift
notObjectLiteral747470fix
parameterList414141
insideAString333318fix (split)
behindScannerDesync11fix (new class)
ownedBySibling.objlitconst404041fix
candidatesReachingReduction216219222drift + fix
declinedByReduction214217218drift + fix
declinedTypeAnnotation150155155drift
declinedRuntimeValue646263drift + fix
livePositions224fix
distinctValues559fix
newVerdictRows222
unregisteredWireCodesHiding000
positionsBehindScannerDesync2211re-attributed (below)
positionsRecoveredByTemplateMode2new

Newly PLACED positions, classified

Over the whole 906-token code: population, 19 positions go from unplaced
to placed. Two of them reach objlitexpr — exactly the two the card predicted:

positionclassification
domains/automation.ts:1384code: targetName === undefined ? 'required' : 'invalid_type'ADR-0112 D6 field-addressed validation catalog. Same file, same shape, same two values as the :1125 instance the gate already saw ⇒ derives the site keys that already exist ⇒ 0 new verdict rows.
domains/automation.ts:1399code: targetLabel === undefined ? 'required' : 'invalid_type'Same class, same verdict, 0 new verdict rows.

The other 17 are objlit/objlitconst tokens (those shapes are not guarded by
enclosingOpeners, so they were never blind there) or type annotations. Two
enter objlithelper's candidate population, which is why localTwinCensus
moves by 2.

No new value surfaced anywhere.deriveSites diffed key-by-key across the
change: sites 59 → 59, unresolved 5 → 5, added [], removed [], per-shape
counts identical. So the ⛔ escalation clause (an unregistered value at a newly
placed position ⇒ report blocked) was not reached, and
packages/runtime/src/dispatcher-error-vocabulary.ts is untouched.

OBJECT_LITERAL_CODE_HELPER_BLINDNESS

measured.* and declarationFormClosure.* are historical deltas of #13233 /
#13226, not censuses of today's tree; re-checked rather than assumed —
objlithelper derives the same 29 sites and the same 5 unresolved before and
after — and left as they were, with that check written into the block.

localTwinCensus IS a census of this tree, and it had already drifted:

fieldpinned (#13478)pre-fix, todayafter
candidatePositions125134136
alreadyCoveredByObjlitconst364041
newlyReached899495
newlyReachedDistinctFileIdent697273
newlyReachedDistinctFiles454747
reduceToEmptyByClass.typeKeywordPosition495353
reduceToEmptyByClass.namedTypePosition161616
reduceToEmptyByClass.runtimeValueLocal151617
reduceToEmptyByClass.bindingWithoutDeclarator999
reduce000
newVerdictRows / unregisteredWireCodesHiding0 / 00 / 00 / 0

The headline zero survives at both ends — the reducible subclass is still
empty — so no verdict row is owed. The instrument was the #13478 replica: the
real objlithelper branch with the resolveConstant fallback switched on,
driven through the real deriveSites; its zero is backed by the positive
control already in --self-test (resolveConstant still reduces
const c = cond ? A : B to both branches).

SCANNER_LITERAL_BLIND_SPOTS — the class this card did NOT close

The sweep found a second desync class and it is declared with numbers rather
than left to be rediscovered: a regex literal whose character class carries a
quote or a backtick
. The live one is
packages/rest/src/error-response.ts's RELATION_DOES_NOT_EXIST, whose class
contains ", ' and a backtick; a textual walk opens a string the source does
not have.

files swept2187
whole-file walks health-certified, pre-fix → post-fix2108 → 2119 (+11)
files still desynchronised68
code: positions unplaced pre-fix86
↳ newly placed by the template mode19
↳ certified genuinely inside a string21
↳ still behind a desync46
unregistered wire codes surfaced0

⛔ Deliberately not fixed here: telling /re/ from division needs the preceding
token's grammatical class, which is lexer state this scan does not carry — a
heuristic would trade a blind spot that is now COUNTED for one that is not.
Filed as its own card. --self-test pins the declaration in the direction it
can fail: a regex-literal fixture must still come back desync, with the same
source minus the regex as the positive control, so the block cannot quietly
outlive the blind spot it declares.

Scope

  • git diff --stat: 1 file changed, scripts/check-dispatcher-error-vocabulary.mjs.
  • scripts/check-error-code-casing.mjsuntouched. Confirmed by the diff
    and by pnpm check:error-code-casing in the gate family below.
  • packages/runtime/src/dispatcher-error-vocabulary.tsuntouched. No
    ledger row added or moved; no site or unresolved entry changed.
  • ⛔ No shape grammar changed: objlit, objlitconst, objlittemplate,
    objlithelper and objlitexpr regexes are byte-identical.

Verification

The union below ran at HEAD 160c99d5 (git rev-parse --short HEAD), the
final commit on this branch — a merge of origin/main after the fix commit
0dc603cd2. The whole union was run again on that merged head rather than
carried over from the pre-merge run. Each exit code was captured by redirect
BEFORE any pipe.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-dispatcher-error-vocabulary.mjs derives 18 commands from this
tree (objectstack-ai/objectstack at 160c99d51, --repo asserted and held —
the derivation is re-run from the merged head, never a git diff list fed in by
hand). All 18 run, all green except one NOT MEASURED:

node scripts/check-ci-filter-parity.mjs exit=0
node scripts/check-cross-package-test-inputs.mjs exit=0
node scripts/check-self-test-wired.mjs exit=0
node scripts/check-shard-attestation.mjs exit=0
node scripts/check-test-completeness.mjs exit=3 NOT MEASURED
node scripts/check-whole-set-label-write.mjs exit=0
node scripts/pm/bare-root-worklist.mjs --self-test exit=0
pnpm check:agent-test-spelling exit=0
pnpm check:bash32-floor exit=0
pnpm check:cli-command-ids exit=0
pnpm check:cross-package-test-inputs exit=0
pnpm check:dispatcher-error-vocabulary exit=0
pnpm check:entry-guard exit=0
pnpm check:parse-guard exit=0
pnpm check:pm-dispatch-gates exit=0
pnpm check:pnpm-filter-targets exit=0
pnpm check:ratchet-remedy-authority exit=0
pnpm check:watch-hint-literal exit=0

node scripts/pm/dispatch-gates.mjs --ran RECORD_FILE on the --commands output
reconciles the run: "Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED,
0 UNRUN"
, exit 0.

check-test-completeness exit 3 is its own declared NOT-MEASURED branch, in
its own words: "Arrived here from the gate family scripts/pm/dispatch-gates.mjs
derives? … There is no local log to hand it, so the local reading for this gate
is NOT MEASURED. ⛔ It is not a red."
CI tees a real turbo run test log into
it, so CI measures it.

Named by the dispatch and outside the derived family, run anyway:

pnpm check:error-code-casing exit=0 (the ⛔ untouched gate — still green)
pnpm check:nul-bytes exit=0 "OK (scanned 8042 text file(s) … no raw ASCII control bytes)"

The gate's own verdict lines:

check-dispatcher-error-vocabulary --self-test: 10 shapes + 282 assertions OK
(vocabulary + #9098 door typing) [was 277 before this card]
check-dispatcher-error-vocabulary: OK — 59 unregistered code-stamping site(s),
all classified; 1 awaiting a ledger entry (#8846).

Lint, narrowed and the narrowing measured (not "not run"):

  • Population comes from eslint's own config, not from a guess about which files
    count; the diff is 1 file, so npx eslint --no-inline-config --format json scripts/check-dispatcher-error-vocabulary.mjs is the whole of it.
  • File count read from that JSON: 1 file linted, 0 errors, 0 warnings,
    exit 0.
  • Invariance: eslint.config.* states it runs with no parserOptions.project
    and no typed @typescript-eslint rules, so type-aware linting is off and a
    one-file diff cannot move the verdict on any untouched file. Repo-wide
    pnpm lint is CI's run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

…shared textual scanners (#14626)
`scanTopLevel`, `enclosingOpeners`, `sliceBalanced` and `splitTopLevel` each
carried the same seek-to-matching-quote skip, which a NESTED template literal
walks straight out of: the outer template closed at the first inner backtick
and every `code:` position after it in that file went unplaced.
All four now call ONE shared `skipStringLiteral`, a small state machine that
tracks `${ … }` nesting and recurses into literals opened inside an
interpolation. `enclosingOpeners` answers every requested index with a TAGGED
result, so "genuinely inside a string" and "the scanner lost its place" stop
arriving as the same `undefined`; the other three take an optional
`scanReport()`. The tag is certified — `inside-string` is asserted only for a
walk that ends with no unterminated literal, no bracket underflow and an empty
stack.
`--self-test` gains a nested-template fixture per primitive, each PAIRED with a
positive control run through the pre-fix `legacySeekQuote` kept in test scope.
`OBJECT_LITERAL_CODE_HELPER_BLINDNESS` and `INLINE_LITERAL_EXPRESSION_CENSUS`
are re-derived on this tree, with tree drift separated from what the fix moved,
and `SCANNER_LITERAL_BLIND_SPOTS` records the residual class the sweep found
(a regex literal carrying a quote) rather than leaving it to be rediscovered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 23:25
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33696604625 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 30 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 41978ecSep 3, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14626-scanner-nested-template-mode branch September 3, 2026 00:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners - #14739

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode
Sep 3, 2026
Merged

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners#14739
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14626

A nested template literal desynchronised the vocabulary gate's shared textual
scanners, and every code: position after it in that file went unplaced. This
adds a template-literal mode to all four primitives through ONE shared skipper,
makes the two undefined answers distinguishable, and re-derives both pinned
censuses on this tree.

The primitive population, re-derived by grep (not from the card's list)

On the branch base 4d0d9445a, grep -n "c === \"'\" || c === '\"' || c === ' + "" + '"finds the quote-skip at exactly FOUR sites, and the seek idiomwhile (i < src.length && src[i] !== quote) i += ... ? 2 : 1` at the four lines
beside them — the four the triage named, at this base's line numbers:

functionif (c === quote)seek
scanTopLevel11401143
enclosingOpeners15481551
sliceBalanced15661569
splitTopLevel15841587

After the change, grep -c "!== quote) i +=" over the file returns 1, and
that one is legacySeekQuote inside --self-test — the positive control.

The helper's state machine, in five lines

skipStringLiteral(src, at) returns { end, closed }, where end is the index
of the CLOSING quote (the same post-condition the four seek loops had, so each
caller still advances past it with its own i += 1).

  1. A STACK of frames, not a single mode: quote and template are string
    modes, interp is a code mode.
  2. ' / " — walk to the matching quote; \ escapes the next byte.
  3. ` — TEMPLATE: \ escapes, a bare ` closes, ${ pushes an
    interp frame with brace depth 1.
  4. ${ … } — CODE: {/} move the brace depth (so `${ {a: 1}.a }`
    closes where it should), and a quote there pushes a NEW literal frame, which
    recurses through the same stack to any depth.
  5. Running off the end with a frame still open returns closed: false — the
    only self-evident signature of a scan that lost its place.

The two undefineds, told apart — and CERTIFIED

enclosingOpeners now answers EVERY requested index with a tagged value:
{kind:'bracket', ch, at} · {kind:'top-level'} · {kind:'inside-string'} ·
{kind:'desync'}. The other three primitives take an optional scanReport()
(desynchronised, unterminatedAt, underflow, unbalanced), because their
return values have no room for a per-position tag.

inside-string is asserted only for a walk that ends health-certified: no
unterminated literal, no bracket underflow, an empty stack at EOF. Comments are
masked and brackets inside real literals are skipped, so a correct walk of
well-formed source ends empty; not ending empty is proof it did not. In a walk
that is not certified, every unvisited index is tagged desync — "cannot place
this, and cannot certify why" — rather than being called inside-string, which
would be the same over-claim one layer up.

--self-test: a fixture AND a positive control per primitive

The pre-fix legacySeekQuote is kept in test scope, with all four primitives
re-implemented around it differing in exactly the skip. Fixture:
`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\).join(', ')} …` ``

primitivefixture assertscontrol (pre-fix algorithm) asserts
scanTopLevel2 top-level ; past the templatelegacy sees 0
enclosingOpenersthe code: after it is {-enclosedlegacy answers undefined
sliceBalancedthe whole argument list comes backlegacy truncates at the inner backtick
splitTopLevel2 partslegacy answers 1

Plus: inside-string on a code: written inside a string; desync on a
position behind an unterminated template; the skipper's two interpolation cases
(braces and quotes inside ${ … }); and the scanReport on all three.

Mutation on the committed tree. Deleting the ${-tracking branch from
skipStringLiteral (the skipper falls back to seek-to-matching-backtick)
reddens exactly the four primitive cases:

check-dispatcher-error-vocabulary --self-test FAILED:
- scanTopLevel saw 0 top-level `;` past a nested template, not 2 …
- enclosingOpeners answered {"kind":"desync"} for a `code:` after a nested template …
- sliceBalanced returned "`a${xs.map((k) => `\`${k}\`` " for an argument list …
- splitTopLevel cut a nested-template argument list into 1 parts, not 2 …

Mutation confirmed on disk before the run (grep -c on the deleted text: 1 → 0;
on the injected marker: 0 → 1; blob hash 9d4b84921dce95d3). Restored by
git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap and proven back:
blob hash 9d4b8492 again, git diff HEAD --name-only empty.

Censuses re-derived — tree drift separated from what the fix moved

Both readings come from the same sweep, run with the PRE-fix primitives and the
POST-fix ones over today's packages/** non-test source.

INLINE_LITERAL_EXPRESSION_CENSUS

fieldpinned (#13790)pre-fix, todayafterwhat moved it
filesScanned218621872187drift
objectLiteralCodeTokens902906906drift
anchorHits346349349drift
notObjectLiteral747470fix
parameterList414141
insideAString333318fix (split)
behindScannerDesync11fix (new class)
ownedBySibling.objlitconst404041fix
candidatesReachingReduction216219222drift + fix
declinedByReduction214217218drift + fix
declinedTypeAnnotation150155155drift
declinedRuntimeValue646263drift + fix
livePositions224fix
distinctValues559fix
newVerdictRows222
unregisteredWireCodesHiding000
positionsBehindScannerDesync2211re-attributed (below)
positionsRecoveredByTemplateMode2new

Newly PLACED positions, classified

Over the whole 906-token code: population, 19 positions go from unplaced
to placed. Two of them reach objlitexpr — exactly the two the card predicted:

positionclassification
domains/automation.ts:1384code: targetName === undefined ? 'required' : 'invalid_type'ADR-0112 D6 field-addressed validation catalog. Same file, same shape, same two values as the :1125 instance the gate already saw ⇒ derives the site keys that already exist ⇒ 0 new verdict rows.
domains/automation.ts:1399code: targetLabel === undefined ? 'required' : 'invalid_type'Same class, same verdict, 0 new verdict rows.

The other 17 are objlit/objlitconst tokens (those shapes are not guarded by
enclosingOpeners, so they were never blind there) or type annotations. Two
enter objlithelper's candidate population, which is why localTwinCensus
moves by 2.

No new value surfaced anywhere.deriveSites diffed key-by-key across the
change: sites 59 → 59, unresolved 5 → 5, added [], removed [], per-shape
counts identical. So the ⛔ escalation clause (an unregistered value at a newly
placed position ⇒ report blocked) was not reached, and
packages/runtime/src/dispatcher-error-vocabulary.ts is untouched.

OBJECT_LITERAL_CODE_HELPER_BLINDNESS

measured.* and declarationFormClosure.* are historical deltas of #13233 /
#13226, not censuses of today's tree; re-checked rather than assumed —
objlithelper derives the same 29 sites and the same 5 unresolved before and
after — and left as they were, with that check written into the block.

localTwinCensus IS a census of this tree, and it had already drifted:

fieldpinned (#13478)pre-fix, todayafter
candidatePositions125134136
alreadyCoveredByObjlitconst364041
newlyReached899495
newlyReachedDistinctFileIdent697273
newlyReachedDistinctFiles454747
reduceToEmptyByClass.typeKeywordPosition495353
reduceToEmptyByClass.namedTypePosition161616
reduceToEmptyByClass.runtimeValueLocal151617
reduceToEmptyByClass.bindingWithoutDeclarator999
reduce000
newVerdictRows / unregisteredWireCodesHiding0 / 00 / 00 / 0

The headline zero survives at both ends — the reducible subclass is still
empty — so no verdict row is owed. The instrument was the #13478 replica: the
real objlithelper branch with the resolveConstant fallback switched on,
driven through the real deriveSites; its zero is backed by the positive
control already in --self-test (resolveConstant still reduces
const c = cond ? A : B to both branches).

SCANNER_LITERAL_BLIND_SPOTS — the class this card did NOT close

The sweep found a second desync class and it is declared with numbers rather
than left to be rediscovered: a regex literal whose character class carries a
quote or a backtick
. The live one is
packages/rest/src/error-response.ts's RELATION_DOES_NOT_EXIST, whose class
contains ", ' and a backtick; a textual walk opens a string the source does
not have.

files swept2187
whole-file walks health-certified, pre-fix → post-fix2108 → 2119 (+11)
files still desynchronised68
code: positions unplaced pre-fix86
↳ newly placed by the template mode19
↳ certified genuinely inside a string21
↳ still behind a desync46
unregistered wire codes surfaced0

⛔ Deliberately not fixed here: telling /re/ from division needs the preceding
token's grammatical class, which is lexer state this scan does not carry — a
heuristic would trade a blind spot that is now COUNTED for one that is not.
Filed as its own card. --self-test pins the declaration in the direction it
can fail: a regex-literal fixture must still come back desync, with the same
source minus the regex as the positive control, so the block cannot quietly
outlive the blind spot it declares.

Scope

  • git diff --stat: 1 file changed, scripts/check-dispatcher-error-vocabulary.mjs.
  • scripts/check-error-code-casing.mjsuntouched. Confirmed by the diff
    and by pnpm check:error-code-casing in the gate family below.
  • packages/runtime/src/dispatcher-error-vocabulary.tsuntouched. No
    ledger row added or moved; no site or unresolved entry changed.
  • ⛔ No shape grammar changed: objlit, objlitconst, objlittemplate,
    objlithelper and objlitexpr regexes are byte-identical.

Verification

The union below ran at HEAD 160c99d5 (git rev-parse --short HEAD), the
final commit on this branch — a merge of origin/main after the fix commit
0dc603cd2. The whole union was run again on that merged head rather than
carried over from the pre-merge run. Each exit code was captured by redirect
BEFORE any pipe.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-dispatcher-error-vocabulary.mjs derives 18 commands from this
tree (objectstack-ai/objectstack at 160c99d51, --repo asserted and held —
the derivation is re-run from the merged head, never a git diff list fed in by
hand). All 18 run, all green except one NOT MEASURED:

node scripts/check-ci-filter-parity.mjs exit=0
node scripts/check-cross-package-test-inputs.mjs exit=0
node scripts/check-self-test-wired.mjs exit=0
node scripts/check-shard-attestation.mjs exit=0
node scripts/check-test-completeness.mjs exit=3 NOT MEASURED
node scripts/check-whole-set-label-write.mjs exit=0
node scripts/pm/bare-root-worklist.mjs --self-test exit=0
pnpm check:agent-test-spelling exit=0
pnpm check:bash32-floor exit=0
pnpm check:cli-command-ids exit=0
pnpm check:cross-package-test-inputs exit=0
pnpm check:dispatcher-error-vocabulary exit=0
pnpm check:entry-guard exit=0
pnpm check:parse-guard exit=0
pnpm check:pm-dispatch-gates exit=0
pnpm check:pnpm-filter-targets exit=0
pnpm check:ratchet-remedy-authority exit=0
pnpm check:watch-hint-literal exit=0

node scripts/pm/dispatch-gates.mjs --ran RECORD_FILE on the --commands output
reconciles the run: "Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED,
0 UNRUN"
, exit 0.

check-test-completeness exit 3 is its own declared NOT-MEASURED branch, in
its own words: "Arrived here from the gate family scripts/pm/dispatch-gates.mjs
derives? … There is no local log to hand it, so the local reading for this gate
is NOT MEASURED. ⛔ It is not a red."
CI tees a real turbo run test log into
it, so CI measures it.

Named by the dispatch and outside the derived family, run anyway:

pnpm check:error-code-casing exit=0 (the ⛔ untouched gate — still green)
pnpm check:nul-bytes exit=0 "OK (scanned 8042 text file(s) … no raw ASCII control bytes)"

The gate's own verdict lines:

check-dispatcher-error-vocabulary --self-test: 10 shapes + 282 assertions OK
(vocabulary + #9098 door typing) [was 277 before this card]
check-dispatcher-error-vocabulary: OK — 59 unregistered code-stamping site(s),
all classified; 1 awaiting a ledger entry (#8846).

Lint, narrowed and the narrowing measured (not "not run"):

  • Population comes from eslint's own config, not from a guess about which files
    count; the diff is 1 file, so npx eslint --no-inline-config --format json scripts/check-dispatcher-error-vocabulary.mjs is the whole of it.
  • File count read from that JSON: 1 file linted, 0 errors, 0 warnings,
    exit 0.
  • Invariance: eslint.config.* states it runs with no parserOptions.project
    and no typed @typescript-eslint rules, so type-aware linting is off and a
    one-file diff cannot move the verdict on any untouched file. Repo-wide
    pnpm lint is CI's run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

…shared textual scanners (#14626)
`scanTopLevel`, `enclosingOpeners`, `sliceBalanced` and `splitTopLevel` each
carried the same seek-to-matching-quote skip, which a NESTED template literal
walks straight out of: the outer template closed at the first inner backtick
and every `code:` position after it in that file went unplaced.
All four now call ONE shared `skipStringLiteral`, a small state machine that
tracks `${ … }` nesting and recurses into literals opened inside an
interpolation. `enclosingOpeners` answers every requested index with a TAGGED
result, so "genuinely inside a string" and "the scanner lost its place" stop
arriving as the same `undefined`; the other three take an optional
`scanReport()`. The tag is certified — `inside-string` is asserted only for a
walk that ends with no unterminated literal, no bracket underflow and an empty
stack.
`--self-test` gains a nested-template fixture per primitive, each PAIRED with a
positive control run through the pre-fix `legacySeekQuote` kept in test scope.
`OBJECT_LITERAL_CODE_HELPER_BLINDNESS` and `INLINE_LITERAL_EXPRESSION_CENSUS`
are re-derived on this tree, with tree drift separated from what the fix moved,
and `SCANNER_LITERAL_BLIND_SPOTS` records the residual class the sweep found
(a regex literal carrying a quote) rather than leaving it to be rediscovered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 23:25
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33696604625 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 30 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 41978ecSep 3, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14626-scanner-nested-template-mode branch September 3, 2026 00:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners - #14739

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode
Sep 3, 2026
Merged

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners#14739
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14626

A nested template literal desynchronised the vocabulary gate's shared textual
scanners, and every code: position after it in that file went unplaced. This
adds a template-literal mode to all four primitives through ONE shared skipper,
makes the two undefined answers distinguishable, and re-derives both pinned
censuses on this tree.

The primitive population, re-derived by grep (not from the card's list)

On the branch base 4d0d9445a, grep -n "c === \"'\" || c === '\"' || c === ' + "" + '"finds the quote-skip at exactly FOUR sites, and the seek idiomwhile (i < src.length && src[i] !== quote) i += ... ? 2 : 1` at the four lines
beside them — the four the triage named, at this base's line numbers:

functionif (c === quote)seek
scanTopLevel11401143
enclosingOpeners15481551
sliceBalanced15661569
splitTopLevel15841587

After the change, grep -c "!== quote) i +=" over the file returns 1, and
that one is legacySeekQuote inside --self-test — the positive control.

The helper's state machine, in five lines

skipStringLiteral(src, at) returns { end, closed }, where end is the index
of the CLOSING quote (the same post-condition the four seek loops had, so each
caller still advances past it with its own i += 1).

  1. A STACK of frames, not a single mode: quote and template are string
    modes, interp is a code mode.
  2. ' / " — walk to the matching quote; \ escapes the next byte.
  3. ` — TEMPLATE: \ escapes, a bare ` closes, ${ pushes an
    interp frame with brace depth 1.
  4. ${ … } — CODE: {/} move the brace depth (so `${ {a: 1}.a }`
    closes where it should), and a quote there pushes a NEW literal frame, which
    recurses through the same stack to any depth.
  5. Running off the end with a frame still open returns closed: false — the
    only self-evident signature of a scan that lost its place.

The two undefineds, told apart — and CERTIFIED

enclosingOpeners now answers EVERY requested index with a tagged value:
{kind:'bracket', ch, at} · {kind:'top-level'} · {kind:'inside-string'} ·
{kind:'desync'}. The other three primitives take an optional scanReport()
(desynchronised, unterminatedAt, underflow, unbalanced), because their
return values have no room for a per-position tag.

inside-string is asserted only for a walk that ends health-certified: no
unterminated literal, no bracket underflow, an empty stack at EOF. Comments are
masked and brackets inside real literals are skipped, so a correct walk of
well-formed source ends empty; not ending empty is proof it did not. In a walk
that is not certified, every unvisited index is tagged desync — "cannot place
this, and cannot certify why" — rather than being called inside-string, which
would be the same over-claim one layer up.

--self-test: a fixture AND a positive control per primitive

The pre-fix legacySeekQuote is kept in test scope, with all four primitives
re-implemented around it differing in exactly the skip. Fixture:
`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\).join(', ')} …` ``

primitivefixture assertscontrol (pre-fix algorithm) asserts
scanTopLevel2 top-level ; past the templatelegacy sees 0
enclosingOpenersthe code: after it is {-enclosedlegacy answers undefined
sliceBalancedthe whole argument list comes backlegacy truncates at the inner backtick
splitTopLevel2 partslegacy answers 1

Plus: inside-string on a code: written inside a string; desync on a
position behind an unterminated template; the skipper's two interpolation cases
(braces and quotes inside ${ … }); and the scanReport on all three.

Mutation on the committed tree. Deleting the ${-tracking branch from
skipStringLiteral (the skipper falls back to seek-to-matching-backtick)
reddens exactly the four primitive cases:

check-dispatcher-error-vocabulary --self-test FAILED:
- scanTopLevel saw 0 top-level `;` past a nested template, not 2 …
- enclosingOpeners answered {"kind":"desync"} for a `code:` after a nested template …
- sliceBalanced returned "`a${xs.map((k) => `\`${k}\`` " for an argument list …
- splitTopLevel cut a nested-template argument list into 1 parts, not 2 …

Mutation confirmed on disk before the run (grep -c on the deleted text: 1 → 0;
on the injected marker: 0 → 1; blob hash 9d4b84921dce95d3). Restored by
git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap and proven back:
blob hash 9d4b8492 again, git diff HEAD --name-only empty.

Censuses re-derived — tree drift separated from what the fix moved

Both readings come from the same sweep, run with the PRE-fix primitives and the
POST-fix ones over today's packages/** non-test source.

INLINE_LITERAL_EXPRESSION_CENSUS

fieldpinned (#13790)pre-fix, todayafterwhat moved it
filesScanned218621872187drift
objectLiteralCodeTokens902906906drift
anchorHits346349349drift
notObjectLiteral747470fix
parameterList414141
insideAString333318fix (split)
behindScannerDesync11fix (new class)
ownedBySibling.objlitconst404041fix
candidatesReachingReduction216219222drift + fix
declinedByReduction214217218drift + fix
declinedTypeAnnotation150155155drift
declinedRuntimeValue646263drift + fix
livePositions224fix
distinctValues559fix
newVerdictRows222
unregisteredWireCodesHiding000
positionsBehindScannerDesync2211re-attributed (below)
positionsRecoveredByTemplateMode2new

Newly PLACED positions, classified

Over the whole 906-token code: population, 19 positions go from unplaced
to placed. Two of them reach objlitexpr — exactly the two the card predicted:

positionclassification
domains/automation.ts:1384code: targetName === undefined ? 'required' : 'invalid_type'ADR-0112 D6 field-addressed validation catalog. Same file, same shape, same two values as the :1125 instance the gate already saw ⇒ derives the site keys that already exist ⇒ 0 new verdict rows.
domains/automation.ts:1399code: targetLabel === undefined ? 'required' : 'invalid_type'Same class, same verdict, 0 new verdict rows.

The other 17 are objlit/objlitconst tokens (those shapes are not guarded by
enclosingOpeners, so they were never blind there) or type annotations. Two
enter objlithelper's candidate population, which is why localTwinCensus
moves by 2.

No new value surfaced anywhere.deriveSites diffed key-by-key across the
change: sites 59 → 59, unresolved 5 → 5, added [], removed [], per-shape
counts identical. So the ⛔ escalation clause (an unregistered value at a newly
placed position ⇒ report blocked) was not reached, and
packages/runtime/src/dispatcher-error-vocabulary.ts is untouched.

OBJECT_LITERAL_CODE_HELPER_BLINDNESS

measured.* and declarationFormClosure.* are historical deltas of #13233 /
#13226, not censuses of today's tree; re-checked rather than assumed —
objlithelper derives the same 29 sites and the same 5 unresolved before and
after — and left as they were, with that check written into the block.

localTwinCensus IS a census of this tree, and it had already drifted:

fieldpinned (#13478)pre-fix, todayafter
candidatePositions125134136
alreadyCoveredByObjlitconst364041
newlyReached899495
newlyReachedDistinctFileIdent697273
newlyReachedDistinctFiles454747
reduceToEmptyByClass.typeKeywordPosition495353
reduceToEmptyByClass.namedTypePosition161616
reduceToEmptyByClass.runtimeValueLocal151617
reduceToEmptyByClass.bindingWithoutDeclarator999
reduce000
newVerdictRows / unregisteredWireCodesHiding0 / 00 / 00 / 0

The headline zero survives at both ends — the reducible subclass is still
empty — so no verdict row is owed. The instrument was the #13478 replica: the
real objlithelper branch with the resolveConstant fallback switched on,
driven through the real deriveSites; its zero is backed by the positive
control already in --self-test (resolveConstant still reduces
const c = cond ? A : B to both branches).

SCANNER_LITERAL_BLIND_SPOTS — the class this card did NOT close

The sweep found a second desync class and it is declared with numbers rather
than left to be rediscovered: a regex literal whose character class carries a
quote or a backtick
. The live one is
packages/rest/src/error-response.ts's RELATION_DOES_NOT_EXIST, whose class
contains ", ' and a backtick; a textual walk opens a string the source does
not have.

files swept2187
whole-file walks health-certified, pre-fix → post-fix2108 → 2119 (+11)
files still desynchronised68
code: positions unplaced pre-fix86
↳ newly placed by the template mode19
↳ certified genuinely inside a string21
↳ still behind a desync46
unregistered wire codes surfaced0

⛔ Deliberately not fixed here: telling /re/ from division needs the preceding
token's grammatical class, which is lexer state this scan does not carry — a
heuristic would trade a blind spot that is now COUNTED for one that is not.
Filed as its own card. --self-test pins the declaration in the direction it
can fail: a regex-literal fixture must still come back desync, with the same
source minus the regex as the positive control, so the block cannot quietly
outlive the blind spot it declares.

Scope

  • git diff --stat: 1 file changed, scripts/check-dispatcher-error-vocabulary.mjs.
  • scripts/check-error-code-casing.mjsuntouched. Confirmed by the diff
    and by pnpm check:error-code-casing in the gate family below.
  • packages/runtime/src/dispatcher-error-vocabulary.tsuntouched. No
    ledger row added or moved; no site or unresolved entry changed.
  • ⛔ No shape grammar changed: objlit, objlitconst, objlittemplate,
    objlithelper and objlitexpr regexes are byte-identical.

Verification

The union below ran at HEAD 160c99d5 (git rev-parse --short HEAD), the
final commit on this branch — a merge of origin/main after the fix commit
0dc603cd2. The whole union was run again on that merged head rather than
carried over from the pre-merge run. Each exit code was captured by redirect
BEFORE any pipe.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-dispatcher-error-vocabulary.mjs derives 18 commands from this
tree (objectstack-ai/objectstack at 160c99d51, --repo asserted and held —
the derivation is re-run from the merged head, never a git diff list fed in by
hand). All 18 run, all green except one NOT MEASURED:

node scripts/check-ci-filter-parity.mjs exit=0
node scripts/check-cross-package-test-inputs.mjs exit=0
node scripts/check-self-test-wired.mjs exit=0
node scripts/check-shard-attestation.mjs exit=0
node scripts/check-test-completeness.mjs exit=3 NOT MEASURED
node scripts/check-whole-set-label-write.mjs exit=0
node scripts/pm/bare-root-worklist.mjs --self-test exit=0
pnpm check:agent-test-spelling exit=0
pnpm check:bash32-floor exit=0
pnpm check:cli-command-ids exit=0
pnpm check:cross-package-test-inputs exit=0
pnpm check:dispatcher-error-vocabulary exit=0
pnpm check:entry-guard exit=0
pnpm check:parse-guard exit=0
pnpm check:pm-dispatch-gates exit=0
pnpm check:pnpm-filter-targets exit=0
pnpm check:ratchet-remedy-authority exit=0
pnpm check:watch-hint-literal exit=0

node scripts/pm/dispatch-gates.mjs --ran RECORD_FILE on the --commands output
reconciles the run: "Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED,
0 UNRUN"
, exit 0.

check-test-completeness exit 3 is its own declared NOT-MEASURED branch, in
its own words: "Arrived here from the gate family scripts/pm/dispatch-gates.mjs
derives? … There is no local log to hand it, so the local reading for this gate
is NOT MEASURED. ⛔ It is not a red."
CI tees a real turbo run test log into
it, so CI measures it.

Named by the dispatch and outside the derived family, run anyway:

pnpm check:error-code-casing exit=0 (the ⛔ untouched gate — still green)
pnpm check:nul-bytes exit=0 "OK (scanned 8042 text file(s) … no raw ASCII control bytes)"

The gate's own verdict lines:

check-dispatcher-error-vocabulary --self-test: 10 shapes + 282 assertions OK
(vocabulary + #9098 door typing) [was 277 before this card]
check-dispatcher-error-vocabulary: OK — 59 unregistered code-stamping site(s),
all classified; 1 awaiting a ledger entry (#8846).

Lint, narrowed and the narrowing measured (not "not run"):

  • Population comes from eslint's own config, not from a guess about which files
    count; the diff is 1 file, so npx eslint --no-inline-config --format json scripts/check-dispatcher-error-vocabulary.mjs is the whole of it.
  • File count read from that JSON: 1 file linted, 0 errors, 0 warnings,
    exit 0.
  • Invariance: eslint.config.* states it runs with no parserOptions.project
    and no typed @typescript-eslint rules, so type-aware linting is off and a
    one-file diff cannot move the verdict on any untouched file. Repo-wide
    pnpm lint is CI's run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

…shared textual scanners (#14626)
`scanTopLevel`, `enclosingOpeners`, `sliceBalanced` and `splitTopLevel` each
carried the same seek-to-matching-quote skip, which a NESTED template literal
walks straight out of: the outer template closed at the first inner backtick
and every `code:` position after it in that file went unplaced.
All four now call ONE shared `skipStringLiteral`, a small state machine that
tracks `${ … }` nesting and recurses into literals opened inside an
interpolation. `enclosingOpeners` answers every requested index with a TAGGED
result, so "genuinely inside a string" and "the scanner lost its place" stop
arriving as the same `undefined`; the other three take an optional
`scanReport()`. The tag is certified — `inside-string` is asserted only for a
walk that ends with no unterminated literal, no bracket underflow and an empty
stack.
`--self-test` gains a nested-template fixture per primitive, each PAIRED with a
positive control run through the pre-fix `legacySeekQuote` kept in test scope.
`OBJECT_LITERAL_CODE_HELPER_BLINDNESS` and `INLINE_LITERAL_EXPRESSION_CENSUS`
are re-derived on this tree, with tree drift separated from what the fix moved,
and `SCANNER_LITERAL_BLIND_SPOTS` records the residual class the sweep found
(a regex literal carrying a quote) rather than leaving it to be rediscovered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 23:25
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33696604625 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 30 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 41978ecSep 3, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14626-scanner-nested-template-mode branch September 3, 2026 00:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners - #14739

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode
Sep 3, 2026
Merged

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners#14739
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14626

A nested template literal desynchronised the vocabulary gate's shared textual
scanners, and every code: position after it in that file went unplaced. This
adds a template-literal mode to all four primitives through ONE shared skipper,
makes the two undefined answers distinguishable, and re-derives both pinned
censuses on this tree.

The primitive population, re-derived by grep (not from the card's list)

On the branch base 4d0d9445a, grep -n "c === \"'\" || c === '\"' || c === ' + "" + '"finds the quote-skip at exactly FOUR sites, and the seek idiomwhile (i < src.length && src[i] !== quote) i += ... ? 2 : 1` at the four lines
beside them — the four the triage named, at this base's line numbers:

functionif (c === quote)seek
scanTopLevel11401143
enclosingOpeners15481551
sliceBalanced15661569
splitTopLevel15841587

After the change, grep -c "!== quote) i +=" over the file returns 1, and
that one is legacySeekQuote inside --self-test — the positive control.

The helper's state machine, in five lines

skipStringLiteral(src, at) returns { end, closed }, where end is the index
of the CLOSING quote (the same post-condition the four seek loops had, so each
caller still advances past it with its own i += 1).

  1. A STACK of frames, not a single mode: quote and template are string
    modes, interp is a code mode.
  2. ' / " — walk to the matching quote; \ escapes the next byte.
  3. ` — TEMPLATE: \ escapes, a bare ` closes, ${ pushes an
    interp frame with brace depth 1.
  4. ${ … } — CODE: {/} move the brace depth (so `${ {a: 1}.a }`
    closes where it should), and a quote there pushes a NEW literal frame, which
    recurses through the same stack to any depth.
  5. Running off the end with a frame still open returns closed: false — the
    only self-evident signature of a scan that lost its place.

The two undefineds, told apart — and CERTIFIED

enclosingOpeners now answers EVERY requested index with a tagged value:
{kind:'bracket', ch, at} · {kind:'top-level'} · {kind:'inside-string'} ·
{kind:'desync'}. The other three primitives take an optional scanReport()
(desynchronised, unterminatedAt, underflow, unbalanced), because their
return values have no room for a per-position tag.

inside-string is asserted only for a walk that ends health-certified: no
unterminated literal, no bracket underflow, an empty stack at EOF. Comments are
masked and brackets inside real literals are skipped, so a correct walk of
well-formed source ends empty; not ending empty is proof it did not. In a walk
that is not certified, every unvisited index is tagged desync — "cannot place
this, and cannot certify why" — rather than being called inside-string, which
would be the same over-claim one layer up.

--self-test: a fixture AND a positive control per primitive

The pre-fix legacySeekQuote is kept in test scope, with all four primitives
re-implemented around it differing in exactly the skip. Fixture:
`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\).join(', ')} …` ``

primitivefixture assertscontrol (pre-fix algorithm) asserts
scanTopLevel2 top-level ; past the templatelegacy sees 0
enclosingOpenersthe code: after it is {-enclosedlegacy answers undefined
sliceBalancedthe whole argument list comes backlegacy truncates at the inner backtick
splitTopLevel2 partslegacy answers 1

Plus: inside-string on a code: written inside a string; desync on a
position behind an unterminated template; the skipper's two interpolation cases
(braces and quotes inside ${ … }); and the scanReport on all three.

Mutation on the committed tree. Deleting the ${-tracking branch from
skipStringLiteral (the skipper falls back to seek-to-matching-backtick)
reddens exactly the four primitive cases:

check-dispatcher-error-vocabulary --self-test FAILED:
- scanTopLevel saw 0 top-level `;` past a nested template, not 2 …
- enclosingOpeners answered {"kind":"desync"} for a `code:` after a nested template …
- sliceBalanced returned "`a${xs.map((k) => `\`${k}\`` " for an argument list …
- splitTopLevel cut a nested-template argument list into 1 parts, not 2 …

Mutation confirmed on disk before the run (grep -c on the deleted text: 1 → 0;
on the injected marker: 0 → 1; blob hash 9d4b84921dce95d3). Restored by
git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap and proven back:
blob hash 9d4b8492 again, git diff HEAD --name-only empty.

Censuses re-derived — tree drift separated from what the fix moved

Both readings come from the same sweep, run with the PRE-fix primitives and the
POST-fix ones over today's packages/** non-test source.

INLINE_LITERAL_EXPRESSION_CENSUS

fieldpinned (#13790)pre-fix, todayafterwhat moved it
filesScanned218621872187drift
objectLiteralCodeTokens902906906drift
anchorHits346349349drift
notObjectLiteral747470fix
parameterList414141
insideAString333318fix (split)
behindScannerDesync11fix (new class)
ownedBySibling.objlitconst404041fix
candidatesReachingReduction216219222drift + fix
declinedByReduction214217218drift + fix
declinedTypeAnnotation150155155drift
declinedRuntimeValue646263drift + fix
livePositions224fix
distinctValues559fix
newVerdictRows222
unregisteredWireCodesHiding000
positionsBehindScannerDesync2211re-attributed (below)
positionsRecoveredByTemplateMode2new

Newly PLACED positions, classified

Over the whole 906-token code: population, 19 positions go from unplaced
to placed. Two of them reach objlitexpr — exactly the two the card predicted:

positionclassification
domains/automation.ts:1384code: targetName === undefined ? 'required' : 'invalid_type'ADR-0112 D6 field-addressed validation catalog. Same file, same shape, same two values as the :1125 instance the gate already saw ⇒ derives the site keys that already exist ⇒ 0 new verdict rows.
domains/automation.ts:1399code: targetLabel === undefined ? 'required' : 'invalid_type'Same class, same verdict, 0 new verdict rows.

The other 17 are objlit/objlitconst tokens (those shapes are not guarded by
enclosingOpeners, so they were never blind there) or type annotations. Two
enter objlithelper's candidate population, which is why localTwinCensus
moves by 2.

No new value surfaced anywhere.deriveSites diffed key-by-key across the
change: sites 59 → 59, unresolved 5 → 5, added [], removed [], per-shape
counts identical. So the ⛔ escalation clause (an unregistered value at a newly
placed position ⇒ report blocked) was not reached, and
packages/runtime/src/dispatcher-error-vocabulary.ts is untouched.

OBJECT_LITERAL_CODE_HELPER_BLINDNESS

measured.* and declarationFormClosure.* are historical deltas of #13233 /
#13226, not censuses of today's tree; re-checked rather than assumed —
objlithelper derives the same 29 sites and the same 5 unresolved before and
after — and left as they were, with that check written into the block.

localTwinCensus IS a census of this tree, and it had already drifted:

fieldpinned (#13478)pre-fix, todayafter
candidatePositions125134136
alreadyCoveredByObjlitconst364041
newlyReached899495
newlyReachedDistinctFileIdent697273
newlyReachedDistinctFiles454747
reduceToEmptyByClass.typeKeywordPosition495353
reduceToEmptyByClass.namedTypePosition161616
reduceToEmptyByClass.runtimeValueLocal151617
reduceToEmptyByClass.bindingWithoutDeclarator999
reduce000
newVerdictRows / unregisteredWireCodesHiding0 / 00 / 00 / 0

The headline zero survives at both ends — the reducible subclass is still
empty — so no verdict row is owed. The instrument was the #13478 replica: the
real objlithelper branch with the resolveConstant fallback switched on,
driven through the real deriveSites; its zero is backed by the positive
control already in --self-test (resolveConstant still reduces
const c = cond ? A : B to both branches).

SCANNER_LITERAL_BLIND_SPOTS — the class this card did NOT close

The sweep found a second desync class and it is declared with numbers rather
than left to be rediscovered: a regex literal whose character class carries a
quote or a backtick
. The live one is
packages/rest/src/error-response.ts's RELATION_DOES_NOT_EXIST, whose class
contains ", ' and a backtick; a textual walk opens a string the source does
not have.

files swept2187
whole-file walks health-certified, pre-fix → post-fix2108 → 2119 (+11)
files still desynchronised68
code: positions unplaced pre-fix86
↳ newly placed by the template mode19
↳ certified genuinely inside a string21
↳ still behind a desync46
unregistered wire codes surfaced0

⛔ Deliberately not fixed here: telling /re/ from division needs the preceding
token's grammatical class, which is lexer state this scan does not carry — a
heuristic would trade a blind spot that is now COUNTED for one that is not.
Filed as its own card. --self-test pins the declaration in the direction it
can fail: a regex-literal fixture must still come back desync, with the same
source minus the regex as the positive control, so the block cannot quietly
outlive the blind spot it declares.

Scope

  • git diff --stat: 1 file changed, scripts/check-dispatcher-error-vocabulary.mjs.
  • scripts/check-error-code-casing.mjsuntouched. Confirmed by the diff
    and by pnpm check:error-code-casing in the gate family below.
  • packages/runtime/src/dispatcher-error-vocabulary.tsuntouched. No
    ledger row added or moved; no site or unresolved entry changed.
  • ⛔ No shape grammar changed: objlit, objlitconst, objlittemplate,
    objlithelper and objlitexpr regexes are byte-identical.

Verification

The union below ran at HEAD 160c99d5 (git rev-parse --short HEAD), the
final commit on this branch — a merge of origin/main after the fix commit
0dc603cd2. The whole union was run again on that merged head rather than
carried over from the pre-merge run. Each exit code was captured by redirect
BEFORE any pipe.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-dispatcher-error-vocabulary.mjs derives 18 commands from this
tree (objectstack-ai/objectstack at 160c99d51, --repo asserted and held —
the derivation is re-run from the merged head, never a git diff list fed in by
hand). All 18 run, all green except one NOT MEASURED:

node scripts/check-ci-filter-parity.mjs exit=0
node scripts/check-cross-package-test-inputs.mjs exit=0
node scripts/check-self-test-wired.mjs exit=0
node scripts/check-shard-attestation.mjs exit=0
node scripts/check-test-completeness.mjs exit=3 NOT MEASURED
node scripts/check-whole-set-label-write.mjs exit=0
node scripts/pm/bare-root-worklist.mjs --self-test exit=0
pnpm check:agent-test-spelling exit=0
pnpm check:bash32-floor exit=0
pnpm check:cli-command-ids exit=0
pnpm check:cross-package-test-inputs exit=0
pnpm check:dispatcher-error-vocabulary exit=0
pnpm check:entry-guard exit=0
pnpm check:parse-guard exit=0
pnpm check:pm-dispatch-gates exit=0
pnpm check:pnpm-filter-targets exit=0
pnpm check:ratchet-remedy-authority exit=0
pnpm check:watch-hint-literal exit=0

node scripts/pm/dispatch-gates.mjs --ran RECORD_FILE on the --commands output
reconciles the run: "Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED,
0 UNRUN"
, exit 0.

check-test-completeness exit 3 is its own declared NOT-MEASURED branch, in
its own words: "Arrived here from the gate family scripts/pm/dispatch-gates.mjs
derives? … There is no local log to hand it, so the local reading for this gate
is NOT MEASURED. ⛔ It is not a red."
CI tees a real turbo run test log into
it, so CI measures it.

Named by the dispatch and outside the derived family, run anyway:

pnpm check:error-code-casing exit=0 (the ⛔ untouched gate — still green)
pnpm check:nul-bytes exit=0 "OK (scanned 8042 text file(s) … no raw ASCII control bytes)"

The gate's own verdict lines:

check-dispatcher-error-vocabulary --self-test: 10 shapes + 282 assertions OK
(vocabulary + #9098 door typing) [was 277 before this card]
check-dispatcher-error-vocabulary: OK — 59 unregistered code-stamping site(s),
all classified; 1 awaiting a ledger entry (#8846).

Lint, narrowed and the narrowing measured (not "not run"):

  • Population comes from eslint's own config, not from a guess about which files
    count; the diff is 1 file, so npx eslint --no-inline-config --format json scripts/check-dispatcher-error-vocabulary.mjs is the whole of it.
  • File count read from that JSON: 1 file linted, 0 errors, 0 warnings,
    exit 0.
  • Invariance: eslint.config.* states it runs with no parserOptions.project
    and no typed @typescript-eslint rules, so type-aware linting is off and a
    one-file diff cannot move the verdict on any untouched file. Repo-wide
    pnpm lint is CI's run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

…shared textual scanners (#14626)
`scanTopLevel`, `enclosingOpeners`, `sliceBalanced` and `splitTopLevel` each
carried the same seek-to-matching-quote skip, which a NESTED template literal
walks straight out of: the outer template closed at the first inner backtick
and every `code:` position after it in that file went unplaced.
All four now call ONE shared `skipStringLiteral`, a small state machine that
tracks `${ … }` nesting and recurses into literals opened inside an
interpolation. `enclosingOpeners` answers every requested index with a TAGGED
result, so "genuinely inside a string" and "the scanner lost its place" stop
arriving as the same `undefined`; the other three take an optional
`scanReport()`. The tag is certified — `inside-string` is asserted only for a
walk that ends with no unterminated literal, no bracket underflow and an empty
stack.
`--self-test` gains a nested-template fixture per primitive, each PAIRED with a
positive control run through the pre-fix `legacySeekQuote` kept in test scope.
`OBJECT_LITERAL_CODE_HELPER_BLINDNESS` and `INLINE_LITERAL_EXPRESSION_CENSUS`
are re-derived on this tree, with tree drift separated from what the fix moved,
and `SCANNER_LITERAL_BLIND_SPOTS` records the residual class the sweep found
(a regex literal carrying a quote) rather than leaving it to be rediscovered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 23:25
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33696604625 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 30 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 41978ecSep 3, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14626-scanner-nested-template-mode branch September 3, 2026 00:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners - #14739

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode
Sep 3, 2026
Merged

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners#14739
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14626

A nested template literal desynchronised the vocabulary gate's shared textual
scanners, and every code: position after it in that file went unplaced. This
adds a template-literal mode to all four primitives through ONE shared skipper,
makes the two undefined answers distinguishable, and re-derives both pinned
censuses on this tree.

The primitive population, re-derived by grep (not from the card's list)

On the branch base 4d0d9445a, grep -n "c === \"'\" || c === '\"' || c === ' + "" + '"finds the quote-skip at exactly FOUR sites, and the seek idiomwhile (i < src.length && src[i] !== quote) i += ... ? 2 : 1` at the four lines
beside them — the four the triage named, at this base's line numbers:

functionif (c === quote)seek
scanTopLevel11401143
enclosingOpeners15481551
sliceBalanced15661569
splitTopLevel15841587

After the change, grep -c "!== quote) i +=" over the file returns 1, and
that one is legacySeekQuote inside --self-test — the positive control.

The helper's state machine, in five lines

skipStringLiteral(src, at) returns { end, closed }, where end is the index
of the CLOSING quote (the same post-condition the four seek loops had, so each
caller still advances past it with its own i += 1).

  1. A STACK of frames, not a single mode: quote and template are string
    modes, interp is a code mode.
  2. ' / " — walk to the matching quote; \ escapes the next byte.
  3. ` — TEMPLATE: \ escapes, a bare ` closes, ${ pushes an
    interp frame with brace depth 1.
  4. ${ … } — CODE: {/} move the brace depth (so `${ {a: 1}.a }`
    closes where it should), and a quote there pushes a NEW literal frame, which
    recurses through the same stack to any depth.
  5. Running off the end with a frame still open returns closed: false — the
    only self-evident signature of a scan that lost its place.

The two undefineds, told apart — and CERTIFIED

enclosingOpeners now answers EVERY requested index with a tagged value:
{kind:'bracket', ch, at} · {kind:'top-level'} · {kind:'inside-string'} ·
{kind:'desync'}. The other three primitives take an optional scanReport()
(desynchronised, unterminatedAt, underflow, unbalanced), because their
return values have no room for a per-position tag.

inside-string is asserted only for a walk that ends health-certified: no
unterminated literal, no bracket underflow, an empty stack at EOF. Comments are
masked and brackets inside real literals are skipped, so a correct walk of
well-formed source ends empty; not ending empty is proof it did not. In a walk
that is not certified, every unvisited index is tagged desync — "cannot place
this, and cannot certify why" — rather than being called inside-string, which
would be the same over-claim one layer up.

--self-test: a fixture AND a positive control per primitive

The pre-fix legacySeekQuote is kept in test scope, with all four primitives
re-implemented around it differing in exactly the skip. Fixture:
`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\).join(', ')} …` ``

primitivefixture assertscontrol (pre-fix algorithm) asserts
scanTopLevel2 top-level ; past the templatelegacy sees 0
enclosingOpenersthe code: after it is {-enclosedlegacy answers undefined
sliceBalancedthe whole argument list comes backlegacy truncates at the inner backtick
splitTopLevel2 partslegacy answers 1

Plus: inside-string on a code: written inside a string; desync on a
position behind an unterminated template; the skipper's two interpolation cases
(braces and quotes inside ${ … }); and the scanReport on all three.

Mutation on the committed tree. Deleting the ${-tracking branch from
skipStringLiteral (the skipper falls back to seek-to-matching-backtick)
reddens exactly the four primitive cases:

check-dispatcher-error-vocabulary --self-test FAILED:
- scanTopLevel saw 0 top-level `;` past a nested template, not 2 …
- enclosingOpeners answered {"kind":"desync"} for a `code:` after a nested template …
- sliceBalanced returned "`a${xs.map((k) => `\`${k}\`` " for an argument list …
- splitTopLevel cut a nested-template argument list into 1 parts, not 2 …

Mutation confirmed on disk before the run (grep -c on the deleted text: 1 → 0;
on the injected marker: 0 → 1; blob hash 9d4b84921dce95d3). Restored by
git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap and proven back:
blob hash 9d4b8492 again, git diff HEAD --name-only empty.

Censuses re-derived — tree drift separated from what the fix moved

Both readings come from the same sweep, run with the PRE-fix primitives and the
POST-fix ones over today's packages/** non-test source.

INLINE_LITERAL_EXPRESSION_CENSUS

fieldpinned (#13790)pre-fix, todayafterwhat moved it
filesScanned218621872187drift
objectLiteralCodeTokens902906906drift
anchorHits346349349drift
notObjectLiteral747470fix
parameterList414141
insideAString333318fix (split)
behindScannerDesync11fix (new class)
ownedBySibling.objlitconst404041fix
candidatesReachingReduction216219222drift + fix
declinedByReduction214217218drift + fix
declinedTypeAnnotation150155155drift
declinedRuntimeValue646263drift + fix
livePositions224fix
distinctValues559fix
newVerdictRows222
unregisteredWireCodesHiding000
positionsBehindScannerDesync2211re-attributed (below)
positionsRecoveredByTemplateMode2new

Newly PLACED positions, classified

Over the whole 906-token code: population, 19 positions go from unplaced
to placed. Two of them reach objlitexpr — exactly the two the card predicted:

positionclassification
domains/automation.ts:1384code: targetName === undefined ? 'required' : 'invalid_type'ADR-0112 D6 field-addressed validation catalog. Same file, same shape, same two values as the :1125 instance the gate already saw ⇒ derives the site keys that already exist ⇒ 0 new verdict rows.
domains/automation.ts:1399code: targetLabel === undefined ? 'required' : 'invalid_type'Same class, same verdict, 0 new verdict rows.

The other 17 are objlit/objlitconst tokens (those shapes are not guarded by
enclosingOpeners, so they were never blind there) or type annotations. Two
enter objlithelper's candidate population, which is why localTwinCensus
moves by 2.

No new value surfaced anywhere.deriveSites diffed key-by-key across the
change: sites 59 → 59, unresolved 5 → 5, added [], removed [], per-shape
counts identical. So the ⛔ escalation clause (an unregistered value at a newly
placed position ⇒ report blocked) was not reached, and
packages/runtime/src/dispatcher-error-vocabulary.ts is untouched.

OBJECT_LITERAL_CODE_HELPER_BLINDNESS

measured.* and declarationFormClosure.* are historical deltas of #13233 /
#13226, not censuses of today's tree; re-checked rather than assumed —
objlithelper derives the same 29 sites and the same 5 unresolved before and
after — and left as they were, with that check written into the block.

localTwinCensus IS a census of this tree, and it had already drifted:

fieldpinned (#13478)pre-fix, todayafter
candidatePositions125134136
alreadyCoveredByObjlitconst364041
newlyReached899495
newlyReachedDistinctFileIdent697273
newlyReachedDistinctFiles454747
reduceToEmptyByClass.typeKeywordPosition495353
reduceToEmptyByClass.namedTypePosition161616
reduceToEmptyByClass.runtimeValueLocal151617
reduceToEmptyByClass.bindingWithoutDeclarator999
reduce000
newVerdictRows / unregisteredWireCodesHiding0 / 00 / 00 / 0

The headline zero survives at both ends — the reducible subclass is still
empty — so no verdict row is owed. The instrument was the #13478 replica: the
real objlithelper branch with the resolveConstant fallback switched on,
driven through the real deriveSites; its zero is backed by the positive
control already in --self-test (resolveConstant still reduces
const c = cond ? A : B to both branches).

SCANNER_LITERAL_BLIND_SPOTS — the class this card did NOT close

The sweep found a second desync class and it is declared with numbers rather
than left to be rediscovered: a regex literal whose character class carries a
quote or a backtick
. The live one is
packages/rest/src/error-response.ts's RELATION_DOES_NOT_EXIST, whose class
contains ", ' and a backtick; a textual walk opens a string the source does
not have.

files swept2187
whole-file walks health-certified, pre-fix → post-fix2108 → 2119 (+11)
files still desynchronised68
code: positions unplaced pre-fix86
↳ newly placed by the template mode19
↳ certified genuinely inside a string21
↳ still behind a desync46
unregistered wire codes surfaced0

⛔ Deliberately not fixed here: telling /re/ from division needs the preceding
token's grammatical class, which is lexer state this scan does not carry — a
heuristic would trade a blind spot that is now COUNTED for one that is not.
Filed as its own card. --self-test pins the declaration in the direction it
can fail: a regex-literal fixture must still come back desync, with the same
source minus the regex as the positive control, so the block cannot quietly
outlive the blind spot it declares.

Scope

  • git diff --stat: 1 file changed, scripts/check-dispatcher-error-vocabulary.mjs.
  • scripts/check-error-code-casing.mjsuntouched. Confirmed by the diff
    and by pnpm check:error-code-casing in the gate family below.
  • packages/runtime/src/dispatcher-error-vocabulary.tsuntouched. No
    ledger row added or moved; no site or unresolved entry changed.
  • ⛔ No shape grammar changed: objlit, objlitconst, objlittemplate,
    objlithelper and objlitexpr regexes are byte-identical.

Verification

The union below ran at HEAD 160c99d5 (git rev-parse --short HEAD), the
final commit on this branch — a merge of origin/main after the fix commit
0dc603cd2. The whole union was run again on that merged head rather than
carried over from the pre-merge run. Each exit code was captured by redirect
BEFORE any pipe.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-dispatcher-error-vocabulary.mjs derives 18 commands from this
tree (objectstack-ai/objectstack at 160c99d51, --repo asserted and held —
the derivation is re-run from the merged head, never a git diff list fed in by
hand). All 18 run, all green except one NOT MEASURED:

node scripts/check-ci-filter-parity.mjs exit=0
node scripts/check-cross-package-test-inputs.mjs exit=0
node scripts/check-self-test-wired.mjs exit=0
node scripts/check-shard-attestation.mjs exit=0
node scripts/check-test-completeness.mjs exit=3 NOT MEASURED
node scripts/check-whole-set-label-write.mjs exit=0
node scripts/pm/bare-root-worklist.mjs --self-test exit=0
pnpm check:agent-test-spelling exit=0
pnpm check:bash32-floor exit=0
pnpm check:cli-command-ids exit=0
pnpm check:cross-package-test-inputs exit=0
pnpm check:dispatcher-error-vocabulary exit=0
pnpm check:entry-guard exit=0
pnpm check:parse-guard exit=0
pnpm check:pm-dispatch-gates exit=0
pnpm check:pnpm-filter-targets exit=0
pnpm check:ratchet-remedy-authority exit=0
pnpm check:watch-hint-literal exit=0

node scripts/pm/dispatch-gates.mjs --ran RECORD_FILE on the --commands output
reconciles the run: "Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED,
0 UNRUN"
, exit 0.

check-test-completeness exit 3 is its own declared NOT-MEASURED branch, in
its own words: "Arrived here from the gate family scripts/pm/dispatch-gates.mjs
derives? … There is no local log to hand it, so the local reading for this gate
is NOT MEASURED. ⛔ It is not a red."
CI tees a real turbo run test log into
it, so CI measures it.

Named by the dispatch and outside the derived family, run anyway:

pnpm check:error-code-casing exit=0 (the ⛔ untouched gate — still green)
pnpm check:nul-bytes exit=0 "OK (scanned 8042 text file(s) … no raw ASCII control bytes)"

The gate's own verdict lines:

check-dispatcher-error-vocabulary --self-test: 10 shapes + 282 assertions OK
(vocabulary + #9098 door typing) [was 277 before this card]
check-dispatcher-error-vocabulary: OK — 59 unregistered code-stamping site(s),
all classified; 1 awaiting a ledger entry (#8846).

Lint, narrowed and the narrowing measured (not "not run"):

  • Population comes from eslint's own config, not from a guess about which files
    count; the diff is 1 file, so npx eslint --no-inline-config --format json scripts/check-dispatcher-error-vocabulary.mjs is the whole of it.
  • File count read from that JSON: 1 file linted, 0 errors, 0 warnings,
    exit 0.
  • Invariance: eslint.config.* states it runs with no parserOptions.project
    and no typed @typescript-eslint rules, so type-aware linting is off and a
    one-file diff cannot move the verdict on any untouched file. Repo-wide
    pnpm lint is CI's run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

…shared textual scanners (#14626)
`scanTopLevel`, `enclosingOpeners`, `sliceBalanced` and `splitTopLevel` each
carried the same seek-to-matching-quote skip, which a NESTED template literal
walks straight out of: the outer template closed at the first inner backtick
and every `code:` position after it in that file went unplaced.
All four now call ONE shared `skipStringLiteral`, a small state machine that
tracks `${ … }` nesting and recurses into literals opened inside an
interpolation. `enclosingOpeners` answers every requested index with a TAGGED
result, so "genuinely inside a string" and "the scanner lost its place" stop
arriving as the same `undefined`; the other three take an optional
`scanReport()`. The tag is certified — `inside-string` is asserted only for a
walk that ends with no unterminated literal, no bracket underflow and an empty
stack.
`--self-test` gains a nested-template fixture per primitive, each PAIRED with a
positive control run through the pre-fix `legacySeekQuote` kept in test scope.
`OBJECT_LITERAL_CODE_HELPER_BLINDNESS` and `INLINE_LITERAL_EXPRESSION_CENSUS`
are re-derived on this tree, with tree drift separated from what the fix moved,
and `SCANNER_LITERAL_BLIND_SPOTS` records the residual class the sweep found
(a regex literal carrying a quote) rather than leaving it to be rediscovered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 23:25
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33696604625 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 30 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 41978ecSep 3, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14626-scanner-nested-template-mode branch September 3, 2026 00:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners - #14739

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode
Sep 3, 2026
Merged

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners#14739
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14626

A nested template literal desynchronised the vocabulary gate's shared textual
scanners, and every code: position after it in that file went unplaced. This
adds a template-literal mode to all four primitives through ONE shared skipper,
makes the two undefined answers distinguishable, and re-derives both pinned
censuses on this tree.

The primitive population, re-derived by grep (not from the card's list)

On the branch base 4d0d9445a, grep -n "c === \"'\" || c === '\"' || c === ' + "" + '"finds the quote-skip at exactly FOUR sites, and the seek idiomwhile (i < src.length && src[i] !== quote) i += ... ? 2 : 1` at the four lines
beside them — the four the triage named, at this base's line numbers:

functionif (c === quote)seek
scanTopLevel11401143
enclosingOpeners15481551
sliceBalanced15661569
splitTopLevel15841587

After the change, grep -c "!== quote) i +=" over the file returns 1, and
that one is legacySeekQuote inside --self-test — the positive control.

The helper's state machine, in five lines

skipStringLiteral(src, at) returns { end, closed }, where end is the index
of the CLOSING quote (the same post-condition the four seek loops had, so each
caller still advances past it with its own i += 1).

  1. A STACK of frames, not a single mode: quote and template are string
    modes, interp is a code mode.
  2. ' / " — walk to the matching quote; \ escapes the next byte.
  3. ` — TEMPLATE: \ escapes, a bare ` closes, ${ pushes an
    interp frame with brace depth 1.
  4. ${ … } — CODE: {/} move the brace depth (so `${ {a: 1}.a }`
    closes where it should), and a quote there pushes a NEW literal frame, which
    recurses through the same stack to any depth.
  5. Running off the end with a frame still open returns closed: false — the
    only self-evident signature of a scan that lost its place.

The two undefineds, told apart — and CERTIFIED

enclosingOpeners now answers EVERY requested index with a tagged value:
{kind:'bracket', ch, at} · {kind:'top-level'} · {kind:'inside-string'} ·
{kind:'desync'}. The other three primitives take an optional scanReport()
(desynchronised, unterminatedAt, underflow, unbalanced), because their
return values have no room for a per-position tag.

inside-string is asserted only for a walk that ends health-certified: no
unterminated literal, no bracket underflow, an empty stack at EOF. Comments are
masked and brackets inside real literals are skipped, so a correct walk of
well-formed source ends empty; not ending empty is proof it did not. In a walk
that is not certified, every unvisited index is tagged desync — "cannot place
this, and cannot certify why" — rather than being called inside-string, which
would be the same over-claim one layer up.

--self-test: a fixture AND a positive control per primitive

The pre-fix legacySeekQuote is kept in test scope, with all four primitives
re-implemented around it differing in exactly the skip. Fixture:
`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\).join(', ')} …` ``

primitivefixture assertscontrol (pre-fix algorithm) asserts
scanTopLevel2 top-level ; past the templatelegacy sees 0
enclosingOpenersthe code: after it is {-enclosedlegacy answers undefined
sliceBalancedthe whole argument list comes backlegacy truncates at the inner backtick
splitTopLevel2 partslegacy answers 1

Plus: inside-string on a code: written inside a string; desync on a
position behind an unterminated template; the skipper's two interpolation cases
(braces and quotes inside ${ … }); and the scanReport on all three.

Mutation on the committed tree. Deleting the ${-tracking branch from
skipStringLiteral (the skipper falls back to seek-to-matching-backtick)
reddens exactly the four primitive cases:

check-dispatcher-error-vocabulary --self-test FAILED:
- scanTopLevel saw 0 top-level `;` past a nested template, not 2 …
- enclosingOpeners answered {"kind":"desync"} for a `code:` after a nested template …
- sliceBalanced returned "`a${xs.map((k) => `\`${k}\`` " for an argument list …
- splitTopLevel cut a nested-template argument list into 1 parts, not 2 …

Mutation confirmed on disk before the run (grep -c on the deleted text: 1 → 0;
on the injected marker: 0 → 1; blob hash 9d4b84921dce95d3). Restored by
git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap and proven back:
blob hash 9d4b8492 again, git diff HEAD --name-only empty.

Censuses re-derived — tree drift separated from what the fix moved

Both readings come from the same sweep, run with the PRE-fix primitives and the
POST-fix ones over today's packages/** non-test source.

INLINE_LITERAL_EXPRESSION_CENSUS

fieldpinned (#13790)pre-fix, todayafterwhat moved it
filesScanned218621872187drift
objectLiteralCodeTokens902906906drift
anchorHits346349349drift
notObjectLiteral747470fix
parameterList414141
insideAString333318fix (split)
behindScannerDesync11fix (new class)
ownedBySibling.objlitconst404041fix
candidatesReachingReduction216219222drift + fix
declinedByReduction214217218drift + fix
declinedTypeAnnotation150155155drift
declinedRuntimeValue646263drift + fix
livePositions224fix
distinctValues559fix
newVerdictRows222
unregisteredWireCodesHiding000
positionsBehindScannerDesync2211re-attributed (below)
positionsRecoveredByTemplateMode2new

Newly PLACED positions, classified

Over the whole 906-token code: population, 19 positions go from unplaced
to placed. Two of them reach objlitexpr — exactly the two the card predicted:

positionclassification
domains/automation.ts:1384code: targetName === undefined ? 'required' : 'invalid_type'ADR-0112 D6 field-addressed validation catalog. Same file, same shape, same two values as the :1125 instance the gate already saw ⇒ derives the site keys that already exist ⇒ 0 new verdict rows.
domains/automation.ts:1399code: targetLabel === undefined ? 'required' : 'invalid_type'Same class, same verdict, 0 new verdict rows.

The other 17 are objlit/objlitconst tokens (those shapes are not guarded by
enclosingOpeners, so they were never blind there) or type annotations. Two
enter objlithelper's candidate population, which is why localTwinCensus
moves by 2.

No new value surfaced anywhere.deriveSites diffed key-by-key across the
change: sites 59 → 59, unresolved 5 → 5, added [], removed [], per-shape
counts identical. So the ⛔ escalation clause (an unregistered value at a newly
placed position ⇒ report blocked) was not reached, and
packages/runtime/src/dispatcher-error-vocabulary.ts is untouched.

OBJECT_LITERAL_CODE_HELPER_BLINDNESS

measured.* and declarationFormClosure.* are historical deltas of #13233 /
#13226, not censuses of today's tree; re-checked rather than assumed —
objlithelper derives the same 29 sites and the same 5 unresolved before and
after — and left as they were, with that check written into the block.

localTwinCensus IS a census of this tree, and it had already drifted:

fieldpinned (#13478)pre-fix, todayafter
candidatePositions125134136
alreadyCoveredByObjlitconst364041
newlyReached899495
newlyReachedDistinctFileIdent697273
newlyReachedDistinctFiles454747
reduceToEmptyByClass.typeKeywordPosition495353
reduceToEmptyByClass.namedTypePosition161616
reduceToEmptyByClass.runtimeValueLocal151617
reduceToEmptyByClass.bindingWithoutDeclarator999
reduce000
newVerdictRows / unregisteredWireCodesHiding0 / 00 / 00 / 0

The headline zero survives at both ends — the reducible subclass is still
empty — so no verdict row is owed. The instrument was the #13478 replica: the
real objlithelper branch with the resolveConstant fallback switched on,
driven through the real deriveSites; its zero is backed by the positive
control already in --self-test (resolveConstant still reduces
const c = cond ? A : B to both branches).

SCANNER_LITERAL_BLIND_SPOTS — the class this card did NOT close

The sweep found a second desync class and it is declared with numbers rather
than left to be rediscovered: a regex literal whose character class carries a
quote or a backtick
. The live one is
packages/rest/src/error-response.ts's RELATION_DOES_NOT_EXIST, whose class
contains ", ' and a backtick; a textual walk opens a string the source does
not have.

files swept2187
whole-file walks health-certified, pre-fix → post-fix2108 → 2119 (+11)
files still desynchronised68
code: positions unplaced pre-fix86
↳ newly placed by the template mode19
↳ certified genuinely inside a string21
↳ still behind a desync46
unregistered wire codes surfaced0

⛔ Deliberately not fixed here: telling /re/ from division needs the preceding
token's grammatical class, which is lexer state this scan does not carry — a
heuristic would trade a blind spot that is now COUNTED for one that is not.
Filed as its own card. --self-test pins the declaration in the direction it
can fail: a regex-literal fixture must still come back desync, with the same
source minus the regex as the positive control, so the block cannot quietly
outlive the blind spot it declares.

Scope

  • git diff --stat: 1 file changed, scripts/check-dispatcher-error-vocabulary.mjs.
  • scripts/check-error-code-casing.mjsuntouched. Confirmed by the diff
    and by pnpm check:error-code-casing in the gate family below.
  • packages/runtime/src/dispatcher-error-vocabulary.tsuntouched. No
    ledger row added or moved; no site or unresolved entry changed.
  • ⛔ No shape grammar changed: objlit, objlitconst, objlittemplate,
    objlithelper and objlitexpr regexes are byte-identical.

Verification

The union below ran at HEAD 160c99d5 (git rev-parse --short HEAD), the
final commit on this branch — a merge of origin/main after the fix commit
0dc603cd2. The whole union was run again on that merged head rather than
carried over from the pre-merge run. Each exit code was captured by redirect
BEFORE any pipe.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-dispatcher-error-vocabulary.mjs derives 18 commands from this
tree (objectstack-ai/objectstack at 160c99d51, --repo asserted and held —
the derivation is re-run from the merged head, never a git diff list fed in by
hand). All 18 run, all green except one NOT MEASURED:

node scripts/check-ci-filter-parity.mjs exit=0
node scripts/check-cross-package-test-inputs.mjs exit=0
node scripts/check-self-test-wired.mjs exit=0
node scripts/check-shard-attestation.mjs exit=0
node scripts/check-test-completeness.mjs exit=3 NOT MEASURED
node scripts/check-whole-set-label-write.mjs exit=0
node scripts/pm/bare-root-worklist.mjs --self-test exit=0
pnpm check:agent-test-spelling exit=0
pnpm check:bash32-floor exit=0
pnpm check:cli-command-ids exit=0
pnpm check:cross-package-test-inputs exit=0
pnpm check:dispatcher-error-vocabulary exit=0
pnpm check:entry-guard exit=0
pnpm check:parse-guard exit=0
pnpm check:pm-dispatch-gates exit=0
pnpm check:pnpm-filter-targets exit=0
pnpm check:ratchet-remedy-authority exit=0
pnpm check:watch-hint-literal exit=0

node scripts/pm/dispatch-gates.mjs --ran RECORD_FILE on the --commands output
reconciles the run: "Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED,
0 UNRUN"
, exit 0.

check-test-completeness exit 3 is its own declared NOT-MEASURED branch, in
its own words: "Arrived here from the gate family scripts/pm/dispatch-gates.mjs
derives? … There is no local log to hand it, so the local reading for this gate
is NOT MEASURED. ⛔ It is not a red."
CI tees a real turbo run test log into
it, so CI measures it.

Named by the dispatch and outside the derived family, run anyway:

pnpm check:error-code-casing exit=0 (the ⛔ untouched gate — still green)
pnpm check:nul-bytes exit=0 "OK (scanned 8042 text file(s) … no raw ASCII control bytes)"

The gate's own verdict lines:

check-dispatcher-error-vocabulary --self-test: 10 shapes + 282 assertions OK
(vocabulary + #9098 door typing) [was 277 before this card]
check-dispatcher-error-vocabulary: OK — 59 unregistered code-stamping site(s),
all classified; 1 awaiting a ledger entry (#8846).

Lint, narrowed and the narrowing measured (not "not run"):

  • Population comes from eslint's own config, not from a guess about which files
    count; the diff is 1 file, so npx eslint --no-inline-config --format json scripts/check-dispatcher-error-vocabulary.mjs is the whole of it.
  • File count read from that JSON: 1 file linted, 0 errors, 0 warnings,
    exit 0.
  • Invariance: eslint.config.* states it runs with no parserOptions.project
    and no typed @typescript-eslint rules, so type-aware linting is off and a
    one-file diff cannot move the verdict on any untouched file. Repo-wide
    pnpm lint is CI's run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

…shared textual scanners (#14626)
`scanTopLevel`, `enclosingOpeners`, `sliceBalanced` and `splitTopLevel` each
carried the same seek-to-matching-quote skip, which a NESTED template literal
walks straight out of: the outer template closed at the first inner backtick
and every `code:` position after it in that file went unplaced.
All four now call ONE shared `skipStringLiteral`, a small state machine that
tracks `${ … }` nesting and recurses into literals opened inside an
interpolation. `enclosingOpeners` answers every requested index with a TAGGED
result, so "genuinely inside a string" and "the scanner lost its place" stop
arriving as the same `undefined`; the other three take an optional
`scanReport()`. The tag is certified — `inside-string` is asserted only for a
walk that ends with no unterminated literal, no bracket underflow and an empty
stack.
`--self-test` gains a nested-template fixture per primitive, each PAIRED with a
positive control run through the pre-fix `legacySeekQuote` kept in test scope.
`OBJECT_LITERAL_CODE_HELPER_BLINDNESS` and `INLINE_LITERAL_EXPRESSION_CENSUS`
are re-derived on this tree, with tree drift separated from what the fix moved,
and `SCANNER_LITERAL_BLIND_SPOTS` records the residual class the sweep found
(a regex literal carrying a quote) rather than leaving it to be rediscovered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 23:25
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33696604625 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 30 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 41978ecSep 3, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14626-scanner-nested-template-mode branch September 3, 2026 00:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@baozhoutao@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners - #14739

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode
Sep 3, 2026
Merged

fix(tooling): a template-literal mode for the vocabulary gate's four shared textual scanners#14739
baozhoutao merged 2 commits into
mainfrom
claude/issue-14626-scanner-nested-template-mode

Conversation

@claude

@claudeclaudeBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Fixes#14626

A nested template literal desynchronised the vocabulary gate's shared textual
scanners, and every code: position after it in that file went unplaced. This
adds a template-literal mode to all four primitives through ONE shared skipper,
makes the two undefined answers distinguishable, and re-derives both pinned
censuses on this tree.

The primitive population, re-derived by grep (not from the card's list)

On the branch base 4d0d9445a, grep -n "c === \"'\" || c === '\"' || c === ' + "" + '"finds the quote-skip at exactly FOUR sites, and the seek idiomwhile (i < src.length && src[i] !== quote) i += ... ? 2 : 1` at the four lines
beside them — the four the triage named, at this base's line numbers:

functionif (c === quote)seek
scanTopLevel11401143
enclosingOpeners15481551
sliceBalanced15661569
splitTopLevel15841587

After the change, grep -c "!== quote) i +=" over the file returns 1, and
that one is legacySeekQuote inside --self-test — the positive control.

The helper's state machine, in five lines

skipStringLiteral(src, at) returns { end, closed }, where end is the index
of the CLOSING quote (the same post-condition the four seek loops had, so each
caller still advances past it with its own i += 1).

  1. A STACK of frames, not a single mode: quote and template are string
    modes, interp is a code mode.
  2. ' / " — walk to the matching quote; \ escapes the next byte.
  3. ` — TEMPLATE: \ escapes, a bare ` closes, ${ pushes an
    interp frame with brace depth 1.
  4. ${ … } — CODE: {/} move the brace depth (so `${ {a: 1}.a }`
    closes where it should), and a quote there pushes a NEW literal frame, which
    recurses through the same stack to any depth.
  5. Running off the end with a frame still open returns closed: false — the
    only self-evident signature of a scan that lost its place.

The two undefineds, told apart — and CERTIFIED

enclosingOpeners now answers EVERY requested index with a tagged value:
{kind:'bracket', ch, at} · {kind:'top-level'} · {kind:'inside-string'} ·
{kind:'desync'}. The other three primitives take an optional scanReport()
(desynchronised, unterminatedAt, underflow, unbalanced), because their
return values have no room for a per-position tag.

inside-string is asserted only for a walk that ends health-certified: no
unterminated literal, no bracket underflow, an empty stack at EOF. Comments are
masked and brackets inside real literals are skipped, so a correct walk of
well-formed source ends empty; not ending empty is proof it did not. In a walk
that is not certified, every unvisited index is tagged desync — "cannot place
this, and cannot certify why" — rather than being called inside-string, which
would be the same over-claim one layer up.

--self-test: a fixture AND a positive control per primitive

The pre-fix legacySeekQuote is kept in test scope, with all four primitives
re-implemented around it differing in exactly the skip. Fixture:
`Unknown key${n > 1 ? 's' : ''} ${keys.map((k) => `\`${k}\).join(', ')} …` ``

primitivefixture assertscontrol (pre-fix algorithm) asserts
scanTopLevel2 top-level ; past the templatelegacy sees 0
enclosingOpenersthe code: after it is {-enclosedlegacy answers undefined
sliceBalancedthe whole argument list comes backlegacy truncates at the inner backtick
splitTopLevel2 partslegacy answers 1

Plus: inside-string on a code: written inside a string; desync on a
position behind an unterminated template; the skipper's two interpolation cases
(braces and quotes inside ${ … }); and the scanReport on all three.

Mutation on the committed tree. Deleting the ${-tracking branch from
skipStringLiteral (the skipper falls back to seek-to-matching-backtick)
reddens exactly the four primitive cases:

check-dispatcher-error-vocabulary --self-test FAILED:
- scanTopLevel saw 0 top-level `;` past a nested template, not 2 …
- enclosingOpeners answered {"kind":"desync"} for a `code:` after a nested template …
- sliceBalanced returned "`a${xs.map((k) => `\`${k}\`` " for an argument list …
- splitTopLevel cut a nested-template argument list into 1 parts, not 2 …

Mutation confirmed on disk before the run (grep -c on the deleted text: 1 → 0;
on the injected marker: 0 → 1; blob hash 9d4b84921dce95d3). Restored by
git checkout HEAD -- ABSOLUTE_PATH under an EXIT/INT/TERM trap and proven back:
blob hash 9d4b8492 again, git diff HEAD --name-only empty.

Censuses re-derived — tree drift separated from what the fix moved

Both readings come from the same sweep, run with the PRE-fix primitives and the
POST-fix ones over today's packages/** non-test source.

INLINE_LITERAL_EXPRESSION_CENSUS

fieldpinned (#13790)pre-fix, todayafterwhat moved it
filesScanned218621872187drift
objectLiteralCodeTokens902906906drift
anchorHits346349349drift
notObjectLiteral747470fix
parameterList414141
insideAString333318fix (split)
behindScannerDesync11fix (new class)
ownedBySibling.objlitconst404041fix
candidatesReachingReduction216219222drift + fix
declinedByReduction214217218drift + fix
declinedTypeAnnotation150155155drift
declinedRuntimeValue646263drift + fix
livePositions224fix
distinctValues559fix
newVerdictRows222
unregisteredWireCodesHiding000
positionsBehindScannerDesync2211re-attributed (below)
positionsRecoveredByTemplateMode2new

Newly PLACED positions, classified

Over the whole 906-token code: population, 19 positions go from unplaced
to placed. Two of them reach objlitexpr — exactly the two the card predicted:

positionclassification
domains/automation.ts:1384code: targetName === undefined ? 'required' : 'invalid_type'ADR-0112 D6 field-addressed validation catalog. Same file, same shape, same two values as the :1125 instance the gate already saw ⇒ derives the site keys that already exist ⇒ 0 new verdict rows.
domains/automation.ts:1399code: targetLabel === undefined ? 'required' : 'invalid_type'Same class, same verdict, 0 new verdict rows.

The other 17 are objlit/objlitconst tokens (those shapes are not guarded by
enclosingOpeners, so they were never blind there) or type annotations. Two
enter objlithelper's candidate population, which is why localTwinCensus
moves by 2.

No new value surfaced anywhere.deriveSites diffed key-by-key across the
change: sites 59 → 59, unresolved 5 → 5, added [], removed [], per-shape
counts identical. So the ⛔ escalation clause (an unregistered value at a newly
placed position ⇒ report blocked) was not reached, and
packages/runtime/src/dispatcher-error-vocabulary.ts is untouched.

OBJECT_LITERAL_CODE_HELPER_BLINDNESS

measured.* and declarationFormClosure.* are historical deltas of #13233 /
#13226, not censuses of today's tree; re-checked rather than assumed —
objlithelper derives the same 29 sites and the same 5 unresolved before and
after — and left as they were, with that check written into the block.

localTwinCensus IS a census of this tree, and it had already drifted:

fieldpinned (#13478)pre-fix, todayafter
candidatePositions125134136
alreadyCoveredByObjlitconst364041
newlyReached899495
newlyReachedDistinctFileIdent697273
newlyReachedDistinctFiles454747
reduceToEmptyByClass.typeKeywordPosition495353
reduceToEmptyByClass.namedTypePosition161616
reduceToEmptyByClass.runtimeValueLocal151617
reduceToEmptyByClass.bindingWithoutDeclarator999
reduce000
newVerdictRows / unregisteredWireCodesHiding0 / 00 / 00 / 0

The headline zero survives at both ends — the reducible subclass is still
empty — so no verdict row is owed. The instrument was the #13478 replica: the
real objlithelper branch with the resolveConstant fallback switched on,
driven through the real deriveSites; its zero is backed by the positive
control already in --self-test (resolveConstant still reduces
const c = cond ? A : B to both branches).

SCANNER_LITERAL_BLIND_SPOTS — the class this card did NOT close

The sweep found a second desync class and it is declared with numbers rather
than left to be rediscovered: a regex literal whose character class carries a
quote or a backtick
. The live one is
packages/rest/src/error-response.ts's RELATION_DOES_NOT_EXIST, whose class
contains ", ' and a backtick; a textual walk opens a string the source does
not have.

files swept2187
whole-file walks health-certified, pre-fix → post-fix2108 → 2119 (+11)
files still desynchronised68
code: positions unplaced pre-fix86
↳ newly placed by the template mode19
↳ certified genuinely inside a string21
↳ still behind a desync46
unregistered wire codes surfaced0

⛔ Deliberately not fixed here: telling /re/ from division needs the preceding
token's grammatical class, which is lexer state this scan does not carry — a
heuristic would trade a blind spot that is now COUNTED for one that is not.
Filed as its own card. --self-test pins the declaration in the direction it
can fail: a regex-literal fixture must still come back desync, with the same
source minus the regex as the positive control, so the block cannot quietly
outlive the blind spot it declares.

Scope

  • git diff --stat: 1 file changed, scripts/check-dispatcher-error-vocabulary.mjs.
  • scripts/check-error-code-casing.mjsuntouched. Confirmed by the diff
    and by pnpm check:error-code-casing in the gate family below.
  • packages/runtime/src/dispatcher-error-vocabulary.tsuntouched. No
    ledger row added or moved; no site or unresolved entry changed.
  • ⛔ No shape grammar changed: objlit, objlitconst, objlittemplate,
    objlithelper and objlitexpr regexes are byte-identical.

Verification

The union below ran at HEAD 160c99d5 (git rev-parse --short HEAD), the
final commit on this branch — a merge of origin/main after the fix commit
0dc603cd2. The whole union was run again on that merged head rather than
carried over from the pre-merge run. Each exit code was captured by redirect
BEFORE any pipe.

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/check-dispatcher-error-vocabulary.mjs derives 18 commands from this
tree (objectstack-ai/objectstack at 160c99d51, --repo asserted and held —
the derivation is re-run from the merged head, never a git diff list fed in by
hand). All 18 run, all green except one NOT MEASURED:

node scripts/check-ci-filter-parity.mjs exit=0
node scripts/check-cross-package-test-inputs.mjs exit=0
node scripts/check-self-test-wired.mjs exit=0
node scripts/check-shard-attestation.mjs exit=0
node scripts/check-test-completeness.mjs exit=3 NOT MEASURED
node scripts/check-whole-set-label-write.mjs exit=0
node scripts/pm/bare-root-worklist.mjs --self-test exit=0
pnpm check:agent-test-spelling exit=0
pnpm check:bash32-floor exit=0
pnpm check:cli-command-ids exit=0
pnpm check:cross-package-test-inputs exit=0
pnpm check:dispatcher-error-vocabulary exit=0
pnpm check:entry-guard exit=0
pnpm check:parse-guard exit=0
pnpm check:pm-dispatch-gates exit=0
pnpm check:pnpm-filter-targets exit=0
pnpm check:ratchet-remedy-authority exit=0
pnpm check:watch-hint-literal exit=0

node scripts/pm/dispatch-gates.mjs --ran RECORD_FILE on the --commands output
reconciles the run: "Run reconciliation — 18 derived, 18 run, 0 NOT-MEASURED,
0 UNRUN"
, exit 0.

check-test-completeness exit 3 is its own declared NOT-MEASURED branch, in
its own words: "Arrived here from the gate family scripts/pm/dispatch-gates.mjs
derives? … There is no local log to hand it, so the local reading for this gate
is NOT MEASURED. ⛔ It is not a red."
CI tees a real turbo run test log into
it, so CI measures it.

Named by the dispatch and outside the derived family, run anyway:

pnpm check:error-code-casing exit=0 (the ⛔ untouched gate — still green)
pnpm check:nul-bytes exit=0 "OK (scanned 8042 text file(s) … no raw ASCII control bytes)"

The gate's own verdict lines:

check-dispatcher-error-vocabulary --self-test: 10 shapes + 282 assertions OK
(vocabulary + #9098 door typing) [was 277 before this card]
check-dispatcher-error-vocabulary: OK — 59 unregistered code-stamping site(s),
all classified; 1 awaiting a ledger entry (#8846).

Lint, narrowed and the narrowing measured (not "not run"):

  • Population comes from eslint's own config, not from a guess about which files
    count; the diff is 1 file, so npx eslint --no-inline-config --format json scripts/check-dispatcher-error-vocabulary.mjs is the whole of it.
  • File count read from that JSON: 1 file linted, 0 errors, 0 warnings,
    exit 0.
  • Invariance: eslint.config.* states it runs with no parserOptions.project
    and no typed @typescript-eslint rules, so type-aware linting is off and a
    one-file diff cannot move the verdict on any untouched file. Repo-wide
    pnpm lint is CI's run.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV


Generated by Claude Code

…shared textual scanners (#14626)
`scanTopLevel`, `enclosingOpeners`, `sliceBalanced` and `splitTopLevel` each
carried the same seek-to-matching-quote skip, which a NESTED template literal
walks straight out of: the outer template closed at the first inner backtick
and every `code:` position after it in that file went unplaced.
All four now call ONE shared `skipStringLiteral`, a small state machine that
tracks `${ … }` nesting and recurses into literals opened inside an
interpolation. `enclosingOpeners` answers every requested index with a TAGGED
result, so "genuinely inside a string" and "the scanner lost its place" stop
arriving as the same `undefined`; the other three take an optional
`scanReport()`. The tag is certified — `inside-string` is asserted only for a
walk that ends with no unterminated literal, no bracket underflow and an empty
stack.
`--self-test` gains a nested-template fixture per primitive, each PAIRED with a
positive control run through the pre-fix `legacySeekQuote` kept in test scope.
`OBJECT_LITERAL_CODE_HELPER_BLINDNESS` and `INLINE_LITERAL_EXPRESSION_CENSUS`
are re-derived on this tree, with tree drift separated from what the fix moved,
and `SCANNER_LITERAL_BLIND_SPOTS` records the residual class the sweep found
(a regex literal carrying a quote) rather than leaving it to be rediscovered.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLJQhde67SeTccsmnBVarV
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 2, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review September 2, 2026 23:25
@baozhoutao
baozhoutao added this pull request to the merge queueSep 2, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 33696604625 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (1/6) — 失败步骤: Run this shard's tests

    @objectstack/cli:test: FAIL integration test/run-dev-unbuilt-workspace.e2e.test.ts > the mirror direction: a reader that is never coming back > gives up and exits instead of waiting forever
    ↳ 失败原因: @objectstack/cli:test: AssertionError: the harness SIGKILLed the child — it was still alive at the ceiling. cap 180000 ms (RUN_TIMEOUT_MS, constant and load-independent by design); this child ran 1801
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

⚠️断言这一侧有一类例外,判据是断言在测什么,不是它是不是 AssertionError 断言的对象是产品行为(一个值、一个形状、一次拒收)⇒ 照上面读:真实的行为改变,去查,⛔ 不要重排掉;
断言的对象是这次实验自身的有效性前提(跑完的耗时、负载下的先后、任何只在时间预算内才成立的条件)⇒ 它跟超时是同一类,同样对负载敏感,重排一次是合法的判别手段。
识别是机械的:断言的消息或它比较的值本身点名了一段时长、一个时间戳、一个耗时计数。实测过的一对 —— AssertionError: SecurityPlugin.init() ran: expected false to be true 测的是产品行为(真回归);
AssertionError: this run took over a second, so second-precision stamps could have differed too: expected 1006 to be less than 1000 测的是实验前提:它守护的那条不变式当时是绿的,同一个 head 原样重排一次即成功。
穿着 AssertionError 外衣的时间测量,仍然是时间测量。(⛔ 这只改「怎么读一次红」,不改「哪些测试可以重排」——后者由别处管。)

跨 PR 相同签名(24h,按失败测试文件聚合):

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 30 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Merged via the queue into main with commit 41978ecSep 3, 2026
39 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-14626-scanner-nested-template-mode branch September 3, 2026 00:49
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants

@baozhoutao@claude