test(client): the envelope-caller census names the string-literal trap in its own failure text - #15397
Merged
Conversation
…p in its own failure text The census walks the workspace for four SDK spellings and blanks COMMENTS only, deliberately: a scanner that also blanked literals would open a phantom comment on any block-comment opener inside a string and shrink the census in silence. The price is that example code quoted inside a literal -- a refusal message showing an author what to write, a usage banner, a fixture embedded in a gate script -- counts as a real call site. That happened, and the merge queue ejected the PR. What made it expensive was never the count; it was the failure text. It read `expected 21 to be 19` and said nothing about strings, masking or gate scripts, in a package the author had not edited, naming a ledger the author had never read, at the most expensive point in the pipeline. So the count is untouched and the message explains itself. Every assertion that compares a census-derived number now carries a note that re-reads the counted sites and says which of them sit inside a literal -- with file, line, receiver, the opening delimiter and the line that delimiter opened on -- plus where the repair belongs and what must not be done to make it green. Strictly additive: - the literal question is answered by `scanSource`, the pass `maskComments` is itself built on, which already reports a per-character `literal` flag beside the `comment` flag. No second private scanner, and `maskComments` and `js-comment-mask.mjs` are untouched; - the note is built only while a message is being built, over the counted sites alone, and decides nothing; - it cannot throw: an unreadable file, a stale offset and a garbage site all degrade to "not in a literal", pinned in section 6; - the negative half is pinned too -- a real call site outside any literal produces the note that says the trap is NOT the cause, so the positive reading stays worth acting on. `scanCallSites`' per-file matching is lifted out unchanged as `sitesInSource` so section 6 drives THE MATCHER THAT COUNTS over its fixtures rather than a copy of it. Every fixture is assembled from parts, because this file is inside the tree the census walks and a fixture spelled as one literal would arm the very trap it documents; the last test pins this file at zero counted sites. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-litant
marked this pull request as ready for review
September 4, 2026 14:44
os-litant
enabled auto-merge
September 4, 2026 14:44
os-litant
deleted the
claude/issue-13874-census-literal-span-diagnostic
branch
September 4, 2026 15:20
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 free
to 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 #13874
Triage ruled option 2 on this card (comment
5486747463, 2026-09-01): when a countedsite falls inside a string-literal span, the census's failure text says so. Options 1 and 4
were rejected, option 3 is suspended. This PR is that and only that.
The defect, restated from the measurement rather than the card
packages/client/src/envelope-caller-census.test.tswalks the workspace for four SDKspellings and blanks comments only. That is deliberate, and the reason is recorded in
two headers: a scanner that also blanked literals opens a phantom comment on any
block-comment opener inside a string and shrinks the census in silence.
The price is that example code quoted inside a literal — a refusal message showing an
author what to write, a usage banner, a fixture embedded in a gate script — counts as a
real call site. It happened, and the merge queue ejected the PR.
What made it expensive was never the count. It was the failure text. It read
expected 21 to be 19and said nothing about strings, masking or gate scripts, in apackage the author had not edited, naming a ledger the author had never read, at the most
expensive point in the pipeline, invisible to every local gate a
scripts/**edit derives.So the count is untouched and the message explains itself.
The one design decision worth reviewing
The obvious implementation is a small literal-span scanner written here. It is the wrong
one, and the file's own header says why: this tree has ONE code/prose separator, and a
private one is the exact defect that separator exists to prevent.
It also turned out to be unnecessary.
scripts/js-comment-mask.mjsalready exportsscanSource— the single passmaskCommentsis itself built on — and it already reports aper-character
literalflag beside thecommentflag the mask is made of. It is alreadydeclared in the hand-written
js-comment-mask.d.mts, so it resolves with types.So the diagnostic borrows the house scanner:
scripts/js-comment-mask.mjsis not edited, andmaskComments' behaviour isunchanged — the census still masks comments only;
pass;
(
check-comment-mask-corpus.mjs, re-run here: 5907 files, 0 disagree), rather than onewritten this afternoon.
Which of the two
maskCommentsconsumers the note belongs to — measured, not assumedThe file has two. The dispatch asked which one takes the diagnostic, and the answer is
the first only:
scanCallSitesproducesCENSUS.sites, i.e. the counted sites. The ruling is about acounted site, so every assertion that compares a census-derived number carries the note.
methodSourcemasks the SDK'sindex.tsto slice one method body for section 4'stoMatchassertions. It produces no count, its assertions already carry per-methodmessages, and extending there would widen the card past its ruling.
What the note says
Five assertions carry it — section 2's producer-receiver count (where a quoted example
lands first, because it rarely spells
client.in front of the method), section 3'senumeration-versus-ledger equality, the bare
ledgerTotalmismatch that is the ejectingshape, the production-site assertion, and the SDK site count. The three verdict sums are
ledger arithmetic and cannot move on a census reading, so they deliberately do not.
When it applies, the text names each offending site with file, line, method, receiver, the
opening delimiter and the line that delimiter opened on; then where the repair belongs; then
the two prohibitions, so the reader does not reach for the ledger or the census to go green.
And it says the opposite when it does not apply. A note that only ever speaks up says
"it is the literal trap" about every failure. The negative reading is pinned as its own test.
Proof the count did not move
Re-derived with an independent scanner — the census's own
METHODSand regex throughthe repo's own
maskComments, run outside the test — before and after the change:Byte-identical in both runs (
diffof the two outputs is empty). The new section 6fixtures add zero sites, which is not luck: this file sits inside the tree the census
walks, so every fixture is assembled from parts — the spelling is split in the SOURCE and
restored in the VALUE handed to the matcher. The last test pins the whole file at zero
counted sites.
scanCallSites' per-file matching is lifted out unchanged assitesInSourceso section 6drives the matcher that counts over its fixtures instead of a copy of it. A pin written
against a hand-rolled second matcher measures the copy and stays green through any drift in
the original.
Ablation — direction predicted first, both legs, mutation proved on disk
Prediction, recorded before running: breaking the span check leaves every count assertion
in sections 1-5 green and reds only section 6.
red the template-banner test too. It stayed green, correctly — that site genuinely is inside
a literal, so disabling the guard does not change its walk-back. Direction and the
load-bearing claim held; the count of red tests did not.
Both legs proved the mutation reached the disk before reading anything (injected marker
present = 1, original line gone = 0 — an editor that matches nothing exits 0 too). Restore
is by
git checkout HEAD -- ABSOLUTE_PATHfrom a committed implementation, proved by blobhash equality (
03d7789e4b769267e2cc34cdda4fa44e5725e9a0==HEAD:the file) plus an emptygit diff HEAD, with an absolute-path trap onEXIT INT TERM.⇒ The diagnostic can be broken in either direction without moving a single count. That
is the strict-additivity proof the ruling asked for.
The extra reading triage asked for: the population under
scripts/**Derived with the census's own
METHODSthrough the repo's own scanner, classifying eachmention by
scanSource's comment and literal flags rather than by a hand grep.Under
scripts/**: 0 literals embed any of the four spellings. There are 6 mentions,all in comments (correctly masked, contributing nothing), all in one file —
scripts/check-adr-0087-registration.mjs, which is the file whose fixture caused theejection and which now discusses the trap in prose instead of embedding it.
The raw grep's other 3 hits, in
scripts/analytics-reconcile/**, are allanalytics.queryDataset— the protected counter-example, a different symbol that merely hasanalytics.queryas a prefix. The census's pattern requires the call shape, so it does notmatch them.
For context, repo-wide: 70 literal-embedded mentions across 8 files, and 0 of them
are counted — none is written as a call shape. They are
it(...)titles, route-ledgerrows, permission arrays and migration prose. That is the real reservoir, and every one of
them is one added argument list away from becoming a phantom site.
⇒ On the
scripts/**number triage asked for, option 3's priority is not raised. Therepo-wide 70 is offered as the fuller picture for whoever files it; ⛔ nothing here widens
this card.
Changeset
None, and
skip-changesetapplied.packages/clientpublishesdist,README.mdandCHANGELOG.mdonly (check:published-filesgreen, and it independently refuses any packagethat admits a test file), so a change confined to
src/*.test.tsreleases nothing.Verification — union re-run on the final commit
afdc12c7bb, clean treepnpm --filter '@objectstack/client^...' build— VERDICT command-exit 0pnpm --filter @objectstack/client exec vitest run --maxWorkers=2 src/envelope-caller-census.test.ts— 20 passed (20), VERDICT command-exit 0pnpm --filter @objectstack/client typecheck— VERDICT command-exit 0.tsc --noEmithere excludes**/*.test.ts, so on its own it says nothing aboutthis file. Proved with
--listFilesOnly: the file appears 1 time in the TEST program(
tsconfig.test.json) and 0 times in the build program.check:test-typecheckis thehalf that measured it, and it is green.
dispatch-gates --commandsre-derived on the clean tree at final HEAD: identical tothe pre-commit derivation, 1 path in the change set. 35 of 38 derived commands green,
including
check:comment-mask-adoption(+ self-test),check:comment-mask-corpus,check:cross-package-test-inputs,check:test-source-alias,check:nul-bytes,check:published-files,check:type-check-coverage, plus the four artifact rosters thetool flags as "silence is not evidence" (
authz-resolver,error-code-casing,filter-alias-parity,swallow-census-controls).unbuilt world and says so:
check:dual-build-cjs-loads(exit 3, PREREQUISITE NOT MET),check:type-check-debt(exit 3, PREREQUISITE NOT MET),spec check:skill-examples(refuses because
packages/spec/distholds no declarations). CI builds first and runsthem properly.
eslint.config.mjsitself, and this file is in it — it was linted, not ignored.(2)
--format jsonreports 1 file, 0 errors, 0 warnings, exit 0. (3) The configenables no type-aware linting for any file — every
parserOptionsin it is{ ecmaVersion, sourceType }, with noprojectand noprojectService, a property itsown header states with a measured positive control. So no untouched file's verdict can
move because of this diff.
printed line, never from a bare
$?.Generated by Claude Code
Generated by Claude Code