fix(app-shell): the field-rule wrong-layer verdict comes from @objectstack/lint, not a second copy of it - #9366
Conversation
…stack/lint, not a second copy of it `rowCanonAdvisory` answered "is this root bound on this surface?" from objectui's own knowledge: `@object-ui/core`'s `detectNonCanonicalRowSpelling` hard-codes the single root `data`, and the docblock justified that from `ROW_PREDICATE_ROOTS` / `FIELD_RULE_ROOTS` / `FORMULA_ROOTS`. The platform publishes the same judgement as `fieldRuleRootIssue` / `FIELD_RULE_BOUND_ROOTS`. They agree today and nothing keeps them agreeing. `CelSchemaHint.slot` names the authored key, and on the slots the published vocabulary covers — visibleWhen / readonlyWhen / requiredWhen — the verdict and the message are now the helper's. Both symbols are module-internal to app-shell; no package export moves. Coverage is not shrunk to fit the helper: a formula `expression` binds `FORMULA_ROOTS` (narrower) and a conditional-formatting `condition` binds `ROW_PREDICATE_ROOTS` (wider), so both keep the local instrument, pinned as live controls. Part of #9318 Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZ
|
| Step | Outcome |
|---|---|
| Build packages | success |
| Check console performance budget | failure |
Which half objected:
| Eager-closure half | Verdict |
|---|---|
| Aggregate closure ceiling | ✅ pass |
| Per-chunk ceilings | ✅ pass |
| Ceiling sensitivity (headroom) | |
| Ceiling freshness (checkout vs. base branch) | ✅ pass |
⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. TheCheck console performance budgetstep log carries the ceiling and the number it was compared against.
Reason: The entry chunk measured 144.3 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.
See the workflow run for details.
📦 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) | 502.02KB | 115.16KB |
| core (index.js) | 8.52KB | 3.41KB |
| create-plugin (index.js) | 27.94KB | 9.51KB |
| data-objectstack (index.js) | 211.58KB | 58.68KB |
| fields (index.js) | 247.89KB | 62.50KB |
| 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.25KB | 13.99KB |
| plugin-charts (index.js) | 71.51KB | 19.97KB |
| plugin-chatbot (index.js) | 195.34KB | 46.51KB |
| plugin-dashboard (index.js) | 131.22KB | 34.59KB |
| plugin-designer (index.js) | 215.94KB | 44.33KB |
| plugin-detail (index.js) | 253.46KB | 65.85KB |
| plugin-editor (index.js) | 2.23KB | 1.05KB |
| plugin-form (index.js) | 136.77KB | 34.17KB |
| plugin-gantt (index.js) | 166.95KB | 41.04KB |
| plugin-grid (index.js) | 211.58KB | 57.48KB |
| plugin-kanban (index.js) | 46.01KB | 14.30KB |
| plugin-list (index.js) | 112.58KB | 27.65KB |
| plugin-map (index.js) | 20.64KB | 6.86KB |
| 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) | 9.55KB | 3.32KB |
| plugin-view (index.js) | 84.42KB | 20.79KB |
| 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) | 94.03KB | 31.02KB |
| 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.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
…-from-lint Base-only sync: brings the branch onto a main that contains 8524372 (feat(react)!: unbind the data-source adapter from the expression scope). No file owned by this pull request is modified by this commit. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
|
| Step | Outcome |
|---|---|
| Build packages | success |
| Check console performance budget | failure |
Which half objected:
| Eager-closure half | Verdict |
|---|---|
| Aggregate closure ceiling | ✅ pass |
| Per-chunk ceilings | ✅ pass |
| Ceiling sensitivity (headroom) | |
| Ceiling freshness (checkout vs. base branch) | ✅ pass |
⚠️ A broken gauge half is a verdict about the ceiling, not about the bundle: that line has drifted out of range of the regression it exists to catch, or the report behind it cannot be trusted. It does not say anything grew. TheCheck console performance budgetstep log carries the ceiling and the number it was compared against.
Reason: The entry chunk measured 144.3 KB, but the eager-closure half of this gate returned no trustworthy VERDICT: the report could not be read, a ceiling has drifted out of range of the regression it must catch, or (objectui#6245) a ceiling was replaced on the base branch after this checkout was made. The step log says which. This is not a passing budget — and it is not a size regression either.
See the workflow run for details.
📦 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) | 502.02KB | 115.16KB |
| core (index.js) | 8.52KB | 3.41KB |
| create-plugin (index.js) | 27.94KB | 9.51KB |
| data-objectstack (index.js) | 211.58KB | 58.68KB |
| fields (index.js) | 247.89KB | 62.50KB |
| 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.25KB | 13.99KB |
| plugin-charts (index.js) | 71.34KB | 19.90KB |
| plugin-chatbot (index.js) | 195.34KB | 46.51KB |
| plugin-dashboard (index.js) | 131.22KB | 34.59KB |
| plugin-designer (index.js) | 215.94KB | 44.33KB |
| plugin-detail (index.js) | 253.46KB | 65.85KB |
| plugin-editor (index.js) | 2.23KB | 1.05KB |
| plugin-form (index.js) | 136.77KB | 34.17KB |
| plugin-gantt (index.js) | 166.95KB | 41.04KB |
| plugin-grid (index.js) | 211.66KB | 57.50KB |
| plugin-kanban (index.js) | 46.00KB | 14.30KB |
| plugin-list (index.js) | 112.58KB | 27.65KB |
| plugin-map (index.js) | 20.64KB | 6.86KB |
| 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) | 9.55KB | 3.32KB |
| plugin-view (index.js) | 84.42KB | 20.79KB |
| 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) | 96.00KB | 31.71KB |
| 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
|
| reading | card said | measured then |
|---|---|---|
objectui refs to fieldRuleRootIssue / FIELD_RULE_BOUND_ROOTS under packages/ |
0 | 0 |
rowCanonAdvisory |
celAuthoring.ts:184-283 |
celAuthoring.ts:271 |
FIELD_RULE_BOUND_ROOTS export |
validate-expressions.ts:688 |
:680 |
fieldRuleRootIssue export |
validate-expressions.ts:790 |
:782 |
5651747919 — the os-dev-report, status: done, pushed c931579b3f, premise still valid, with
the zero backed by a lit control (rowCanonAdvisory 4 hits) so 「not used」 is a reading and not a
broken grep.
5652798679 — this seat's carrier repair adding the standalone Branch: line, part of the
board-wide seat-template fault that hit 13 cards.
⇒ the subject of the card is 「rowCanonAdvisory re-derives a verdict @objectstack/lint already
publishes」, and its tier is default.
What this seat is doing, and not doing
⛔ Not filing a replacement card: re-filing would fork the history and the original may be
restorable — GitHub issue deletion is an admin action and this seat cannot see who took it or undo it.
⛔ Not stripping Fixes #9318 from the body, and ⛔ not reviewing or landing this PR while its carrier
is missing.
For the maintainer, two questions, both one word:
- Can objectui#9318 be restored? If yes, nothing else here needs doing.
- If not, should this seat re-file it from the salvaged record above, and does the new number
replace theFixesreference?
The PR itself is otherwise healthy: Fixes #9318, 6 files, 32 of 36 checks green, and its only red is
the board-wide ui-components Bundle Analysis debt being paid down on objectui#9251 / PR
objectui#9399.
Generated by Claude Code
Card rebuilt — this PR's
|
Contract reviewReviewed head: ① derived judgmentsAccept sets. Two callers reach
Public surface — How the published-claim population was bounded (measured for THIS package, not assumed):
Claims enumerated: 60 — A1–A16 shipped source comments, B1–B6 changeset/ A · comments that reach
|
…nale
Contract review found the prose shipped alongside objectui#9318's mechanism
false in three places. The mechanism is unchanged; only comments, the changeset
body and the test narration move.
- The stated reason for taking the engine's message ("it refuses the
`record.<root>` rewrite by name") is false on both roots it named. Measured
against the installed @objectstack/lint@17.4.0, the `current_user` message
PRESCRIBES that rewrite ("To gate on record state, rewrite the predicate
against `record`"), and the one message that does refuse by name — `app`'s,
"Do NOT write `record.app`" — is unreachable, because `app` does not resolve
at this tier and the pre-existing bare-reference ERROR fires before the
advisory's `issues.every((i) => i.severity !== 'error')` gate. The swap still
stands, on reasons that are true: the local instrument owns a sentence for
exactly one root (`METADATA_LAYER_ROOT`, `data`), so for the five roots a
covered slot newly reports there is no objectui prose to keep, and writing it
would rebuild the second copy this card deletes.
- "Advises on EVERY root the field level leaves unbound" inherits that same
gate. Replaced with the measured set: `data`, `current_user`, `user`,
`features`, `os`, `ctx`, and an explicit note that a root this tier cannot
resolve never reaches the helper.
- `ROW_PREDICATE_ROOTS` is not "strictly WIDER" than `FIELD_RULE_BOUND_ROOTS`:
it lacks `previous` and `parent`, so the two overlap on `record` alone and
neither contains the other. And routing the formula surface through the helper
would not "stop advising" `previous.*` / `parent.*` — measured, nothing
advises them there today. The real asymmetry is a wrong verdict in opposite
directions: a false red on the condition, a false green on the formula.
Also records that `fieldRuleRootIssue` has no slot vocabulary of its own — it
judges any slot name handed to it — which is what makes FIELD_RULE_VERDICT_SLOTS
objectui's own load-bearing coverage answer rather than a formality.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
Contract reviewReviewed head: ① derived judgmentsAccept sets — unmoved. Public surface — Bounding, re-measured here, not taken.
The two structural claims1. 2. "The swap still stands, on reasons that survive measurement" — the withdrawal is real; the ⛔ The defect: the repair swapped one false measurement for another, in three carriersA11 / B4 / D15 — "a covered slot now advises on the roots this tier RESOLVES but the field level does
Lit control It ships. D14 — the re-justification's arithmetic fails with it. "For the five further roots a covered slot A10 / D14 — the quotation. "the The changeset was not brought along. It still frames the list as "the slots the published vocabulary The sixteen, judged on what is there NOWDischarged, re-derived at this head (11): A7 ( Discharged in substance but carrying a false quotation (1): A10. ⛔ NOT discharged — replaced by a new false statement (4): A11, B4, D15, D14. The invalidation set, and how it was bounded
⇒ invalidation set = 48 — 4 A (A7, A8, A10, A11) + all 6 B + all 5 C + all 33 D. The remaining Counts: 45 re-derived · 11 spot-checked · 4 carried structurally.
⭐ The missing measurement, locatedThe bare-reference message "Write Not grounds for this verdict
② semver grading
③ boundary flags
Are the sixteen discharged at THIS head, measured? No — 12 of 16. A11, B4 and D15 carry a new false Implemented-by: claude/issue-9318-rowcanon-verdict-from-lint (mode:subagent) FAIL Generated by Claude Code |
The previous repair replaced a false universal ("every root the field level
leaves unbound is advised") with a closed six-root enumeration. Measured
through `lintCelPredicate` at `scope: 'record'` over the whole candidate
population, the enumeration was false by a wider margin than the sentence it
replaced: it named six and omitted eighteen.
The advised set is not a list worth writing down. It is a universal with one
structural exception: every root the field level leaves unbound is advised
except `app`, which the helper judges but which never reaches the advisory
because `app` is the single judged root the platform does not declare, so the
pre-existing bare-reference error fires first and the advisory is gated behind
`issues.every((i) => i.severity !== 'error')`.
Repaired in all three carriers that shipped the enumeration: the
`rowCanonAdvisory` docblock (reaches `dist/**/*.js`), the changeset body
(reaches `CHANGELOG.md`) and the PR description. Also corrected in the same
docblock: the re-justification's root count, and the claim that the
`current_user` message "ends" with the rewrite sentence (it contains it;
measured `endsWith` false, `includes` true).
Nothing is enumerated, so nothing goes stale when the root set moves. The
universal is now defended by an instrument rather than by prose: a new sweep
in `celAuthoring.fieldRuleVerdict-9318.test.ts` re-derives the candidate
population on every run and asserts the exception is exactly `app`, with a
second test pinning why (the helper judges `app`; `SCOPE_ROOTS` does not
contain it).
Comments and changeset only; no product code, no exported signature, no accept
set moves.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5xpA5q533BgTTNADibEFt
✅ Console Performance Budget
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
Size Limits
|
Contract reviewReviewed head: ① derived judgmentsAccept sets — unmoved. Public surface —
Bounding, re-measured. The four rows plus A10, judged on what is there NOWI re-derived the candidate population myself before reading any of them.
The new executable code, judged as codeIt is sound, and it re-derives rather than restates. The sweep reads ⭐ The reconstruction holds, and more strongly than the report claims. The stated limit is honest, and it is the only option the published surface permits. It can fail, and it fails exactly where claimed. Ablation re-run here: replacing Two observations, neither a defect: The invalidation set, and how it was boundedBounded at byte level, not file level:
⇒ invalidation set = 38 (2 A + 3 B + 33 D). I re-derived 15 rows beyond it anyway — A5, A6, A7, Named spot-check sampleA1 — Counts48 re-derived · 8 spot-checked · 4 carried structurally.
|
ADOPTED — the PASS at
|
| check | reading | |
|---|---|---|
| ① same-form PASS at tier | ✅ | record 5659604058, head 3316efde626b220077dac407099781a04dba8cd0, heading · sha in a code span · ①②③ itemized · independence pair · exactly one unhedged **PASS** on its own line, zero **FAIL**. Tier claude-opus-5 on all 161 harness-stamped messages, no fallback. |
| ② both carriers, machine-read | ✅ after a repair this seat owed | first run read exit 4 — C2: card objectui#9440 had no claim comment at all, so the declaration limb had nothing to read. Repaired at 5659622802; re-run reads exit 0. |
| ③ every check | ✅ | 36 runs: 33 success / 3 skipped / 0 failure / 0 cancelled. ⭐ Test (shard 1/4) is success — the .vite-oracle temp-dir race did not recur — and Bundle Analysis is success. |
⚠️ The carrier fault was mine, and the machine caught it, not a human
objectui#9440 is a rebuild of the unreachable objectui#9318. Its original claim comment was one of the three comments the rebuild was reconstructed from — and those three now answer 404 too. ⇒ the body was carried across and the claim was not. The declaration limb has been empty since the rebuild, through three contract reviews, and nothing noticed until the landing gate ran.
⭐ A missing reading is not a declared no — one is a decision, the other is an absent one. The gate says so in those words and it was right to refuse.
⇒ the durable lesson, written onto the card: a rebuilt card needs its machine-readable carriers rebuilt as a separate item from its content. The salvage carried the finding, the fences and the Clause-② value as prose in the body — and prose is exactly what the predicate refuses to read, on purpose.
⭐ The review went past what the repair claimed, in the repair's favour
The last repair defended its universal with a sweep test that re-derives the root population every run. The reviewer did not take that at face value and found it stronger than claimed: @objectstack/lint does not bundle SCOPE_ROOTS — it imports it from @objectstack/formula — and lint's formula and app-shell's resolve to the same physical package in this install. ⇒ the sweep reads the very object lint judges with, ⛔ not a copy.
It also verified the reconstruction (27 + 1 = 28 judged, 3 bound, 25 candidates), confirmed FIELD_RULE_AMBIENT_ROOTS = ['app'] twice (installed dist index.js:1164 and objectstack source validate-expressions.ts:709), and established that the stated limit is the only option the published surface permits — neither FIELD_RULE_JUDGED_ROOTS nor FIELD_RULE_AMBIENT_ROOTS appears in any .d.ts of either subpath.
⭐ And the blind spot is one-sided in the safe direction: a root joining or leaving SCOPE_ROOTS moves both sets together, and any divergence lands in silent, which the test asserts empty. Only a new ambient root is invisible — exactly what the test's own comment declares.
The ablation reproduced independently, same mutant blob (0120d42aba → 91c736bc96), reddening exactly the two new tests (Tests 2 failed | 49 passed (51), expected [] to deeply equal [ 'app' ]), restored by hash equality with git diff HEAD empty under trap.
16 of 16 discharged, with 48 re-derived / 8 spot-checked (named) / 4 carried structurally, the invalidation set bounded at byte level rather than file level: in celAuthoring.ts 0 of the ±lines fall outside JSDoc and both hunks sit inside the rowCanonAdvisory docblock, below FIELD_RULE_VERDICT_SLOTS.
⭐ Recorded, ⛔ not blocking — and the observation is sharper than the items
Three figures in the PR body went stale on this very commit — the acceptance grep prints :6 not :4, the red-first fence reads 4 failed | 8 passed (12) not 3 failed | 7 passed (10), and the pin figure is 51 not 49. None ships (files publishes dist/** + CHANGELOG.md, not the description), every conclusion survives, and each error runs conservative.
Also recorded: the docblock's set identity JUDGED \ BOUND \ data (24) is one wider than the clause before it (23) — an antecedent to tighten, ⛔ not a false measurement; the adjacent bullet states the app exception.
② semver: .changeset/9318-field-rule-verdict-from-lint.md declares minor, and minor is right. Presence / no-major / claims / new-line-citations / control-bytes / test-path-roots all exit 0.
Card provenance re-confirmed at review time: objectui#9318 404, its three salvaged comments 404, objectui#9440 200 — ⛔ its body remains the only copy.
Carriers cleared from both this PR and card objectui#9440 in one pass. Landing next: ready → auto-merge → merge queue.
Generated by Claude Code
Fixes #9440
rowCanonAdvisoryanswered "is this root bound on this surface?" from objectui's ownknowledge.
@objectstack/lintpublishes the same judgement. Two hand-maintained copies ofone verdict, agreeing today, with nothing keeping them agreeing.
Second prose repair — contract review returned
FAILagainContract review
5659211993found 12 of the 16 discharged and four not: the first repair replaced a false universal with a
closed six-root enumeration, which was false by a wider margin than the sentence it replaced.
⛔ The fix is not a better list. It is the original universal plus the one documented exception,
with nothing enumerated, so nothing goes stale when the root set moves.
Re-derived here, through the BUILT dist, over the whole candidate population.
FIELD_RULE_JUDGED_ROOTSandFIELD_RULE_AMBIENT_ROOTSare module-private at 17.4.0 (neither isin
@objectstack/lint's export list), so the judged set is reconstructed asSCOPE_ROOTS+ the ambient root, read from@objectstack/formula— a declared dependency ofthis package, not a phantom one.
SCOPE_ROOTShas 27 members;FIELD_RULE_BOUND_ROOTSisrecord/previous/parent, all three of them inSCOPE_ROOTS; so the candidate setJUDGED \ BOUNDis 25. Sweeping all 25 throughlintCelPredicateatscope: 'record'withslot: 'visibleWhen', classifying each by verbatim equality withfieldRuleRootIssue's ownmessage: 24 advised · 1 blocked · 0 silent, and the blocked set is exactly
["app"].Controls on the same instrument:
data.status == 1⇒ onewarning(lit);record.status == 1/previous.status == 1/parent.status == 1⇒[](dark, and thehelper judges none of them);
app.status == 1⇒ one error, no warning;zzz.x == 1⇒ oneerror and
fieldRuleRootIssuereturnsnull, so an unjudged name was never a candidate.⭐ The universal is now defended by an instrument rather than by prose. A new sweep in
⚠️ The reconstruction is a declared limit, written into the test: a NEW ambient root added
celAuthoring.fieldRuleVerdict-9318.test.tsre-derives the candidate population on every run andasserts
silent = [],blocked = ['app'],advised = candidates − app; a second test pinswhy
appis the exception (SCOPE_ROOTSdoes not contain it, the helper judges it anyway).upstream would be invisible to the sweep until that literal moves, and nothing in this repo can
see it. Ablation — removing the error gate
(
if (issues.every((i) => i.severity !== 'error') && hint.scope === 'record')⇒if (hint.scope === 'record'), proved on disk: anchor 1→0, mutant 0→1, blob0120d42aba→91c736bc96) reddens exactly those two tests(
Tests 2 failed | 10 passed (12)) withexpected [] to deeply equal [ 'app' ]— the exceptionitself disappearing, which is the predicted direction. Restored by hash equality to the
HEADblob
0120d42abawithgit diff HEADempty, undertrap … EXIT INT TERM.Carriers, before → after, censused on the STORED artifacts (the built
⚠️ Escaping was checked rather than assumed before those
dist/views/metadata-admin/celAuthoring.js, not the source;perl -0777occurrence counts):the six-root list 1→0,
For the five further1→0,five by hand1→0,`current_user` text ends1→0; the replacements 0→1 each. In the changeset body:the six-root list 1→0 and
the slots the published vocabulary covers1→0, replacements0→1. Lit controls unmoved —
ROW_PREDICATE_ROOTS3→3 in the dist and 2→2 in thechangeset,
METADATA_LAYER_ROOT1→1 in the dist; dark controlsstrictly BROADERandzzzNotPresentZZZ0→0 in both.counts were trusted: the emitted comments carry
`record`unescaped (10 occurrences,backslash-escaped form 0), so an unescaped needle is the right instrument for this artifact.
⛔ Three statements the review confirmed TRUE are deliberately untouched:
ROW_PREDICATE_ROOTSis a six-entry local constant and "five roots the field tier does not bind" is correct for it —
that is a different claim from the advised set, and it is not repaired into a universal.
Prose repair — contract review returned
FAIL(first round)Contract review⚠️ An earlier draft of this line cited
5658789972swept 60 claims across this PR's shipped comments, its changeset body and this description, and
found 16 false. ⭐ The bounding is load-bearing:
@object-ui/app-shellbuilds withtsc(
"build": "tsc && node ../../scripts/check-dist-completeness.mjs"), and nothing in its configchain sets
removeComments—packages/app-shell/tsconfig.jsonextends../../tsconfig.json,which has no
extendskey and noremoveComments— so tsc's own default (false) stands andcomments reach the emit.
tsconfig.base.json:22for that.Measured:
tsconfig.base.jsonis extended only bytsconfig.node.json,tsconfig.scripts.json,tsconfig.react.jsonandexamples/byo-backend-console/tsconfig.json— never by app-shell.Right conclusion, wrong instrument; the instrument is corrected here and the conclusion is
additionally confirmed by reading the emitted bytes.
filesshipsdist/**andCHANGELOG.md,so the comments under repair are published artifacts, not notes.
⛔ The mechanism is unchanged. The repair commit moves comments, the changeset body and the
test narration only; the red-first block below still reproduces and the pins still pass. Three
of the sixteen were load-bearing rather than decorative:
roots it named. Because it was the justification for a behavioural choice, the question
underneath it was re-asked rather than the sentence reworded — the swap stands, for measured
reasons, and the old one is withdrawn in place;
the answer survives, both arguments were false, and the real asymmetry is written in their
place;
Re-read after writing, from the built
dist(⛔ not the source):packages/app-shell/dist/views/metadata-admin/celAuthoring.js,perl -0777occurrence census,before → after —
strictly WIDER1→0,strictly NARROWER1→0,stop advising1→0,EVERY root the field level leaves unbound1→0; the four replacement anchors 0→1 each. Litcontrol
ROW_PREDICATE_ROOTS3 before and 3 after (same file, same census, varying onlythe claim); dark control
strictly BROADER0 both, so the zeros are absence and not abroken read.
Re-measurement — both sides, at implementation time
The card instructs that the re-measurement wins over the card. Both sets of line numbers
moved; the exported shape did not, so the substance survives intact.
9c44eed4/d7f0601)fieldRuleRootIssue/FIELD_RULE_BOUND_ROOTSunderpackages/2e471dc0a(git grepexit 1; controlrowCanonAdvisoryexit 0, 4 hits) —rowCanonAdvisorycelAuthoring.ts:184-283celAuthoring.ts:271on objectuiorigin/main2e471dc0aFIELD_RULE_BOUND_ROOTSexportvalidate-expressions.ts:688:688on objectstackorigin/main2b6a207fieldRuleRootIssueexportvalidate-expressions.ts:790:790on objectstackorigin/main2b6a207@objectstack/lint@17.4.0, installed; both symbols on the package root entry2e471dc0a, and is false if read as a statement about thisPR's head. Re-measured on the head tree,
git grep -c FIELD_RULE_BOUND_ROOTS -- packages/exits 0 and prints four files. Three are this PR's own. The fourth is not —
packages/react/src/SchemaRenderer.tsx:844, a comment that was already onmainat thisPR's merge-base (
dfb585059, where that grep exits 0 with exactly that one file) and thatarrived here through this PR's merge of
main. Drift in the reading, not a fabrication, andnothing about the seam it was measuring has moved: the four files hold no call of either
symbol outside
celAuthoring.ts.One correction to the dispatch note, offered as a reading and not a complaint: the
:680/:782figures are the sibling checkout's localHEAD(86c5052, an ancestor oforigin/main), notorigin/mainitself. Againstorigin/mainthe card's own:688/:790still hold. Either way the line numbers are informational — what this PR consumes isthe published
distof@objectstack/lint@17.4.0, where the declared shape readsFIELD_RULE_BOUND_ROOTS: readonly ["record", "previous", "parent"]andfieldRuleRootIssue(slot: string, source: string).Part 3 — the helper has NO slot vocabulary, so coverage is objectui's call, and the fallback stays
This is the part the card most wants answered, so it is answered first and explicitly.
fieldRuleRootIssuejudges againstFIELD_RULE_BOUND_ROOTS— the FIELD-RULE tier,record/previous/parent. The surfacesrowCanonAdvisoryguards are everything thatreaches
lintCelPredicateatscope: 'record'. Measured, they do not share one bound set:visibleWhen/readonlyWhen/requiredWhen*WhenkeysFIELD_RULE_ROOTS=record,previous,parentvalidateObjectFieldRulesformulaexpressionexpression, rolevalueFORMULA_ROOTS=recordconditionconditionROW_PREDICATE_ROOTS=record,current_user,user,features,os,ctxprevious, noparentFORMULA_ROOTSis a proper subset ofit, and
ROW_PREDICATE_ROOTSis not a superset, since it lackspreviousandparent. Thetwo overlap on
recordalone. AndfieldRuleRootIssuehas no slot vocabulary of its own:hand it any string and it judges against
FIELD_RULE_BOUND_ROOTSand interpolates the nameinto its message (measured on 17.4.0 —
('expression', 'current_user.x')and('condition', 'current_user.x')each return a finding, notnull). The helper neverdeclines a surface, so
FIELD_RULE_VERDICT_SLOTSis objectui's own load-bearing answer, not aformality.
What routing the two uncovered surfaces through it would actually cost — measured, and wrong
in opposite directions:
current_user,user,features,osandctx— five roots it binds. A false red: the author is told torewrite a predicate that works there;
expressionwould getprevious.*/parent.*back clean, because thehelper reports them bound at the field tier, where a formula binds only
record. A falsegreen — and the message it does print for other roots names
previousandparentasavailable here, which on this surface is wrong prose as well as a wrong verdict.
⛔ Neither is a coverage shrink, and the earlier draft of this section said it was.
Measured on the head tree, both surfaces report nothing at all for
previous.*/parent.*today (
lintCelPredicate("previous.status == 'x'", { scope: 'record', role: 'value' })⇒[],parentlikewise), and@object-ui/core'sMETADATA_LAYER_ROOTis'data'— the only rootobjectui has ever advised on any surface. So nothing that is advised now would stop being
advised. The hazard is the wrong-direction verdict above, not a lost one.
So those two keep the local instrument, byte-for-byte as it behaves today, and each is pinned
as a live control that reddens if a later tidy-up routes them through the helper anyway.
Ablation leg B1 below performs that exact tidy-up and shows both controls going red.
PermissionAdvancedFacets(RLSUSING/CHECK) andConditionBuilderdefault toscope: 'flattened'and were never guarded by this advisory at all.What changed
CelSchemaHint.slot(and the matchingCelPredicateFieldprop) names the authored key. On acovered slot the verdict and the message are the helper's; everywhere else the local path
runs unchanged. Exactly one message ships per finding — never both.
Why the engine's message and not objectui's.⚠️ An earlier draft of this section said
"the engine's message refuses that rewrite by name", of
current_userandapp. Thatis false on both, and it was this PR's stated reason for a behavioural choice, so it is
withdrawn rather than reworded. Measured against the installed
@objectstack/lint@17.4.0:current_userthe engine's message prescribes the rewrite it was said to refuse —it contains "To gate on record state, rewrite the predicate against
record.", one ofthree remedies it offers (the others being an option-level
visibleWhenand field-levelsecurity).
against 17.4.0,
includes(…)is true andendsWith(…)is false — every coveredslot's text closes on "it is not a fourth answer.";
appthe engine's message does refuse by name ("⛔ Do NOT writerecord.app") —and it is unreachable from this diff.
lintCelPredicate('app.theme == "dark"', { slot: 'visibleWhen', scope: 'record' })returns exactly one issue, a pre-existingerror ("bare reference
app… Writerecord.app."), and the advisory is gated behindissues.every((i) => i.severity !== 'error'), so the helper is never called.The swap still stands, on reasons that survive measurement. The verdict widens, and
objectui has no message for most of what it now judges. Its local instrument produces a
sentence for exactly one root —
@object-ui/core'sMETADATA_LAYER_ROOT,data. Forevery other root a covered slot now reports there is no objectui sentence to keep; keeping
"objectui's message" there would mean hand-writing one per root, which is precisely the second
hand-maintained copy this card exists to delete — and they would have to be per-root, since the
engine's texts for
data, the user-root family, the platform-wide family and the ambient familyare four different remedies, not one sentence with the root substituted. ⛔ That population is
deliberately not written down here — an earlier draft of this paragraph said "five further
roots", which was false — because it moves whenever the platform moves
FIELD_RULE_JUDGED_ROOTSor
FIELD_RULE_BOUND_ROOTS. The sweep named above re-derives it on every run instead.The one message genuinely swapped is
data's, and objectui's tail there— "Re-root the reference on
record" — is the half that does not generalise: right fordata, wrong forcurrent_user, which is not a field of the record. Reading a verdict fromone authority and explaining it from another drifts the same way two verdicts do.
⛔ This changes nothing the diff does; it replaces the reason given for what it already
did.
Declared behaviour change. Those three editors now advise on every root the field level
leaves unbound — except
app. ⛔ No enumeration, and that is the point: any list goes stalethe next time the platform moves a root, and two earlier drafts of this line were false in turn —
first "every unbound root" with no exception at all, then a closed six-root list that named a
fraction of the population and omitted the rest. The exception is a mechanism, not a special
case: the advisory runs only once the predicate is error-free
(
issues.every((i) => i.severity !== 'error')), andappis the single judged root the platformdoes not declare (
FIELD_RULE_JUDGED_ROOTSisSCOPE_ROOTSplus the ambientapp), so forappalone the pre-existing bare-reference error fires first and the advisory never runs — eventhough the helper judges it and carries a bespoke message for it. A name the helper does not judge
at all is stopped by that same error and was never a candidate. Severity
stays objectui's own
warning: every save gate on this tier countsseverity === 'error', sono accept set moves and nothing already stored in customer metadata is refused. The
data.status == 'x'case still reports atwarning, nevererror.Progressive enhancement is preserved throughout — the
import()stays dynamic (a static onewould pull the lint bundle onto the eager console graph, objectui#5266), the export is
feature-detected, and a lint package without it falls through to the local instrument rather
than going quiet.
Not reopened
The three fences the card sets are untouched: objectui already warns on wrong-layer
data.*and still does; the advisory stays
warningand is never promoted; the bare-shorthand armstays disabled (
row = null) on the fallback path, and the helper path has no such arm at all.Red-first, verbatim
The pin was written and run on the unmodified tree first:
Ablation — three legs, all falsifiable, no null results
Every leg mutates on disk, proves the mutation reached disk before any result is read, and
restores by hash equality, never by an exit code. All ran under
trap ... EXIT INT TERM.Leg A — does the verdict really come from the published constant? Mutated the installed
@objectstack/lint@17.4.0dist/index.jsto dropparentfromFIELD_RULE_BOUND_ROOTS.The reading:
parent.status == "paid"atvisibleWhenis immediately advised, carrying theengine's own message ("
visibleWhenreadsparent, but a field-level conditional rule bindsonly
record…"). That is the card's required demonstration — a root the platform DOES bindcomes back clean through the new path, and stops being clean the moment the platform stops
binding it. The file is not git-tracked, so the restore proof is sha256 equality against the
pristine snapshot plus the anchor counts, and
git statuson the worktree is empty.Leg B1 — can the part-3 live controls fail? Replaced the slot condition with an
unconditional one, i.e. performed the exact tidy-up the card warns against.
Leg B2 — is the helper path actually reached? Emptied the covered-slot list.
No leg was a null result. All three could fail and all three did.
Verification
Acceptance grep:
git grep -c fieldRuleRootIssue packages/app-shell/srcexits 0 with twolines —
…/celAuthoring.fieldRuleVerdict-9318.test.ts:4and…/celAuthoring.ts:9. Both arenon-zero; an earlier draft transcribed only the second, and read
:8— correct then, stale now,because this repair added one more mention of the symbol to that file's docblock.
objectui#8972's five pins are green and byte-unchanged: the pin file's blob hash equals its
origin/mainblob (dbfc2108221cf22955927ac565804abdc3b4a1edboth sides), andcelAuthoring.test.tsreportsTests 39 passed (39).Heavy runs went through the shared verify lock on slot
os-dev-objectui-9318:The one skip is pre-existing; no test was skipped, quarantined or loosened by this change.
they are not re-derived at the current head and should be read as such. Re-run after the
repair commit, through the same shared lock (slot
os-dev-objectui-9440), all with the lock'sown
VERDICTline as the verdict rather than a bare$?:Gates re-run on the repaired tree, each exit code captured before any pipe:
check:control-bytes0,check:changeset-claims0,check:new-line-citations0,check:test-path-roots0,check:shell-escape-residue0,check:comment-mask-corpus0,check-changeset-presence.mjs0 ("5 source file(s) of 1 released package(s) changed, and thischange declares 1 changeset(s)"),
check-changeset-no-major.mjs0.Red-first still reproduces on the repaired tree. The four product files reverted to the
merge-base, the mutation proved on disk first (
FIELD_RULE_VERDICT_SLOTSoccurrences 5 → 0 incelAuthoring.ts) and restored undertrap … EXIT INT TERMby blob-hash equality againstHEAD, never by an exit code:Gate family, derived by hand from
package.jsonplus.github/workflows/(this repo has nodispatch-gates deriver), all exit 0:
check:control-bytes,check:designer-field-key-parity,check:i18n-keys,check:phantom-deps,check:unreferenced-sources,check:test-path-roots,check:new-line-citations,check:changeset-claims,check:eager-closure,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:self-import,check-changeset-presence.mjs,check-governed-queue-guard.mjs --test(NOT GOVERNED, 6 paths against 5 surfaces).
Lint was run over the whole tree rather than narrowed:
pnpm exec eslint . --format jsonoverthe population eslint's own config selects — 4920 files, 0 errors, 12879 pre-existing
warnings. The five touched files carry 0 errors; their 12 warnings are pre-existing
no-explicit-anyandset-state-in-effectsites this change neither adds to nor moves.Dependent-set membership read, done here rather than inherited. Three different lists in
this repo share the word "exclude" and are NOT the same set:
.changeset/config.jsonignore=@object-ui/example-*,@object-ui/site,@object-ui/test-support— excluded from version bumping only;@object-ui/app-shell=@object-ui/console,@object-ui/example-byo-backend-console,@object-ui/example-console-starter— measured byreading every workspace manifest for a dependency edge on
@object-ui/app-shell;--filter=!@object-ui/site.@object-ui/sitesits in the first and the third and has no dependency edge onapp-shell, so it is not in the second at all; the genuine overlap between the first two is@object-ui/example-*.pnpm type-checkwas run in full, so all three dependents are coveredregardless.
@object-ui/app-shellis in the singlefixedgroup of 40, so a changeset isrequired and one is included (
minor).majoris not withheld "by group size", as anearlier draft said:
scripts/check-changeset-no-major.mjsrefusesmajorfor everypackage in this repo irrespective of group, because objectui's major is pinned to
@objectstack's — and it is available, underOBJECTUI_ALLOW_MAJOR=1, for the onesynchronized release that follows objectstack across ITS major.
Clause-② — still
no, verified rather than assumedNo exported signature of
@object-ui/app-shellmoves.CelSchemaHint,CelPredicateField,celAuthoring,rowCanonAdvisoryandFIELD_RULE_VERDICT_SLOTSeach appear 0 times inthe built
packages/app-shell/dist/index.d.ts, against a lit control (MetadataResourceRouter,1 hit) proving the grep and the file, plus a dark control (
MetadataResourceRouterZZZ, 0)proving the same grep can return zero honestly. The package's
exportsmap has twoentries —
"."and"./styles.css"— not one, as an earlier draft said; the substance isunchanged, since neither admits an importable subpath for these symbols. Nothing for me to
hang, and I have hung nothing.
CI — named instrument, not a frozen tally
⛔ An earlier draft of this section said
maincarried two red checks, Doc Snippet TypeCheck and Skill Example Check, inherited by every PR. Both halves are false. Re-derived
from
GET /repos/objectstack-ai/objectui/commits/{sha}/check-runs(⛔ notactions/runs?head_sha=):90b0bf7162Bundle Analysisdfb585059git merge-base, notbase.sha)7ca6ddd4b5maintip, read 2026-09-14T03:40ZDoc Snippet Type CheckandSkill Example Checkreadsuccessat all three. They werenever red here, and the paragraph naming them is deleted rather than corrected.
The one real red is
Bundle Analysis— job103733469427at90b0bf7162, theui-componentsper-chunk headroom:387.8 KB measured / 389.6 KB ceiling (headroom 1.9 KB = 0.02x the 89.0 KB regression, under the 0.10x floor …). The gate states its own reading:Saying it here, as the gate asks: the
ui-componentsrow is being paid down onobjectui#9251 / PR objectui#9399 (both reachable). It is not this PR's to fix, this diff does
not touch that chunk, and ⛔ neither the ceiling nor the allowance is touched here. In the same
run the entry chunk (
144.3 KB / 350 KB), the aggregate closure (3106.2 / 3134.8 KB) andceiling freshness all pass.
⛔ The table above is a reading at named shas, not a live tally — this PR has been pushed
to since. Re-derive at the current head rather than trusting these counts.
Acceptance notes
Noted while reading, deliberately not filed and not repaired here:
clientValidation.validateObjectFieldRuleskeeps onlyseverity === 'error', so theadvisory it now asks for is discarded the moment it is produced. That is correct for a draft
gate and is stated in its own docblock; the slot is wired there anyway so the two
scope: 'record'callers ask the same authority the same question. An observation, not adefect — nothing is wrong today and no reader is misled.
appat acovered slot the author is told "bare reference
app… Writerecord.app" by@objectstack/formula's bare-reference check, while@objectstack/lint's own message forthe same root says "⛔ Do NOT write
record.app:appis not a field on this object, sothat spelling only trades this diagnostic for an
unknown fielderror onapp." The twopublished diagnostics contradict each other, and the error wins because it fires first. That
is upstream of this repo and pre-existing — this diff neither creates nor worsens it, and
⛔ widening objectui's local path to paper over it is exactly the consumer-side workaround
this card exists to stop. Recorded here for the seat; no card filed from this repair order.
FIELD_RULE_SLOT_CONSEQUENCE's slot vocabulary is module-private upstream, so a consumercannot ask
@objectstack/lintwhich slots it covers and must state its own answer, as thisPR does in
FIELD_RULE_VERDICT_SLOTS. A drift tripwire pins the bound set so the nextplatform move arrives as a red test here rather than as silence. Worth an upstream export
one day; nothing is broken, so no card. Carrier for that observation: none — no queued PR or
seat is touching that file.
Session:
https://claude.ai/code/session_01UzHd6hDYatoDn17BuwKxnZGenerated by Claude Code
Generated by Claude Code