chore(tooling): a spec-alignment claim is judged by the symbols it cites, not by incidental ties (#4607) - #4609
Merged
Conversation
…tes, not by incidental ties (#4607) Rule 2 of `check:spec-symbols` flags an exported declaration whose doc comment claims `@objectstack/spec` alignment while the declaration references nothing spec-bound. That tie test was symbol-AGNOSTIC — it asked whether the declaration referenced ANY spec-bound identifier — so a claim about symbol X passed on an incidental reference to an unrelated symbol Y. `FeedItem` (packages/types/src/views.ts) was the live specimen: it cited `FeedItemSchema`, removed from `@objectstack/spec/data` in the 16.0.0 major, and never appeared in a single gate run purely because one member is typed `FeedItemType` — the one feed symbol the removal kept. Measured on origin/main@92876f097, the scanner returns 0 findings for that shape; it returns the finding after this change. The tie is now judged against the symbols the claim CITES: when a claim names symbols and the installed spec exports none of them, the declaration is flagged whatever else it references. Deliberately narrow — a claim naming at least one LIVE symbol stays governed by the tie test unchanged, since a claim-vs-tie mismatch among live symbols needs a name-relatedness allowance for the legitimate `type: FeedItemType` shape, and that is a different instrument. Making `symbols` load-bearing exposed two things that were harmless while it only decorated a failure message: - the citation window scraped the capitalised opening words of the NEXT sentence and reported them as cited symbols (`ActionDef` cites `ActionSchema`, and prose `Open`). Symbols are now taken from the mention's own sentence, the same discipline the claim/mention pairing already applies; - the test suite's spec-name stub omitted `ListView`, which the spec really exports, which made two green fixtures red for a reason existing nowhere but that map. Measured repo-wide: the hidden population is zero. 20 findings before, 20 after, 0 newly flagged, 0 no longer flagged; CLAIM_ALLOW 2 and CLAIM_DEBT 18 in 5 packages, unchanged both sides. Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3 Co-authored-by: Claude <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
yinlianghui
marked this pull request as ready for review
August 13, 2026 19:37
yinlianghui
pushed a commit
that referenced
this pull request
Aug 13, 2026
…anode-round2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
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 #4607
The hole
Rule 2 of
check:spec-symbolsflags an exported declaration whose doc comment claims@objectstack/specalignment while the declaration references nothing spec-bound. Thattie test was symbol-AGNOSTIC — it asked only
so a claim about symbol X passed on an incidental reference to an unrelated symbol Y.
FeedItem(packages/types/src/views.ts) was the live specimen: it citedFeedItemSchema,removed from
@objectstack/spec/datain the 16.0.0 major, and never appeared in a singlegate run — purely because one member is typed
FeedItemType, the one feed symbol thatremoval kept. It sat four lines from a section banner making the same claim, in the same
file as four declarations that were flagged, and it was found by reading the file
rather than by any run of the script.
The asymmetry is what made it expensive: the more spec-integrated a declaration was, the
weaker the check on its prose became. A fully hand-written fork got its claim scrutinised;
one importing a single live spec type for one member did not.
The rule
Deliberately narrow, per the ruling recorded on #4607:
unchanged;
name-relatedness allowance for the legitimate
type: FeedItemTypeshape, where citationand tie are genuinely different-but-related symbols, and that is a different instrument;
stays out of the way entirely, rather than manufacturing a verdict from ignorance of the
spec.
The failure message keeps its existing shape — the
danglinglist was already rendered("…and names
X, which @objectstack/spec does not export"). What changed is that anincidental live tie no longer suppresses the finding.
Discrimination proof, verbatim both ways
One fixture (the specimen: claim phrase, citation of a dangling symbol, live tie to a
different symbol via
import+ member use), one spec-name map faithful to the pinned17.0.0-rc.6 (
FeedItemTypelive,FeedItemSchemaabsent — both verified against theinstalled package), run through both scanners:
The current gate provably passes it. Both halves are pinned in the suite, and the green
half of the pair is byte-identical to the red one except for which symbol the claim cites
(
FEED_ITEM_SPECIMEN.replace('FeedItemSchema', 'FeedItemType')) — so the discrimination isthe citation, not the fixture.
Reverse verification (rule reverted by patch +
git checkout, tests kept, restored andsha256-verified identical — no
git stash):Exactly the two tests that pin this change go red; every #4592 must-not-change fixture and
every new precision guard stays green without the fix, which is what makes them guards
rather than restatements of the rule.
Population census, verbatim
Both scanners over the identical file set, diffing raw finding lists rather than the gate
verdict (the verdict hides anything already in the
CLAIM_DEBTledger):The hidden population is zero. So that zero is a measurement and not an absence of
firing, here is the surface the sharpened rule newly examines — every exported declaration
carrying a claim that the old tie test waved through:
Dispositions: none needed. No instance requires a reword, a
CLAIM_DEBTentry, or aCLAIM_ALLOWentry — the population is empty, so the10-instance STOP condition is notapproached. PR #4606 removed the only known specimen as in-file collateral; this card
closes the hole it demonstrated and confirms the tree has no second one. Nothing under
#4580-round2's in-flight surfaces was read for a verdict or edited: the only files this PR
touches are
scripts/**plus one changeset.Two things the change exposed
Making
symbolsload-bearing surfaced two defects that were invisible while it onlydecorated a failure message. Both are fixed here because both are ways this change could
have gone wrong, not scope growth:
The citation window scraped the next sentence.
ActionDefreads "…mirroring@objectstack/spec'sActionSchema. Open key set on a data bag is correct" — andOpenwas reported as a cited symbol. Harmless as message decoration; as a rule inputit is a new false-positive class: a claim whose only "citations" are prose words is
all-dangling, so a green declaration with a live tie would go red with a nonsense
message. Measured and reproduced before fixing. Symbols are now taken from the
mention's own sentence — the same discipline the claim/mention pairing already applies —
and the truncation is a measured no-op on the tree (census above is post-containment;
ActionDefmoved frommixedtoall-cited-livewith no verdict change). It must notfire on
ListView.navigation, where the.is a member separator; pinned.The suite's spec-name stub omitted
ListView. The real spec exports it from@objectstack/spec/ui(verified). WhilespecNameswas decoration the omission changednothing; the moment it decides whether the tie test applies, two long-standing green
fixtures went red for a reason existing nowhere but that map. The map is now faithful to
rc.6 for every name the fixtures touch, with the reason written at the declaration.
Residual, pinned as a known limitation rather than fixed: a claim sentence that never
terminates still donates prose words (
PageNodeSchemacitesPageSchema, This, SDUI, NODE). It costs nothing today —PageSchemais live, so the tie test governs — andtightening it further means deciding what a citation looks like, which is a different
instrument. See the open question below.
The #4606 idiom stays green — pinned against the real tree
PR #4606's rewordings name the dead symbol on purpose; that provenance is the remedy this
guard exists to produce, so a rule that turned on it would punish its own fix. Pinned two
ways:
PluralRuleSchema, retired in 17.0.0-rc.6…" and "its citedFeedItemSchemawent withthe 16.0.0 feed removal";
packages/types/src/views.tsandpackages/i18n/src/utils/spec-formatters.tsarescanned as they exist on
mainand must return no finding, plus a check that they stillcarry the load-bearing provenance phrases so the pin cannot be satisfied by deleting the
comments. Both green. What makes them green is that they claim nothing: no alignment
phrase sits next to a
@objectstack/specmention, so there is no claim to have anythingbehind.
Governance — unchanged, both sides
CLAIM_ALLOWCLAIM_DEBTALLOW/DEBTNo ledger regeneration was needed (nothing new to ledger),
CLAIM_PATTERNSwas notwidened, and all three ratchets are untouched. Gate output is identical before and after:
Verification
pnpm exec vitest run --maxWorkers=2 scripts/— 42 files, 957 tests passed (943 before;14 added).
pnpm type-check:scripts— green.eslinton both changed files — clean, 0 problems.check-{control-bytes,phantom-dependencies,changeset-presence,changeset-no-major,changeset-fixed,type-check-coverage,lint-coverage,spec-symbol-derivation,doc-links}.mjs.packages/**file changed, so no.d.tsmoves and no per-package patch is owed;check-changeset-presenceagrees ("0 of them under the src/ of a package the releasecovers"). The changeset is empty-frontmatter, tooling-only, never major.
Open question for the PM
The sentence-scoped citation window (item 1 above) is a narrowing I made inside this
card because the false-positive class it contains is one this change would otherwise have
introduced. If you would rather it were a separate instrument change, it is a self-contained
15-line hunk in
findClaimplus three tests and can be split out — the sharpened tie ruledoes not depend on it, only its precision does.
Generated by Claude Code