Uh oh!
There was an error while loading. Please reload this page.
fix(scripts): check-org-identifier takes the shared string-aware comment mask - #9496
Merged
Merged
Conversation
…ent mask The gate decided "comment or code?" per line, with a `trimmed.startsWith` triple and `line.replace(/\/\/.*$/, '')`. That truncates at the FIRST doubled slash on the line whatever it is, so a URL or any slash-bearing string literal deleted the rest of its own line -- including the very `session.tenantId` read the gate exists to catch. Silent under-reporting: it printed OK over a line it had truncated. It was wrong in the mirror direction too, which the card did not name: only a line STARTING with `*`, `//` or `/*` counted as a comment, so an interior line of a block comment and a trailing `/* … */` on a code line both read as live code and would have been reported as findings. Both directions go away with `maskComments` from scripts/js-comment-mask.mjs (#9367), which is string-, template- and regex-aware and blanks in place, so the reported line number stays true. The waiver marker keeps being read from the RAW line: it lives in a comment, and testing the masked line for it would silently revoke every waiver in the tree. Measured on 2051 author-facing files: the two projections disagree on the text of 271 files, the gate's verdict on 0 -- all 10 corpus lines naming the alias are comments, so the defect is LATENT. Structurally so: this is a zero-occurrence hard-fail guard, so a corpus holding the hazard is a corpus where the gate is already red. The near-miss half is everywhere -- 665 lines in 181 files carry a doubled slash inside a literal. The 14 shapes are pinned in a new `--self-test`, wired into `check:org-identifier` the way every other gate in this tree wires its own. Fixes#9444 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XqDQYVU5smx29ts9pAErja
os-steve
marked this pull request as ready for review
August 18, 2026 05:58
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#9444
The seventh gate in the family PR #9445 fixed.
scripts/check-org-identifier.mjsnowtakes
maskCommentsfrom the sharedscripts/js-comment-mask.mjsinstead of answering"comment or code?" itself. That shared module is not touched by this PR.
The defect, and the one the card did not name
The card names the truncation:
It cuts at the first doubled slash on the line, whatever that slash is, so a URL or
any slash-bearing string literal deletes the rest of its own line — including the very
session.tenantIdread the gate exists to catch. Silent under-reporting: the gate printsOK over a line it truncated.
Reading the whole script found the mirror defect, which the card does not mention. The
guard above the truncation is
trimmed.startsWith('*' | '//' | '/*'), so only a line whosefirst non-space characters open a comment counted as one. An interior line of a block
comment (opener on one line, prose with no leading star on the next) and a trailing
/* … */on a code line both read as live code — the gate would manufacture a findingout of prose. So this gate belongs to both families in #9445's table at once: it blinds
and it fabricates. One mask closes both, and both are pinned in
--self-test.Those two are the whole of the comment handling in this script; there is no third
hand-rolled parser in it.
Reachability: LATENT, and structurally so
Measured on
af2a989be, over this gate's own corpus (2051 author-facing source files),this gate's old strip diffed against the shared masker:
session.tenantIdsession.tenantIdreads in the corpusSo: LATENT, not live. And it is worth being precise about why, because for this gate
"latent" is not luck. This is a hard-fail zero-occurrence guard — a corpus that holds
the hazard is a corpus in which the gate is already red. The intersection the card asks
for (doubled-slash-in-a-string × a real read, on one line) can only ever be empty while
the gate is green, so an empty intersection is not evidence about the mechanism. The
measurement that carries information is the near miss, and that half is everywhere:
literal.
The day one of those lines also carries the removed read, the old gate goes quiet. That is
the same conclusion #9445 reached for its two latent gates, arrived at from the other side.
Projection:
maskComments, measured against the alternativeThis gate reports a file and a line, so it takes the blanking projection — the masked text
stays byte-aligned with the source and line
iis still linei. The card flags #9367's51x cliff (6.4s → 5m27s), which came from dragging a lazy
[\s\S]*?across thewhitespace blanking leaves behind. This gate's matcher is a short anchored pattern run per
line, so it is not exposed to that. Measured anyway, same corpus, best of 3, scan + match
only:
.replacemaskComments(blank)stripComments(delete)No cliff — the ~2s is the scanner's own linear cost over 29 MB, and both projections return
the same verdict here. Whole-gate wall clock goes 0.41s → 2.39s; flagging that plainly
since a gate quietly getting slower is a cost its own green never shows.
The verification that matters
A green run over a corpus with zero occurrences proves nothing at all here, so the gate was
driven end to end against a planted fixture (tracked, so
git ls-filespicks it up),once with the original script from
origin/mainand once with this one:const docs = 'https://objectstack.ai/docs/hooks'; return ctx.session.tenantId;/* … */, both naming the aliasNote the second row runs the reverse-verification backwards from the usual template:
ablating the fix turns the gate red, not green, because on that shape the defect
invents findings rather than dropping them. Both fixtures were removed from the index and
the tree afterwards.
The shapes themselves are pinned in a new
--self-test(14 cases), the way every othergate in this tree pins its matcher, and
check:org-identifiernow runs it first — theconvention already used by
check:error-code-casing,check:nul-bytesand ~60 others. Anunwired self-test is the gap lint.yml already calls out for
dispatch-gates. One draftedcase was discarded because the reference disagreed with it: a bare
* …line with noopener above it is not a comment in JavaScript, only in the old heuristic, so the case was
rewritten as a real docblock.
Two deliberate non-changes, both of which a naive conversion gets wrong:
os-allow-tenant-idwaiver keeps being read from the raw line. It lives in acomment, and the mask blanks comments — testing the masked line for it would silently
revoke every waiver in the tree. (There are none in the corpus today; that is exactly why
a green run would not have caught it.)
that spells the removed alias inside a template is still a finding. That was already the
semantics whenever no doubled slash preceded it; it is now consistent, and the corpus has
zero such lines (the 0-verdict-change row above).
CI
check:org-identifieris a CI gate —.github/workflows/lint.yml, step "Org-identifierauthoring guard". Unlike
check:platform-checklistin #9445, this one's green is CI's, notonly mine.
Gates run locally, on
ed6f91066Families re-derived from the actual changed paths with
node scripts/pm/dispatch-gates.mjs scripts/check-org-identifier.mjs package.json— itplaces
check:org-identifierand nothing else.node scripts/check-org-identifier.mjs --self-test— 14 cases passnode scripts/check-org-identifier.mjs— OK, 2051 files, exit 0node scripts/check-nul-bytes.mjs --self-test && node scripts/check-nul-bytes.mjs— OK,6130 files, no raw control bytes
node scripts/js-comment-mask.mjs --self-test— 15 cases pass (unchanged module, run toshow the shared instrument is still green under its new consumer)
No changeset: root
scripts/plus one rootpackage.jsonscript line, nothing published.The red check on this PR is #9350, not this diff
Temporal Conformance (live PG + MySQL)is red on this PR. It is the already-open flakecard #9350 — same file, same describe block:
It is a timeout, not an assertion mismatch: that case does
initObjects→previewDeferredSchemaWork→flushDeferredSchemaDdl(anALTER … MODIFYfull tablerebuild) →
initObjects→ re-plan, all on vitest's default 5000ms because the file sets noexplicit timeout. The two files in that package that do set one use 40s and 60s. Full
evidence, including why this occurrence discriminates between #9350's two hypotheses, is
posted on that card; this is the first PR-side occurrence of a pattern recorded there as
queue-only.
Why it cannot be this diff: the failing step runs
pnpm --filter @objectstack/driver-sql test(vitest inside
packages/drivers/driver-sql). This PR adds no code underpackages/;scripts/check-org-identifier.mjsis imported by nothing outside itself, and the changedroot
package.jsonline is the value ofcheck:org-identifier, which that step neverinvokes. Turbo's global hash does include the root
package.json, so this job likelyrebuilt cold instead of restoring cache — but that is step 11, which passed, and step 12
does not invoke turbo at all. driver-sql's vitest config aliases
@objectstack/specand@objectstack/coreto their src trees, so the test step transforms TypeScript fromsource and its workload is identical on a cache hit or a miss.
The one line outside the claimed file surface is that
package.jsonentry, which runs thenew
--self-test. It is kept deliberately — a self-test nothing runs is the gaplint.ymlalready names for
dispatch-gates— and reverting it is a one-line change if the surfacefence should win instead.
Generated by Claude Code
Generated by Claude Code