Repository navigation
Commit 9905e61
Fixes #20051
Clause-②: no
Stage (iv), the last stage, of ruling 甲 (`5856781584`, confirmed
`5856866699`), carried out under letter B of ruling `5910106571`.
Maintainer verbatim: 「同意 批次 #256」. The stored-row census stays waived:
「20051 不考虑现有的数据」 (`5909085121`). The at-tier contract review is the
`domain:spec` seat's, and it is owed before this PR is enqueued. This PR
carries no review record of its own.
## What changes
**1. A saved view stores the parsed value of every key its body
carried.** `saveMetaItem` now stores
`projectStorableViewBody(request.item, parsed.data, reparse)` for
`view`, where it used to store the request body with two grafts. The
rules:
- An undeclared key is dropped. Every key the console reads back is
declared (`VIEW_CONSOLE_ROUND_TRIP_KEYS`), so nothing it needs is lost.
- A declared key keeps its parsed, normalized value.
- A moved key is stored under its canonical spelling: `groups` →
`sections`, and `visibleOn` → `visibleWhen` (never grafted before).
- A schema default the author did not write is not stored. This is
ADR-0087's `storable` rule, the one flows follow.
Keys that are moved and defaults look alike in the parse output, so the
projection re-parses the body it built. A key the parse added is kept
only when the re-parse does NOT reproduce it. The loop ends when the
stored body re-parses to exactly the save's parse. If it does not
converge, the fallback stores the whole parse output: no key is lost,
and a warning is logged once per view. Nothing in today's `view` schema
reaches that fallback.
The projection is gated on `singularType === 'view'`, the same way the
flow branch is. Every other type keeps its body verbatim with the two
grafts. Both grafts still serve other types: `ViewFilterRuleSchema` is
reused by page, component and skill schemas, and `FormViewSchema` by
component, sharing and field schemas. A page save stays pinned verbatim
here as the control.
**2. A ViewItem record's top-level `options` bag is refused by name (Q3
= A).** It is pinned absent on both `ViewItemWireSchema` arms. The
response is `422 INVALID_METADATA` at `options`, with the prescription
to write `config.KIND`. It uses `z.never()`, not the form overlay's
`z.undefined()`, because `ViewItemWire.json` is published on its own and
`undefined` has no JSON Schema form. With `z.undefined()` the build
refused ("1 previously published schema(s) disappeared"). The ADR-0087
disposition is `registered view-item-options-bag-refused`, a new step-18
semantic entry.
**3. ADR citation.** The five "ADR-0005 §Validation" citations in
`protocol.ts` now cite ADR-0005 appendix (c), the "Addendum — 2026-05-16
(c): spec validation on overlay save". The same goes for the one in
`view.zod.ts`.
`scripts/adr-anchors/packages__metadata-protocol__src__protocol.ts.json`
gains ADR-0005 and ADR-0087 with the invariant. No `docs/adr/**` file is
edited.
## Measured before the change (the console's own writes at
`.objectui-sha` `db11afd4967c`, through the real `saveMetaItem`)
- **W1 to W5**, the toolbar saves of sort, density, hidden fields,
column widths and inline edit: the parse adds `type: 'grid'`. Now none
is stored. W1 also stops storing its sort row `id`.
- **W6b**, the saved-view toolbar toggle, which writes the whole tab
back: `id` and `objectName` are no longer stored.
- **W7 to W9**, config save, create, pin, set-default, reorder and
rename: stored unchanged.
- **Form overlay:** it no longer stores `sharing.enabled: false` or the
section `collapsible` / `collapsed` / `columns` defaults.
- **Why letter B beats A, measured by ablation 2 below:** storing
`parsed.data` whole would have minted toolbar rows that FAIL their own
re-save. A column-less patch carrying the `grid` default is refused as
"sets `type` but lists no `columns`".
## Pins
- **Re-judged in one round (the full-repo sweep):**
- metadata-protocol `protocol.graft-folded-form-sections.test.ts`: the
two GUARDs and the coexistence case.
- The Studio round-trip GUARD moves to the list overlay, where the keys
are declared. It now covers every key `VIEW_CONSOLE_ROUND_TRIP_KEYS`
maps to `listOverlay`.
- The form overlay now drops `isPinned` / `sortOrder` (undeclared there)
and keeps `isDefault`.
- "A form with canonical `sections` is stored as authored": no section
default, no `sharing.enabled`, and no undeclared `isPinned`.
- objectql `protocol-meta.test.ts`: the declared round-trip keys are
stored, and `objectName` is dropped (`object` carries the binding).
- objectql `protocol-publish-rollback.test.ts`: the diff's added key is
the declared `rowHeight`, and the undeclared `extra` never reaches a
version.
- objectql `protocol-view-identity-overlay.test.ts`: the sort row `id`
is not stored.
- spec `view-union-retirement-prescription.test.ts`: the never-leaf
population admits a closed two-entry family of pinned-absent refusals.
Each entry must be reached and must match its curated text.
- **New**, in the same metadata-protocol file on its pinned engine
double:
- W1 to W5 store no `type`.
- An undeclared key is dropped (`id`, `objectName`, a form-only
`layout`).
- A declared key keeps its normalized value (`exportOptions`, operator).
- A key moved inside a moved key is stored canonical, with no default
beside it.
- The persisted body parses to exactly what the save parsed.
- A record's `options` is refused on both arms, asserting envelope
`code` + `status` + path + message, with 0 rows written.
- The page control.
- Four structural cases for the helper.
- **New in spec** `view-overlay-options-bag.test.ts`: the record refusal
on both arms, plus a control.
- The card body's three pins still stand:
- A direct and an `options`-wrapped out-of-contract key get the same
refusal (door-half block).
- A legal `options.map` round-trips.
- The persisted body equals the parse result: the new parse-equivalence
pin.
## Evidence (head `41913270bf`)
- **Suites, locked runs:**
- metadata-protocol full: 191 files, 2819 passed, 19 skipped.
- objectql full: 345 files, 6778 passed.
- rest full: 237 files, 4641 passed, 99 skipped.
- spec `src/ui` + `src/migrations`: 97 files, 3560 passed. The whole
spec `local` project also ran before the `origin/main` merge, all green
once the retirement-population pin was re-judged.
- runtime, the 19 files that save or read a view: 990 passed.
- **Typecheck:** spec, metadata-protocol, objectql and lint all exit 0.
The spec run chains `check:scripts-typecheck` and
`check:test-typecheck`.
- **Ablation 1** (the view branch disabled, and the record refusal
renamed away, with `dist/` rebuilt and `ablation-dist-preflight` markers
present in both packages):
- metadata-protocol file: 9 red, 40 green.
- objectql's three re-judged files: 3 red.
- spec: 4 red. One of them is the population pin reading the renamed
leaf.
- Restore: blob equals HEAD for both files, markers absent from both
`dist/`, and `git status --porcelain` empty.
- **Ablation 2** (store `parsed.data` whole, which is option A): 20 red
of 49 in the metadata-protocol file, all five toolbar pins among them.
Restored to the HEAD blob.
- **Gates:** `dispatch-gates --commands --repo
objectstack-ai/objectstack` derived 125.
- 123 exit 0. `check:engine-split-ratio` first refused on the shallow
clone and passed after `fetch --shallow-since`. `check:skill-examples`
first refused on a missing client-react `dist` and passed after building
it.
- NOT MEASURED: `check:dual-build-cjs-loads` and
`check:type-check-debt`, both exit 3 PREREQUISITE NOT MET (they need a
whole-repo build; CI runs it).
- `--ran` reconciliation: 0 UNRUN.
- `check:generated`: all 15 artifacts current;
`content/docs/references/ui/view.mdx` was regenerated.
- **eslint (narrowed):**
- Measured over the 15 changed `.ts` files; each resolves under `eslint
--print-config`.
- `--no-inline-config --format json`: 15 files, 0 errors, 0 warnings.
- `eslint.config.mjs` enables no type-aware linting (its own comment
says so), so this diff cannot move a verdict on an untouched file.
## Docs follow-up (Tier H, separate PR for the maintainer's hand)
ADR-0005 must change in three places:
1. The Status line clause: "and the storage switch itself is the
ruling's last stage, not yet landed — see the amendment note under that
addendum's persisted-document bullet".
2. Appendix (c): "⚠️ **Ruled, not yet in force:** the storage switch is
the ruling's last stage and has not landed, so today `saveMetaItem`
still stores the request body (`request.item`)."
3. Appendix (c)'s bullet "The persisted document is the **parsed body**
(`parsed.data`) once every round-trip key is declared". Under letter B
it becomes: the parsed value of every key the body carried, with no
schema default materialised.
## Acceptance notes
- **Other types.** `resolveOverlaySchema` validates every type
`getMetadataTypeSchema` registers, not only `view` and `dashboard`, and
all of them still store the request body. Widening the projection is its
own decision.
- **An objectui read-side drop, present today and independent of this
PR.** objectui's adapter flattens a ViewItem record in `listViews` and
keeps only `config` plus `name` / `label` / `isDefault`. A saved view's
toolbar toggle writes that tab back whole, so it rewrites the row
without `isPinned` / `sortOrder` / `visibility` / `columnState`. This is
a code read at the pin, not measured in a browser. Its carrier is
objectui#11013, the objectui card on aligning console reads.
- **Other stale citations.** "ADR-0005 §Validation" is still cited
outside the files this PR touches (for example
`packages/metadata-core/src/injected-system-columns.ts:132` and three
objectql object-roundtrip test headers). Those stay for the next change
that touches them.
---
_Generated by [Claude
Code](https://claude.ai/code/session_018fxqvRJW12TaHC7DUQ89Y6)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2d5fe76 commit 9905e61
19 files changed
Lines changed: 1021 additions & 118 deletions
File tree
- .changeset
- content/docs/references/ui
- packages
- lint/src
- metadata-protocol/src
- objectql/src
- spec/src
- migrations
- entries/semantic
- ui
- scripts/adr-anchors
| 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 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2260 | 2260 | | |
2261 | 2261 | | |
2262 | 2262 | | |
| 2263 | + | |
2263 | 2264 | | |
2264 | 2265 | | |
2265 | 2266 | | |
| |||
2361 | 2362 | | |
2362 | 2363 | | |
2363 | 2364 | | |
| 2365 | + | |
2364 | 2366 | | |
2365 | 2367 | | |
2366 | 2368 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | | - | |
52 | | - | |
53 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
54 | 55 | | |
55 | 56 | | |
56 | 57 | | |
| |||
264 | 265 | | |
265 | 266 | | |
266 | 267 | | |
267 | | - | |
| 268 | + | |
268 | 269 | | |
269 | 270 | | |
270 | 271 | | |
| |||
0 commit comments