Skip to content

docs(closure-claims): single-source the thresholds claim, bound the block-family one - #6337

Merged
os-warren merged 3 commits into
mainfrom
claude/issue-6186-closure-claims-bounded-or-derivable
Aug 25, 2026
Merged

docs(closure-claims): single-source the thresholds claim, bound the block-family one#6337
os-warren merged 3 commits into
mainfrom
claude/issue-6186-closure-claims-bounded-or-derivable

Conversation

@os-warren

Copy link
Copy Markdown
Collaborator

Fixes#6186

Applies the 2026-08-25 maintainer ruling (batch adjudication close-out, decision 乙/B1, verbatim 「同意」) — bounded or derivable — to the two closure claims left open after claim 1 landed as PR #6195. Claim 1 is untouched here and its presence pin is not weakened.

Closing keyword deliberate. The card's three claims are now all resolved: claim 1 landed and was accepted; claims 2 and 3 are this PR. The ruling's own "Remaining work on this card" section lists exactly those two. The card was moved out of needs-user-decision into pm:queue by the ruling, so it is not a decision-box card.

Re-anchoring: every cited line number, checked at my base

The card carries a standing warning that all its line numbers have moved once. Measured at f8c70f4f3:

CitedFound at baseMoved?
plugin-dashboard.mdx:322322no
dashboard-widget-options.ts:3838no
block-schema.mdx:2524yes, by one

Claim 2 — single-sourced, and then derived

The same closure claim was written twice: the census header in packages/sdui-parser/src/dashboard-widget-options.ts and, in its own words, on content/docs/plugins/plugin-dashboard.mdx. Two copies drift apart independently and neither knows when the other stopped being true.

The census header is now canonical; the page points at it. The census module is the copy that owns the fact — it is the module that emits the unconsumed-widget-option warning on that basis, and it already had a maintenance test attached. The page is a consumer of the fact, not its owner.

And then derived, because single-sourcing is what made it load-bearing. Concentrating the claim into one copy is only an improvement if that copy cannot rot quietly. New gate scripts/__tests__/unconsumed-widget-option-claim-6186.test.ts re-derives it from source on every test run.

⚠️The existing census test does not cover this claim, which is why a new gate was needed.dashboard-widget-options-census.test.ts derives the consumed set (leg 2: the DatasetWidget.tsx read set equals the declared set) and tripwires new files reading the bag (leg 4). Neither names thresholds, and neither is a repo-wide statement. The two files now derive different halves and neither pretends to do the other's job.

Scanned population, derived rather than listed

git ls-files filtered to the JS/TS family (.ts, .tsx, .mts, .cts, .js, .mjs, .cjs, .jsx) — 3,743 files. node_modules, dist and every build output are untracked and so are out by construction; scripts/check-control-bytes.mjs reads the repo the same way.

The JS half is in on purpose: the claim is about the whole repository, not about its TypeScript. Scanning only .ts would make the gate assert less than the prose while looking like it covers it — this card's own defect, one level up.

The walk refuses to collapse: expect(tracked.length, 'the source walk found no JS/TS files at all').toBeGreaterThan(1000). A scan that finds nothing reads exactly like a scan that found nothing wrong.

format and invert are deliberately NOT scanned this way

Their claim is bounded, not repo-wide, and the census header now says so per key. format is a live key in other vocabularies (measureField(...).format is how a dataset-bound value is really formatted), so a repo-wide bare scan would red on a true claim — and an assertion that reds on legitimate code gets deleted by the next person who hits it, putting the claim back where it started. Leg 2 of the census test already derives that bound.

Claim 3 — bounded, because measurement showed the absolute version is false

block-schema.mdx asserted: "Nothing reads slots, slotContent or template at runtime." Measured at base, two of the three keys have live runtime readers under a different vocabulary:

  • packages/plugin-detail/src/synth/buildDefaultPageSchema.ts:821-885 reads options.slots, then slots.header / slots.actions / slots.alerts / slots.highlights / slots.tabsPageSchema.slots, driving slotted record pages.
  • packages/components/src/renderers/layout/page.tsx:397-398 reads schema.template and resolves it through TEMPLATE_REGISTRYPageNodeSchema.template, selecting a page layout.

Only slotContent has genuinely zero runtime readers. The page was already in tension with itself: eight lines below the absolute rider it tells the reader that record pages support kind: "slotted" with a slots map, "wired end to end".

Route taken: bounded (ruling option (a)), not derived — and this is the honest primitive rather than the cheap one. A gate could only ever derive the bounded claim, because the absolute one is false; bounding is therefore not a retreat from derivation, it is the thing derivation would have had to encode. A gate over the block family would also have to exempt every legitimate slots / template reader in the live vocabulary, which is a maintenance liability that reds on ordinary work.

The page's actual point — this family is declarations only; nothing expands a block — survives bounding intact, and the rewrite turns the self-contradiction into content by naming the live vocabulary explicitly.

Controls — the negative half is load-bearing

Directions predicted before each run; mutation proven on disk by anchored counts, never an editor exit code; restore via git checkout HEAD -- plus the explicit path under a trap, proven by an empty git diff HEADand a git hash-object / HEAD blob comparison.

No build leg is owed, and this is stated rather than skipped: the subject is read off disk with fs.readFileSync and the mask is imported from scripts/js-comment-mask.mjs as source. Nothing resolves through a package exports into dist/, so no dist could go stale.

  • Positive control (tree). Appended a real member read to packages/react/src/hooks/usePerformanceBudget.ts; anchored count 0 → 1. Predicted RED naming the file; observed usePerformanceBudget.ts:316: member access .thresholds, 1 failed | 5 passed.
  • Negative controls (tree) — the load-bearing leg. Injected five confusables into that same tracked file: alertThresholds, thresholdsCount, an object-literal thresholds: key, a thresholds: { lines: 40 } config block, and ['format', 'invert', 'thresholds'] as string content. Predicted GREEN; observed 6 passed (6), exit 0. That file's two pre-existing prose mentions of the word are a bonus control the comment mask handled silently.
  • Computed-access leg (tree).bag["thresholds"] predicted RED, observed reported as a computed access — re-run after the literal guard below to prove the guard did not blind it. Both legs fire, with correct line numbers.
  • Single-sourcing, three legs. Removing the pointer from the page → RED. Restoring the old duplicate wording → RED. Deleting the claim from the census header → RED on the canonical-copy assertion. Each restored clean.

⭐ Two instrument defects the controls caught, both fixed

  1. The computed-access leg never fired.codeOnly masks string literals, and a computed access spells its key as a string literal — so the mask blanked the very thing being looked for. The positive control caught it on the first run. That leg now reads comment-masked source, where literals survive.
  2. The gate reddened on its own source the moment it was committed — which is exactly when a tracked-files population first contained it. Its fixture table and diagnostic message both quote an access. scanSource's literal flags settle the difference precisely: in real code the bracket is code and the key is the literal; in a quoted access the bracket is literal too. The leg now requires the bracket itself to be code, and both shapes this file really writes are pinned as negative controls.

Both are recorded in the file's header as measurements rather than as design notes.

Gates — union re-run at e87969072 on a clean tree

git status --porcelain empty. Exit codes captured by redirect before any pipe; each quotes the gate's own verdict line. Gate list derived by hand from this repo's package.json and .github/workflows/ — objectui has no scripts/pm/dispatch-gates.mjs.

GateExitVerdict
vitest run scripts/__tests__/ packages/sdui-parser/0Test Files 87 passed (87) / Tests 2317 passed (2317)
check:doc-types0Every documented component type is registered.
check:doc-fences0every TypeScript block in 223 document(s) is fenced ts/tsx/typescript
docs:check-links0Links are valid across 17 scan roots.
check:control-bytes0OK (scanned 5217 tracked text file(s); skipped 85 binary)
type-check:scripts0clean tsc
lint:root028 problems (0 errors, 28 warnings) — all pre-existing, none in changed files
check-changeset-presence.mjs0Every one of them has an EMPTY frontmatter — declared as releasing nothing, which is the explicit exemption

Changeset. The gate initially exited 1 naming @object-ui/sdui-parser. The empty-frontmatter form is what it names for a change that should release nothing, and that is measured rather than asserted: all 33 changed lines in that file are block-comment lines, so no exported symbol, runtime behaviour or emitted diagnostic moves. objectui has no skip-changeset label mechanism (#4912 records that nothing reads it), so a real changeset is the declaration form here.

Declared narrowing — check:doc-snippets not run locally. Exit 2, with its own verdict: "This is 'I could not run', NOT 'I ran and found errors' (exit 1) ... this run says nothing about whether the documentation compiles." An unbuilt-dist precondition, independent of this diff. The narrowing is measured, not assumed: this diff touches zero fenced blocks — no fence marker is added or removed anywhere in the doc diff, and the fence counts are identical base vs head (plugin-dashboard.mdx 18 to 18, block-schema.mdx 22 to 22). CI builds and runs it.

Out of scope, deliberately

Other prose closure claims exist in this tree (packages/types/src/zod/complex.zod.ts, MetadataProvider.tsx, useObjectChat.ts and others all say "Nothing reads …"). ⛔ Not filed and not swept: the ruling states conversion is opportunistic only — applied when a page is touched or a claim is load-bearing — and explicitly rules out a blanket sweep. Filing a sweep card would contradict the ruling this PR implements.

Generated by Claude Code


Generated by Claude Code

…e block-family one
Applies the 2026-08-25 "bounded or derivable" ruling to the two closure claims
left open on objectui#6186 after claim 1 landed as PR #6195.
Claim 2 (`thresholds`) was written twice — in the plugin-dashboard page and in
the sdui-parser census header — and two copies of one closure claim drift apart
independently. The census header is now the canonical statement and the page
points at it. Single-sourcing makes the surviving copy load-bearing, so it is
also DERIVED: a new gate re-scans every JS/TS file git tracks for an access to a
key of that name, with positive and negative controls, and a population that
refuses to collapse on an empty walk.
Claim 3 (block-schema) was a whole-tree negative that measurement showed to be
false as written: `PageSchema.slots` and `PageNodeSchema.template` both have
live runtime readers under a different vocabulary. Bounded to the block family's
own keys, which is what the page's actual point survives.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe
… access
The gate reddened on its own fixture table and diagnostic message the moment it
was committed — which is the moment a tracked-files population first contained
it. Keeping string literals visible is what lets that leg see `x['thresholds']`
at all, but it also makes an access merely quoted inside a string look identical
to a real one.
`scanSource`'s literal flags settle the difference exactly: in real code the
bracket is code and the key is the literal; in a quoted access the bracket is
literal too. The leg now requires the bracket itself to be code, and the two
shapes this file really writes are pinned as negative controls.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe
The only published-source edit is a comment header in @object-ui/sdui-parser's
census module — all 33 changed lines are block-comment lines, no exported symbol
or runtime behaviour moves. Empty frontmatter is the form check-changeset-presence
names for that case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012CZgmFFzqA9cX8tBMhvpFe
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3223.4 KB3266.6 KB
Main entry chunk (gzip)154.2 KB350 KB
Entry fileindex-eImmK-Ei.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.96KB4.16KB
app-shell (runtime-config.js)18.10KB6.51KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)25.05KB9.16KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)40.18KB10.59KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)506.08KB114.67KB
core (index.js)5.30KB2.13KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)171.74KB47.48KB
fields (index.js)238.89KB60.02KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (fallbackInterpolation.js)6.25KB2.77KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)26.89KB9.04KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)5.60KB2.33KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.66KB18.32KB
plugin-chatbot (index.js)188.21KB44.67KB
plugin-dashboard (index.js)133.35KB34.45KB
plugin-designer (index.js)211.95KB42.75KB
plugin-detail (index.js)245.10KB62.31KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)126.92KB30.85KB
plugin-gantt (index.js)164.14KB39.87KB
plugin-grid (index.js)201.21KB54.43KB
plugin-kanban (index.js)52.83KB14.55KB
plugin-list (index.js)111.94KB27.24KB
plugin-map (index.js)20.09KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.49KB7.59KB
plugin-tree (index.js)9.26KB3.13KB
plugin-view (index.js)84.55KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)4.47KB1.63KB
react (SchemaRenderer.js)54.84KB18.43KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.35KB0.70KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (dashboard-widget-options.js)3.08KB1.30KB
sdui-parser (index.js)4.93KB2.24KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)7.54KB2.63KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)2.74KB1.41KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (icon-key-migration.js)4.26KB1.63KB
types (index.js)4.49KB2.14KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)6.28KB2.87KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-warren@claude