Repository navigation
Commit ecf90b2
fix(spec): the top-level report alias table routes the ten block spellings it claimed to mirror (#19855)
Fixes #19822
Clause-②: no
`ReportSchema`'s alias table in `packages/spec/src/ui/report.zod.ts`
says it is "kept deliberately parallel" to `JoinedReportBlockSchema`'s
table in the same file. It was not: ten of the block's entries were
missing at the top level, even though every target (`values`, `rows`,
`order`, `dataset`) is a key `ReportSchema` declares. This PR adds those
ten entries. After it, the top-level table carries all 17 block entries,
in the block's order and wording.
## Measured first: what the author saw on `origin/main` `71ef221988`
Setup: a minimal valid top-level report (`name`, `label`, `type:
'summary'`, `dataset: 'sales'`, `rows: ['stage']`, `values:
['revenue']`) plus one extra key, run through `ReportSchema.safeParse`.
Each parse gave exactly one issue, `unrecognized_keys`. Each message
then ended with the same `history` sentence ("Until this shape was
closed these were dropped silently — …"), which is left out of the
table.
| key | message on `71ef221988` (before) | suggestion before | after
this PR |
| :-- | :-- | :-- | :-- |
| `measures` | `` Unrecognized key(s) on this report: `measures`. `` |
none | `` Did you mean `measures` → `values`? `` |
| `metrics` | `` Unrecognized key(s) on this report: `metrics`. `` |
none | `` Did you mean `metrics` → `values`? `` |
| `dimensions` | `` Unrecognized key(s) on this report: `dimensions`. ``
| none | `` Did you mean `dimensions` → `rows`? `` |
| `groupBy` | `` Unrecognized key(s) on this report: `groupBy`. `` |
none | `` Did you mean `groupBy` → `rows`? `` |
| `groupings` | `` Unrecognized key(s) on this report: `groupings`. `` |
none | `` Did you mean `groupings` → `rows`? `` |
| `sort` | `` Unrecognized key(s) on this report: `sort`. `` | none | ``
Did you mean `sort` → `order`? `` |
| `orderBy` | `` Unrecognized key(s) on this report: `orderBy`. Did you
mean `orderBy` → `order`? `` | `order` (edit distance), **correct** | ``
Did you mean `orderBy` → `order`? `` (now from the table) |
| `sortBy` | `` Unrecognized key(s) on this report: `sortBy`. `` | none
| `` Did you mean `sortBy` → `order`? `` |
| `objectName` | `` Unrecognized key(s) on this report: `objectName`. ``
| none | `` Did you mean `objectName` → `dataset`? `` |
| `object` | `` Unrecognized key(s) on this report: `object`. `` | none
| `` Did you mean `object` → `dataset`? `` |
**Wrong suggestions: none.** No key drew a suggestion pointing at the
wrong key. In particular, `objectName` did **not** suggest `name`. Nine
of the ten got no suggestion at all. `orderBy` alone reached its target,
by edit distance, and that target was correct. As a control, the same
ten keys on a `JoinedReportBlockSchema` in the same run each got the
block table's prescription.
## What changed
- **`report.zod.ts`: `ReportSchema`'s `aliases`.** Added
`objectName`/`object` → `dataset`, `measures`/`metrics` → `values`,
`groupings`/`groupBy`/`dimensions` → `rows`, and
`sort`/`orderBy`/`sortBy` → `order`. They sit in the block table's
order, under the block's comments, adapted from "block" to "report".
- **The history note stays true.** The "kept deliberately parallel"
paragraph now says what parallel means: every block entry whose target
this schema declares is carried here to the same target. A key that must
not route at the top level gets a `guidance` line. The note also names
where the two tables still differ, and why: the `guidance` tables. The
block's three guidance entries point at keys this schema declares, and
`drillDown` is report-only. The existing note about the three removed
entries (`columns`, `chart`, `chartConfig`) is unchanged and still
accurate. A new paragraph records that the ten were missing and that
only `orderBy` reached its target by distance.
- **No `guidance` entry was needed.** None of the ten has a reason not
to route at the top level. Each target is declared on `ReportSchema`,
and none of the ten keys is declared there. One path was checked end to
end: on a `type: 'joined'` report, `sort:` is told `order`, and writing
`order` there is refused by the existing refinement ("a `joined` report
orders per block — move `order` onto `blocks[]`"). So that chain ends
with a prescription, never with silence. The comment above the ordering
entries says so.
## Tests
`packages/spec/src/ui/report.test.ts` has a new `describe`,
"ReportSchema — routes the block vocabulary the way a block does":
- **Per key, ×10: what the author sees.** The parse fails, the one
`unrecognized_keys` issue names exactly that key, and the message
contains `` Did you mean `KEY` → `TARGET`? ``.
- **Per key, ×10: taking the advice parses.** A full green `safeParse`
of the valid report with the target key. These are preservation pins and
are green before and after this change.
- **Parity, derived at runtime.** The pin reads both tables from
`strictObjectDeclarations()`, the same registry
`alias-integrity.test.ts` uses, looked up by `surface`, with exactly one
declaration asserted per surface. Every block alias whose target is in
`ReportSchema`'s shape must be routed at the top level to the same
target, or be answered by a top-level `guidance` entry. A non-vacuity
floor asserts that the derived set contains all ten keys this card
names.
**Firing control.** Run at `8a688435cc`: the alias region was swapped
back to `71ef221988`'s with `node scripts/ablation-replace.mjs`, which
wraps the run and restores the file. The on-disk blob during the
mutation was `fe9d289f63ba`, which is exactly `71ef221988`'s blob for
`report.zod.ts`, so the test ran against the file as it is on main.
Result: `Tests 10 failed | 40 passed (50)`.
- The per-key pin went red for 9 of the 10 keys.
- The per-key pin for `orderBy` stayed green, because edit distance
already answered `order`, as measured above. Its table entry is held by
the parity pin.
- The parity pin went red with exactly the ten rows, for example
`measures → values (top level: absent)` and `orderBy → order (top level:
absent)`.
Restore was proven: the blob after restore equals `HEAD`'s
`452f138f054d`, and `git diff HEAD` is empty.
**Runs.**
- Head `926cff321d`: `@objectstack/spec` `report.test.ts` +
`alias-integrity.test.ts` + `strictness-batch14.test.ts` gave `3 passed
(3)`, `91 passed (91)`. At `c78242fe05` the same set plus
`strict-object.test.ts` gave `4 passed (4)`, `123 passed (123)`.
- Head `926cff321d`: `pnpm --filter @objectstack/platform-objects test`,
run after rebuilding spec and its closure, gave `54 passed (54)` files
and `883 passed (883)` tests. `pnpm --filter
@objectstack/platform-objects typecheck` exits 0.
- Full `@objectstack/spec` `local` project at `8a688435cc`: `523 passed
(523)` files, `15434 passed | 1 todo` tests. `packages/` is
byte-identical between `8a688435cc` and `c78242fe05`: `git diff --quiet`
exit 0.
- `pnpm --filter @objectstack/spec typecheck`: exit 0.
`check:test-typecheck` reports OK and holds the ledger unchanged, and
`tsc -p tsconfig.test.json --listFilesOnly` includes
`src/ui/report.test.ts`.
**`alias-integrity.test.ts`** is not edited; PR #19789 holds that file.
Run as-is: `20 passed (20)` at `8a688435cc` and again at `c78242fe05`,
inside the 123 above. Its three claims hold for the new entries. No key
is declared, every target is accepted, and no two keys in the table
collapse onto one probe.
**Reading pin reversed in `@objectstack/platform-objects`.**
`packages/platform-objects/src/apps/translations/report-form-echo-decisions.test.ts`
pinned the gap this PR closes as a reading: it asserted that `measures`
on a top-level report drew no rename suggestion. That turned `Test Core
(4/6)` red on `c78242fe05`. The test is reversed in place, not deleted.
Under a retitled `it`, it now asserts that the top-level report refuses
`measures` AND names `` `measures` → `values` ``; the block half is
unchanged. The file header bullet that described the asymmetry is
rewritten to match. No other test in that file changed.
## Generated artifacts
`pnpm --filter @objectstack/spec build` runs `gen:schema`. After it,
`git status --porcelain` was empty: no generated file moved, as the
claim predicted. Alias keys are not part of `authorable-surface/`. `pnpm
--filter @objectstack/spec check:generated` then reported `All 15
generated artifacts are up to date`, measured against the dist that
build produced (declaration stamp match).
## Changeset reading
The change ships, so it carries a `patch` changeset
(`.changeset/19822-report-top-level-aliases.md`), with `Clause-②: no`.
- `@objectstack/spec`'s `files[]` includes `dist` and `src/**/*.zod.ts`,
so `report.zod.ts` is published both as source and compiled into
`dist/ui/`.
- Positive control: `measures: 'values'` occurs once in `71ef221988`'s
`report.zod.ts` (block only) and twice at head, and the built
`dist/ui/index.js` and `index.mjs` each carry it twice.
- The change is a behaviour change in a released package (`17.4.0`),
namely the text of a rejection. AGENTS.md makes that a `patch`, and ⛔
never `skip-changeset`.
- It is not breaking, since every accept/reject verdict is unchanged.
Only the prescription inside an existing `unrecognized_keys` message is
new. `check-adr-0087-registration` reads it as `1 non-breaking
changeset(s) seen`.
## Gates
Derived with `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` from this worktree: 85 commands
for the 4-path change set, the first round's 83 plus `check:i18n` and
`check:i18n-stale-fill`. All 85 were run at head `926cff321d` after a
full package build. `dispatch-gates --ran` read: `85 derived, 85 run, 0
NOT-MEASURED, 0 UNRUN`, with every recorded exit code 0. That union
covers `check:*` families only. Package test suites run in CI's `Test
Core`, which the tool lists as a path-scheduled CI job outside its
derived total.
- Three families needed built packages that a fresh worktree lacks, and
first answered exit 3: `check:dual-build-cjs-loads`,
`check:type-check-debt` and `check:doc-formula-expressions`.
- The package closure was then built, with declarations, under the
shared verify lock, and all three were re-run on the same head, each
exiting 0. The `type-check-debt` verdict line was "4 ledger entr(ies)
re-measured … none above its recorded number".
Lint, as a proven narrowing rather than a repo scan:
- The three changed source files were linted with `eslint
--no-inline-config --format json` at `926cff321d`: 3 files, 0 errors, 0
warnings.
- The config applied to them (`--print-config`) has no
`parserOptions.project`, and `eslint.config.mjs` states that it never
enables type-aware linting. So this diff cannot move the lint verdict of
any untouched file.
- The changeset is not an eslint input.
## Acceptance notes
- **Out of scope, reported for a separate card, not touched here.** A
`type: 'joined'` report accepts top-level `dataset`, `rows`, `columns`
and `values`. For example, `{ type: 'joined', blocks: [...], dataset:
'sales', rows: ['stage'], columns: ['region'], values: ['revenue'] }`
parses clean at head. But the report's own contract says "For a `joined`
report, the data lives on `blocks`", and the renderer's joined branch
returns before it reads any of them (objectui `0cf2d66`,
`packages/plugin-report/src/DatasetReportRenderer.tsx`, the
`presentation === 'joined'` branch). So those keys are silently dropped.
`order` is the only top-level selection key the joined refinement
refuses. This predates this PR: the existing `fields → values` and
`dataSet → dataset` entries already pointed there, and so do the new
`measures`, `dimensions` and `objectName` routes on a joined report. The
loud fix is to extend the joined arm of the refinement the way `order`
already is. That narrows acceptance, so it needs its own card and
changeset.
---
_Generated by [Claude
Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 77f54bf commit ecf90b2
4 files changed
Lines changed: 155 additions & 14 deletions
File tree
- .changeset
- packages
- platform-objects/src/apps/translations
- spec/src/ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
114 | 114 | | |
115 | 115 | | |
116 | 116 | | |
117 | | - | |
118 | | - | |
119 | | - | |
120 | | - | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
121 | 123 | | |
122 | 124 | | |
123 | 125 | | |
| |||
629 | 631 | | |
630 | 632 | | |
631 | 633 | | |
632 | | - | |
633 | | - | |
634 | | - | |
635 | | - | |
636 | | - | |
637 | | - | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
638 | 642 | | |
639 | 643 | | |
640 | | - | |
| 644 | + | |
641 | 645 | | |
642 | 646 | | |
643 | 647 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
| 13 | + | |
13 | 14 | | |
14 | 15 | | |
15 | 16 | | |
| |||
248 | 249 | | |
249 | 250 | | |
250 | 251 | | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
| 286 | + | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
| 305 | + | |
| 306 | + | |
| 307 | + | |
| 308 | + | |
| 309 | + | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
| 315 | + | |
| 316 | + | |
| 317 | + | |
| 318 | + | |
| 319 | + | |
| 320 | + | |
| 321 | + | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
| 327 | + | |
| 328 | + | |
| 329 | + | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
251 | 338 | | |
252 | 339 | | |
253 | 340 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
251 | 251 | | |
252 | 252 | | |
253 | 253 | | |
254 | | - | |
255 | | - | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
256 | 261 | | |
257 | 262 | | |
258 | 263 | | |
| |||
262 | 267 | | |
263 | 268 | | |
264 | 269 | | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
265 | 276 | | |
266 | | - | |
| 277 | + | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
| 282 | + | |
| 283 | + | |
| 284 | + | |
| 285 | + | |
267 | 286 | | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
268 | 292 | | |
269 | 293 | | |
270 | 294 | | |
271 | 295 | | |
272 | 296 | | |
273 | 297 | | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
| 303 | + | |
| 304 | + | |
274 | 305 | | |
275 | 306 | | |
276 | 307 | | |
| |||
0 commit comments