chore(tooling): spec-symbol gate sees renamed hand-copies via their own alignment claims (#4592) - #4599
Merged
Conversation
…wn alignment claims (#4592) `check:spec-symbols` matches BY NAME, so a hand copy that was RENAMED away from the spec's symbol has nothing for it to match. `ViewNavigationConfig` (#4588) was exactly that — the spec's six navigation keys, hand-written, drifted on `mode`, under the comment "Aligned with @objectstack/spec ListView.navigation" — and it passed every CI run until a manual census found it. Rule 2 flags an exported declaration whose doc comment claims alignment with `@objectstack/spec` while the declaration references nothing spec-bound, with the governance the file already uses: a reasoned CLAIM_ALLOW map (2 entries) and a shrink-only CLAIM_DEBT ledger (27 symbols, regenerated by --claim-ledger). The structural alternative was built as a one-off census and measured before being rejected: 38 sites at >= 0.80 key overlap with a spec object type, nearly all legitimately distinct layers, and blind to the largest sub-class — eight declarations cite spec symbols the spec does not export. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Qqyix2QcnpUC9XeYVDzx3
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
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 #4592
scripts/check-spec-symbol-derivation.mjsmatches BY NAME, so a hand copy that was renamed away from the spec's symbol has nothing for it to match.ViewNavigationConfig(#4588) was exactly that — the spec's six navigation keys, hand-written, drifted onmode, under the comment "Aligned with @objectstack/spec ListView.navigation" — and it passed every CI run until a manual census found it.This adds rule 2 to the same file: flag an exported declaration whose doc comment claims alignment with
@objectstack/specwhile the declaration references nothing spec-bound. That prose claim is the one thing both known instances carried.Surface:
scripts/**plus its test plus one changeset. Three files, no package source touched.Phase 1 — the census, before any gate edit
A one-off structural scan (scratchpad only, not wired to CI, not in this diff): for every exported object-shaped
@objectstack/spectype, compare key sets against local exported interfaces and type-literals underpackages/*/src, and report every match with >= 4 shared keys and >= 0.80 Jaccard overlap whose local name differs from the spec symbol's.The 38 hits, in full:
MarketplacePackageTranslationapp-shell/.../marketplaceApi.ts:89PackageTranslationLocalizedPackageapp-shell/.../usePackageL10n.ts:79PackageTranslationApprovalActionAttachmentLiteapp-shell/.../useRecordApprovals.ts:120ApprovalActionAttachmentApprovalActionLiteapp-shell/.../useRecordApprovals.ts:134ApprovalActionRowContextSelectorDefapp-shell/src/layout/ContextSelectors.tsx:56AppContextSelectorRawActionParamapp-shell/.../resolveActionParams.ts:77ActionParamParsedFormSectionSpecapp-shell/.../SchemaForm.tsx:423FormSectionFormFieldSpecapp-shell/.../SchemaForm.tsx:436FormFieldInputDecisionEdgeapp-shell/.../flow-decision-edges.ts:38FlowEdgeFlowDesignerEdgeapp-shell/.../flow-canvas-layout.ts:129FlowEdgeSimEdgeapp-shell/.../flow-sim-types.ts:28FlowEdgeAuthSocialProviderauth/src/types.ts:160AuthProviderInfoPreviewModeOptionsauth/src/types.ts:403PreviewModeConfigAuthOrganizationauth/src/types.ts:429OrganizationResultDialogSpeccore/src/actions/ActionRunner.ts:506ActionResultDialogTranslationElementDataSourceConfigcore/.../element-data-source.ts:75ElementDataSourceResolvedResponsiveConfigcore/.../ResponsiveProtocol.ts:47ResponsiveConfigResolvedSharingConfigcore/.../SharingProtocol.ts:54SharingConfigDashboardFilterDefcore/.../dashboard-filters.ts:34GlobalFilterMetadataLayereddata-objectstack/.../metadata-client.ts:243GetMetaItemLayeredResponseFileValueViewfields/.../file-value.ts:35ApprovalActionAttachmentAppShellBrandinglayout/src/AppShell.tsx:13AppBrandingSpecResponsiveConfigmobile/.../useResponsiveConfig.ts:26ResponsiveConfigNormalizedAxisplugin-charts/.../normalizeChartSchema.ts:122ChartAxisAgentCapabilitiesplugin-chatbot/src/useAgents.ts:23AiAgentCapabilitiesBulkResultplugin-grid/.../useBulkExecutor.ts:27MetadataBulkResultSavedMappingEntryplugin-grid/src/savedMapping.ts:6ImportFieldMappingCacheManagertypes/src/data-protocol.ts:1420ICacheServiceBatchTransactionOperationtypes/src/data.ts:185CrossObjectBatchOperationImportFieldMappingEntrytypes/src/data.ts:872FieldMappingEntryImportRequestOptionstypes/src/data.ts:884CreateImportJobRequestImportRecordsResulttypes/src/data.ts:938ImportResponseCreateImportJobResulttypes/src/data.ts:960CreateImportJobResponseImportJobProgressInfotypes/src/data.ts:978ImportJobProgressImportJobSummaryInfotypes/src/data.ts:1024ImportJobSummaryImportJobUndoResulttypes/src/data.ts:1046UndoImportJobResponseListImportJobsOptionstypes/src/data.ts:1062ListImportJobsRequestBPMNEdgetypes/src/designer.ts:264FlowEdgeBoth known instances are correctly absent, as the ruling predicted: #4588's
ViewNavigationConfigis nowexport type ViewNavigationConfig = NavigationConfigand #4550's sibling is a spec-derived alias, so neither is a hand-written shape any more.Phase 2 — the decision the census forced
The structural gate was NOT built. 38 standing exemptions is not a guard, it is a second copy of the codebase, and nearly all 38 are legitimately distinct layers (
ApprovalActionLiteis a projection,SimEdgeis the simulator's own node,BulkResultis four counters). That is far outside the "single digits" the ruling set as the condition for building it.A second, unplanned result made the case stronger: the structural instrument is blind to the largest sub-class. Eight declarations claim alignment with spec symbols the installed spec does not export at all —
DateFormatSchema,NumberFormatSchema,PluralRuleSchema,LocaleConfigSchema,FieldChangeEntrySchema,MentionSchema,ReactionSchema,RecordSubscriptionSchema. A key-set comparison has nothing on the other side to compare against; only the prose says anything at all. Filed as #4597.The prose heuristic was built, with
CLAIM_ALLOW(reasoned, per-entry) and a shrink-onlyCLAIM_DEBTledger, mirroring the file's existingALLOW+DEBTshape, and--claim-ledgerregenerating the ledger so it is never hand-maintained.Red-first: the discrimination proof
Run over an identical tree — the real repo plus one fixture package — with predictions written before the runs. The fixture is #4588's declaration renamed (
ViewNavigationConfigFixture), six keys,moderequired where the spec publishes it input-optional, under the canonical-sounding comment.Current gate (
origin/main), predicted PASS:Hardened gate, identical tree, predicted RED:
The dangling-symbol annotation, on a fixture citing
ReactionSchema:Green cases, all five on the same tree, exit 0: a
z.inferderivation, aSpecAuthoredInputderivation, a similarly-shaped local type with no claim, a claim phrase belonging to a different sentence than the spec mention, and a renderer carrying an alignment claim. Plus, from the test suite: an interface that merely uses a spec type on a member, an unexported declaration, and a licence banner that must not donate its prose to the first declaration.Reverse verification (patch +
git checkout+ sha256-verified restore; nogit stash). With the fix removed, the new suite cannot even run:— which also proves the new
invokedDirectlyguard is load-bearing rather than cosmetic. Restore verified:scripts/check-spec-symbol-derivation.mjs: OKagainst the pre-revert sha256.False-positive analysis — two predictions failed, and the heuristic changed both times
This is the part worth reviewing. The first pattern set flagged 51 declarations. Three precision rules brought that to 27, and two of them exist because a fixture I predicted green came out red:
ChartDataSeriesreads "positionally aligned with the chart'scategories" and separately explains what@objectstack/spec/uiowns. A bare co-occurrence test flags a model citizen.SPEC_ACTION_KEYSas a now-stale ledger entry — a second real false positive removed by the same rule.AuthProvider/ListView/UserFiltersALLOW entries already make. Suppresses 9 sites.Where the 27 remaining sit: essentially none is a false positive in the sense that the flag is wrong — every one is a spec-alignment claim with no compile-time tie. They split into deliberate-and-reasoned (
CLAIM_ALLOW) and untriaged (CLAIM_DEBT).CLAIM_ALLOW — 2 entries, both with the reason already written at the declaration
@object-ui/app-shell:MarketplacePackageTranslation— the app-shell bundle must not pull in@objectstack/specfor five translatable marketplace strings. A bundle-size duplication is a decision, and the comment says so rather than claiming the copy IS the spec's type.@object-ui/core:ElementDataSourceConfig—filteris typedunknownrather than the spec'sFilterConditionbecause three legitimately different filter shapes reach a renderer. The comment names the diverging key and its reason.Deliberately kept to 2.
RecordLookupBindingreads as a candidate but its own comment says "import it once a published ^16 release…" — that is temporary, so the ledger is the honest home, not a permanent exemption. The remaining 25 sit on surfaces this card does not own (#4580 holdspackages/types), and triaging them is the owning seat's call, not ascripts/**card's. A test pinsCLAIM_ALLOWat <= 10 entries so it cannot quietly become the 38-entry map this PR declined to build.CLAIM_DEBT — 27 symbols, shrink-only, mechanically regenerated
@object-ui/types17,@object-ui/i18n4,@object-ui/core2, and one each inapp-shell,mobile,plugin-view,react. Every entry is--claim-ledgeroutput verbatim; the ratchet fails the build if one stops matching, so it cannot outlive what it excuses. Eight of them are the dangling-citation cases (#4597) and one is a true renamed hand-copy (#4598).Verification
node scripts/check-{control-bytes,phantom-dependencies,changeset-presence,changeset-no-major,changeset-fixed,type-check-coverage,lint-coverage,spec-symbol-derivation,doc-links}.mjs— all 9 PASS.13 declared dialects, 3 untriaged collisions(rule 1 output byte-identical toorigin/main) and2 declared deliberate copies, 27 unbacked claims in 7 packages.pnpm exec vitest run --maxWorkers=2 scripts/— 42 files, 943 tests, all pass (20 of them new).pnpm type-check:scripts— exit 0.pnpm exec eslinton both touched files — 0 problems.grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]'over every touched file — clean.One incidental catch worth flagging to reviewers:
scripts-type-check.test.tspins that no file in the scripts tsconfig program imports an@object-uipackage, and it looks for the import text — so a fixture string inside a template literal tripped it and would have moved a CI step below the workspace build for no real dependency. The fixture now uses a relative specifier, with a comment saying why, since the helper is recognised by name rather than by module.Changeset
Empty-frontmatter (
.changeset/spec-symbol-rename-blindspot-4592.md) — tooling only, releases nothing, per the documented exemption.Out of scope, filed unassigned
SpecResponsiveConfigis a renamed hand-copy of the spec'sResponsiveConfig; it agrees with the spec today, which is precisely the "one spec release away from drifting" state.Neither is fixed here: #4592 owns
scripts/**, and those surfaces belong to other seats.Generated by Claude Code