Repository navigation
test(types): the JoinedReportBlock pin compiles on the installed spec and on spec main, and trips at the bump (objectui#10916) - #10934
Conversation
… and on spec main, and trips at the bump (objectui#10916) objectstack#20369 typed JoinedReportBlockSchema on objectstack main, so the inverted `true satisfies IsUnknown` pin fired on the Spec Main Shape Gate, as its docblock intended. The PR type-check still compiles the same file against the published 17.4.0, which erases the type. The single pin becomes two halves. At compile time a state pin admits `erased` and `typed` and refuses `any`; on a typed spec, divergence probes record that the spec's block is the ADR-0021 dataset-bound shape and the local interface the legacy inline-query one. At test time, a tripwire reads the installed spec's declarations and fails on the first bump past 17.4.0, which is when the burn-down becomes possible. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…d the burn-down owed at the bump (objectui#10916) The ALLOW reason said the spec declares `JoinedReportBlockSchema` as a bare `z.ZodTypeAny` and that the pin fails the day the spec types it. Both became false on objectstack main with objectstack#20369. The row now records the installed 17.4.0 as case 2b, spec main as case 2c (precise, but the ADR-0021 dataset-bound shape), and the burn-down as a published type change owed at the first spec bump past 17.4.0. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
…ing nothing (objectui#10916) check-changeset-presence counts every file under a released package's src/, tests included, so the test-only rewrite needs an empty-frontmatter declaration. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk
|
changeset-claim-re-read
|
✅ 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 reviewServed-tier: Isolated adversarial contract review of PR #10934 (card objectui#10916). Inputs: the card and its five comments (the dev's ① Derived judgmentsNot a weakening of the gate. The gate (ruling C) is objectui compiling against spec The dual admission is justified and load-bearing. Read at the head: Declared limit, judged real and acceptable. A re-erasure on spec What forces the burn-down at the bump, and it fires. The Spec Main Shape Gate runs The seven probes, each read against spec main today and mutated. Spec Published surface untouched. Ledger edit. Head check-runs. 43 on ② Semver levelEmpty frontmatter, releasing nothing: correct. ③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
…ocks Picks up PR #10934 (objectui#10916), which clears the repo-wide Spec Main Shape Gate red. No conflict. Claude-Session: https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm Co-authored-by: Claude <noreply@anthropic.com>
…bjectui#4356) (objectstack-ai#10930) Fixes objectstack-ai#4356 Clause-②: yes — published runtime behaviour is removed (the bare-string lift, and the exported `resetDashboardFilterWarnings` if it retires with its memo), per the execution parameters in `5511028727`. The claim's "if" resolved to **stays**: the memo survives (it now dedupes the dropped-member warning, see "The audible-skip question"), so `resetDashboardFilterWarnings` keeps its job and its export; the changeset names no removed export. Final head `a890654027a69911412efbcb146e326da8476256`. Commits on this branch since the branch point `733fd5ac6`: `bc4ea976e` (the retirement), `98623a24d` (rework round 1: W1–W3), `e0647486b` (patch round 2: W5), the two merges of `origin/main` (`1daf7edf7` at `29c76e573`, `a1058f90e` at `2eaf5be27`), and `a89065402` (W4, the dated note). Implemented in session `session_012UwY3ahMixEFkfTUxMVkYm`. ## What changed Phase 2 of objectui#4356 — the runtime bare-string `globalFilters[].options` lift retires. - `packages/core/src/utils/dashboard-filters.ts` — in `normalizeFilterOptions`, the non-object arm no longer pushes `{ value: String(o), label: String(o) }`. A member that is not an object (string, number, boolean) yields NO option: a shorthand-only filter resolves with no `options`, a mixed array keeps only its object members. The nullish / value-less skips are unchanged. The Phase 1 deprecation warning (`warnShorthandOptions`, whose text said "Still lifted here") is deleted; a dev-mode, once-per-filter `warnDroppedShorthandOptions` names the filter, the dropped members and the rewrite, and promises no lift. The `DashboardFilterDef.options` JSDoc, the `normalizeFilterOptions` docblock, the memo docblock and the `resetDashboardFilterWarnings` docblock stop describing a lift. No lift, alias, fallback or migration is added anywhere. - `packages/core/src/utils/__tests__/dashboard-filters.test.ts` — every lift-pinning test is flipped to pin the new contract (red-first, quoted under Tests): `normalizes options: spec {value,label} objects AND bare-string shorthand` became `normalizes spec {value,label} objects, and DROPS a bare-string member rather than lifting it`; the `[objectstack-ai#4356]` describe is `retired: dropped, not lifted` — a shorthand-only filter yields no `options`; a mixed array keeps only its object members; a number and a boolean are dropped too; warn ONCE across renders while dropping on every render; a different filter warns separately; canonical `{ value, label }` options and an `I18nLabel` map are untouched and silent; `resetDashboardFilterWarnings` re-arms the memo. The stale Phase 1 header above the block is removed (W3). - `content/docs/guide/dashboard-filters.md` — the "Deprecated: … still lifted by the runtime" callout became false and now reads "Not accepted": refused at publish, no longer lifted, a bare-string member yields no option, rewrite `"X"` as `{ "value": "X", "label": "X" }`. No fenced block changed. - `packages/types/src/zod/complex.zod.ts` — the JSDoc of the exported `GlobalFilterSchema` said the runtime "still LIFTS a stored bare-string option on read"; that sentence ships in `@object-ui/types`' emitted declarations and is corrected in place. Outside the claim's original surface; accepted by the seat's addendum, see "Deviations". - `packages/plugin-dashboard/README.md` (W1, ships in that package's `files`) — the `globalFilters` example's `options` comment no longer says a stored bare string "is still lifted at runtime, and warns"; it says NOT accepted, the runtime no longer lifts it (objectui#4356), a bare member yields no option, a dev-mode warning names it, rewrite each X as `{ "value": X, "label": X }`. - Three test docblocks state the retirement instead of the lift (W2): `packages/types/src/__tests__/dashboard-global-filters-spec-binding.test.ts`, `packages/types/src/__tests__/report-chart-query-spec-parity.test.ts` (its header docblock only — nothing near objectui#10916's `JoinedReportBlock` pin, which PR objectstack-ai#10934 rewrote and this branch has merged), `packages/plugin-dashboard/src/__tests__/DashboardRenderer.filters.test.tsx`. A fourth (W5), `packages/plugin-dashboard/src/__tests__/DashboardFilterBar.i18nLabel.test.tsx`, no longer says the shorthand "is deprecated and now warns" nor names the renamed lift pin; it names the current DROP pin. No assertion moved in any of the four. - `.changeset/retire-options-shorthand-lift-4356.md` — `@object-ui/core: minor`, opening with the ruled wording: "bare-string `globalFilters[].options` is no longer lifted; use `{ value, label }` objects, the spec's form". Never `major` (AGENTS.md §9 version policy: the break is stated in the body, the bump stays `minor`); `check-changeset-no-major` exit 0. - `.changeset/7759-dashboard-header-global-filters-spec.md` (W4) — a dated note appended, nothing else touched. That pending entry says "The runtime is unchanged too: `@object-ui/core` still converts a STORED bare-string option into a pair when it reads the document, and logs a deprecation warning. That conversion keeps its own retirement schedule (objectstack#7917)." — a sentence this PR makes false in the same release. The note (PR objectstack-ai#10891 form, dated 2026-09-29) says which sentences no longer hold and what ships. Authorised by the maintainer's ruling of 2026-09-29, option 「Allow the appended note (Recommended)」, recorded on the card as `5883360525`; the option reads, verbatim: "You allow developers to append dated correction notes to pending .changeset files (appended only, no existing line or frontmatter edited). The objectstack-ai#10930 developer adds the W4 note inside that PR, then it merges main, gets a final review, and merges." Measured: 16 insertions, 0 deletions; frontmatter md5 `ac1bfd0472c0d0b981d923c32d435f18` before and after; whole-file md5 `94edd5855709afe137f9184c630f298c` → `5c8f0508bed5ed6f5b76254bb74d622d`; 0 angle-bracket characters; `diff` against `origin/main` shows only the appended block. Untouched on purpose: `examples/schema-catalog/test/plugin-dashboard-global-filters-spec.test.ts` (the `GlobalFilterSchema` catalog guardrail from PR objectstack-ai#4601 — the positive control; its docblock narrates history and spec facts only, and stays true); the assertions of the two `packages/types` tests that pin the SPEC's refusal of `options: ['EMEA']` (rejection pins, not lift pins); `@objectstack/spec`; `content/docs/releases/**`; `skills/**` (no file there becomes false — measured below). ## Rulings executed - objectstack-ai/objectstack#7917, maintainer 2026-08-12, verbatim 「7917 ②」 — the spec stays strict; the runtime bare-string lift retires. - Maintainer, 2026-09-02, verbatim 「objectstack#7917 不考虑现有数据」 (director release `5511028727` on objectui#4356) — Phase 2 on release cadence alone; no live-tenant survey, no migration entry. - Maintainer direct dispatch to the `domain:spec @ objectui` seat, verbatim 「这个你也接管继续 objectstack-ai#4356」. - Maintainer, 2026-09-29, 「Allow the appended note (Recommended)」 (card comment `5883360525`) — authorises the W4 append in exactly the form landed here. ## The audible-skip question — governing text found The dispatch asked whether any governing text requires the post-removal skip to be audible. What was read, verbatim: - objectstack `AGENTS.md`, "Degradation log levels — `warn` vs `error`": "**Functional degradation → `warn` / `info`.** A screen is missing, a trigger is not armed, a capability is not enabled … The system is *visibly* smaller than it should be, and the next person to use the missing thing finds out." and "**prefer failing to falling back**, and when you must fall back, say what was lost." — A `select` filter whose members were all bare strings now renders with an empty option list (`DashboardFilterBar`: `const authored = def.options?.length ? def.options : (dynamicOptions ?? [])`): visibly smaller, functional, `warn`-level — and "say what was lost" is the operative sentence. - objectstack `docs/adr/0078-no-silently-inert-metadata.md`, §4 "Loud-not-silent at the runtime seam": "Where the runtime *skips* an inert instance … it emits a **dev-mode diagnostic** at registration rather than swallowing it silently. This is the only layer that is **authoring-tool-agnostic**". The ADR's completeness gate (§1) is about Zod-VALID instances and a bare string is Zod-invalid, but §4 is exactly the case here: a STORED dashboard is the one document no author-time gate re-reads. - objectui `AGENTS.md`: no clause on audibility; #0.1 (contract-first, no lenient fallback) is honoured — a diagnostic is not a fallback, and nothing is lifted. - The module's own convention, same file: `buildFilterCondition` — "anything else → skipped with a console warning, never silently downgraded"; `buildWidgetScopedFilter` skips an unknown default field "with a console warning instead of emitting a query". Conclusion: yes, at `warn`, dev-mode, deduped — the smallest audible path consistent with the ruling. Implemented as `warnDroppedShorthandOptions`: same `isDev()` gate and memo key as Phase 1 (no per-render flood), new sentence (DROPPED, "no longer lifted", the rewrite), no lift. Consequence: the memo and therefore `resetDashboardFilterWarnings` stay. The contract review (`5866198610`) judged this RIGHT; dev-only rather than always-on is the same review's answer for this PR. ## Measurements against the dispatch's mechanism assumptions 1. Lift live on `origin/main` `733fd5ac6` — confirmed: the `else` arm pushed `{ value: String(o), label: String(o) }` and called `warnShorthandOptions` for EVERY non-nullish primitive. Pinned now in `drops EVERY non-object member — a number and a boolean, not only a string`. 2. `resetDashboardFilterWarnings` — `git grep` repo-wide: importers are `dashboard-filters.test.ts` only (plus a `packages/core/CHANGELOG.md` paragraph); it clears only `warnedShorthandOptions`. The inference "with the memo gone it has no job" is falsified by (4): the memo does not go. It stays, with a docblock saying why. 3. Fixture recount, repo-wide over every file containing `globalFilters` (tests included; positive control: the same matcher found the callout it was meant to find): lift-pinning bare-string members existed only in `dashboard-filters.test.ts` — six members across five tests (the dispatch's list lacked the bare `'lost'` of the mixed-array test; the director's "three" and the dispatch's "five" were both stale). All six retired or flipped. The two `packages/types` occurrences pin the spec's REFUSAL and keep their assertions. 4. Skip audibility — see the section above. 5. Teaching sites — swept at the final head with `git grep -n -i -E 'lift' -- ':!**/CHANGELOG.md' ':!content/docs/releases/**' ':!pnpm-lock.yaml' | grep -i -E 'shorthand|bare[- ]string|4356'` over the whole tracked tree (a superset of every package's `files[]`, `content/docs`, `skills/`, `examples/`): 20 hits on `bc4ea976e` with `packages/plugin-dashboard/README.md`'s "still lifted at runtime" line as the found positive control; 18 on `a89065402` — every hit either states the retirement (this PR's changeset, source, docs and test text) or is the unrelated `data`-array shorthand in `plugin-grid` / `plugin-map`. The first-round sweep's `option|filter` post-filter is what hid the README line (W1) and the `i18nLabel` comment (W5); the rework sweep has no post-filter. When the sweep runs on a ref, the ref prefix is stripped before the second grep — this branch's name contains both "shorthand" and "lift". Three other sites author `options: ['…']` on `select` FIELDS (form / object field schemas: `content/docs/guide/expressions.md`, `content/docs/guide/schema-playground.md`, `skills/objectui/guides/project-setup.md`) — a different contract, out of this card, untouched; the governed `skills/**` file does not become false. 6. Release — `packages/core/package.json` is `17.6.0`; `.changeset/config.json` has `@object-ui/core` in the `fixed` group; a `minor` changeset makes the next release ≥ 17.7.0; release PR objectstack-ai#5400 is open (not touched). 7. Pending changesets — swept on the merged tree (2326 pending entries) with `git grep -n -i -E 'normalizeFilterOptions|bare[- ]string|shorthand|lift' -- '.changeset/*.md'` filtered to option / `globalFilters` / 4356 / 7917 context: exactly ONE pending sentence this PR makes false, the `7759` paragraph quoted above (W4). Its earlier sentence "the bare-string option shorthand (`options: ['EMEA']`) is refused with `invalid_type`" stays true. Every other hit is a different contract (export-options bare array, object-map / gantt `data` arrays, kanban `columns`, picklist / field `options`, widget shorthand). The nine entries the `changeset-claim-re-read` comment `5865883972` lists were re-read: none says anything about the options lift; all still true. ## Merges of `main` - `1daf7edf7` — `origin/main` `29c76e573` (PR objectstack-ai#10934, objectui#10916). No conflicts; `report-chart-query-spec-parity.test.ts` auto-merged (disjoint hunks); both sides verified — my header-docblock correction present, PR objectstack-ai#10934's pin text byte-for-byte as `main` has it. - `a1058f90e` — `origin/main` `2eaf5be27` (70 commits, among them PR objectstack-ai#10914's citation edits in `dashboard-filters.ts` and three `complex.zod.ts` commits). No conflicts; after the merge the files differing from `origin/main` are exactly this PR's ten, and `git diff origin/main` on `complex.zod.ts` and on the parity test is exactly this PR's docblock hunks. No rebase, no amend, no force-push at any point; every push was a plain `git push`. ## Tests Red-first (at `bc4ea976e`, the new pins against the UNTOUCHED source, `pnpm exec vitest run packages/core/src/utils/__tests__/dashboard-filters.test.ts`, exit 1): ```text × normalizes spec {value,label} objects, and DROPS a bare-string member rather than lifting it (objectui#4356) × DROPS a bare string: a shorthand-only filter resolves with no `options`, and says so × keeps ONLY the object members of a MIXED array, and names only the dropped ones × drops EVERY non-object member — a number and a boolean, not only a string × warns ONCE per offending filter across repeated renders, and drops on every one of them AssertionError: expected [ …(2) ] to be undefined AssertionError: expected [ Array(2) ] to deeply equal [ { value: 'won', label: 'Won' } ] AssertionError: expected [ { value: '42', label: '42' }, …(2) ] to deeply equal [ { value: 'gold', label: 'Gold' } ] Test Files 1 failed (1) Tests 5 failed | 44 passed (49) ``` The three that stayed green on the old code are the ones the block labels as non-discriminating (different-filter memo, canonical silence, memo reset). Green after the source change, same command: `Tests 49 passed (49)`. Final head `a89065402`, one `pnpm exec vitest run` from the repo root under the shared verify lock over `packages/core/` plus the two `packages/types` global-filter tests and the two `plugin-dashboard` dashboard-filter tests this PR touched: `Test Files 188 passed (188)` · `Tests 3725 passed | 27 skipped (3752)`, exit 0. The changeset script tests (`check-changeset-{presence,no-major,fixed,overwrite,claims}`, `check-pending-changeset-literals`, `changeset-polarity-census`): `Test Files 7 passed (7)` · `Tests 292 passed (292)`, exit 0. Consumer sweep at `bc4ea976e` (`resolveDashboardFilterDefs` consumers re-derived by `git grep`: `core`, `types`, `plugin-dashboard`, `plugin-designer`, `app-shell`, `examples/schema-catalog`), exit 0: `packages/core` 182 files / 3643 tests; `packages/plugin-dashboard` 147 / 1346 (6 pre-existing skips); `packages/types` 3 / 58; `packages/plugin-designer` (`DashboardDesignPage.presetAlias`) 1 / 4; `packages/app-shell` (`DashboardWidgetInspector.test.tsx`) 1 / 19; `examples/schema-catalog` (the guardrail) 1 / 9 — totals `335 passed (335)` · `5073 passed | 6 skipped (5079)`. No consumer fixture asserted the old lift. The code has not changed since that head (every later commit is prose or a merge), and the final-head run above re-covers `core`, `types` and the touched `plugin-dashboard` tests. ## Gates on the final head `a89065402` (hand-derived from root `package.json` and `.github/workflows/`; exit codes captured before any pipe) | gate | exit | note | |---|---|---| | `pnpm --filter '@object-ui/core^...' build` then `pnpm --filter @object-ui/core build` | 0 / 0 | dependency closure first, so the type-checks read rebuilt declarations | | `pnpm --filter @object-ui/core type-check` · `pnpm --filter @object-ui/types type-check` | 0 / 0 | | | `pnpm --filter @object-ui/core lint` · `pnpm --filter @object-ui/types lint` | 0 / 0 | 0 errors; the warnings are pre-existing `no-explicit-any` — on the touched files the `as any` counts equal the base (2 / 10 / 0), unchanged since `bc4ea976e` | | vitest `packages/core/` + the four touched dashboard-filter tests (under the lock) | 0 | 188 files, 3725 passed, 27 skipped | | vitest changeset script tests ×7 | 0 | 292 passed | | `node scripts/check-changeset-presence.mjs` | 0 | 11 files changed vs merge-base `2eaf5be27`, 7 published source files of 3 released packages, 1 changeset added | | `node scripts/check-changeset-no-major.mjs` · `check-changeset-fixed.mjs` · `check-changeset-claims.mjs` · `check-changeset-overwrite.mjs` | 0 ×4 | claims and overwrite are report-only; the W4 append is overwrite's documented "correcting a declaration on purpose" case | | `pnpm check:pending-changeset-literals` | 0 | "No test source names a pending changeset" | | `node scripts/check-control-bytes.mjs` | 0 | 9331 tracked text files | | `node scripts/check-new-cross-file-line-citations.mjs` | 0 | 0 new citations (the W4 note cites by quoted sentence) | | `node scripts/check-governed-queue-guard.mjs --test` (every touched path) | 0 | NOT GOVERNED | | `pnpm check:doc-fences` · `check:doc-types` · `check:doc-example-ids` · `check:spec-symbols` · `node scripts/check-doc-links.mjs` · `check:test-path-roots` · `check:vi-mock-specifiers` · `check:phantom-deps` | 0 ×8 | measured at `bc4ea976e` / `98623a24d`; the files they read have not changed since | | `pnpm check:doc-snippets` · `pnpm check:readme-exports` | 2 / 1 | NOT MEASURED locally — both `PRECONDITION NOT MET` (unbuilt packages); CI's Doc Snippet Type Check and README Exports are the readings. The docs hunk changes no fenced block; the README hunk is a JSON comment with no import binding | Not run locally, CI-owned: repo-wide `pnpm lint`, `pnpm test`, `pnpm type-check` (the public type face is unchanged — no export added or removed, no signature changed; emitted declarations differ in JSDoc text only), `pnpm check`, the i18n gates, `plugin-dashboard`'s own type-check (comment-only hunks in two test files). ## Deviations - `packages/types/src/zod/complex.zod.ts` was outside claim `5865368364`'s original file surface; edited under the bounded in-place-fix exemption (same defect class; mechanical; no other open PR on the file; inside core's type-checked closure) and accepted by the seat's addendum `5865980488`. The later addenda `5866223135`, `5866346457`, `5866480991` added the W1–W5 files and the `7759` changeset. - The first-round teaching sweep under-reported (W1, W5) because of its `option|filter` post-filter; corrected above. - The W4 append was first refused by the auto-mode classifier ("Modify Shared Resources", record `5866443287`), held and not re-routed; it landed only after the maintainer's 2026-09-29 ruling. ## Acceptance notes - `warnDroppedShorthandOptions` is dev-only, exactly as the Phase 1 warning was; the module's other skip warnings are not dev-gated. Always-on is a one-line product decision the review left to the maintainer; not this PR's. - Field-level `select` schemas teaching `options: ['…']` are a different contract and were not judged here — observation only, not filed. - `packages/core/CHANGELOG.md` and `packages/plugin-dashboard/CHANGELOG.md` narrate the lift as history; published record, not re-addressed (the CHANGELOG clause under AGENTS.md objectstack-ai#11: never re-address a paragraph in a published CHANGELOG). - PR objectstack-ai#10914 (objectui#10803 batch 6) has landed on `main` and is merged into this branch; no other open PR touches this PR's files. --- _Generated by [Claude Code](https://claude.ai/code/session_012UwY3ahMixEFkfTUxMVkYm)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #10916
Clause-②: no — a test-side tripwire and the ledger text beside it move to what spec
mainnow says; no published type, no export and no accepted document changes (the localJoinedReportBlockinterface and theindex.tsexports are untouched).What was red, and why the pin was right to go red
objectstack#20369 (
681868ca7) removed the barez.ZodTypeAnyannotation fromJoinedReportBlockSchemaon objectstackmain. The inverted pin_specJoinedReportBlockIsStillUntyped(true satisfiesIsUnknown OF SpecJoinedReportBlock) inpackages/types/src/__tests__/report-chart-query-spec-parity.test.tsthen failed with TS1360 ("Type 'true' does not satisfy the expected type 'false'.") on the Spec Main Shape Gate, which compiles every head against spec built from objectstackmain. Its docblock said that failure is the instruction: re-run the triage and burn it down.The pull-request type-check compiles the SAME file against the INSTALLED spec, the published 17.4.0 (the newest release on npm, published 2026-09-09, so before #20369). There the type is still
unknown. Whatever stands in the file has to compile against both until a spec bump installs a release carrying #20369.The re-run triage: spec
mainagainst the local interface, member by memberRead with the TypeScript checker off the source-built spec's
dist(objectstackdf3ba164a588);JoinedReportBlockis neitheranynorunknownthere and has no index signature.JoinedReportBlock(spec-report.ts)mainJoinedReportBlock(input)namestring, requiredstring, required (snake_case at runtime)typetabular/summary/matrixtabular)label,descriptiondefaultandtranslationscolumnsfield,aggregate, ...)objectNamedataset)groupingsDown,groupingsAcrossfilterruntimeFilter)chart[k: string]: unknowndataset,rows,values,runtimeFilter,orderVerdict. The spec type is now precise, and it describes a DIFFERENT shape: guard case 2c on spec
main, no longer 2b. The local interface is the legacy inline-query block, and the spec's closed schema refuses every one of its five local-only keys. So it is derivable, but not by a re-export beside the local interface: the burn-down REPLACES the publishedJoinedReportBlock(and theblocksofJoinedSpecReport) with the spec's type. That changes a published type, so it is its own slice, and it cannot land before the bump, because against the installed spec the derived type would beunknown. Nothing in this tree reads the local interface's legacy members (from a grep of the five local-only keys and a reading of the renderer, not a runtime probe): the joined branch ofDatasetReportRendererreads dataset-bound blocks, and the local type is only re-exported and used byJoinedSpecReport.The chosen shape, and the precedent it follows
objectui#10080 (PR #10089) is the precedent for "the gate compiles against spec
main, the PR check against the installed pin": it kept both jobs of the red declaration and changed its form so the file compiles against both specs, then proved both legs with a control. objectui#3177 (case 2c forNavigationItem) is the precedent for what replaces anIsAny/ IsUnknown pin once the spec is typed: per-key probes that each stop compiling the day that one divergence moves. This PR does both. The guard now has two halves, because two different runs read two different specs:_specJoinedReportBlockTyping, admitserasedandtypedand refusesany; theIsAnypin stays as it was. On a typed spec, seven divergence and agreement probes (_localBlockIsNotTheSpecBlock,_specOnlyBlockKeys,_localOnlyBlockKeys,_blockColumnsAreTwoConcepts,_blockLabelObjectArmIsNotASpecLabel,_blockNameAgrees,_blockTypeAgrees) record the triage above and stop compiling the day one of them moves, on the Spec Main Shape Gate first. On the erased spec they are not asked (OnTypedSpec), since an erased type declares no member to compare.erasedarm so that an erasure stops compiling again. If that release still erases the block, the spec erased it again, which is an upstream regression; theerasedarm loses its licence either way. A lit control (ReportSortmust readtyped) keeps the reader from passing vacuously.mainBEFORE that bump cannot be told apart, at the type level, from the installed 17.4.0: both are the same barez.ZodTypeAny. The state pin therefore admits it on the gate. The producer's own pin covers that window (packages/spec/src/ui/joined-report-block-type.test.tsin objectstack, landed with #20369), and the tripwire's version row catches it at the bump at the latest.Nothing was skipped, disabled or ts-ignored, and no workflow changed. The ledger row (the
JoinedReportBlockALLOW entry inscripts/check-spec-symbol-derivation.mjs, which is objectui#3162's ledger in machine-readable form) and that script's case-2b header now say what is true of both specs, and when the burn-down fires.Both compile legs, from committed state
Legs taken with
pnpm run type-checkinpackages/types(all threetscprograms,tsconfig.test.jsonincluded). The spec-mainlegs follow the gate's own procedure:@objectstack/specbuilt from objectstackdf3ba164a588(a detached worktree,pnpm install --frozen-lockfile --filter @objectstack/spec...,pnpm --filter @objectstack/spec build),npm pack --ignore-scripts, thennode scripts/spec-main-shape-gate.mjs injectinto this worktree's install. The inject printed its consumer list and re-pointedzodto 4.6.1, as the gate does. The installed legs ran after a clean reinstall, with the gate's injection marker confirmed absent.unknown733fd5ac6(base)main, beforedf3ba164a588, block typed733fd5ac6_specJoinedReportBlockIsStillUntyped(the same diagnostic the gate reports)main, after8ce61bad38ce61bad3, and again at head22226a8e9The two commits after
8ce61bad3touch onlyscripts/and.changeset/, which nopackages/typesprogram reads.turbo run type-checkover every package. Locally only@object-ui/typeswas compiled against specmain, because it is the only package the gate reported and the only one this diff touches. The full farm is the gate's own run on this head.The tripwire, fired for real
Under the injected spec-
maininstall,pnpm exec vitest run packages/types/src/__tests__/report-chart-query-spec-parity.test.tsgives 1 failed, 46 passed (47). The one failure is the typing row, with its message: "the installed @objectstack/spec 17.4.0 now types JoinedReportBlock — this is the bump the burn-down is owed at. [...] expected 'typed' to be 'erased'". The lit control and the version row pass (a source-builtmainstill reports version 17.4.0). Under the installed spec the same file gives 47 passed (47).Ablations, each proved on disk
All through
ablation-replace.mjs(objectstackscripts/) in wrap mode. For every leg the tool reported "mutation landed", and after it "restored: blob == HEAD (a1b2c707461e) andgit diff HEADis empty".Spec
maininjected,tsc -p tsconfig.test.json:orderfrom_specOnlyBlockKeys' expected setchartfrom_localOnlyBlockKeys' expected setcolumnsexpected asnumber[]_blockColumnsAreTwoConceptstypedarmerasedarm_blockTypeAgreescomparesnameagainsttype_blockLabelObjectArmIsNotASpecLabelInstalled 17.4.0:
erasedarm17.3.0mainwas a no-op. The replacement text was a substring of the anchor, soablation-replacerefused it ("the replacement count moved 1 to 1", paraphrased) and restored. Those three legs were re-run in--deletemode, and the table shows the re-run.Tests and gates, on head
22226a8e9pnpm exec vitest run --maxWorkers=2 packages/types/: 264 files, 5856 tests, all passed (installed spec).scripts/check-spec-symbol-derivation.mjs(31 files,scripts/__tests__and package suites): 31 files, 630 tests, all passed.pnpm check:spec-symbols: exit 0 ("0 untriaged collisions in 0 packages").check-changeset-presence: RED first ("1 source file(s) of 1 released package(s) changed, and this change adds no changeset"). That gate counts every file under a released package'ssrc/, tests included. So this PR carries.changeset/10916-joined-report-block-pin-two-specs.mdwith an EMPTY frontmatter, and the gate then reads exit 0 ("declared as releasing nothing").check-changeset-no-major,check-changeset-fixed,check-changeset-overwrite,check:changeset-claims,check:pending-changeset-literals,check:control-bytes,check:new-line-citations(0 new citations),check:test-path-roots,check:installed-pin-claims,check:shell-escape-residue,check:vi-mock-specifiers,check:vi-mock-inherit.check-governed-queue-guard --teston the three paths: NOT GOVERNED.--no-inline-config) on the two changed source files: 2 files linted, 0 errors, 0 warnings. Narrowed on purpose; the config enables no type-aware linting, so this diff cannot move the verdict on an untouched file.check:spec-floorsneeds every package built (it reportsno-artifactfor all 19 spec consumers in an unbuilt worktree); this diff moves no manifest ordist.The second slice this owes (not in this PR)
At the first spec bump past 17.4.0, derive
JoinedReportBlockfrom@objectstack/spec/uiand convergeJoinedSpecReport'sblocks. Reading: Clause-② yes (narrowing). The published local type accepts any key through its index signature and types the legacy members; the spec's is closed and dataset-bound. It should land WITH the bump: on a typed spec,JoinedSpecReportintersects the spec'sblocks(fromReportParsed) with the local ones, which makescolumnsboth names and objects (read from the two declarations, not compiled).Acceptance notes
spec-derived-unions.test.tskeeps its batch-8 note that this symbol was "STILL erased"; that note is a dated reading at spec 17.2.0 and is left as history.This change was made in Claude Code session
https://claude.ai/code/session_01DuWo5bdP9SdVebamn99GGk.Generated by Claude Code