Skip to content

fix(ci): published-README call sites assert the leading boundary instead of consuming it - #9618

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-9610-callsite-boundary-lookbehind
Aug 18, 2026
Merged

fix(ci): published-README call sites assert the leading boundary instead of consuming it#9618
os-project-manager merged 1 commit into
mainfrom
claude/issue-9610-callsite-boundary-lookbehind

Conversation

@os-project-manager

@os-project-manageros-project-manager commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

Fixes#9610

⚠️Reading note. A negative-lookbehind literal cannot be carried in a GitHub body: the ! in (? + less-than + ! + [ is deleted in storage (measured in #9621, with controls both ways). So wherever the real source has those two characters, this body writes NLB. The genuine line is in the diff.

The defect, re-measured before fixing

extractMemberCalls found Name.member( with a leading alternation whose non-^ arm consumes a character. The regex is global, so a receiver starting at the very next character after a previous match had no boundary left to match against. A match always ends at its own ( — which makes the swallowed position exactly outer(Inner.m(.

Re-measured on the published regex, receiver set {CacheServicePlugin}:

await kernel.use(CacheServicePlugin.configure({ a: 1 }));
all: ["kernel.use"] kept: [] MISSED
await kernel.use( CacheServicePlugin.configure({ a: 1 }));
all: ["kernel.use","CacheServicePlugin.configure"] kept: ["CacheServicePlugin.configure"] caught

And end to end on the built tree, service-cache's README given the genuine CacheServicePlugin with a fabricated static, one space apart:

README linepre-fixpost-fix
await kernel.use(CacheServicePlugin.configure({...}))exit 0, greenexit 1, line 299: documents CacheServicePlugin.configure(...)
await kernel.use( CacheServicePlugin.configure({...}))exit 1exit 1, same finding, same key

That nested position is not a corner — kernel.use(new SomePlugin({...})) is the house spelling of every README this gate polices, so the blind spot sat in the gate's most important position, and on the likeliest wrong rewrite of any of those pages.

The fix

A negative lookbehind: the boundary is asserted zero-width, nothing is consumed, and the ^ arm folds in because a negative lookbehind is satisfied at position 0.

// NLB stands for the two characters this body cannot carry; see the reading note.constrx=/(?NLB[\w$.'"`])([A-Za-z_$][\w$]*)\s*\.\s*([A-Za-z_$][\w$]*)\s*\(/g;

The character class is byte-for-byte the old one, so the fence is unchanged. Group indices shift 2/3 to 1/2 with the capture gone.

The card's proposal is sound — the missing ! is a platform artifact, not an authoring error

The regex literal in #9610's "Suggested fix" reads back without its !, which is a SyntaxError — V8 reads a less-than right after (? as the opener of a named group. I did not file that as a defect in the card:

⇒ Nothing in the card needs changing. Filed as #9621, because AGENTS.md's sanitizer clause names only a less-than followed by a letter and so does not cover this, and because the same read path truncated a body at a doctype token (#9557's family).

--self-test, in the same edit

The hole survived a gate written with self-tests in both directions because every existing fixture placed the wanted receiver where no earlier match on the line had consumed anything in front of it. Three cases added:

  • extractMemberCalls — a receiver directly inside a discarded call is still read — fixture is await kernel.use(CacheServicePlugin.configure({...})), receiver adjacent to a discarded kernel.use( match with no separating character.
  • analyzeDocument — a fabricated static nested inside another call is reported — the same shape end to end.
  • extractMemberCalls — the nested position does not widen the fence — the other direction in that same position: wrapper.CacheServicePlugin.configure(, all three quote styles, and the correct new CacheServicePlugin( spelling all stay silent.

Reverse verification

Fix committed first, then the boundary reverted to the consuming form with the new fixtures left in place:

✗ check:published-readme-exports --self-test — 2 failure(s)
extractMemberCalls — a receiver directly inside a discarded call is still read
expected ["CacheServicePlugin.configure"]
actual []
analyzeDocument — a fabricated static nested inside another call is reported
expected ["...|member|@objectstack/kernel|Kernel.configure"]
actual []

Restored from the committed branch state; git status clean afterwards. Honest note on direction: the third case is green under both regexes by design — it is a fence-preservation pin (it reddens if someone widens the character class), not a regression detector for this change.

No drift in what is detected

The fix changes zero known instances. Same numbers before and after, on the same built tree:

✓ check:published-readme-exports — 60 published document(s) across 77 workspace package(s);
166 import statement(s), 47 workspace type entr(ies).
16 known instance(s) still in scripts/published-readme-exports.baseline.json;
2 of the findings are call sites.

Cross-check against PR #9602 (not yet merged; this does not address it)

PR #9602 rewrites the five service READMEs and shrinks the baseline 16 to 10. Its head content was overlaid onto this branch and measured both ways:

gateover #9602's READMEs + baseline
this branch's fixed gategreen, 10 known instances, 2 call sites
origin/main's pre-fix gategreen, 10 known instances, 2 call sites

⇒ The fix newly detects nothing in #9602's rewritten content, and the numbers match #9602's own reported result exactly. No finding about that PR, and no baseline interaction in either landing order.

Gates

The gate union derived by node scripts/pm/dispatch-gates.mjs from git diff --name-only $(git merge-base origin/main HEAD) (one changed path, scripts/check-published-readme-exports.mjs) is check:published-readme-exports; check:nul-bytes and eslint added for any-edit coverage. All run at a87e7f0bd, the final commit, with the working tree clean against it — all exit 0:

  • pnpm check:published-readme-exports (self-test + full scan) — exit 0
  • pnpm check:nul-bytes (self-test + scan of 6171 files) — exit 0
  • npx eslint scripts/check-published-readme-exports.mjs --no-inline-config — exit 0

The full workspace was built first (turbo run build, 71 tasks, 71 successful): this gate hard-errors on a missing type entry rather than skipping, so on an unbuilt tree it refuses and measures nothing — a refusal is not a pass.

No changeset

Repo tooling only — scripts/ ships in no package's files array, so nothing user-visible changes. skip-changeset applied as a label.


Generated by Claude Code

…ead of consuming it
`extractMemberCalls` found `Name.member(` with a leading alternation
`(^|[^\w$.'"`])`. The non-`^` arm CONSUMES a character, and the regex is
global, so a receiver starting at the very next character after a previous
match had no boundary left to match against. A match always ends at its own
`(`, which makes the swallowed position exactly `outer(Inner.m(` — and
`kernel.use(SomePlugin.configure({...}))` is the house spelling of every
README this gate was built for. The gate was blind in its most important
position; two spellings one space apart disagreed:
kernel.use(CacheServicePlugin.configure({...})) green, exit 0
kernel.use( CacheServicePlugin.configure({...})) red, exit 1
A negative lookbehind asserts the boundary zero-width, so nothing is consumed
and the `^` arm folds in. The character class is unchanged, so the fence is
unchanged: `a.b.c(` and `'str'.trim(` stay out, now in the nested position too.
`--self-test` gains the adversarial fixture in the same edit, in both
directions plus end to end. The hole survived a gate written with self-tests
both ways because every existing fixture placed the wanted receiver where no
discarded match had consumed anything in front of it.
Baseline reconciliation is unchanged: 16 known instances, 2 of them call
sites, before and after.
Co-Authored-By: Claude <noreply@anthropic.com>
@os-project-manageros-project-manager added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 18, 2026 — with Claude
@os-project-manager
os-project-manager marked this pull request as ready for review August 18, 2026 13:28
@os-project-manager
os-project-manager added this pull request to the merge queueAug 18, 2026
Merged via the queue into main with commit 40162f1Aug 18, 2026
26 of 27 checks passed
@os-project-manager
os-project-manager deleted the claude/issue-9610-callsite-boundary-lookbehind branch August 18, 2026 13:46
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

2 participants

@os-project-manager@claude