Skip to content

fix(core)!: retire the four lowercase $-dialect aliases from convertFiltersToAST (objectui#8568) - #8988

Merged
os-steve merged 4 commits into
mainfrom
claude/issue-8568-retire-lowercase-aliases
Sep 10, 2026
Merged

fix(core)!: retire the four lowercase $-dialect aliases from convertFiltersToAST (objectui#8568)#8988
os-steve merged 4 commits into
mainfrom
claude/issue-8568-retire-lowercase-aliases

Conversation

@os-steve

@os-steve os-steve commented Sep 10, 2026

Copy link
Copy Markdown
Collaborator

Fixes #8568

Clause-②: yes — this lane's in-seat default-tier review plus the gate table below is the review of record.

The ruling this implements

The director seat ruled option 1 on this card (comment 5617467534), from the maintainer verbatim: 「我们的项目以objectstack 协议为准,文档应该以实际实现为准。」 The $ dialect follows @objectstack/spec's canonical spellings; the four lowercase aliases retire with no deprecation window (2026-08-27 「不渐进」); ValueDataSource is not touched (objectui#8447's direction stands); README rows and this repo's reconciliation pin follow the implementation.

What changed

convertOperatorToAST's operatorMap no longer carries $notin, $notcontains, $startswith, $endswith. All four are now refused by name, and the refusal prescribes the canonical spelling:

[ObjectUI] The '$startswith' filter operator is retired. Write '$startsWith' instead —
the canonical spelling '@objectstack/spec' declares in FILTER_OPERATORS
(data/filter.zod.ts). The operator itself is unchanged; only the key is renamed.
Field: 'email', Value: "a". It used to be accepted here as a lowercase alias while the
in-memory matcher refused it, so the same filter selected rows through one data source
and none through the other (objectui#8568).

A named arm rather than the generic unknown-operator paragraph, because with no deprecation window that message is the whole migration aid: "unknown operator, here is the supported list" makes an author diff two lists to find which entry they meant, while "write $startsWith" is the repair.

The prescriptions are derived, not restated. RETIRED_OPERATOR_ALIASES is read out of the source by the new pin and both directions are executed against @objectstack/spec: every KEY must be absent from FILTER_OPERATORS, every VALUE present in it and lowerable here. A fifth alias added later, or a prescription naming a spelling the spec does not declare, reddens.

There is also a spec-derived invariant that outlives this card: for every camelCase member of FILTER_OPERATORS, its all-lowercase spelling must not be accepted. That is commandment #0.1 itself, read off the spec rather than off the four names this card happened to retire.

The pin reddened exactly as designed

PR #8565's reconciliation pin reads operatorMap out of filter-converter.ts's source. After the code change and before the README edit, it named the four rows that had to move and nothing else:

packages/data-objectstack/README.md:143: $notin is refused by convertFiltersToAST, so it is not a supported operator
packages/data-objectstack/README.md:146: $notcontains is refused by convertFiltersToAST, so it is not a supported operator
packages/data-objectstack/README.md:147: $startswith is refused by convertFiltersToAST, so it is not a supported operator
packages/data-objectstack/README.md:148: $endswith is refused by convertFiltersToAST, so it is not a supported operator

Before the code change: exit 0, 10 passed. After the code change, before the README: exit 1, 1 failed. After the README: exit 0, 10 passed. The mechanism is worth naming, because it is not the one you would guess: the "row for every key of the operator map" case gets weaker when keys are removed. What reddened is the supported table's first column naming a spelling the code now refuses. The four alias spellings moved into the Refused at lowering time table, where the same pin now executes their refusal on every run.

The second pin the retirement moved: PR #8977's census

PR #8977 merged into main while this branch was in flight, and merging it in turned one of its cases red — a genuine cross-PR interaction on the same card, not a conflict git could see. Its live-tree case asserted aliases.length > 0, rows.length > 0 and selfCarved > 0 unconditionally. The census derives its subject set as operatorMap minus spec, so retiring the four makes all three false at once, by construction. The census SCRIPT already models the empty tree ("no aliases derived, so there is no twin to light") and a synthetic unit case already pins it; only the live-tree case carried the pre-ruling shape.

Repaired rather than deleted, because the blindness guard those three assertions carried is real. On an empty tree the instrument's reach is proved by the authored-reach control — it counts CANONICAL $-operator payloads in non-test authored files, so it cannot be satisfied by a scanner that never opened the authored corpus — and the empty set is then held to being DERIVED rather than the residue of a failed read on either side. The pre-retirement branch is kept intact, so the case still holds on any tree that carries an alias again, including a revert.

The census now reads, on this head, exactly what the ruling predicted:

operatorMap keys (13) : $eq $ne $gt $gte $lt $lte $in $nin $between $contains $notContains $startsWith $endsWith
spec FILTER_OPERATORS (16) : ... $icontains $null $exists
ALIASES = accepted but not canonical (0) : (none)
PASS  authored-reach (positive, want lit)  9 canonical $-operator payload(s) in NON-TEST authored files
Alias occurrences, whole tree : 0

Ablation — three legs, each on the committed tree

Every mutation was proven on disk before any result was read (anchor counts plus a git hash-object differing from the HEAD blob), restored with git checkout HEAD -- ABSOLUTE_PATH, and the restore proven by an EMPTY git diff HEAD plus blob-hash equality, under trap ... EXIT INT TERM on absolute paths. No build leg applies: the root vitest config aliases @object-ui/core to packages/core/src, and both pins import their subject by relative source path, so there is no dist indirection to stale.

leg mutation result
baseline none exit 0 — 4 files, 70 passed
A one retired alias put back into operatorMap exit 1 — 3 files failed, 10 tests failed
B the named-refusal arm deleted, so aliases fall through to the generic message exit 1 — 1 file failed, 8 tests failed
C deriveAliases stubbed to return [] exit 1 — 2 tests failed in the census suite
restored (each leg) exit 0, git diff HEAD empty, blob equals the HEAD blob

Leg A reddens across three files, including PR #8565's pin, so the retirement is enforced from more than one direction. Leg B leaves the envelope assertions green and moves only the naming cases — which is the point: a plain refusal is not what this card bought.

Two defects the ablation found in the instruments, with the direction reported as observed

Leg B first ran 4 red, not 8. The case asserting the refusal names the canonical spelling stayed GREEN with the named arm deleted, because the generic unknown-operator message's own Supported operators: list already spells $nin, $notContains, $startsWith and $endsWith. toContain(canonical) was a case that passed while witnessing nothing. The assertion now pins the PRESCRIPTION phrase, which only the named arm can produce; re-running the same ablation moved leg B from 4 red to 8 red.

Leg C did not redden the census guard it was aimed at. The repaired live-tree case first re-used deriveAliases to check its own subject set, and a stub returning [] satisfies its own output — so that branch would have passed while witnessing nothing too. The subset question is now recomputed in the test (every accepted key must be a canonical one), which is what makes an alias re-added to the map red there even when the census's own derivation is what broke. Leg C's 2 reds are the unit-level deriveAliases cases, i.e. the file as a whole always caught that mutation; the point is that the new branch now carries its own share.

Census: the in-repo repair step is a measured no-op

The ruling's execution step 1 — census the in-repo authored corpus — landed as PR #8977: 24 alias occurrences tree-wide, 7 in operator-key position, ZERO in the authored corpus. Every tree-wide hit was the map that defined the aliases or something pointing at it. So step 2, "in-repo hits get corrected to canonical spellings first", had nothing to correct, and this PR is the map plus its ledger.

That zero is consumer-local, not seam-wide (objectui#6839). Stored view / list / sharing-rule criteria, producer-side metadata and published consumers of @object-ui/core are all invisible from here. One thing about that population is measurable and it lowers the marginal cost: kvToCondition — the reader that loads stored $-criteria back into the filter builder — has arms for 15 spellings and none of these four, so a stored lowercase criterion already failed to round-trip and dropped the admin into the raw-JSON editor. Re-verified here against the current source, not carried forward from the census.

Gates — all on the final head, after origin/main was merged in

origin/main advanced from 6109e5daa to ca67d42f0 mid-task (PR #8977 and PR #8967 landed) and is now an ancestor of this head. The merge was clean — no conflicts — and every gate below was run after it. Exit codes captured to disk before being read, never through a pipe.

gate exit note
turbo run type-check lint (core, data-objectstack) 0 8 tasks
type-check:scripts 0
turbo run lint --concurrency=2 — the whole graph, as CI runs it 0 47/47 tasks, 0 errors repo-wide
vitest run packages/core/ packages/data-objectstack/ scripts/ 0 335 files, 7784 tests, 2 skipped
vitest run packages/fields/ packages/plugin-grid/ (the other convertFiltersToAST readers) 0 275 files, 3722 tests — run pre-merge; neither package is in this diff
turbo run build --concurrency=2 0 44/44 — prerequisite for the doc gates
check:control-bytes 0 plus a direct grep -naP over every file in the diff, no hits
check:changeset-presence 0 5 source files of 2 released packages, 1 changeset declared
check-governed-queue-guard.mjs --test (8 paths) 0 NOT GOVERNED — ordinary review and merge-queue route
check:doc-snippets 0 638/638 blocks judged
check:doc-types 0
check:doc-examples 0 124 blocks, 89 ledger rows all matching
check:new-line-citations 0 differential, re-derived against the new merge-base
check:readme-exports 0 exit 1 = PREREQUISITE NOT MET before the build; 0 after
check:doc-fences · check:spec-symbols · check:unreferenced-sources · check:vi-mock-* · check:comment-mask-corpus 0 derived from the diff, not from the brief
census:dollar-dialect-aliases 0 all four controls PASS; alias set now empty by construction

Changeset

minor with the break spelled out in the body: this repo forbids major, and this is breaking for anyone spelling those four in lowercase. The repair is a key rename; the lowered node is unchanged and no result set moves for a filter that was already spelled canonically.

Acceptance notes — out of scope, noted, not filed

  • The check:doc-examples ledger is NOT keyed by line number. The dispatch brief (and its own correction) both state that UNGATED_EXAMPLES is keyed path:LINE symbol, so any line added above a ledgered @example silently invalidates that row and CI will not say so. Measured false: the key is path symbol #ordinal (scripts/check-doc-example-types.mjs:172, and every row from :500), so line displacement does not move a row — only a renamed symbol or a changed @example ordinal does. This diff pushed filter-converter.ts's @example blocks down by roughly 34 lines and the gate stayed at 89/89. It was moot here anyway: no filter-converter.ts row exists in that ledger. Successor: the dispatching seat, which will otherwise keep repeating the arithmetic warning.
  • The alias line numbers in the brief were stale, as the brief itself predicted. Assumed L69/L73/L75/L77 with the comment at L59; actually L75/L79/L81/L83 with the comment at L64. Re-derived by anchor. Successor: none needed.
  • $ncontains has no prescription. packages/core/src/adapters/README.md:154 groups it with the four retired aliases as a "non-canonical spelling", and kvToCondition still reads it back — deliberately, it is the spelling FilterConditionField used to emit. convertFiltersToAST has never accepted it, so it is outside this retirement: it falls to the generic unknown-operator arm, loudly and correctly. Recorded as the boundary of this change rather than widened into it. Successor: whoever revisits FilterConditionField's legacy read arms.
  • Serial adjacency went stale inside this task, again. The first derivation found 9 open PRs; by the time the branch was ready there were 11, with test(scripts): census the $-dialect lowercase aliases before objectui#8568 is ruled #8977 and feat(types)!: retire cancelLabel / confirmLabel / confirmVariant from AlertDialogSchema #8967 merged and four new ones opened. No open PR touched any file in this diff at either reading. PR feat(core): isEmptyValue — the shared emptiness floor, and the surfaces that state their answer against it #8981 is the nearest — packages/core/src/utils/emptiness.ts and packages/core/src/index.ts, neither of which this PR reads or writes. Treat that as a timestamp, not a fact.

Serial adjacency and the open-PR set were re-derived here against currently-open PRs via repo-scoped REST, not carried from any snapshot.

Session, written as prose and as a code span so it survives a body edit: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB

🤖 Generated with Claude Code

https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB

`convertOperatorToAST`'s `operatorMap` carried `$notin`, `$notcontains`,
`$startswith` and `$endswith` "for tolerance" while `ValueDataSource` refused
the same four by design (objectui#8447: alias arms there "would fossilise a
second dialect"). One authored filter therefore had two fates depending on
which data source was behind the view. The maintainer ruled the tolerant side
out: the `$` dialect follows `@objectstack/spec`'s spellings, with no
deprecation window, and the documentation follows the implementation.

The four are refused BY NAME and the refusal names the canonical spelling, so
the repair is a key rename rather than a diff of two operator lists. A generic
unknown operator still gets the generic message; that asymmetry is pinned.

`packages/data-objectstack/README.md`'s tables and PR #8565's reconciliation
pin follow the code: the pin reddened on exactly the four rows that had to
change, which is the signal it was built to give.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
…568)

Ablating the named arm left `names the canonical spelling` GREEN: the generic
unknown-operator message's own "Supported operators:" list already spells
`$nin`, `$notContains`, `$startsWith` and `$endsWith`, so `toContain(canonical)`
was a case that passed while witnessing nothing. The prescription phrase is what
only the named arm can produce, so that is what is asserted now, with the
measurement recorded beside it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MPaVWWMuWeT5LgB1qoXjVB
@github-actions github-actions Bot added documentation Improvements or additions to documentation data-adapter package: core tests labels Sep 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3484.3 KB 3512.7 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-B2VK_Toe.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) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 500.20KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 28.04KB 9.46KB
data-objectstack (index.js) 206.14KB 56.97KB
fields (index.js) 247.15KB 62.32KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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.99KB 0.87KB
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 (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.63KB 19.99KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 132.41KB 34.84KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 253.19KB 65.62KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.65KB 40.91KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 46.06KB 14.30KB
plugin-list (index.js) 112.54KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
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) 83.34KB 27.61KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
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 (expression.js) 0.20KB 0.18KB
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.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

…empty (objectui#8568)

PR #8977's live-tree case asserted `aliases.length > 0`, `rows.length > 0` and
`selfCarved > 0` unconditionally. Retiring the four aliases makes all three
false at once, by construction: the census derives its subject set as
operatorMap-minus-spec, and that set is now empty. The census script already
models the empty tree and a synthetic unit case already pins it; only the
live-tree case had the pre-ruling shape baked in.

The blindness guard those three carried is real, so it is re-expressed rather
than deleted. On an empty tree the instrument's reach is proved by the
`authored-reach` control, which counts canonical `$`-operator payloads in
non-test authored files and cannot be satisfied by a scanner that never opened
the authored corpus; the empty set is then held to being DERIVED rather than the
residue of a failed read on either side. The pre-retirement branch is kept
intact, so the case still holds on any tree that carries an alias again.

The subset check is recomputed in the test rather than by calling
`deriveAliases` again: measured under ablation, a stubbed `deriveAliases`
returning `[]` satisfies its own output, so reusing it would have made this
branch pass while witnessing nothing.

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3484.3 KB 3512.7 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-B2VK_Toe.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) 16.69KB 6.21KB
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) 11.08KB 4.58KB
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) 500.20KB 114.67KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 28.04KB 9.46KB
data-objectstack (index.js) 206.14KB 56.97KB
fields (index.js) 247.15KB 62.32KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
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.99KB 0.87KB
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 (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
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) 8.39KB 3.10KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.63KB 19.99KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 132.41KB 34.84KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 253.19KB 65.62KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.65KB 40.91KB
plugin-grid (index.js) 210.86KB 57.28KB
plugin-kanban (index.js) 46.06KB 14.30KB
plugin-list (index.js) 112.54KB 27.65KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.55KB 3.32KB
plugin-view (index.js) 84.42KB 20.80KB
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) 83.34KB 27.61KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
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) 14.82KB 4.99KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
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 (expression.js) 0.20KB 0.18KB
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.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
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 (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
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

Copy link
Copy Markdown
Collaborator Author

ACCEPT + 席内 Clause-② 复核 PASS — 派发席 domain:ui @ objectui

session_01MPaVWWMuWeT5LgB1qoXjVB(os-steve),复核时刻 2026-09-10T15:3xZ。⛔ 本复核对 GitHub 与 diff 本身核验,不对终报自述核验。

门禁

34 个 check 全部收敛:31 success · 3 skipped · 0 红 · 0 悬。 逐个 job 结论读取,⛔ 未用 get_status、⛔ 未认 required 子集。Governed Surface Queue Guard = success。Type Check 15:13:25Z、Test (shard 1..4/4) 15:23:39Z / 15:22:41Z / 15:16:51Z / 15:19:07Z 全 success。

⭐ 裁决落实,由本席按内容亲核

裁决:评论 5617467534(2026-09-10T10:50:04Z,总监席,选项 1)。

operatorMap 的键集,两侧逐键读出:

键数 小写别名
origin/main 17 $notin · $notcontains · $startswith · $endswith 全在
本分支 13 一个不剩,13 个全部是规范拼写

四个退役别名移入 RETIRED_OPERATOR_ALIASES,各自映射到规范写法:

const RETIRED_OPERATOR_ALIASES: Record<string, string> = {
  '$notin': '$nin',
  '$notcontains': '$notContains',
  '$startswith': '$startsWith',
  '$endswith': '$endsWith',
};

⇒ 这正是裁决要的形状:按名拒绝,并点名规范拼写。⭐ 而且在「无宽限窗口」的前提下,那条 prescription 就是全部的迁移援助 —— 一个只说「unknown operator」的拒绝会把这次退役的成本整个推给作者。ValueDataSource 未动,objectui#8447 的方向维持。

⭐ 两处「绿着却什么也没见证」被 dev 自己抓出来,方向按实测报告

这是本轮最值得记的一条,因为两处都是通过而不见证的形状:

  1. 消融 leg B(删掉具名拒绝臂)最初只红 4 个。那条断言「拒绝信息里出现规范拼写」的用例保持绿色 —— 因为通用 unknown-operator 信息自带的 Supported operators: 列表里本来就印着 $nin / $notContains / $startsWith / $endsWith,于是 toContain(canonical) 恒真。改钉 prescription 短语(只有具名臂能产出)后,同一次消融从 4 红变 8 红
  2. 消融 leg C 没能让它瞄准的普查守卫变红:那条用例先调用 deriveAliases 去取自己的被测集合,而一个返回空数组的桩满足它自己的输出。改为独立重算子集关系后才有区分力。

⚠️ 与本仓 ⑥ 同族,并把它推进了一步:一个断言「通过」不等于它「见证了什么」;消融跑出的红数如果少于预期,先怀疑用例而不是先怀疑代码。

pin 的三步测量

PR #8565 的对账 pin,按顺序实测:改代码前绿(exit 0,10 passed)→ 改代码后红(exit 1,只点名 README 第 143/146/147/148 行,别无其他)→ 改 README 后绿(exit 0,10 passed)。⇒ 该 pin 变红是设计信号,⛔ 不是缺陷,且它精确指出了要改的行。

changeset

minor + 正文写明破坏。本仓禁 major,这是规定形态。

needs:contract-review 本席不清,理由在案

dev 把该标签同笔挂在卡与 PR 两个载体上并回读了两侧。scripts/pm/check-half-states.mjsh31ContractReviewCarrierSplit 只在劈叉时报红,两侧一致(都挂或都不挂)是它的静默态。

⇒ 只清 PR 一侧会制造劈叉;两侧同清则是一次「清闸动作」,而本席复核的正是本车道自己的活,自清等于自我背书。⛔ 本席两者都不做,标签保持挂起,清闸留给维护者或总监席。⚠️ 该标签不是 check,⛔ 不阻塞入队。

放行:翻 ready、挂 SQUASH,按队列 ref 阳性命中确认,再按内容带亮/死对照核落地。


Generated by Claude Code

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

One $-dialect, two acceptance sets: convertFiltersToAST accepts lowercase aliases that ValueDataSource refuses by design

2 participants