|
3 | 3 | > **v5.0 update (2026):** Throughout this document, the term *project* has been renamed to *environment* (no aliases; CLI flags, URL paths, schemas, env vars all hard-renamed). See [ADR-0006 v4 — the v5.0 rename and its no-alias decision](./0006-project-environment-split.v4.md#the-v50-rename-and-its-no-alias-decision) for the rationale. The body below is preserved verbatim for historical context. |
4 | 4 |
|
5 | 5 |
|
6 | | -**Status**: Accepted (2026-05-16) · **Amended** (2026-05-22, see "Amendment: post-ADR-0006 v4 scope") · **Amended** (2026-04-13, branch concept removed — see [ADR-0008 §0](./0008-metadata-repository-and-change-log.md#0-2026-04-13-amendment--drop-project-and-branch-from-metaref)) · **Amended** (2026-08-09, #6825 — the Phase-1 overlay-index migration is deleted; see "Amendment (2026-08-09, #6825): overlay-index delivery after the Phase-1 migration was deleted") · **Amended** (2026-09-04, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D6 — the per-organization overlay axis is **retired for now**: `sys_metadata` carries no organization column, org-scoped writes of the five tier-A types are refused, and the environment layer is the whole ledger; the environment → code layered resolution and the "no overlay row = the registry" reading are unchanged) |
| 6 | +**Status**: Accepted (2026-05-16) · **Amended** (2026-05-22, see "Amendment: post-ADR-0006 v4 scope") · **Amended** (2026-04-13, branch concept removed — see [ADR-0008 §0](./0008-metadata-repository-and-change-log.md#0-2026-04-13-amendment--drop-project-and-branch-from-metaref)) · **Amended** (2026-08-09, #6825 — the Phase-1 overlay-index migration is deleted; see "Amendment (2026-08-09, #6825): overlay-index delivery after the Phase-1 migration was deleted") · **Amended** (2026-09-04, [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D6 — the per-organization overlay axis is **retired for now**: `sys_metadata` carries no organization column, org-scoped writes of the five tier-A types are refused, and the environment layer is the whole ledger; the environment → code layered resolution and the "no overlay row = the registry" reading are unchanged) · **Amended** (2026-09-30, #20051 ruling 甲 — in "Addendum — 2026-05-16 (c)", which code cites as appendix (c), the persisted document is the **parsed body** once every round-trip key is declared, reversing the verbatim `request.item` save; a stored `view` row's round-trip keys are declared, 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) |
7 | 7 | **Deciders**: ObjectStack Protocol Architects |
8 | 8 | **Builds on**: [ADR-0003](./0003-package-as-first-class-citizen.md) (Package as first-class citizen), [ADR-0004](./0004-cloud-multi-kernel.md) (Cloud + per-project kernels) |
9 | 9 | **Amended by**: [ADR-0006 v4](./0006-project-environment-split.v4.md) (drops `sys_project` entirely), [ADR-0008](./0008-metadata-repository-and-change-log.md) (re-expresses overlay as `LayeredRepository`; subsequently drops `project`/`branch` from `MetaRef`), [ADR-0029 D9](./0029-kernel-object-ownership-and-platform-objects-decomposition.md#amendment-2026-08-09-6853-a-tenant-overlay-of-an-object-is-its-own-contributor-layer-not-a-second-own) (for `object` only: the overlay is a registry contributor LAYER over the packaged owner, resolved as `base = overlay ?? own`, instead of a destructive in-place overwrite) |
@@ -334,12 +334,76 @@ Implementation (`packages/metadata-protocol/src/protocol.ts`): |
334 | 334 | carrying `path/message/code` for each Zod issue. REST layer |
335 | 335 | (`packages/rest/src/rest-server.ts`) already propagates `status` |
336 | 336 | and `code` to the response. |
337 | | -- The persisted document is the **original** `request.item`, NOT |
338 | | - `parsed.data`. Studio attaches auxiliary fields (`isPinned`, |
339 | | - `isDefault`, `sortOrder`, `objectName`, …) that aren't in the canonical |
340 | | - schema; storing `parsed.data` would silently strip them on every save. |
341 | | - This trades strict-stripping against forward compatibility for Studio |
342 | | - extensions — the canonical fields are still type-checked. |
| 337 | +- The persisted document is the **parsed body** (`parsed.data`) once every |
| 338 | + round-trip key is declared. A key a client writes onto a stored row and |
| 339 | + reads back after a reload is declared on the type's schema, with its |
| 340 | + meaning, so the parse keeps it; a key nothing declares is not stored. The |
| 341 | + canonical fields are type-checked, and the stored row is what the contract |
| 342 | + says it is. ⚠️ **Ruled, not yet in force:** the storage switch is the |
| 343 | + ruling's last stage and has not landed, so today `saveMetaItem` still |
| 344 | + stores the request body (`request.item`). The note below records what is |
| 345 | + declared and what remains. |
| 346 | + |
| 347 | + > **Amended (2026-09-30) — the persisted document is the parsed body once |
| 348 | + > every round-trip key is declared, and a stored `view` row's round-trip |
| 349 | + > keys now are.** Provenance: ruling 甲 on #20051 — maintainer |
| 350 | + > 「开始总监决裁」, 2026-09-27, director batch #227 item 2, recorded on the |
| 351 | + > card as comment 5856781584 — which keeps the persistence half of ruling A |
| 352 | + > (comment 5824043998, item 2) as the end state: 「a saved view is the |
| 353 | + > parsed body, every key the console reads back is declared on the |
| 354 | + > contract, and ADR-0005 appendix (c) is amended to say so」. Code cites |
| 355 | + > this addendum as "ADR-0005 appendix (c)". Why: stored as sent, the Studio |
| 356 | + > keys lived in the store and nowhere in the contract, and nothing failed |
| 357 | + > while the parse dropped them unread. State the console needs after a |
| 358 | + > reload is declared, not kept alive by a store-the-raw-request exception. |
| 359 | + > |
| 360 | + > 1. **Superseded.** The bullet above read, as written on 2026-05-16: |
| 361 | + > "The persisted document is the **original** `request.item`, NOT |
| 362 | + > `parsed.data`. Studio attaches auxiliary fields (`isPinned`, |
| 363 | + > `isDefault`, `sortOrder`, `objectName`, …) that aren't in the |
| 364 | + > canonical schema; storing `parsed.data` would silently strip them on |
| 365 | + > every save. This trades strict-stripping against forward compatibility |
| 366 | + > for Studio extensions — the canonical fields are still type-checked." |
| 367 | + > 2. **What "every round-trip key is declared" means for `view`** (stage |
| 368 | + > (ii) of the ruling, #20456, PR #20474). It means one record: |
| 369 | + > `packages/spec/src/ui/view.zod.ts#VIEW_CONSOLE_ROUND_TRIP_KEYS`, each key the console writes onto a stored `view` row and reads back, mapped to the `#VIEW_METADATA_MEMBERS` branches it is declared on. |
| 370 | + > `packages/spec/src/ui/view-console-round-trip-keys.test.ts` holds the |
| 371 | + > record equal to the census of objectui's console at the |
| 372 | + > `.objectui-sha` pin, and holds each (key, member) pair declared, |
| 373 | + > described and kept by a parse. A new round-trip key joins the record, |
| 374 | + > declared on its members, in the change that adds the console write. |
| 375 | + > 3. **The declarations, key by key** — a reading at `origin/main` |
| 376 | + > `c9c182ed`; the record, not this list, is the authority. Two members |
| 377 | + > carry them: `viewItem`, published as `packages/spec/src/ui/view.zod.ts#ViewItemWireSchema`, and `listOverlay`, published as `packages/spec/src/ui/view.zod.ts#VIEW_METADATA_MEMBERS.listOverlay`. |
| 378 | + > - `isPinned`, `sortOrder`, `visibility`: one declaration, `packages/spec/src/ui/view.zod.ts#viewSwitcherRowStateFields`, spread into the two members by `#viewItemWireFields` and `#listOverlayRoundTripFields`. |
| 379 | + > - `isDefault`: `packages/spec/src/ui/view.zod.ts#viewItemBaseShape` on `viewItem`, `#flattenedViewOverlayFields` on `listOverlay`. |
| 380 | + > - `columnState`: `packages/spec/src/ui/view.zod.ts#viewItemWireFields` on `viewItem`, `#flattenedViewOverlayFields` on `listOverlay`. |
| 381 | + > - `_isOverride`: `packages/spec/src/ui/view.zod.ts#listOverlayRoundTripFields`, on `listOverlay` only, the one row shape the console's settings-overlay marker is written on. |
| 382 | + > |
| 383 | + > No form-overlay row carries these keys: the switcher lists list-family |
| 384 | + > views only. |
| 385 | + > 4. **The census's other finds map to a spelling already declared, not to |
| 386 | + > a new key.** `objectName`, named in the superseded bullet, maps to |
| 387 | + > `object`; a top-level `id` maps to `name`; a bare-array |
| 388 | + > `exportOptions` maps to the object form the parse lifts it to. |
| 389 | + > `filter[].id` / `sort[].id` are removed before the parse by `packages/spec/src/ui/view.zod.ts#stripViewConsoleDecorations` (`#VIEW_CONSOLE_ROW_DECORATIONS`). |
| 390 | + > `_draft` / `_diagnostics` are `packages/spec/src/kernel/metadata-read-decorations.ts#METADATA_READ_DECORATIONS`, stamped on the read and never stored. |
| 391 | + > The record's docblock gives the reason for each. |
| 392 | + > 5. **Interim state: the storage switch is stage (iv) of #20051, and it |
| 393 | + > has not landed.** Until it does, `packages/metadata-protocol/src/protocol.ts#saveMetaItem` stores the request body, with the normalizations it grafts back from the parse, and the pins that hold that verbatim save stay green. |
| 394 | + > One of them is `packages/objectql/src/protocol-meta.test.ts` |
| 395 | + > "preserves Studio-only auxiliary fields verbatim (not stripped)", the |
| 396 | + > 2026-05-16 "unknown extras preserved" case listed below. So today a |
| 397 | + > key no member declares is still stored: "declared" does not yet mean |
| 398 | + > "the only keys stored". |
| 399 | + > 6. **What stage (iv) does, on #20051 itself.** Persistence stores the |
| 400 | + > parsed body; the GUARD pins flip to assert the declared round-trip |
| 401 | + > instead of the verbatim save; the top-level `options` of |
| 402 | + > `ViewItemWireSchema` is judged in the same step; and the switch lands |
| 403 | + > only once the count of stored `view` rows carrying undeclared top-level |
| 404 | + > keys has been measured against production `sys_metadata`. Made before |
| 405 | + > its prerequisites, the switch turns 「stored and unread」 into 「200, |
| 406 | + > then the user's pinned / sort / visibility state silently dropped」. |
343 | 407 |
|
344 | 408 | Sample 422 response: |
345 | 409 |
|
|
0 commit comments