Skip to content

fix(types): read the SPEC_DERIVED_PAIRS re-check off the AST, not raw text - #7277

Merged
yinlianghui merged 4 commits into
mainfrom
claude/issue-6705-parity-scan-comments
Sep 2, 2026
Merged

yinlianghui merged 4 commits into
mainfrom
claude/issue-6705-parity-scan-comments

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #6705

What was wrong

The SPEC_DERIVED_PAIRS re-check decided which zod mirrors are spec-derived by slicing the source between export const boundaries and testing the slice against a \bSpec[A-Z] word pattern. Two independent defects, both of which one docstring fired:

  1. A comment counted as a reference. Raw text cannot tell SpecGanttConfigSchema.extend(...) from the same token in prose.
  2. The window was not the declaration. The slice ran from export const X to the next export, so everything in between — private consts and the next export's own docstring — was charged to X.

The fix

specReferencingExports(fileName, source) parses instead of scanning:

  • Spec… identifiers come off the AST, so comments and string literals are not references. Nothing strips anything, so there is no comment-stripping regex to get wrong on a string containing * / sequences — a fixture pins exactly that string.
  • Attribution is by declaration. File-local references through private consts are resolved to a fixed point, so a mirror built from a spec-derived field map keeps its dependency; exported mirrors are not hopped through, since each is a registered pair carrying its own entry.

Two entries leave the derived list — please read this part

This is the one thing in the PR that is not purely mechanical, and it is reported rather than absorbed. Neither mirror changed; the re-check stopped mis-reading them. Neither references a spec schema:

Entry Why it was in the list
base.zod.ts#BaseSchema Its file's only Spec… token is in a comment: rather than calling `SpecSchema.omit({…}).partial()`. Prose.
objectql.zod.ts#ObjectViewSchema BaseSchema.extend({…}) where every member is a local literal. Its declaration ends at line 212; the next export const is ~50 lines later, and the old window swallowed the private KanbanConfig = SpecKanbanConfigSchema… block that belongs to no export.

⚠️ The ObjectViewSchema removal has a consequence this PR deliberately did not settle. This file's header records objectui#6058's remedy split, which routes ObjectViewSchema's unmirrored declared keys as SPEC-DERIVED — "the LOCAL declaration carries members the spec schema does not model, which is objectui#2231's unification question and NOT a local mirror edit". That routing rests on the false positive. Re-routing those keys is a remedy decision on the UnmirroredDeclared ledger, which this card was fenced out of, so the split is left standing as written, counts and all, with a ⚠️ note at the site telling whoever works it off to re-derive that side first. No ledger (KnownDrift / UnmirroredDeclared / RuntimeOnlyDeclared) and no corpus schema is touched.

Evidence — both directions, and the gate is not blinded

The card's fence is that a fix which stops seeing comments and stops seeing real references goes green on the old suite while destroying the gate.

Live reproduction (ablation 1). The pre-fix scanner, run against this branch's restored docstring, reproduces the reported failure exactly — and names the innocent neighbour:

AssertionError: a mirror gained or lost a spec dependency: expected [ …(11) ] to deeply equal [ …(10) ]
+   "objectql.zod.ts#ObjectTreeSchema"

Mutation proven on disk (old window code present = 1, new AST helper present = 0); restored by blob hash, 1a45bced… before and after.

Positive direction (ablation 2). Neutering the fixed helper to return an empty set reddens 5 tests — the live re-check plus every positive fixture:

AssertionError: ... expected [] to deeply equal [ …(8) ]
AssertionError: expected [] to deeply equal [ 'ObjectGanttSchema' ]   (x3)
AssertionError: expected [] to include 'ObjectGanttSchema'
Tests  5 failed | 7 passed (12)

Mutation proven on disk (injected marker = 1, removed expression = 0); restored by blob hash with git diff HEAD empty. All eight surviving pairs are found by code reference alone.

Seven fixtures pin the shape in memory: comment-only mention yields nothing; code reference fires; a Spec… token inside a string literal carrying */ and // is not a reference; a private const between two exports charges its prose to neither neighbour; a private const with a real reference is charged to its user, not its neighbour; the next export's docstring is not charged to the previous export; plus a non-vacuity pin against the real objectql.zod.ts.

The docstring restoration

PR #6704 reworded a docstring to dodge the literal token and left a trap comment asking the next editor to remember. That convention is what a gate is supposed to replace, so with the scanner fixed the workaround is retired and the docstring says what it wanted to say. This is the only change under packages/types/src/zod/, it is comment-only, and it doubles as the live positive proof above.

Gates run — all verdict lines quoted, against final commit 85e014af2

Gate Verdict
parity file Test Files 1 passed (1) / Tests 12 passed (12)
packages/types suite Test Files 84 passed (84) / Tests 1070 passed (1070)
@object-ui/types type-check exit 0 — tsc --noEmit && tsc -p tsconfig.examples.json && tsc -p tsconfig.test.json
check:control-bytes ✅ check-control-bytes: OK (scanned 6005 tracked text file(s); skipped 85 binary).
check:vi-mock-specifiers ✅ check-vi-mock-specifiers: OK (4129 tracked source file(s) …)
check:vi-mock-inherit ✅ check-vi-mock-inherit: OK (…)
check:spec-symbols ✅ spec symbol derivation: 1333 files scanned against 4959 spec export names; … 0 untriaged collisions
check:phantom-deps ✅ Every in-scope import is declared by the package that publishes it.
changeset presence ✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s) — empty frontmatter, "the explicit exemption and a complete answer to this gate"
check-changeset-fixed / -no-major / -overwrite all ✅

type-check is a real measurement, not a vacuous pass: tsc -p tsconfig.test.json --listFiles shows the edited test file (2 hits), objectql.zod.ts (1) and typescript/lib/typescript.d.ts (1) in the compiled set.

Declared narrowing — lint. Repo-wide turbo run lint is CI's run. Locally lint was narrowed to the affected package and the narrowing is measured, not assumed: ① the population came from eslint's own traversal under the root flat config, not a guess; ② --format json reports 148 files linted, 0 errors, 267 warnings, exit 0 — the package's own lint script (eslint .) verbatim; ③ eslint.config.js declares no projectService and no parserOptions.project, so linting is not type-aware and this diff cannot move the verdict of any file it does not contain.

Merge state. origin/main was merged at 1a1235c94 (picking up #7241, #7216, #7236, #7262), and every verdict above was taken after that merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b


Generated by Claude Code

… text

`zod-mirror-parity.test.ts` decided which mirrors are spec-derived by slicing
the source between `export const` boundaries and testing the slice against a
`\bSpec[A-Z]` word pattern. That had two independent defects, and one docstring
fired both: a `Spec…` token in PROSE counted as a reference, and the slice ran
past its own declaration to the next `export`, so the red named the neighbouring
export instead of the edited one.

`specReferencingExports()` parses instead. `Spec…` identifiers come off the AST,
so comments and string literals are not references and nothing has to strip
them. Attribution is by declaration; file-local references through PRIVATE
consts are resolved to a fixed point so a mirror built from a spec-derived field
map keeps its dependency, while exported mirrors are not hopped through.

Two entries leave the derived list as false positives of exactly those two
defects — `base.zod.ts#BaseSchema` (its file's only `Spec…` token is a comment)
and `objectql.zod.ts#ObjectViewSchema` (a `BaseSchema.extend` of local literals
whose window swallowed a private `SpecKanbanConfigSchema` block ~50 lines past
its end). No corpus and no ledger changes.

Seven fixtures pin both directions: a comment-only mention yields nothing, a
code reference still fires.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b
PR #6704 reworded this docstring to dodge a literal `Spec` + capital-letter
token and left a comment asking the next editor to remember, because the
`SPEC_DERIVED_PAIRS` re-check scanned raw text and misattributed the mention to
`ObjectTreeSchema`. The previous commit made that scanner parse, so the
convention held by discipline is no longer needed and the workaround is
retired: the docstring names `SpecGanttConfigSchema` in prose again, which is
what it wanted to say.

This doubles as the live positive proof for objectui#6705 — the pre-fix scanner
goes red on this file state, the fixed one stays green.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b
Empty frontmatter — the presence gate's own explicit exemption. The scanner
change is test-only and the one change under `packages/types/src/zod/` is a
docstring; no runtime, type or schema surface moves.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BGMDbrVa8JjZcCQ7DWYH1b
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 48 chunks) 3160.2 KB 3191.4 KB
Main entry chunk (gzip) 142.6 KB 350 KB
Entry file index-BOEfHVe7.js —
Status PASS —

The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it.


📦 Bundle Size Report

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.33KB 5.59KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 5.13KB 2.35KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 514.46KB 117.29KB
core (index.js) 5.55KB 2.23KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 178.20KB 49.60KB
fields (index.js) 244.25KB 61.73KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 4.28KB 1.75KB
i18n (index.js) 3.44KB 1.39KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 33.40KB 8.71KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.98KB 10.98KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.55KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useResponsiveConfig.js) 1.37KB 0.63KB
mobile (useSpecGesture.js) 4.32KB 1.64KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 11.71KB 4.29KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 5.12KB 1.74KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.75KB 3.80KB
plugin-calendar (index.js) 46.92KB 12.93KB
plugin-charts (index.js) 70.02KB 19.44KB
plugin-chatbot (index.js) 190.53KB 45.18KB
plugin-dashboard (index.js) 132.63KB 34.56KB
plugin-designer (index.js) 212.87KB 43.19KB
plugin-detail (index.js) 250.65KB 63.91KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 132.78KB 32.58KB
plugin-gantt (index.js) 166.77KB 40.76KB
plugin-grid (index.js) 208.88KB 56.59KB
plugin-kanban (index.js) 53.21KB 14.66KB
plugin-list (index.js) 113.51KB 27.67KB
plugin-map (index.js) 20.20KB 6.66KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 43.51KB 11.94KB
plugin-timeline (index.js) 29.34KB 8.47KB
plugin-tree (index.js) 8.98KB 3.08KB
plugin-view (index.js) 85.90KB 21.12KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 3.11KB 1.48KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 10.35KB 3.60KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.74KB 1.41KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.72KB 2.24KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 2.59KB 1.31KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants