fix(core): raise declared @objectstack/spec floor to ^17.3.0 - #9032
Conversation
`@object-ui/core` declared `@objectstack/spec: ^17.2.0` in `dependencies`
(consumer-facing), while `normalizeListViewSchema` folds objectui's legacy
toolbar flags onto the `userActions` keys `group` / `hideFields` / `rowColor`,
which the protocol adopted only in 17.3.0. Any consumer resolution landing on
17.0.0 / 17.1.0 / 17.2.0 satisfied the declared range and got a normalizer whose
output the spec refuses BY NAME.
Re-measured against published artifacts, not the workspace copy: every published
17.x installed into its own isolated consumer project, the fold's real output
parsed against that install's own `./ui` entry.
17.0.0 / 17.1.0 / 17.2.0 REFUSED refused-keys=[group, hideFields, rowColor]
17.3.0 / 17.4.0 ACCEPTED
An undeclared firing-control key was refused by ALL FIVE versions, so the
contrast measures those three keys and not the harness. 17.3.0 is the FIRST
accepting version across the whole published 17.x stable line, not the first one
that happened to work.
A second, independent key family lands on the same floor: `ListViewSchema` gained
`pageName` in 17.3.0, so the `page` view fixture this package already pins is
refused before it and accepted from it.
`scripts/check-spec-range-floors.mjs` cannot hold this: its criterion is symbol
PRESENCE, and `UserActionsConfigSchema` is exported by every version above. The
floor is held instead by a new pin carrying firing controls that prove its
comparator can redden. The standing instruction in the objectui#5435 pin — read
the file reddening on a resolved 17.2.x as "the declared floor is too low" — is
discharged in place.
Refs objectui#9012
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
ℹ️ Console Performance Budget — not measuredThis run did not produce a console bundle to measure, so there is no pass/fail verdict for the performance budget. This is not a budget violation. Nothing was measured — the numbers a real violation would carry are simply absent.
See the workflow run for details. No package size report: it is only generated from a complete package build, so a partial one is never shown. |
`pnpm-lock.yaml` records each importer's DECLARED specifier beside the resolved
version, so raising `packages/core`'s range without regenerating the lockfile
left the two disagreeing. CI installs with `--frozen-lockfile`, which refuses
that mismatch before any job does its own work — every check on the first push
failed with one root cause:
specifiers in the lockfile don't match specifiers in package.json:
* 1 dependencies are mismatched:
- @objectstack/spec (lockfile: ^17.2.0, manifest: ^17.3.0)
Regenerated with `pnpm install --lockfile-only`. The diff is exactly one line —
the `specifier:` for `packages/core`'s `@objectstack/spec`. The resolved
`version:` beside it is unchanged at `17.4.0`, which is the direct evidence that
this moves no resolution for anybody: the floor is a declaration, and every
install already landed above it.
`pnpm install --frozen-lockfile` now exits 0, and
`scripts/check-lockfile-integrity.mjs` reports clean.
Refs objectui#9012
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jmxdo7bmeqCQHLSfmLVX9w
✅ 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 reviewHead reviewed: Charter read at: objectstack Tier: Method — independent re-derivation, treating the PR body and the
|
Provenance — carriers cleared, landingClause-② contract review: Reviewer ran at Independence — NOT self-review:
Landing pre-checks — all measured just now
What the review added beyond confirming the dev
One out-of-scope observation, already in flight — ⛔ not a new cardThe reviewer noted that Both carriers ( ⛔ Base is not being hand-merged. The merge queue rebuilds each PR on current Generated by Claude Code |
Fixes #9012
@object-ui/coredeclared@objectstack/spec: ^17.2.0independencies(consumer-facing) whilenormalizeListViewSchemafolds objectui's legacy toolbar flags onto threeuserActionskeys the protocol only adopted in 17.3.0. The declared range therefore admitted a spec that refuses this package's own output by name.This raises the floor to
^17.3.0and puts something in place to hold it there.The measurement, re-derived on this base
Not inherited from the card. Every published 17.x was installed into its own isolated consumer project (
npm install @objectstack/spec@VERSIONinto a throwaway package, so the spec resolves through that project's ownnode_modulesand the package's ownexportsmap). No workspace resolution takes part anywhere. The fold's real output came from the builtpackages/core/dist, not from a hand-copied literal:UserActionsConfigSchema[group, hideFields, rowColor][group, hideFields, rowColor][group, hideFields, rowColor]The firing control is an undeclared key (spelled
zzUndeclared) added to the same block. It is refused by all five versions, so ACCEPTED is not the reading of a schema that accepts everything and REFUSED is not the reading of one that refuses everything.17.3.0 is the FIRST accepting version, not the convenient one
The published 17.x stable line is exactly 17.0.0, 17.1.0, 17.2.0, 17.3.0, 17.4.0 (
npm view @objectstack/spec versions; the only other 17.x tags are 17.0.0-rc.0 through rc.6, which^17.2.0never admitted). All five were measured, so there is no unexamined gap between the last refusing and the first accepting release.A second, independent key family lands on the same floor
ListViewSchemagainedpageNamein the same release. The spec-validpageview fixture this package already pins (normalize-list-view.pageResidual-8429.test.ts) measures:So the new floor is not propped up by one key family. (The separate
viewTyperefusal is objectui's deliberately-declared legacy spelling, unchanged at every version, deferred to objectui#2231.)What holds the floor, since the floors gate cannot
scripts/check-spec-range-floors.mjsjudges symbol presence in the floor's published artifact. I verified its blindness here rather than assuming it: with the floor mutated back to^17.2.0, the gate fetched 17.2.0, judged the same 165 (subpath, symbol) pairs, and returned zero findings for@object-ui/core. Same run at^17.3.0: also zero findings for core. The gate is green either way, exactly as the card said.The floor is held instead by a new package-local pin,
normalize-list-view.declaredSpecFloor-9012.test.ts, which reads the manifest's owndependenciesentry. Its comparator carries firing controls proving it can redden (^17.2.0,^17.1.0,^17.0.0,*andworkspace:*must all fail the predicate;^17.3.0and^17.4.0must pass), and it re-anchors to the live spec so it is not just a number guarding a number.Ablation. With the floor reverted on disk to
^17.2.0(mutation proven to reach disk by blob-hash comparison against the HEAD blob), the new pin FAILS:...while the existing objectui#5435 pin stayed green through the same mutation. That is the point: the new pin catches precisely what the existing one structurally cannot. Restored with
git checkout HEAD -- PATH;git diff HEADclean afterwards.Census: is
corethe only one?The card left this unmeasured. Method, and it is decisive rather than a grep:
28 manifests declare a
@objectstack/specrange; 19 declare it consumer-facing (dependencies/peerDependencies), and only those floor anything. Result:@object-ui/coreis the only package where a control-backed measurement shows the declared floor admitting a spec that refuses what the package itself produces.Two candidates were chased and refuted by measurement, which is worth recording:
@object-ui/plugin-gantt(floor^17.0.0) looked like a stronger case than core: it importsGanttConfigSchemaas a runtime value, callssafeParseon the authored block, and uses all ten keys the spec only declared at 17.4.0. But the firing control did not fire at 17.0.0 through 17.3.0 —GanttConfigSchemais an open schema at those versions, so it refuses nothing and there is no refusal to inherit. Not this defect class.@object-ui/app-shell(floor^17.0.0) readsuserActions.group/.hideFields/.rowColor, but reads them off an already-authored view rather than emitting into a parsed structure; its runtimesafeParsesites (ManifestSchema,ViewFilterRuleSchema) land on schemas that are open at its floor. Not established.Both are written up in the report with their numbers. A real, separate consequence I did measure on plugin-gantt:
FLAT_GANTT_CONFIG_KEYSis derived fromGanttConfigSchema.shapeat runtime, so on a resolved 17.0.0-17.3.0 it carries 19-20 members instead of 30 and its shadow diagnostic goes blind for ten keys. That is a degraded diagnostic, not a protocol refusal — reported, not fixed here.Should the gate learn this class?
Not in this PR, per the dispatch order's own condition: I could not make it small. A behavioural criterion needs the floor tarball's runtime schemas parsed (the gate today reads type declarations with the TypeScript parser and never executes the artifact), plus a fixture per emitting site to parse against. That is a different gate, and the existing gate's header argues at length against widening its criterion. Filed as a card rather than smuggled in.
Verification
pnpm exec vitest run packages/core/— 142 files, 3021 tests passedpnpm --filter @object-ui/core run type-check(tsc --noEmit+tsc -p tsconfig.test.json, which covers the new test) — exit 0node scripts/check-changeset-presence.mjs— exit 0, 1 changeset declarednode scripts/check-control-bytes.mjs— exit 0 (7226 files)node scripts/check-changeset-no-major.mjs— exit 0node scripts/check-governed-queue-guard.mjs --teston all four changed paths — NOT GOVERNED--format json, not grepped text)check:spec-floorsreports 16no-artifactfindings in this container because onlycoreandtypesare built; that is a prerequisite-not-met, not a red, andcoreitself was judged clean. A full-workspace build per PR was ruled against on objectui#4846, so that gate stays CI's.Every exit code was captured to a file before any pipe.
Scope
packages/core/package.json, two test files underpackages/core/src/utils/__tests__/, and a changeset. Nothing in the reserved file surface was touched.Generated by Claude Code