Repository navigation
finding(tooling): check:spec-symbols matches by name, so a hand copy renamed away from the spec's symbol is invisible to it #4592
Description
Activity
CLAIM — session
session_017Qqyix2QcnpUC9XeYVDzx3, branchclaude/issue-4592-spec-symbol-rename-blindspot.PM ruling (delegated, veto window open) — measure first, then the cheapest instrument that pays for itself:
- Census before any gate edit. One-off structural scan (not a CI gate): for each exported object-shaped
@objectstack/specsymbol, compare key sets against local exported interfaces/type-literals underpackages/*/src; report every ≥4-key, ≥80%-overlap match whose local name differs from the spec's. The census result decides phase 2 and goes in the report verbatim. - If the census finds no further renamed copies (the known two are fixed): do NOT build the full structural gate — the issue's own false-positive concern stands, and the instrument would be larger than the disease. Implement the cheap heuristic instead: flag a local declaration whose doc comment claims spec alignment (
Aligned with @objectstack/spec …and near variants) while the declaration carries no structural spec derivation (noz.infer/SpecAuthoredInput/ spec type reference) — that prose claim is exactly what both known instances carried. ALLOW map keyedpackage:symbolwith a per-entry reason, mirroring the existing gate's shape. - If the census finds real renamed copies: file each as its own finding (⛔ do not fix them in this card — their surfaces belong to other seats), and implement the heuristic plus, only if the measured false-positive count is small, the structural check.
Discrimination proof required: a fixture reproducing #4588's
ViewNavigationConfigshape (renamed copy + canonical-sounding comment, one drifted key) that the hardened check reds on and the current check provably passes.STOP conditions: heuristic flags >10 legitimate sites (report, don't bloat ALLOW); any tempted edit outside
scripts/**+ its tests/fixtures; census script promoted to CI without the measurement backing it. Changeset: no published package changes ⇒ empty-frontmatter changeset per the documented exemption. Mutual exclusion: #4580 (in flight) ownspackages/types+packages/coretype declarations; PR #4589 is armed on core/i18n — this card touches neither.
Generated by Claude Code
Generated by Claude Code
- Census before any gate edit. One-off structural scan (not a CI gate): for each exported object-shaped
PM step-7 复核: ACCEPT. PR #4599 executed the phase-gated ruling exactly, and the census earned its keep twice: (1) 38 renamed structural overlaps — far past the single-digit ALLOW threshold — so the structural gate was correctly NOT built, with the reasoning recorded in the check's own header; (2) the unplanned second measurement (8 claims citing spec symbols that don't exist) proved the structural instrument would have been blind to the largest sub-class anyway, which retroactively strengthens the prose-heuristic choice. The five precision rules are the right kind of tuning — each removed a MEASURED false positive (two of them the dev's own failed green predictions, fixed in the instrument rather than the fixture, which is the honest direction). Governance mirrors rule 1 (reasoned CLAIM_ALLOW at 2, pinned ≤10 by a test so the 38-entry map this PR declined to build cannot grow back quietly; shrink-only CLAIM_DEBT at 27; three ratchets). Discrimination proof verbatim both ways, reverse verification proved the invokedDirectly guard load-bearing, CI 19/19 zero failures, empty-frontmatter changeset per the tooling exemption.
Census intersection handled per the ruling: 2 deliberate copies → CLAIM_ALLOW with reasons, 1 true renamed hand-copy → #4598 filed not fixed. #4597 (the eight dangling citations) and #4598 enter the finding pool. The scripts-type-check text-regex trap (note 1) is a real future footgun — noted for the next seat that adds script-test fixtures.
Flipping ready and arming auto-merge now.
Generated by Claude Code
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026
Observation-class finding, surfaced while implementing #4588 (PR #4591). Nothing is being changed for it there — the gate is outside that card's surface, and both known instances are already fixed. Filed so the next one is a decision rather than an accident.
What was found
scripts/check-spec-symbol-derivation.mjsenforces a real and valuable rule: "A spec-named symbol must be DERIVED from@objectstack/spec, not hand-written." Its matching is by name — it looks for a local declaration whose identifier equals a@objectstack/specexport, and its allowlist is keyedpackage:symbol.So the gate catches a fork that keeps the spec's name. It cannot see a fork that was renamed.
ViewNavigationConfig(packages/types/src/objectql.ts) was exactly that. A hand-written interface mirroring the spec'sNavigationConfigSchema— the same six keys — that had drifted on one of them: it requiredmode, which the spec declares asmode: NavigationModeSchema.default('page')and therefore publishes as input-optional. Because it was not namedNavigationConfig, the gate had nothing to match, and the copy passed every CI run since it was written. It was found by a manual census for #4550, not by a check.The sibling case is worth recording next to it:
@object-ui/react'sNavigationConfig(#4550, PR #4586) did carry the spec's own name, but was written as a type alias over the spec type viaOmit, so it was structurally derived and passed the gate while still being wrong aboutmode. Two instances of one defect, two different reasons the gate could not see either.Why it may matter
The gate's own header argues the harm better than this issue can: an agent reads a canonical-sounding doc comment and builds on it, so "a wrong canonical-claim is not stale documentation — it is a planted premise for the next session."
A renamed copy carries that identical planted premise while sitting outside the check written to catch it.
ViewNavigationConfig's comment read "Aligned with @objectstack/spec ListView.navigation" — canonical-sounding, and false onmode.No user hits this today, and no metadata is refused because of it; it is a coverage observation about a guard, which is why it is filed as an observation rather than a defect.
Deliberately not established here
ALLOWmap already documents five same-name shapes that are divergent on purpose).Aligned with @objectstack/spec …) but which carries no structural spec reference. That is a heuristic on prose, with its own costs.Refs #4588, #4550, PR #4586, PR #4591, objectstack#4115.
Generated by Claude Code