Skip to content

feat(core): the ingestion choke point says out loud when it cannot fold a retired spelling (objectui#8938) - #9523

Merged
os-tesla merged 2 commits into
mainfrom
claude/issue-8938-retired-dialect-measured-surface
Sep 14, 2026
Merged

os-tesla merged 2 commits into
mainfrom
claude/issue-8938-retired-dialect-measured-surface

Conversation

@os-tesla

Copy link
Copy Markdown
Collaborator

Part of objectui#8938 — parts (a), (b) and (d). Part (c) is deliberately NOT here.

objectui#8873 landed the retired-dialect fold at the ingestion choke point per the route ruling, losslessly. What it declared was not what it measured, and that gap is this card.

Re-measured against the RESOLVED spec pin

The card measured its figures on @objectstack/spec@17.3.0. This branch resolves @objectstack/spec@17.4.0 (pnpm-lock.yaml), and the surface was re-derived against that pin rather than inherited:

  • 74 declared FieldSchema keys, 0 probe collisions, 42 snake twins that fold — the same width the card measured one release earlier, re-derived, not copied.
  • FIELD_KEY_GUIDANCE.id_field is still in no published version: id_field occurs 0 times in the installed dist/, with startingNumber lit at 32 occurrences in the same read as the control.
  • Neither number is written into any shipped prose. See (a).

(a) The changeset correction

.changeset/7650-retired-dialect-choke-point.md is unreleased — no released CHANGELOG entry carries it (packages/core/CHANGELOG.md has no entry for this slice; the changeset file is still in .changeset/), so the stop clause on amending release history does not apply and correcting it is ordinary work.

It presented four keys as the accepted set. It now states the accepted set is measured, not enumerated: one spelling rule applied to FieldSchema's entire declared key set at run time, so it is a property of the installed spec and grows with it. The classes the original never named are spelled out — the gate keys visible_when / readonly_when / required_when, default_value, required_permissions, masking_rule, track_history, delete_behavior, external_id, depends_on, lookup_page_size, the related_list* and inline_* families, and the managed-by lock keys _lock_reason / _lock_source / _lock_docs_url / _package_id / _package_version.

No count is written there on purpose (AGENTS.md #9): a number in that paragraph is derived once and derived never again, which is the defect this card is about. The prose points at the instrument instead.

(b) The width pin

the width IS the spec's declared key set, not a list anyone typed builds the twin set from FieldSchema.shape at run time — a spelling rule, never a key list — puts every twin the live spec implies on one def, and drives it through the public normalizeSchemaReferenceKeys. Add a camel key to the spec and this pin demands its snake twin fold, with no edit here. Beside it: the four keys the original declaration enumerated are asserted to be a strict subset, a lit-instrument control so an empty key set cannot pass vacuously, a negative control that a non-twin key is still not folded, and a class-membership pin so the subject of the (c) decision is a measured fact.

(d) The ruling's diagnostic — maintainer ruling item 3 (objectui#7650, comment 5572018999)

The fold shipped with a warning for the spelling it can fold and nothing for the ones it cannot. Those are the ones that matter to a reader: a key that folds reaches every consumer, a key that does not reaches none, because the retirement cards in this family narrowed every reader to the canonical spelling.

All three refusals now speak, dev-only and memoised per (object, field, spelling, reason):

refusal when why it is not the existing warn
no-declared-twin FieldSchema declares neither the key nor anything sharing its alias probe — id_field, title_format, the typo sortible that path folded nothing; the existing warn fires only after a successful stamp
ambiguous-probe two declared keys share the probe, so the fold refuses to choose unreachable against a spec with no collision; the pin substitutes a colliding FieldSchema
canonical-occupied the declared twin is on the def carrying a different value the producer's value stands and the retired one is inert — the existing warn never reaches this branch

It ⛔ never offers a near match. The refused alternative on objectui#7650 was the spec's lintAuthoredRecordKeys, whose Levenshtein fall-through answers "did you mean sortable?" for that very input; a serve path that suggests a correction is one revision away from applying it. The pin asserts the message names sortible and never sortable.

Two silences are kept, and pinned as such: a spelling that DID fold (that is the existing warn's case — the stop clause), and reference_to / referenceTo (the reference arm owns those, objectui#6837). Two more silences fell out of the work: same value under both spellings is not a conflict, and neither is the canonical this pass stamped itself — the adapter re-serves a cached schema, so reporting that would have put a line on every folded field on every metadata refresh. The existing idempotence pin in the objectui#7650 suite is what caught it.

The three inverted pins

expect(warn).not.toHaveBeenCalled() in the objectui#7650 suite pinned the ABSENCE of this diagnostic in three places. None is deleted and none is weakened: each keeps every behavioural assertion it had (no fold, no typo correction, no overwrite) and additionally requires the refusal to be audible, with objectui#8938 and the reason written at the assertion.

Both readings, from real run output

Ablation leg Apackages/core/src/utils/reference-keys.ts restored to the merge base, mutation proven on disk (new diagnostic: 0 hits; old fold warn: 1 hit), both pin files run:

Test Files  2 failed (2)
     Tests  10 failed | 34 passed (44)

The 10 are the 7 new diagnostic pins plus the 3 inverted ones. ⭐ Every width pin PASSES on the old implementation — which is the evidence that (a) and (b) change no behaviour: the fold was already this wide, only the declaration was wrong.

Ablation leg B — the derivation replaced by a four-key hand table (injected text 1 hit, removed spec read 0 hits):

Test Files  1 failed (1)
     Tests  13 failed | 6 passed (19)

with the width IS the spec's declared key set, not a list anyone typed among the failures — a declaration that stops tracking the spec cannot satisfy this pin.

Restoration proven both legs, not assumed: git hash-object returns the HEAD blob 5f2c8d62064bb157f43f57a050f978da409caf35 after each, git diff HEAD empty, git status clean.

After the change:

packages/core/src/utils/__tests__/          Test Files  68 passed (68)   Tests  1415 passed (1415)
core + data-objectstack + every named
normalizer consumer test                     Test Files 231 passed (231)  Tests  4253 passed (4253)

Gates: check-changeset-presence OK, check-changeset-no-major OK, check-control-bytes OK, check-new-cross-file-line-citations 0 new, check-governed-queue-guard --test NOT GOVERNED, eslint --no-inline-config on the 3 changed source files 0 errors / 0 warnings.

Scope

  • (c) is untouched. Whether the full width — lock and gate keys included — is the intended accept set is a decision on merged contract behaviour and belongs to the director seat on objectui#7650. This branch reports the width and ⛔ does not narrow the fold. The class-membership pin exists so that any later narrowing is a deliberate edit rather than a quiet drift.
  • Clause-② stays no. Which keys fold is unchanged: every new line sits on a path that already left the key alone, and leg A confirms it by running the width pins green against the pre-change implementation.
  • The diagnostic is a no-op under NODE_ENV=production; the leave arm stays lossless.

Acceptance notes

  • In-place repair, declared: one test title in the file this branch already edits said "even when a snake twin of it exists on the def" over a fixture that carries no snake twin. objectui#8938 names it; it is the card's own declared-versus-measured class one file down, so it is corrected in place (title only — fixture, call and assertion untouched) rather than left for a sweep.
  • Noted, not filed — the unguarded FieldSchema.shape dereference. The state is constructible by substituting the spec module, so it could be pinned; what is not a lane call is what the guard should DO. A named throw is cosmetic, and an empty-fold fallback is the invisible-degradation class this same file explicitly refused for id_field. Carried by objectui#8938, which stays open for (c).
  • Noted, not filed — a correction to the card's own third bullet. It reads "no pin drives a legacy spelling through a production serve path". For the RETIRED-DIALECT arm that holds. For the reference arm it does not: MetadataProvider.itemReferenceKeys-7650.test.tsx drives reference_to through the by-name getItem serve path and asserts both spellings arrive. The claim needs narrowing to the arm this card is about.

Generated by Claude Code

… retired spelling

Maintainer ruling item 3 on objectui#7650 asked the retired-dialect fold for a
loud diagnostic — not a silent drop — for a spelling the choke point cannot
fold. What shipped warned about the spelling it CAN fold and said nothing about
the three it cannot, and those are the ones whose value reaches no consumer: the
retirement cards in this family narrowed every reader to the canonical spelling.

`normalizeSchemaReferenceKeys` now names all three refusals — no declared twin,
an ambiguous probe, an occupied canonical key carrying a different value — in
dev only and memoised per (object, field, spelling, reason). It ⛔ never offers a
near match for a typo, because a serve path that suggests a correction is one
revision away from applying it. Same value under both spellings stays silent:
that is the state the pass leaves behind on its own second run.

Which keys fold is unchanged. The diagnostic fires only on the paths that
already left the key alone, and it is a no-op under NODE_ENV=production.

Also lands the width pin the card asked for — it drives every snake twin the
linked @objectstack/spec implies through the public choke point, so the accepted
set can never again be a sentence somebody typed — and corrects the objectui#7650
changeset, which presented a handful of keys as the whole of it.

Part of objectui#8938

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa
…ver was

objectui#8938 names this beside the width it is really about: the test titled
"leaves a declared key alone even when a snake twin of it exists on the def"
carries a fixture with no snake twin on it. The fixture is right and the title
described something else — the same declared-versus-measured drift the card is
about, one file down, so it is repaired in place rather than left for a sweep.

Title only. The fixture, the call and the assertion are untouched.

Part of objectui#8938

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

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3062.6 KB 3104.5 KB
Main entry chunk (gzip) 145.7 KB 350 KB
Entry file index-CRztz5Ax.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) 544.93KB 130.50KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 215.99KB 59.96KB
fields (index.js) 249.20KB 62.88KB
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) 8.87KB 3.64KB
i18n (index.js) 5.22KB 2.26KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 32.15KB 10.49KB
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.83KB 10.95KB
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.99KB 14.24KB
plugin-charts (index.js) 71.33KB 19.90KB
plugin-chatbot (index.js) 195.34KB 46.51KB
plugin-dashboard (index.js) 131.44KB 34.65KB
plugin-designer (index.js) 215.94KB 44.33KB
plugin-detail (index.js) 252.48KB 65.61KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.71KB 34.16KB
plugin-gantt (index.js) 167.62KB 41.26KB
plugin-grid (index.js) 212.83KB 57.97KB
plugin-kanban (index.js) 46.63KB 14.53KB
plugin-list (index.js) 112.67KB 27.68KB
plugin-map (index.js) 21.48KB 6.99KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.41KB 11.93KB
plugin-timeline (index.js) 30.07KB 8.74KB
plugin-tree (index.js) 10.58KB 3.72KB
plugin-view (index.js) 84.36KB 20.78KB
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) 99.04KB 32.62KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
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) 25.28KB 7.80KB
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.04KB 5.36KB
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

@os-tesla
os-tesla marked this pull request as ready for review September 14, 2026 18:16
@os-tesla
os-tesla added this pull request to the merge queue Sep 14, 2026
Merged via the queue into main with commit 84defab Sep 14, 2026
38 checks passed
@os-tesla
os-tesla deleted the claude/issue-8938-retired-dialect-measured-surface branch September 14, 2026 18:36
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.

2 participants