`ReportComponentSchema.dataSource` and `ReportBuilderSchema.dataSources` were
annotated with `DataSource`, the runtime adapter interface, which no JSON
document can author — and no read site ever consumed either key. Both become
`?: never` on the TypeScript face and `retirementTombstone(...)` on the zod
mirror, so an authored value is refused by name instead of being accepted by
`z.any()` and read by nobody.
The docs page stops teaching the keys and records the retirement in prose. The
replacement binding key the ruling names (`data?: ViewData`) is NOT declared:
`data` is already the report row array on that interface, with a live read in
`LegacyReportRenderer`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Part of #6121 — this lands the RETIREMENT half of the ruling. The replacement
binding key is escalated on the card and is deliberately not declared here, for a
reason that was measured rather than preferred (below).
Maintainer ruling, 2026-08-30, 第 5 场总监席决裁批 #8, verbatim「批次 #8 同意」 —
option A: 「report 类型退役
dataSource/dataSources,声明data?: ViewData」.What landed
ReportComponentSchema.dataSourceandReportBuilderSchema.dataSourceswereannotated
DataSource/DataSource[]— the runtime ADAPTER interface(
find(resource, params),searchAll?()), which no JSON document can author.That mis-annotation is the defect this card was filed for: every example on
content/docs/core/report-schema.mdxauthored a config object against it andreported
TS2353: 'provider' does not exist in type 'DataSource'.?: never, not deleted, so an author whostill writes one gets a
tscerror at the authoring site instead of a silentlystripped key. Same disposition PR fix(types): the 8 remaining
on*handler mirrors declaredz.string()/z.any()refuse BY NAME (objectui#7344) #7468 landed foronSave/onCancelon thesame
ReportBuilderSchema.@object-ui/types/zod) — both keys carryretirementTombstone(...), so an authored value is refused at the key's ownpath with
code: 'invalid_type'and a message that names the key, says why itis retired and points at the spelling that runs. They were
z.any().optional()and
z.array(z.any()).optional(): anything parsed green and was then read bynobody. This moves the accept set of a published validator and the changeset
says so.
keys; a
> **Retired (objectui#6121):**note records what happened and where areport's data actually comes from, in the shape
core/enhanced-actions.mdxalready uses for
condition.packages/types/src/__tests__/report-schema-authoring-face.test.tsgains the retirement section: the
nevertwins as type-level assertions, bothnamed refusals asserted as an ENVELOPE (issue code plus path, not merely "it
threw"), the
.describe()metadata channel, and controls that a report withoutthe key still parses.
Why a retirement is the right reading of "retire", measured
No read site ever consumed either key.
@object-ui/plugin-report'sReportRenderertakes its adapter as a React prop or fromSchemaRendererContext, never offschema.dataSource; the live 9.0 path binds asemantic-layer
dataset(ADR-0021); and no renderer is registered forreport-builderat all — zeroComponentRegistry.register('report-builder', …)sites, with the bare
'report'registration as the positive control that makesthat zero a reading. Authored occurrences measured zero in this repo and in
the sibling
objectstackcheckout, whose report metadata bindsdatasetthroughout (positive control:
datasethits fire in the same grep). That is theruling's own deprecation-window exit criterion, so nothing is being silently
broken.
What did NOT land, and why it is a question rather than an omission
The ruling's replacement key is
data?: ViewData. Re-measured on today'smain— which the dispatch required, because the card's line numbers predate PR #7468 —
datais already declared onReportComponentSchema:data?: any[]("Report data") since
fb7a616553, mirrored asdata: z.array(z.any()), andLIVE:
@object-ui/plugin-report's dispatch table routes a legacy presentation schemato
LegacyReportRenderer, which reads that key as ROWS —data.length,data.map(...), and as the chart's rows.ReportRendererDispatcher.test.tsx, which renders{ type: 'report', title: 'My Old Report', data: [], columns: [] }.SchemaRendererstripsdataSourcefrom the props bag precisely because theadapter shares that name (objectstack#5576, the incident this card's ruling
cites) — but it does NOT strip
data, so a binding object authored under thatkey would also arrive at the React
dataprop.So declaring the binding under
datawould put two authoring contracts on one keyinside one renderer — the same collision the ruling rejected option D for, one key
name over.
ViewDatais a discriminated union onproviderand a bare row arrayis not a member of it, so the two shapes are disjoint, and the spec's own
valuearm already reserves the word (
aliases: { data: 'items' }). ⛔ I did notsubstitute option D back, and I did not pick between the remaining readings: the
options and a four-axis recommendation are on the card.
Verification — all readings at
ba8491de0Exit codes captured by redirecting to a file first, never through a pipe; each
gate quoted by its own verdict line.
pnpm --filter @object-ui/types type-checkEXIT=0 — all three legs(
tsc --noEmit,tsconfig.examples.json,tsconfig.test.json); the third isload-bearing, since
tsconfig.jsonexcludes test files and it is the only legthat judges the type-level pins.
pnpm exec vitest run packages/types/ packages/plugin-report/— Test Files 109passed (109), Tests 1775 passed (1775). The four new pins are named in a verbose
run, so this is not a vacuous file.
check:doc-snippetsEXIT=0 — "Semantic phase: 432 of 432 block(s) judged, 0failed. Every covered documentation snippet compiles against the built types",
after a turbo build of the gate's own
--build-filterclosure (34 packages, 1cached) and a grep confirming both
?: neverdeclarations reachedpackages/types/dist/reports.d.ts.check:doc-fencesEXIT=0 (80 declared files, 90 blocks — this page's ledger rowis unchanged at 8, no fence was added or removed) ·
check:doc-typesEXIT=0("Every documented component type is registered") ·
check:doc-linksEXIT=0 ·check:control-bytesEXIT=0 (6153 tracked text files) ·check:readme-exportsEXIT=0 (406 self-imports judged real, 0 fabricated) ·
check:spec-symbolsEXIT=0 ·
check:type-check-coverageEXIT=0 ·check:shell-escape-residueEXIT=0 · the four changeset gates EXIT=0 ("1 changeset(s) added, 0 modified").
ReportComponentSchema/ReportBuilderSchemaoutside@object-ui/typesis exactly one —@object-ui/plugin-report— withObjectGridSchemaas the positive controlthat fires on six.
pnpm --filter @object-ui/plugin-report type-checkEXIT=0,with the script name echoed so a zero-match filter cannot read as green.
check-governed-queue-guard --teston the five changed paths: NOT GOVERNED.Red-then-green, both faces
Each leg mutated the tree, proved the mutation ON DISK by grepping both the
injected and the removed text plus a blob-hash change, ran, then restored with
git checkout HEAD -- ABSOLUTE_PATHunder atrap ... EXIT INT TERM, and provedthe restore BY STATE (
git diff HEADempty AND blob equality with the HEAD blob),never by an exit code. No rebuild was needed for either leg and this is measured,
not assumed: the pin file imports its subjects by RELATIVE specifier
(
../reports.js,../zod/reports.zod.js), so vitest andtscread the source,not a
distartifact.dataSource: z.any().optional()— vitest EXIT=1 with exactlytwo failures: "refuses an authored
dataSourceon both faces" and "publishes theretirement guidance as schema metadata". The BUILDER pin stayed green, which is
what makes this attributable to the mutated key rather than a blanket break.
dataSource?: unknown— type-check EXIT=2 with threeINDEPENDENT detections:
TS2344on the type pin,TS2578 Unused '@ts-expect-error' directive, andzod-mirror-parity.test.ts TS2322 Type 'LedgerMismatch' is not assignable to type 'never'.That third detection is also the measurement behind a claim this PR would
otherwise be asserting: no ledger row moves. The parity instrument is
demonstrably live on this very pair — it fires the moment the two faces disagree —
and it is silent on the shipped shape, because a
nevertwin against a refusal armis agreement, which is why
reports.zod.ts#ReportBuilderSchemacarries no entry ineither ledger after PR #7468 either. So
packages/types/src/__tests__/zod-mirror-parity.test.tsis NOT edited by this PR,its header counts are untouched, and PR #7464 / PR #7447 keep that file to
themselves.
Declared narrowing (lint)
The repo-wide scan is CI's. Locally: the three changed TypeScript files with
--format json, where eslint's own reported population lists all three — 0 errors,6 pre-existing
no-explicit-anywarnings onreports.ts, every one of them on aline outside the edited ranges (
git diff --unified=0hunks: 36, 377-407,524-538; warnings at 182, 187, 337, 489, 497, 580). The judged population comes
from
eslint.config.jsitself, whosefiles:entries are TypeScript extensionsonly — no markdown entry — so the
.mdxand.changesetedits are outside it byconfiguration, not by my choice. Invariance for untouched files holds because the
config declares no
parserOptions.projectand noprojectService: with notype-aware rule, this diff cannot move any untouched file's verdict.
Clause ② — stays draft
needs:contract-reviewis applied and this PR stays a draft, per the ruling's「条款②档,PR 走复审链」.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EMrWaQw3XS5DxTHxp4yRyC
Generated by Claude Code