Skip to content

fix(scripts): the where-matcher lift reads identifiers from code, never from template-literal text - #30472

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-o7a942
Nov 5, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/project-thread-o7a942

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Bill · project thread

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

Fixes #29860

Clause-②: no — an internal instrument; no product surface moves
Metadata-protocol: none

维护者速读

  • 改了什么: where-matcher 一致性检查在"抽出测试替身的过滤函数单独运行"时,改为只顺着代码里出现的名字去找同文件的依赖,字符串、模板文字的正文和注释里的单词不再算数;${…} 里的表达式仍算代码照收。
  • 为什么改: 替身的报错文案里写了 "sparse driver",检查就把旁边的 driver 对象一起拉进来,运行时报 name is not defined,一个本来合规的替身被判"无法判定",挡住了 PR fix(objectql): a write answers every stored declared column the driver did not echo as null, as the next read does (#29538) #29858,作者只好改文案绕过去。门禁结论不该取决于报错文案用了哪个英文单词。
  • 风险与回滚: 只动一个仓库内部检查脚本,不发布任何包。全树 684 个过滤函数改前改后判定逐条一致,耗时持平(约 15 秒)。回滚即撤 PR。
  • 先看: scripts/check-where-matcher-conformance.mjs 的 identifiersIn(改为遍历语法树)与 buildCallable 里读 sourceIdentifiers 的三行;self-test 新电池 #29860。
  • 两分钟验证: pnpm check:where-matcher,预期 self-test 打印 OK,全树一行 0 silently-wrong and 0 unjudged(合 main 后共 685 个 matcher)。
  • 需要你做的: 无,席位落地。

What changes

identifiersIn was a word regex over source TEXT (/\b([A-Za-z_$][A-Za-z0-9_$]*)\b/g), so every word in a string literal, a template literal's text, a regular expression or a comment counted as a name the lift should follow. It now walks the parsed tree and collects Identifier nodes only: a ${…} hole is code and is collected, the text around it is not. Discovery computes the sets off the nodes it already holds (the candidate's sourceIdentifiers, and each visible declaration's identifiers beside its text), so nothing is re-parsed and no fragment goes through a second parse door. buildCallable follows those sets instead of re-scanning text. The header's "Extraction" paragraph says what "names" now means.

Over-inclusion stays in one direction only, as before: a property name (row.name) is still an identifier node, and a name that resolves to no same-file declaration is skipped by the lift.

Premise note. The card says ordinary string literals already behaved this way. Measured on main, they did not: the same regex read them too. This PR covers both spellings, and the self-test pins both.

Tests

New self-test battery #29860: the lift reads identifiers from code, never from text (floor 7; roster floor 4 → 5):

  • FIXTURE_TEXT_NAMES_SIBLING, the card's shape: a refusing matcher inside a factory whose messages say "sparse driver" (one template literal with a ${k} hole, one plain string), beside const driver = { name, … } that closes over the factory parameter. Pinned: discovered; driver and implemented not collected; lifts and judges CONFORMING (refusing).
  • FIXTURE_HOLE_NAMES_HELPER, the other direction: a same-file helper label named only inside a ${…} hole on the matcher's answering path. Pinned: label collected; the matcher lifts with it and judges CONFORMING.
Mutation replay (one-off, restored by blob hash == HEAD)
  • Old semantics (word regex over the node's text): self-test exit 1 — template-literal TEXT names no identifier: "driver" in the message is not collected, a matcher beside text naming a sibling binding lifts and is CONFORMING, not UNJUDGED.
  • Holes skipped (if (ts.isTemplateExpression(n)) return;): self-test exit 1 — the helper named only inside the hole is collected, so the hole-reading matcher lifts with its helper and is CONFORMING.
Real producer replay

packages/objectql/src/write-answer-declared-columns.test.ts read in memory with its messages put back to sparse driver: (the wording PR #29858 had to rename away), then discoverInSource + judge:

  • main (989ae42): 46 matches UNJUDGED could not lift: ReferenceError: name is not defined
  • this branch: 46 matches CONFORMING

Acceptance notes

  • Whole-tree verdicts, before (989ae42) and after: 692 structural candidates, 257 CONFORMING, 427 CONFORMING by refusing, 8 OUT_OF_SCOPE, both runs; a per-candidate diff (file, line, name, verdict, reason) is empty. No verdict changed, so no matcher newly lifts. Gate line both runs: 684 matcher(s) discovered … 0 silently-wrong and 0 unjudged. Wall time about 15.6 s before, 14.9 s after. After merging origin/main (head da5a909): 685 matcher(s) discovered … 0 silently-wrong and 0 unjudged.
  • scripts/check-objectql-double-limit.mjs carries a near-identical lifter (identifiersIn at line 400 is the same word regex). Same defect class, no real producer measured there; not touched (outside this card's file surface), reported to the seat.

Verification

Gate list derived by node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (31 commands), each run with its exit code recorded before any pipe and reconciled with --ran; pnpm lint run. Readings are in the os-dev-report on #29860.

No changeset: the diff is one repo-root script that no released package ships (skip-changeset).


Generated by Claude Code

…rom template-literal text

identifiersIn was a word regex over source text, so a refusal message naming
a sibling binding pulled it into the lift and a conforming matcher read
unjudged. It now walks the parsed tree; self-test pins both directions.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Utp6CYZ6tmbWZDWzLW4Yzg
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-bill · session_01Utp6CZMkHvuDeVYgTD73qF

席位意见:ACCEPT(#29860)。where-matcher 一致性检查现在只从代码里收标识符(语法树上的 Identifier,含 ${…} 洞),不再从字符串和模板字符串的文字里收,测试替身的报错文案不会再把一个合规的 matcher 判成「无法判定」。

席位核对

Head-sha: da5a90975a872ce3e45b2585730c99e78c43134f · read 2026-11-05T13:10Z

Item Reading
Shape Draft → main, author objectstack-fleet[bot] (relay pr_create); Fixes #29860, Clause-②: no, Metadata-protocol: none; skip-changeset (scripts only). 1 file, +103/−11: scripts/check-where-matcher-conformance.mjs.
Ruling Card #29860 and triage first-touch 6599367007 (p3); claim on #29860; dev report 6658455882. Premise corrected by the dev: on main ordinary string literals were read by the same word regex too, so both shapes are fixed and pinned.
Seat review Read the diff: identifiersIn walks the parsed node and collects ts.isIdentifier only; candidate and declaration identifier sets are computed once at discovery and carried (sourceIdentifiers, decl.identifiers), so buildCallable no longer re-reads text; the measure() detail strips the new field. A self-test battery for #29860 is added. Dev's evidence: two mutation legs exit 1 and restore; the real producer (write-answer-declared-columns.test.ts with "sparse driver:") reads UNJUDGED on main and CONFORMING here; 692 candidates judged with zero verdict changes.
Gates (check-runs on this head) 46 completed: 26 success, 20 skipped, 0 failed.
Follow-up The same regex in scripts/check-objectql-double-limit.mjs is filed as #30473.
Landing Not governed (check-governed-merges --pr 30472), 114 lines, no !. Seat lands: pr_ready + automerge_enable.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 5, 2026 13:11
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 5, 2026 13:11
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 5, 2026
Any commits made after this event will not be merged.
Merged via the queue into main with commit f6caa12 Nov 5, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-o7a942 branch November 5, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant