Skip to content

test(console): parity ledger reads a retirement from the spec, and no older reason may hide one (objectui#10033) - #10391

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-10033-parity-waiver-retired-keys
Sep 24, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-10033-parity-waiver-retired-keys

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #10033
Clause-②: no

The card's question, answered from the test's own population logic

Is the waiver population derived from the spec at all? Yes. And retired keys are in it, in a special form. That makes this case (b) of the dispatch.

  • Population. covered, registeredWithoutInputs and accounts for every spec-carried block all start from Object.keys(ComponentPropsMap). The partition test requires covered ∪ registeredWithoutInputs ∪ UNJUDGED_SPEC_BLOCKS to equal that set exactly, with each block counted once.
  • A stale waiver (a key outside the population) is already caught, in two places. The first is the partition's converse (accounted for a block ComponentPropsMap does not carry). The second is the ledger rot check (is ledgered but the spec no longer carries it — delete the entry). So this PR adds no new tripwire for that case.
  • Retired keys stay in the population, because the spec keeps their rows on purpose. The row is one of the retirement's refusal doors. Readings from the installed @objectstack/spec 17.4.0 (lockfile-resolved), taken at base ea02938cd, using the @object-ui/test-support tombstone judge:
type in PageComponentType in RETIRED_PAGE_COMPONENT_TYPES row keys listed / tombstoned / authorable empty bag parses
user:profile no yes 0 / 0 / 0 (z.never) refused
element:form no no 6 / 6 / 0 accepted
element:filter no no 6 / 6 / 0 accepted
controls: cloud-connection:panel, marketplace:installed-list, mcp:connect-agent no no 0 / 0 / 0 accepted

Three things follow from that reading:

  • Mechanism assumption 3 is half false. RETIRED_PAGE_COMPONENT_TYPES has exactly one member, user:profile. element:form left PageComponentType through the element-grain retirement (objectstack#9249, listed in the spec's 17.3.0 changelog), not through that table. element:filter left the same way earlier (objectstack#9220, 17.1.0). So "read the spec's retired set" as that table alone would not cover element:form.
  • Excluding retired rows from the population is the wrong way round. A registration of a retired type in this repo would drop out of covered together with its row, and nothing would judge it. objectui#8176 built this ledger to end exactly that absence. With the rows kept in, such a registration is judged (every input it publishes is off-spec) and is also counted twice, which the partition refuses.
  • Both waiver reasons the card names are false today. user:profile is false in both halves, as the card measured. element:form cites a block-types.ts palette exclusion that says "no renderer — use the object-bound object-form block". That entry was deleted by commit 639114c4d, the delivery of objectui#7122 through PR chore(deps): resolve @objectstack/spec at 17.3.0 in the lockfile #7685. It now appears in that file only as a note that the type is RETIRED UPSTREAM.

What changed (test-only, one file plus an empty changeset)

  1. isRetiredUpstream(type) reads both of the spec's retirement spellings, and never a list kept here. A type counts as retired when it is out of PageComponentType and one of these holds:

    • it is named in RETIRED_PAGE_COMPONENT_TYPES (retired by name);
    • its row lists keys and every one of them is a tombstone (retired at element grain).

    Leaving the enum is required but is not enough on its own: the controls above are outside the enum too, and the predicate answers false for them.

  2. A third ledger class, RETIRED UPSTREAM. The rot check asserts two things for it: isRetiredUpstream(type) is true, and the accepted key set is empty. If upstream restores the type or gives its row a key, the entry goes red.

  3. The class-closing guard. An entry under EMPTY SPEC SHAPE or NOT REGISTERED, DELIBERATELY now fails when the spec retires its type. That is the check that stops a hand-written reason from outliving a retirement, which is what happened to both entries here.

  4. Entries rewritten. user:profile and element:form now say what is true, citing the upstream ruling and landing (objectstack#14159, objectstack#15112, objectstack#9249) and the objectui-side reconciliation (objectui#7122).

    • ⚠️ element:filter moves too. This is an in-place fix under the same-class exemption, and I am naming it here because the claim's file surface names only the other two entries. It was retired at element grain by objectstack#9220 (17.1.0), and its EMPTY SPEC SHAPE reason never mentioned that. The new guard turns exactly that state red, so leaving it behind would not pass. It is the same file, the same defect class and the same gate, with nothing new to verify.
  5. The docblock now has three classes (it said two) and a paragraph on why a retired block stays in the population.

The census does not move: specCarried 45, judged 29, registeredPropless 7, ledgeredUnjudgeable 9. It is the same nine entries under corrected reasons.

Changeset. The dispatch expected none. node scripts/check-changeset-presence.mjs refused without one, because it counts any file under a released package's src/, test files included. So the PR adds .changeset/10033-parity-ledger-retired-upstream.md with an empty frontmatter, which declares that nothing is released. The gate now passes.

Verification record at head 4893d87cb

Every exit code is captured before any pipe, and each verdict line is quoted from the gate's own output.

step result
pnpm exec vitest run apps/console/src/__tests__/registry-inputs-spec-parity.test.ts scripts/__tests__/check-installed-spec-pin-claims.test.ts (repo root, under os-verify-lock.sh). The second file is the only other test that reads this file, by content. Test Files 2 passed (2) · Tests 228 passed (228) · VERDICT command-exit 0
same parity file at base ea02938cd, before the edit (control: the stale reasons were green then) Tests 199 passed (199)
pnpm exec turbo run build --filter='@object-ui/console^...' --concurrency=2 (the dependency closure the type-check reads, under the lock) Tasks: 34 successful, 34 total · VERDICT command-exit 0
pnpm --filter @object-ui/console type-check (script echoed: tsc --noEmit && tsc -b tsconfig.node.json --force) exit 0. tsc --listFiles -p apps/console/tsconfig.json includes registry-inputs-spec-parity.test.ts (1 hit), so the test file is in the checked set.
eslint on the touched file (apps/console, the console's own eslint . config) exit 0 · --format json: 1 file, 0 errors, 0 warnings. This is a narrowing, with its evidence: (1) the population comes from eslint's own config, where isPathIgnored is false for the test file and true for the changeset; (2) the file count comes from the JSON output; (3) type-aware linting is off (calculateConfigForFile shows no parserOptions.project or projectService), so this diff cannot change the verdict on any file it does not touch.
pnpm check:control-bytes OK (scanned 8435 tracked text file(s); skipped 85 binary)
pnpm check:new-line-citations VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0
node scripts/check-changeset-presence.mjs exit 0 · 1 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s) (empty frontmatter)
pnpm check:changeset-claims exit 0 (report-only). It names one pending changeset that mentions this file, .changeset/8067-component-input-member-kind.md. I read that paragraph: it is about page:header.actions and is still true.
check-changeset-no-major · -overwrite · -fixed · check-pending-changeset-literals · check-installed-spec-pin-claims · check-test-path-roots · check-vi-mock-{specifiers,inherit,override-shape} · check-type-check-coverage · check-lint-coverage all exit 0
node scripts/check-governed-queue-guard.mjs --test over both paths NOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matched.

objectui has no dispatch-gates.mjs. I derived the gate list by hand from .github/workflows/*.yml, keeping the gates whose scripts read test sources, apps/console or .changeset/. The full pnpm lint and pnpm test shards are left to CI.

Ablation: three legs, from the committed state

Each leg used node ../objectstack/scripts/ablation-replace.mjs in wrap mode, with the test running under the lock. I wrote down the expected result before each run: exactly one failed test, the rot check, naming the mutated type. The observed direction was red on all three, as expected.

leg mutation (landing proven: anchor count 1 → 0, blob changed) result
1 user:profile entry → its exact pre-fix text 1 failed / 198 passed · user:profile is retired upstream, and its ledger reason does not say so — rewrite the entry as RETIRED UPSTREAM
2 element:form entry → its exact pre-fix text 1 failed / 198 passed · element:form is retired upstream, and its ledger reason does not say so …
3 specificity control: cloud-connection:panel (outside the enum, empty shape, not retired) relabelled RETIRED UPSTREAM 1 failed / 198 passed · cloud-connection:panel is ledgered as retired upstream, and the spec does not retire it …

Every restore was git checkout HEAD -- on the absolute path, proven by blob == HEAD (f714fdd786dc), git diff HEAD empty and git status --porcelain empty. The control run at the base (199 passed) shows that legs 1 and 2 describe a state that was green before this PR.

Acceptance notes

  • Nothing is filed. There are no class a/b/c findings from this run.
  • AGENTS.md [WIP] Update documentation for project #11 asks for opportunistic repair of cross-file path:line citations in a file you touch. This file has older ones, outside the ledger entries, and I left them alone because the claim's surface is the ledger. Nobody is carrying that repair.
  • ai:chat_window's NOT REGISTERED, DELIBERATELY reason was re-read against placeholders.tsx and block-types.ts and is still true. The new guard also passes for it.

Generated by Claude Code

… older reason may hide one (objectui#10033)

The unjudged-block ledger in registry-inputs-spec-parity carried the three
ComponentPropsMap rows @objectstack/spec retired under reasons that did not
mention the retirement: user:profile as EMPTY SPEC SHAPE with a false
placeholder claim, element:form as NOT REGISTERED citing a palette entry the
17.3.0 reconciliation deleted, element:filter as EMPTY SPEC SHAPE.

The population is spec-derived (Object.keys(ComponentPropsMap)), and the spec
keeps a retired type's row on purpose as a refusal door, so the rows stay in
the population and stay accounted for. What changes is the reason:

- a third ledger class, RETIRED UPSTREAM, asserted by isRetiredUpstream(),
  which reads both retirement spellings from the spec (out of
  PageComponentType, and either named in RETIRED_PAGE_COMPONENT_TYPES or a
  row whose every listed key is a tombstone), plus an empty accepted set;
- the EMPTY SPEC SHAPE and NOT REGISTERED classes now fail on a type the spec
  retired, so a later retirement cannot leave an old reason in place.

Test-only; empty-frontmatter changeset declares no release.

Claude-Session: https://claude.ai/code/session_01BA3nKVUwKQJf8DBxrSVtNC
@github-actions

Copy link
Copy Markdown
Contributor

changeset-claim-re-read

⚠️ 1 pending changeset(s) describe a file this change touches

Their bodies publish verbatim into the CHANGELOG at the next release, so this is a request to re-read them against your diff — addressed here because you are the one seat that can answer it without re-deriving anything.

⛔ Nothing here blocks, and nothing here is a verdict on your change. This gate exits 0, is not a required context, and judges name resolution, never meaning: it asked whether a pending body names a file you touched. "Is this sentence still true?" is the one question it will not answer, and the one you are being asked to answer.

.changeset/8067-component-input-member-kind.md

  • names apps/console/src/__tests__/registry-inputs-spec-parity.test.ts → apps/console/src/__tests__/registry-inputs-spec-parity.test.ts — edited by this change

    A registration's type: 'array' said a value was a list and stopped there, so a member that drifted from @objectstack/spec was invisible to every layer that reads a declaration. page:header.actions is the measured cost: the contract declares z.array(z.string()) ("Action IDs"), the renderer read the members as ActionDef objects, and the repo-wide parity gate in apps/console/src/__tests__/registry-inputs-spec-parity.test.ts stayed green for the whole life of the drift because both sides carried the key and neither could say what was inside it. What settled it was a maintainer ruling, not a test — and even after the fix, "these are ids" survived only as English in the registration's description.

Read the paragraph, not the line: both false halves of the objectui#8617 claim sat in one paragraph, and correcting either alone would have left it asserting the same wrong thing.

If a claim did go false, correct the body. That is precedented and prose-only, frontmatter untouched; check-changeset-overwrite.mjs will report the correction as its own case 2 ("correcting a declaration on purpose … legitimate"), which is the intended shape — one gate asks for the read, the other records the write.

Not covered, stated so nobody reads this as more: a born-false claim that spells no line address at all (objectui#9495 coordinated one by ORDINAL — "a grep finds that member first" — and deciding that means reading what the sentence means), a claim spelled as a symbol or a package rather than a backticked file name, and a file named ambiguously.

Compared the checked-out tree with 00cdaff1f (merge-base with origin/main): 1 file(s) changed outside .changeset/, read against 1355 pending declaration(s) that publish a body (1928 pending in total). · run

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 329 chunks) 3042.7 KB 3104.5 KB
Main entry chunk (gzip) 148.4 KB 350 KB
Entry file index-DLJZDB3C.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.68KB 6.20KB
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) 27.95KB 10.04KB
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.52KB 3.45KB
auth (index.js) 3.63KB 1.64KB
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) 542.11KB 129.60KB
core (index.js) 8.94KB 3.59KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 224.12KB 62.26KB
fields (index.js) 254.86KB 64.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.24KB 2.27KB
i18n (pickLocalized.js) 9.86KB 3.95KB
i18n (provider.js) 34.99KB 11.45KB
i18n (useDisplayLocale.js) 3.52KB 1.76KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 39.28KB 11.09KB
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) 6.62KB 2.45KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 5.52KB 2.10KB
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) 16.01KB 3.93KB
plugin-calendar (index.js) 51.44KB 14.62KB
plugin-charts (index.js) 72.58KB 20.29KB
plugin-chatbot (index.js) 198.27KB 47.18KB
plugin-dashboard (index.js) 133.19KB 35.25KB
plugin-designer (index.js) 216.12KB 44.37KB
plugin-detail (index.js) 260.94KB 67.93KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 146.34KB 37.47KB
plugin-gantt (index.js) 168.31KB 41.45KB
plugin-grid (index.js) 215.22KB 58.85KB
plugin-kanban (index.js) 49.30KB 15.39KB
plugin-list (index.js) 114.17KB 28.16KB
plugin-map (index.js) 21.74KB 7.07KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.55KB 11.99KB
plugin-timeline (index.js) 30.72KB 8.96KB
plugin-tree (index.js) 10.74KB 3.76KB
plugin-view (index.js) 85.86KB 21.38KB
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) 114.58KB 37.60KB
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 (body-dialect.js) 4.78KB 2.09KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.78KB 2.56KB
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) 18.27KB 6.20KB
types (ai.js) 4.11KB 2.06KB
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) 5.00KB 2.39KB
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) 17.15KB 6.32KB
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

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 24, 2026 21:52
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 24, 2026
Merged via the queue into main with commit 7478334 Sep 24, 2026
45 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-10033-parity-waiver-retired-keys branch September 24, 2026 22:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

1 participant