fix(spec): JoinedReportBlock, a ViewItem config and the overlay viewKind carry the shapes their doors accept (#19920) - #20369
Conversation
…d carry their shapes - JoinedReportBlockSchema drops its z.ZodTypeAny annotation, so JoinedReportBlock and Report/ReportParsed blocks[] are typed. - viewItemArmShape takes its config schema as a generic parameter, so ViewItem / ViewItemWire config is the arm's ListView / FormView type. - flattenedViewOverlayFields is generic in its kind, so each flattened overlay member's static viewKind keeps its own literal. - ViewMetadataParsed / AssembledViewArtifactParsed TSDoc names the list overlay type default the parse applies and the output type lacks. - Type-level pins for each. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…d by reference Inferred, ViewItemSchema, ViewItemWireSchema and the viewItem member of VIEW_METADATA_MEMBERS each spelled both config types out in full, +170 KB of view.zod.d.ts. Both schemas are now annotated through ViewItemArmShape / ViewItemWireArmShape (read off viewItemArmShape itself, identical to the inferred types), and the member is asserted to its schema's own type, so the declaration file names them instead. Adds the changeset, and corrects the pending note that said JoinedReportBlock still resolves to unknown. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…R-0122) JoinedReportBlockSchema, ViewItemSchema and ViewItemWireSchema were pinned input === infer only because their erased types were the same unknown on both sides. Typed, each carries defaults, so the pins are false: they are deleted and JoinedReportBlockParsed, ViewItemParsed and ViewItemWireParsed are declared, as the convention prescribes. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…check ledger The three Parsed aliases enter the ui entry's surface. The report.test.ts ledger shrinks by two signatures: blocks[] is no longer unknown there. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…ory on a refused body The pin count falls 786 -> 783 with the three deleted pins, stated in both prose places and the count history. The defineViewItem type pin now wraps the call: the factory parses, so calling it on the refused body threw at module load. Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
…ported-types-remainder
…declared types narrow Claude-Session: https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f5e4830d4d9e18648da12ea273f94e0df1b6e1e4 && git checkout f5e4830d4d9e18648da12ea273f94e0df1b6e1e4
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 5049a3cf4e3dd591cf5fc23856cc3a42234c9267 777b5097834d92c9e9565878406aa2409c597811 && git checkout -B drift-repro 5049a3cf4e3dd591cf5fc23856cc3a42234c9267 && git merge --no-ff 777b5097834d92c9e9565878406aa2409c597811
node scripts/docs-audit/affected-docs.mjs --json 5049a3cf4e3dd591cf5fc23856cc3a42234c9267
|
Contract reviewServed-tier: Inputs: card #19920 (body and all 11 comments), PR #20369 (body, 12-file list, net diff against merge-base ① Derived judgmentsAccept set: none moves — RIGHT. The source edits are type-level only: Public surface, each change named:
Nothing in the derivations is wrong or missing. ② Semver level
③ Boundary flagsNo
Implemented-by: VERDICT: FAIL Generated by Claude Code |
…ported-types-remainder
…ames are added The diff narrows published types and also adds three exported names (JoinedReportBlockParsed, ViewItemParsed, ViewItemWireParsed), so the line reads `yes (narrowing)`. The BREAKING banner and the ADR-0087 marker reason now say that no EXISTING export changes; the marker token is unchanged. Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH Co-authored-by: Claude <noreply@anthropic.com>
… shape's types
listOverlayPatchFields() read `type` and `columns` off ListViewShapeSchema
through a cast of its shape to a record of z.ZodTypeAny, which typed both
keys `unknown` on the list overlay member. So `{ object, viewKind: 'list',
columns: 42 }` type-checked as ViewMetadata, ViewMetadataParsed,
AssembledViewArtifact and AssembledViewArtifactParsed while that member
refuses it. The shape is now read as typed; the schemas are the same
objects, so no parse moves.
The item-4 TSDoc sentences on ViewMetadataParsed and
AssembledViewArtifactParsed no longer say the two keys are `unknown`, and
the pin that held them `unknown` is replaced by pins that they are typed,
with four refused-body probes and a runtime half. The changeset names the
fourth narrowing.
Claude-Session: https://claude.ai/code/session_01ARcDurZ5j34RdqsGgc4jgH
Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Inputs: card #19920 (body and all 13 comments), PR #20369 (body, 12-file list, net diff against merge base ① Derived judgmentsAccept set: none moves — RIGHT. Every source edit is at the type level and leaves the value expression as it was:
Public surface, each change named:
Nothing derived is wrong or missing. ② Semver level
③ Boundary flagsThe dev's report is
Implemented-by: VERDICT: PASS Generated by Claude Code |
…tstack-ai#20448) Fixes objectstack-ai#19920 Clause-②: no (narrowing) This PR takes items 1 and 3 of the remainder that seat 4's release on objectstack-ai#19920 (comment 5865019059) names: `ApiError.code` and the flattened list overlay's legacy `options` bag. Item 2, `ViewFilterRule.operator`, is not changed: its input type is a contract choice, so it is analysed as a fork (below) for the seat to take to triage. ## What changed Only types change. No schema's parse, no value, and no export moves; no export is added. The FROM column was read by a compiler-API census at the base `0283cb924` and by probes against the source; the TO column is also probed against the built `dist`. | item | FROM | TO | |:--|:--|:--| | 1. `ApiError.code` (`api/error-code-ledger.zod.ts`, `api/contract.zod.ts`) | `unknown`. `ErrorCode` was cast to `z.ZodType` naming only its OUTPUT type parameter, and `z.ZodType`'s INPUT parameter defaults to `unknown`, so the input type of `ApiErrorSchema` typed `code` as `unknown`: `{ code: 42, message: 'x' }` compiled as an `ApiError` while the schema refuses it at `code`. The same `unknown` reached the `error.code` of every response type built on `BaseResponseSchema` (58 input aliases, measured) and each `ApiError` row of a batch result. `makeApiErrorSchema` repeated the one-parameter cast for a caller-supplied vocabulary. | `ErrorCode`: the cast names both parameters, each spelled with the existing `ErrorCode` type alias. `makeApiErrorSchema`: both parameters named, the standard catalogue plus the caller's codes. The `…Parsed` types do not move: their `code` was already typed. | | 3. The list overlay's `options` bag (`ui/view.zod.ts`) | A string-keyed record of `unknown`, on the list overlay member and so on `ViewMetadata`, `ViewMetadataParsed`, `AssembledViewArtifact` and `AssembledViewArtifactParsed`, because `listViewKindBlocks()` returned a record of string to `z.ZodTypeAny`. `options: { foo: 1, kanban: 42 }` type-checked as all four while the member refuses both keys. | One optional entry per list kind that names a block (`calendar`, `chart`, `gallery`, `gantt`, `kanban`, `map`, `timeline`, `tree`), each the kind's own block with every key optional. The return type is a mapped type derived by the function's own rule (a value of the list shape's `type` enum that is also a key of the shape), each entry typed by zod's own `.partial()` answer through a typed helper, never a hand-written copy. The runtime loop is byte-identical; one assertion on its result states what the two derivations share, and the new pin file holds the runtime key set equal to the type's. | ### A change beyond the order's route, forced by a measurement The dispatch suggested dropping `makeApiErrorSchema`'s cast. It stays: its vocabulary is caller-supplied and spread into a `string[]`, so the cast is what carries the caller's codes into the type; dropping it would also change the returned schema class (to `ZodEnum`), a public-type change beyond this item. The defect was the missing input parameter, and that is what changed. The `ErrorCode` cast exists because the spread erases the members to `string`, not to dodge declaration size (mechanism assumption A1). But naming the input parameter DID hit declaration size, measured, and that fixed the spelling: - **First spelling, inline union in both parameters** (`74a132da1`): the built declarations grew 30,647,033 to 34,006,427 B (+3.36 MB, +11.0%); `api/index.d.ts` alone +1,262,450 B. Declaration emit prints an inline union literal by literal wherever a schema embeds `ApiErrorSchema` (78 sites in the `api` entry), and the input parameter doubled those prints. - **Landed spelling, the `ErrorCode` alias** (`539295c9e`): the emitter prints the alias by name, including for the output half the base already printed inline. The declarations SHRINK instead (table below). The bundler emits one new shared chunk, `error-code-ledger.zod` (31,949 B `.d.ts`, 31,950 B `.d.mts`), for the name to be imported from. ## Measurements (spec build, base `0283cb924` against head code `539295c9e`) - **TS7056**: 0 in every spec build of this round (base, `74a132da1`, `539295c9e`). - **Declaration files**: 128 at base, 130 at head; the build's own `check-dts-references` resolves 394/394 relative references across the 130 (382/382 across 128 at base). | declaration file | base | head | delta | |:--|--:|--:|--:| | `api/index.d.ts` (`.d.mts` the same, within 2 B) | 2,532,113 | 1,274,615 | -1,257,498 | | `automation-api.zod` chunk `.d.ts` (and `.d.mts`) | 535,440 | 193,663 | -341,777 | | `api-assembled/index.d.ts` (and `.d.mts`) | 184,582 | 86,529 | -98,053 | | `contracts/index.d.ts` (and `.d.mts`) | 505,045 | 505,100 | +55 | | `view.zod` chunk `.d.ts` (and `.d.mts`) — item 3 | 498,393 | 505,886 | +7,493 | | `error-code-ledger.zod` chunk, new (`.d.ts`) | 0 | 31,949 | +31,949 | | all `.d.ts` / `.d.mts` files | 30,647,033 | 27,331,377 | -3,315,656 (-10.82%) | - **Item 3 against the order's size rule** (A2: implement only if within the same order as PR objectstack-ai#20369's remainder 5, +2,729 B per chunk, 0 TS7056): item 3 moves only the `view.zod` chunk, +7,493 B per chunk (2.7 times that figure, the same order of magnitude, +1.5% of the chunk), 0 TS7056, no `any` anywhere (`check:exported-any` green). Taken on that reading; the ratio is stated so the seat can hold the rule to a tighter reading if it meant one. ## Reverse verification (from committed state `539295c9e`, on disk, through `scripts/ablation-replace.mjs`) Each pin file compiled under `tsconfig.test.json`'s options. The pins import `./contract.zod` / `./view.zod` relatively, so the subject is `src` and no `dist` is on the resolution path. | leg | reverted to (the base spelling) | pin file | result | |:--|:--|:--|:--| | control | nothing | all three pin files | 0 diagnostics | | A | `ErrorCode` cast naming the output parameter only | `api/api-error-code-type.test.ts` | 2 x TS2322, 3 x TS2578 | | B | `makeApiErrorSchema`'s cast naming the output parameter only | `api/api-error-code-type.test.ts` | 1 x TS2322, 2 x TS2578 | | C | `listViewKindBlocks()` returning a record of string to `z.ZodTypeAny` | `ui/view-overlay-options-type.test.ts` | 2 x TS2322, 9 x TS2578 | Every leg: the tool reports the anchor hit once and the mutation landed (blob changed), then the restore proven (blob equals the HEAD blob, `git diff HEAD` empty); an independent `git hash-object` check of all three files after the legs matches HEAD, and `git status --porcelain` is empty. ## Tests and gates Code is identical at `539295c9e` and `4358d1a33` (`4358d1a33` adds the changeset only). - **Spec**: build exit 0 (TS7056 x0); `typecheck` exit 0, `check:test-typecheck: OK — ... 53 file(s) / 251 error(s) / 138 pinned signature(s) held`, and `--listFilesOnly` puts both new pin files in its 540-test-file program; `vitest run --project local` at `4358d1a33`: 565 files passed, 16,604 tests passed, 1 todo; `check:generated`: all 15 generated artifacts up to date, with no tracked file moved by any build. - **Consumers** (after building spec and the 12-package closure of `metadata-protocol`): `@objectstack/metadata-protocol` typecheck exit 0 (192 test files in its program) and tests 189 files passed, 3 skipped, 2,745 tests passed, 19 skipped; `@objectstack/types` typecheck exit 0 (23 of 23 test files in its program) and tests 22 files, 685 passed; `@objectstack/client` `tsc --noEmit` over `src` exit 0. - **Probe against the built `dist`**, from a consumer program importing `dist/api` and `dist/ui`: 0 diagnostics, where every `@ts-expect-error` (a numeric `code` on `ApiError`, an invented one on `BaseResponse`, a numeric `options.kanban` on `ViewMetadata`, an unknown kind on `AssembledViewArtifact`) is consumed and a tuple compiles only if `ApiError.code` is neither `unknown` nor `any`. - **Gates**: `dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `4358d1a33` derived 86 commands (the dispatch's 75 plus 11); all 86 run, exit codes written to disk first: 83 exit 0 (`check:lean-entry-closure` after building `objectql`), 2 exit 3 PREREQUISITE NOT MET (`check:dual-build-cjs-loads`, `check:type-check-debt`: both need the whole-packages build). `--ran`: 86 derived, 84 run, 2 NOT-MEASURED, 0 UNRUN. Readings of note: `check-adr-0087-registration` reads `[BREAKING+clause-②-narrowing] not-required (no-migration-prescription)`; `check:api-surface` "public API surface + factory signatures unchanged"; `check:exported-any` "no exported type resolves to any: 2384 types + 1446 schemas across 18 entry points"; `check-empty-changeset` exit 0. - **Lint, narrowed and proven**: `eslint --no-inline-config --format json` over the 5 changed `.ts` files: 0 errors, 0 warnings; the changeset is outside eslint's configuration. `eslint.config.mjs`:327 enables no type-aware linting for any file, so this diff cannot move an untouched file's verdict. Repo-wide lint is CI's. - **NOT MEASURED, left to CI**: spec `test:repo` (it held the verify lock for the whole foreground window, about 595 s, without finishing, twice); the `@objectstack/client` test-layer typecheck (its 12 dev dependencies include `runtime` and `rest`, a 33-package build); the two exit-3 gates above; the objectui and cloud builds. ## Consumer census - **Item 1 in this repo.** 116 exported spec aliases carry `ApiErrorSchema`'s shape (58 input names; their `…Parsed` twins were already typed), found by walking each alias's properties, arrays and union members. Outside spec, code names them in `@objectstack/client` (return annotations, `as unknown as` casts and `['data']` reads), `@objectstack/metadata-protocol` (`toRowApiError`'s cast from `any` after a `safeParse` guard, and `as BatchUpdateResponse` casts) and `@objectstack/types` (`Pick` of `ApiError`'s optional fields, not `code`). All three typechecks are green above; neither named consumer file needed an edit. - **Item 3 in this repo.** Outside spec, `ViewMetadataSchema` and `AssembledViewArtifactSchema` are called with `safeParse` in `objectql`, `rest` and `metadata-protocol` tests and `objectql`'s `engine.ts`; both schemas' own static types are the erased unions, so no typed `options` read exists outside spec. - **objectui at the pin `f8a9d0fb`.** `ApiError` appears only as `Pick` of `userMessage` (two files); none of the four view types is named. Neither narrowing reaches it (from reading, not compiling). - **cloud**: no checkout in this container, NOT MEASURED. ## Item 2, `ViewFilterRule.operator`: not changed, a fork for triage `operator` is `z.preprocess(normalizeFilterOperator, z.enum(VIEW_FILTER_OPERATORS))`. zod types a preprocess's input as its function's parameter type, and `normalizeFilterOperator` takes `unknown`, so `{ field: 'status', operator: 42 }` compiles as a `ViewFilterRule` (and as a rule on every carrier: `ListView.filter`, tab filters, `Page.filterBy`) while the door refuses it. Who writes the legacy spellings the fold accepts, measured: - `examples/`: 0 legacy spellings on a view-filter carrier, 19 canonical ones in 8 files. (The one legacy-looking hit, `operator: 'ne'` in `app-showcase`'s `invoice.object.ts`, is a field's `lookupFilters`, a separate closed dialect.) - In-repo non-test code: 0 (every other hit is another dialect: lookup filters, auth `where`, skill trigger conditions, analytics). - objectui at the pin `f8a9d0fb`: the filter builder emits camelCase ids (13 of its 22 option values are alias-table keys: `notEquals`, `greaterThan`, `notIn`, `isNull`, …). Its two producers typed against spec's `ViewFilterRule` (`viewFilterFold.ts`, `ObjectDataPage.tsx`) fold through `normalizeFilterOperator` before typing, so the canonical id is what reaches the type. objectui at `9f0c84a44` (its current head) emits the 20 canonical ids only. - Stored `sys_metadata` rows: the alias table exists for them; they are read through the runtime parse, whose input is `unknown` whatever the type says. The three options, the four axes and the recommendation are in the `os-dev-report` on objectstack-ai#19920 (`open_questions`). In short: A, canonical enum only (type the preprocess function's parameter; the runtime fold is untouched); B, the enum plus the alias-table spellings (needs the table's keys typed as literals, and still cannot express the case-folded variants the fold also accepts); C, leave `unknown` with a declared reason. The recommendation is A. ## What stays on objectstack-ai#19920 A compiler-API census of the 2,337 non-generic exported aliases of `packages/spec/src` (tests excluded, 11 generic skipped), with an injected control module that must read lit (it did, at base and head): - **Alias level**: 6 aliases resolve to `unknown` at base and at head, and none belongs to this family: `FlowValueSlot`, `AssignmentValue` and their `Parsed` (value slots), `GetPublishedMetaItemResponse` and its `Parsed` (opaque by ruling). - **Top-level keys**: 194 at base, 193 at head; the one that left is `ApiError.code`, and none entered. The only family site left is `ViewFilterRule.operator` (item 2). Every other key the census reads is declared `z.unknown()` / `z.any()` (the door accepts anything, so the type is honest), a third-party or zod type, a service map or a fixture; `GetMetaItemLayeredResponse.code` and `ViewMetadata.defaults` were checked by hand and are both declared `z.unknown()`. - **Index signatures one level below a top-level key**: 391 at base, 387 at head; the four that left are `options` on `ViewMetadata`, `ViewMetadataParsed`, `AssembledViewArtifact` and `AssembledViewArtifactParsed`, and none entered. Blind spot, declared: deeper nesting is not walked. ## Clause-② Line 2 and the changeset (`b26d6506b`) both read `Clause-②: no (narrowing)`. The changeset keeps its BREAKING banner and the ADR-0087 marker `not-required (no-migration-prescription)`, so `check-adr-0087-registration` still reads the narrowing. The value is `no` because this diff adds no export and moves no accept set; it only narrows published types. That is the PR objectstack-ai#19919 / PR objectstack-ai#20260 shape for this defect class. ## Acceptance notes - `makeApiErrorSchema`'s generic return type still prints the standard catalogue inline: 4 prints in its one declaration (a 4,311 B line in the emitted `contract.zod` declaration), where the base printed 2. A local generic alias would name it; not done here, being one bounded declaration. - A field's `lookupFilters` is its own closed operator dialect (`eq`, `ne`, `gt`, `lt`, `gte`, `lte`, `contains`, `in`, `notIn`), whose members are spellings `ViewFilterRule` treats as deprecated aliases. Both are enforced; noted for item 2's triage, not filed. - The two new pin files follow the two-program shape of `view-overlay-viewkind-type.test.ts`: tsc judges the type half, vitest the runtime half; the refusal cases assert the issue `code` and `path`, not a bare failure. Line 1 was changed from the partial-landing marker to this closing keyword by the `domain:spec` seat 1 (`session_01B3TqpoQbTAfG7G74GMDWNW`): item 2 (`ViewFilterRule.operator`'s input type) now has its own card, objectstack-ai#20450, for triage, which is the dispatch order's A4 condition for closing objectstack-ai#19920 with this PR. Line 2 and the `## Clause-②` section were amended by the same seat after the at-tier record 5871015216 found the value `yes` wrong for this diff; the changeset line moved with them in `b26d6506b`, and the claim on objectstack-ai#19920 was amended in place. The stale `Part of` paragraph was removed. --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #19920
Clause-②: yes (narrowing)
This PR takes the four remainder items that seat 1's release on #19920 (comment 5858219255) lists, plus remainder 5 (the flattened list overlay's
typeandcolumns), onmainafter PR #20286. #19920 remains open because this round's census and probes found three more sites of the same family, outside this PR's surface. See "What stays on #19920" below.What changed
Only types change. The runtime accept set does not move: no schema's parse, no value, and no removed or renamed export. The FROM column was probed on the base
c5dcb3ba0, both against the source (compiler API,tsconfig.test.jsonoptions) and against the builtdist.JoinedReportBlock(ui/report.zod.ts)unknown, becauseJoinedReportBlockSchemawas annotatedz.ZodTypeAny. Eachblocks[]element ofReport/ReportParsed(and so ofdefineReport's parameter) wasunknownfor the same reason.blocks[]elements are blocks.config(ui/view.zod.ts)unknownon both arms ofViewItemandViewItemWire, becauseviewItemArmShape(viewKind, config)typedconfigasz.ZodTypeAny. The sameunknownreacheddefineViewItemand theviewItemmember ofViewMetadata,ViewMetadataParsed,AssembledViewArtifactandAssembledViewArtifactParsed.ListViewconfig underviewKind: 'list', aFormViewconfig underviewKind: 'form'.configis a generic parameter now, besideviewKind.viewKind'list'and'form'on both members, becauseflattenedViewOverlayFields(kind)was not generic, soz.enum([kind])widened.{ type: 'grid', columns: ['name'], object, viewKind: 'form' }type-checked, through the list member, as all four union types above, while both doors refuse it.'list'on the list overlay member,'form'on the form overlay member: the function is generic in the kind.typedefaultViewMetadataParsed, and the same onAssembledViewArtifactParsed. It says, from the code, that the member declarestypewithout.default('grid')and re-applies it in.overwrite(applyListOverlayTypeDefault). An.overwrite()returns the member's own output type, sotypestays optional in the output type while every parse through that member returns it ('grid'when the body named none). On that membertypeis typed as the list shape'stypeenum.typeandcolumns(ui/view.zod.ts)unknownon the list overlay member ofViewMetadata,ViewMetadataParsed,AssembledViewArtifactandAssembledViewArtifactParsed, on input and on output, becauselistOverlayPatchFields()read both keys offListViewShapeSchemathrough a cast of its shape to a record ofz.ZodTypeAny.{ object, viewKind: 'list', columns: 42 }type-checked as all four while that member refuses it.typethe list view type enum,columnsthe field list. The shape is read as typed; the schemas are the same objects, so no parse moves.Changes beyond the five items, each forced, with what forced it
view.zod.d.tsgrows by +170,655 B (500,535 to 671,190, +34%), with 0 TS7056. The cause is thatViewItemSchema,ViewItemWireSchemaandVIEW_METADATA_MEMBERS.viewItemeach spell both config types out in full, at 58,764 B, 59,326 B and +60,108 B.viewItemArmShapeitself.ViewItemArmShapeis the ReturnType oftypeof viewItemArmShape, instantiated.ViewItemWireArmShapeis that plusviewItemWireFields(), written as one mapped type. TheviewItementry ofVIEW_METADATA_MEMBERSis spelledViewItemWireSchema as typeof ViewItemWireSchema, an assertion to the schema's own type, so the emitter names the schema instead of copying it.view.zod.d.tsis 496,008 B, 4,527 B smaller than on the base.Equalprobe (the mutual-conditional form) against in-memory inferred twins of both schemas. It holds for each schema, for its z.input and z.output, and for the member. A control pair answers false.type-alias-convention.pin.test.tsturned into TS2344 ×3:Iso_ui_report__JoinedReportBlockSchema,Iso_ui_view__ViewItemSchemaandIso_ui_view__ViewItemWireSchema. They had held only because input and infer were the same erasedunknown; typed, each schema carries defaults. The pins are deleted. Percheck:spec-parsed-aliasrule 2, three parsed-state aliases are added:JoinedReportBlockParsed,ViewItemParsedandViewItemWireParsed. These are three new type exports on theuientry;api-surface/ui.jsonandexport-origins/ui.jsonwere regenerated. The pin count is restated from 786 to 783 in the file's two prose places and its count history.test-typecheck-debt.json. Thereport.test.tsentry shrinks by two signatures (TS18046 "'b' is of type 'unknown'" and TS2571), becauseblocks[]is typed now. It was regenerated withgen:test-typecheck-debt.Confirmation needed: a pending release note is corrected on purpose (
Check Changesetstays red).changeset/19920-exported-types-not-unknown.mdis PR fix(spec): InlineAction, ViewMetadataParsed and AssembledViewArtifact(Parsed) name their shapes, not unknown (#19920) #20260's pending entry for this card. It ended "JoinedReportBlockis not changed by this change, and still resolves tounknown."JoinedReportBlockis not changed by this change. It stops resolving tounknownin its own entry ([finding] four more exported spec types resolve tounknownwhile their TSDoc promises a shape —ViewMetadataParsed,InlineAction,AssembledViewArtifact,JoinedReportBlock(the #19871 class, other sites) #19920)." That holds whichever release carries either entry. Nothing else in the note moves.node scripts/check-empty-changeset.mjs --base origin/mainexits 1, naming exactly this file, in its DELIBERATE CORRECTION class. Following the gate's prescription, the file is not restored from the base, which would put the false sentence back.main's deletion and the correction is moot.The new entry is
.changeset/19920-exported-types-remainder.md:@objectstack/specminor,Clause-②: yes (narrowing)(the diff narrows published types and also adds three exported type names, per the at-tier record 5863387565), a BREAKING banner naming every type that narrows, FROM and TO per item (four bullets, remainder 5 included), a banner and ADR-0087 reason saying that no EXISTING export changes, and the ADR-0087 markernot-required (no-migration-prescription).Measurements
5049a3cf, the pre-remainder-5 head406335afand the head777b5097.406335afto777b5097): theview.zodchunk grows 495,664 to 498,393 B (+2,729, the same for.d.mts); thepage.zodchunk moves -3 B; every other declaration file is byte-identical; all 128 files +5,452 B.5049a3cfto777b5097):view.zodchunk (.d.ts, and the same for.d.mts)page.zodchunk (report and assembled views)index.d.tssystem/index.d.tsui/index.d.ts.d.ts/.d.mtsfilesreport.zod) are the previous run's and were not re-measured; the chunk table above supersedes them.Reverse verification (on disk, from committed state, via
scripts/ablation-replace.mjs)All five legs re-run at
777b5097, from committed state, throughscripts/ablation-replace.mjsin wrap mode; each pin file compiled undertsconfig.test.json's options:JoinedReportBlockSchema: z.ZodTypeAnyjoined-report-block-type.test.tsconfig: z.ZodTypeAnyinviewItemArmShapeview-item-config-type.test.tsflattenedViewOverlayFieldsview-overlay-viewkind-type.test.tstypekeeping the list defaultview-overlay-viewkind-type.test.tsz.ZodTypeAnyview-overlay-viewkind-type.test.tsEvery leg: the tool reports the mutation landed and the restore proven (blob after restore equals the HEAD blob,
git diff HEADempty), and a second check re-readsgit hash-object. Control, no mutation: the three pin files compile with 0 diagnostics;git status --porcelainempty.Tests and gates, at
777b5097At
777b5097: spec build exit 0 (TS7056 x0); spec typecheck exit 0 (check:test-typecheck OK, 53 files / 253 errors / 140 signatures); spec check:generated exit 0; spec vitest local 560 files, 16,493 passed, 1 todo; spec test:repo 34 files, 620 passed; consumer typechecks exit 0 for lint, metadata, metadata-protocol and objectql after building their 12-package closure (rest NOT MEASURED locally, 26-package closure). dispatch-gates derived 86: 83 exit 0, check-empty-changeset exit 1 (the deliberate correction), 2 NOT MEASURED (check:dual-build-cjs-loads, check:type-check-debt, exit 3);--ran: 86 derived, 84 run, 2 NOT-MEASURED, 0 UNRUN. Lint narrowed and proven: 7 changed.tsfiles, 0 errors, 0 warnings; the 5 other changed files are outside eslint's configuration; no type-aware linting (eslint.config.mjs:327).Consumer census
packages/spec, the code that imports any narrowed name is:defineReportin the two example report files above;Reportinqa/downstream-contract/src/pipeline.report.ts;ReportSchemain one platform-objects test and one downstream-contract test;ViewMetadataSchemain two metadata-protocol tests;AssembledViewArtifactSchemain objectql'sengine.tsand one objectql test (its own static type does not move).viewItemArmShapeorflattenedViewOverlayFields, which are not exported. All consumer typechecks above are green.f8a9d0fb: one break, by design, for the seat to carry.packages/types/src/__tests__/report-chart-query-spec-parity.test.ts:725 is the inverted tripwiretrue satisfies IsUnknownof the spec'sJoinedReportBlock. Compiled with that file's own two type definitions against this head's dist, it gives TS1360 ("Type 'true' does not satisfy the expected type 'false'"); line 726 (IsAny) still holds.scripts/check-spec-symbol-derivation.mjs:636.type-check(tsc -p tsconfig.test.json) on its next spec bump. It does not red this repo's Console Pin Gate, which builds objectui without__tests__.ViewItemSchema/ViewMetadataSchemain code; every other census hit is a comment or objectui's own localJoinedReportBlockinterface. From reading them, not from compiling them: they read.success/.errorof asafeParse, or cast the schemaas unknown asa local interface. None reads a typedconfigoff a parse result.f8a9d0fb. Six calls in four test files parse a body withViewMetadataSchema, and none reads.dataoff the verdict, so the now-typed list-overlaytype/columnsreach no objectui read (from reading, not compiling).What stays on #19920
A compiler-API census of the 2,337 non-generic exported aliases of
packages/spec/src, excluding tests, with a control that must read lit: 6 aliases resolve tounknownbefore and after this PR, and none belongs to this family (FlowValueSlot,AssignmentValueand theirParsed, value slots;GetPublishedMetaItemResponseand itsParsed, opaque by ruling). At top-level-key level it counts 210 keys typedunknownbefore remainder 5 and 202 after; the 8 that left are remainder 5. Three sites of the family remain, outside this PR's surface:ApiError.code(input):ErrorCodeis cast to a one-argumentz.ZodType, whose input defaults tounknown, andmakeApiErrorSchemarepeats the cast.{ code: 42, message: 'x' }compiles asApiErrorwhileApiErrorSchemarefuses it atcode.ViewFilterRule.operator(input): az.preprocess, whose input isunknown(the causeInlineActionhad).{ field: 'status', operator: 42 }compiles while the door refuses it.optionsbag:listViewKindBlocks()returns a record ofz.ZodTypeAny, so the bag is typed as a string-keyed record ofunknown.options: { foo: 1, kanban: 42 }compiles asViewMetadatawhile the door refuses it. The census does not scan nested keys; a probe found this one.Acceptance notes
check:spec-parsed-alias: 1,442 barez.inputaliases, 783 pinned isomorphic, 659 paired).packages/types/src/__tests__/report-chart-query-spec-parity.test.tsat objectui pinf8a9d0fbasserts thatJoinedReportBlockisunknown(line 725). It goes red at the next objectui spec-pin bump, and its docblock says that failure is the instruction to re-run the triage and burn down the local interface. Carrier: the next objectui spec-pin bump. It cannot red this repo: the Console Pin Gate builds objectui without its__tests__.FormViewSchema.layout(spec(ui):layout: 'inline' | 'grid'onobject-form/FormViewparse green and render asvertical— enforce-or-remove (ADR-0049) #20221),ListViewShapeSchema.tabsand the view container's bodyname(spec(ui): retirelist.tabsand the view container's bodyname(2 keys);listViews+ ViewTabBar and the row name already deliver both #20301) are untouched.origin/main5049a3cfwas merged once, at1161545d, with no conflict; commits that landed onmainlater touch none of this PR's files.Generated by Claude Code