You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ff5ca47
Browse filesBrowse the repository at this point in the historyBrowse files
docs(adr): ADR-0005 appendix (c) — the storage switch is in force for view, and a saved view stores the parsed value of every key its body carried (ruling B) (#20899)
Fixes#20879
Clause-②: no
This is the documentation step that ruling `5910106571` on #20051 names
(batch #256 item 3, Q2 = B, maintainer 「同意 批次 #256」): "ADR-0005 appendix
(c)'s `parsed.data` wording and its two "not yet in force" markers
change in a separate Tier H documentation PR for the maintainer's hand."
Tier H (`docs/adr/**`): this PR stays draft until the maintainer's word.
## What this changes
One file, `docs/adr/0005-metadata-customization-overlay.md`, four
places:
1. **Status line.**
- The ruling 甲 entry's clause "the storage switch itself is the ruling's
last stage, not yet landed" now says the switch "is in force for `view`
since PR #20868 (`9905e61ca2`)". It points at "the amendment notes"
(plural).
- One new `**Amended** (2026-09-30, #20051 ruling B — …)` entry: the
parsed body a `view` stores is the parsed value of every key its request
body carried, not the whole parse output. Undeclared keys are dropped,
schema defaults are not materialised, and a moved key is stored under
its canonical spelling. Every other type still stores its request body.
2. **Appendix (c)'s persisted-document bullet** is rewritten from the
landed code:
- A `view` stores the parsed value of every key its request body carried
(`projectStorableViewBody`).
- The stored row re-parses to exactly what the save accepted.
- Every other type still stores its request body (`request.item`).
- It has been in force since PR #20868. Rows stored before it are not
migrated.
- The "⚠️ Ruled, not yet in force" marker is gone, because the bullet
now states the rule in force. The bullet links ADR-0087's addendum
2026-08-01b for the `storable` rule.
3. **The stage (iii) note keeps its text.** A "⚠️ **Historical —
superseded.**" paragraph now sits above its items 5 (the interim state)
and 6 (the stage (iv) plan). Both items are kept verbatim. This is
ADR-0005's own convention: its "Overlay-uniqueness index" block is left
standing as written under the same marker, because an accepted record is
not edited to make the past look like the present.
4. **A new dated note, "Amended (2026-09-30)"**, directly after the
first one, in the stage (iii) form:
- **Provenance.** Ruling B `5910106571` with the maintainer's 「同意 批次
#256」. The maintainer's 「20051 不考虑现有的数据」 (`5909085121`), which waived
the stored-row count. PR #20868, merged as `9905e61ca2`.
- Item 1 quotes the stage (iii) bullet and the Status clause verbatim,
marked **Superseded**.
- Item 2: what a `view` save stores.
- Item 3: the two grafts no longer run on a `view` save, and every other
type still stores its request body.
- Item 4: a view item record's top-level `options` is refused by name
(Q3 = A). This settles the judgement the first note's item 6 left open.
- Item 5: the pins.
The heading `Addendum — 2026-05-16 (c): spec validation on overlay save`
is byte-stable: 0 hits in the diff and 1 in the file. So
`scripts/adr-anchors/packages__metadata-protocol__src__protocol.ts.json`
and the code comments that cite that heading stay as they are, and
nothing outside this file changes.
Per the triage direction (`5914934930`), the text says nothing about
types the switch does not cover, beyond one fact: every other type still
stores its request body, as the landed code does.
## Each sentence against the landed code (`origin/main` `00a92e18d`)
- `saveMetaItem` (`packages/metadata-protocol/src/protocol.ts`) stores
`projectStorableViewBody(request.item, parsed.data, reparse)` inside `if
(singularType === 'view')`. Every other type goes to the `else` branch,
which keeps
`graftNormalizedOperators(graftFoldedFormSections(request.item,
parsed.data), parsed.data)`.
- The rules come from `buildStorableViewNode`'s docblock and body:
- an authored key the parse kept is stored with its parsed value;
- an authored key the parse did not keep is dropped;
- a key the parse added is stored only when the re-parse does not
reproduce it (a moved key), and is not stored when it does (a default).
- The loop returns `converged: true` only when
`storableEqual(again.data, parsed)`. Otherwise it stores `parsed` whole
and warns once per view (`storableViewFallbackWarned`). Item 2 of the
new note says both.
- "with the normalizations `saveMetaItem` applied to it before the
switch" (item 3) covers what the other types already had, and nothing
new:
- the two grafts;
- the page `type` default (`withDeclaredPageTypeDefault`, #20101, which
predates #20868);
- the flow canonicalization that runs before the gate.
- The `options` refusal: `VIEW_ITEM_OPTIONS_REFUSED` and `options:
z.never(…)` in `packages/spec/src/ui/view.zod.ts`, and the ADR-0087
entry `view-item-options-bag-refused` in
`packages/spec/src/migrations/registry.ts`.
- The pins: `protocol.project-storable-view-body.test.ts` covers the
converged arms and the fail-safe.
`protocol.graft-folded-form-sections.test.ts` has the stage (iv)
describe blocks: W1–W5 store no `type`, an undeclared key is dropped,
the persisted body parses to exactly what the save parsed, and a page is
stored as sent.
- `protocol-meta.test.ts` re-judged "preserves Studio-only auxiliary
fields verbatim" as "stores the console's declared round-trip keys, and
drops the `objectName` alias".
## Census of the file after the change
`not yet in force` · `not yet landed` · `has not landed` · `parsed.data`
· `request.item`. Each remaining hit is either true or a dated quote:
| line | text | why it stays |
|---|---|---|
| 6 | ruling 甲 entry: "reversing the verbatim `request.item` save" |
true, since the same entry now says it is in force for `view` |
| 349 | bullet: "Every other type still stores its request body
(`request.item`)" | true (the `else` branch) |
| 367–370 | first note, item 1: the 2026-05-16 bullet quoted | dated
quote (stage (iii)) |
| 406 | first note, item 5: "has not landed" | under the new "Historical
— superseded" marker, verbatim |
| 436–443 | new note, item 1: the stage (iii) bullet quoted | dated
quote, marked Superseded |
## Evidence (head `950180d84`)
- **Gates.** Derived with `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands`, with no paths. The tree is
`950180d84` and the change set is 1 path. It derived 19 commands, the
same 19 as the dispatch. All 19 exit 0:
- `check-adr-links` (+ `--self-test`);
- `check-adr-symbol-anchors` (+ `--self-test`);
- `check-ci-filter-parity`;
- `check-closing-keyword-parity` (+ `--self-test`);
- `check-comment-mask-corpus`;
- `check:doc-formula-expressions`;
- `check:adr-anchors`;
- `check:cross-package-test-inputs`;
- `check:doc-authoring`;
- `check:driver-memory-census`;
- `check:gitlink-declared`;
- `check:nul-bytes`;
- `check:pm-governed-merges`;
- `check:pm-prior-rulings`;
- `check:refd-timer-probe`;
- `check:watch-hint-literal`.
- **Reconciliation.** `--ran` with `COMMAND :: exit CODE` lines: "19
derived famil(ies) accounted for — 19 run, 0 NOT-MEASURED (a DERIVED
zero — all 19 recorded an exit code and none of them is 3)".
- **`check-adr-symbol-anchors`:** "2165 anchors across 140 records
resolve — 325 symbol (299 declaration, 26 literal), 1813 file-level, 27
cross-repo, 6 exempt, 9 continuation".
- **`check:adr-anchors`:** "OK (56 anchored file(s), every governing ADR
still referenced; … 40272 citation(s) across 4997 file(s) resolve …)".
- **`check:doc-formula-expressions`.** I built `@objectstack/formula`
and `@objectstack/lint` first, through the verify lock (`turbo run
build`, 4 of 4 tasks, exit 0). The gate then printed "22 record-scoped
formula example(s) across 458 files / 1381 TS blocks judged clean".
- **The ADR-0087 link fragment.** `check-adr-links` strips `#fragment`
and never checks it (its own header says so). So I computed the slug of
`## Addendum (2026-08-01b) — flows reach the finish line too (#4454)`
with `github-slugger` 2.0.0:
`addendum-2026-08-01b--flows-reach-the-finish-line-too-4454`, which is
what the link uses. That heading occurs once in ADR-0087.
- **Ablation** (one-off, no permanent file).
- Setup: with the change committed, I ran `scripts/ablation-replace.mjs`
in wrap mode. It renamed the new anchor
`view.zod.ts#VIEW_ITEM_OPTIONS_REFUSED` to `…REFUSEZ`: anchor count 1 to
0, blob `62fe9587` to `af2e8fa2`.
- The gate printed "❌ check-adr-symbol-anchors: 1 finding(s)" and
"[unresolved-symbol]
docs/adr/0005-metadata-customization-overlay.md:472".
- Restore: the blob is back to `62fe9587` (the HEAD blob) and `git diff
HEAD` is empty.
- So the gate judges the new anchors, including a non-exported `const`.
- **eslint (narrowed, measured).**
- The population is read from `eslint.config.mjs`: `files:
['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']`. No config object matches
`.md`.
- `eslint --no-inline-config --format json` on the changed file: 1 file,
0 errors, 1 warning, "File ignored because no matching configuration was
supplied".
- The config enables no type-aware linting (its own comment at `:327`).
So a markdown-only diff moves no eslint verdict on any file.
- **No changeset.** `docs/adr/**` publishes nothing. Measured: 82
tracked `package.json` files, 69 not private (the root is private). None
has a `files[]` entry that names `docs/adr` or climbs out of its package
directory. So `skip-changeset` applies, and the seat applies the label.
## Acceptance notes
These are observations, not filed.
1. **The anchor's "only" is narrower than the code.** The invariant in
`scripts/adr-anchors/packages__metadata-protocol__src__protocol.ts.json`
says every other type is stored "with only the operator and `groups` →
`sections` grafts applied".
- The same file's code also applies `withDeclaredPageTypeDefault`
(#20101) to every type.
- A flow body is its canonicalized `storable` before the gate.
- The `resolveOverlaySchema` docblock names the page default. The anchor
text does not.
- That file is outside this card's surface (dispatch item 2), so it is
not touched here. Carrier: none.
2. **The 2026-05-16 test list stays as written.** The line "Tests added
in `packages/objectql/src/protocol-meta.test.ts` … unknown extras
preserved …" is the 2026-05-16 addendum's dated record of what was added
then, so it stays. The new note's item 5 records that the case was
re-judged at stage (iv).
3. **Two Status-line entries carry the same date, 2026-09-30.** The
ruling 甲 entry keeps its own provenance and gains the in-force clause.
The ruling B entry records the refinement. The file's convention is one
entry per decision-changing amendment.
## 维护者速读(草稿)
**改了什么**:只改了 ADR-0005 这一份文档,四处。
- 状态行:裁决甲那一条原来写着"存储切换尚未落地",现在改成"自 PR #20868 起对 `view`
生效"。另外新加一条"已修订",记录裁决 B。
- 附录 (c) 的"持久化文档"条目:原来写的是"存解析结果
`parsed.data`,已裁定、尚未生效"。现在照已合并的代码写:视图保存时,只存请求体里带了的每个键的解析值。没声明的键丢掉,作者没写的
schema 默认值不落库,旧拼写的键按规范拼写存(`groups` → `sections`,`visibleOn` →
`visibleWhen`)。其它类型仍然存请求体。存量行不迁移。
- 第 (iii) 步那段注记的第 5、6 项(过渡状态、第 (iv) 步计划)原文保留,上方加了"⚠️ 历史——已被取代"标记。
- 新加一段 2026-09-30 注记。它写了出处(裁决 B、维护者原话、PR 与合并 sha),并原文引用被取代的第 (iii)
步条目。它还写了视图保存具体存什么、`options` 按名拒收,以及对应的测试。
**为什么改**:裁决 `5910106571` 点名要求这一步。第 (iv) 步(PR
#20868,`9905e61ca2`)已经合并,所以这三处"尚未生效 / 存 `parsed.data`"的文字在 `main`
上已经是错的。
**风险与代价(含回滚)**:
- 纯文档改动,不动代码、不动测试、不发布任何包。
- 附录 (c) 的标题一字未改,所以代码注释和锚点文件都不用跟着动。
- 回滚:revert 本 PR 的提交即可。
**席位意见**:
**你要做的**:审阅后在本 PR 上给 APPROVED。这是 Tier H 治理面 `docs/adr/**`,之后由席位落地。
---
_Generated by [Claude
Code](https://claude.ai/code/session_01KTZmMfzVzjNvyaLyQ8mHvg)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/adr/0005-metadata-customization-overlay.md
+87-10Lines changed: 87 additions & 10 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -3,7 +3,7 @@
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) · **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)
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, the ruling's last stage, is in force for `view` since PR #20868 (`9905e61ca2`) — see the amendment notes under that addendum's persisted-document bullet) · **Amended** (2026-09-30, #20051 ruling B — the parsed body a `view` stores is the parsed value of every key its request body carried, not the whole parse output: undeclared keys dropped, schema defaults not materialised, a moved key stored under its canonical spelling; every other type still stores its request body — see the second amendment note under that bullet)
**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)
> `filter[].id` / `sort[].id` are removed before the parse by `packages/spec/src/ui/view.zod.ts#stripViewConsoleDecorations` (`#VIEW_CONSOLE_ROW_DECORATIONS`).
390
396
> `_draft` / `_diagnostics` are `packages/spec/src/kernel/metadata-read-decorations.ts#METADATA_READ_DECORATIONS`, stamped on the read and never stored.
391
397
> The record's docblock gives the reason for each.
398
+
>
399
+
> ⚠️ **Historical — superseded.** Items 5 and 6 record the interim state and
400
+
> the plan as they stood at stage (iii), and are retained verbatim. The
401
+
> storage switch landed in PR #20868 (`9905e61ca2`) under ruling B, without
402
+
> the production count item 6 names, which the maintainer waived. The next
403
+
> note carries the present tense.
404
+
>
392
405
> 5.**Interim state: the storage switch is stage (iv) of #20051, and it
393
406
> 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
407
> One of them is `packages/objectql/src/protocol-meta.test.ts`
> recorded on the card as comment 5910106571 — which rules that "a saved
426
+
> view stores the keys the author wrote, normalised by the parse: undeclared
427
+
> keys dropped, schema defaults NOT materialised". The maintainer's
428
+
> 「20051 不考虑现有的数据」 (comment 5909085121) waived the stored-row count
429
+
> the first note's item 6 made a precondition. Stage (iv) of ruling 甲
430
+
> landed under ruling B in PR #20868, merged as `9905e61ca2`. Why B rather
431
+
> than the whole parse output: views and flows keep one rule, a stored row
432
+
> carries no schema default the author did not write, so it never pins the
433
+
> day's default or records a choice the author never made.
434
+
>
435
+
> 1.**Superseded.** Written at stage (iii), the bullet above read: "The
436
+
> persisted document is the **parsed body** (`parsed.data`) once every
437
+
> round-trip key is declared. A key a client writes onto a stored row and
438
+
> reads back after a reload is declared on the type's schema, with its
439
+
> meaning, so the parse keeps it; a key nothing declares is not stored.
440
+
> The canonical fields are type-checked, and the stored row is what the
441
+
> contract says it is. ⚠️ **Ruled, not yet in force:** the storage switch
442
+
> is the ruling's last stage and has not landed, so today `saveMetaItem`
443
+
> still stores the request body (`request.item`). The note below records
444
+
> what is declared and what remains." The Status line's ruling 甲 entry
445
+
> ended "and the storage switch itself is the ruling's last stage, not
446
+
> yet landed — see the amendment note under that addendum's
447
+
> persisted-document bullet".
448
+
> 2.**What a `view` save stores.** For `view` only, `packages/metadata-protocol/src/protocol.ts#saveMetaItem` stores what `packages/metadata-protocol/src/protocol.ts#projectStorableViewBody` builds from the request body and its parse:
449
+
> - a key the body carried and the parse kept is stored with its parsed
450
+
> value, so a legacy filter `operator` spelling is stored normalised
451
+
> and a bare-array `exportOptions` in its object form;
452
+
> - a key the body carried and the parse did not keep is dropped: an
453
+
> undeclared key (`objectName`, a top-level `id`), a console
454
+
> decoration (`sort[].id`), or the old spelling of a moved key;
455
+
> - a key the parse added is stored only when a re-parse of the body
456
+
> does not reproduce it: that is a moved key, stored under its
> `visibleWhen`). A key the re-parse reproduces is a schema default,
459
+
> and it is not stored.
460
+
>
461
+
> The projection re-parses the body it builds until that re-parse equals
462
+
> the save's parse, so the stored row re-parses to exactly what the save
463
+
> accepted. If no such body is found, the whole parse output is stored
464
+
> and a warning is logged once per view: nothing the author wrote is
465
+
> lost, only that row's defaults are pinned.
466
+
> 3.**The two grafts no longer run on a `view` save.**`packages/metadata-protocol/src/protocol.ts#graftNormalizedOperators` and `packages/metadata-protocol/src/protocol.ts#graftFoldedFormSections` are special cases of the projection.
467
+
> Every other type still stores its request body, with the
468
+
> normalizations `saveMetaItem` applied to it before the switch.
469
+
> 4.**A `view` item record's top-level `options` is refused by name** (Q3
470
+
> = A of the same ruling), the judgement the first note's item 6 left
471
+
> open: `422 INVALID_METADATA` at `options`, with the prescription to
472
+
> write `config.KIND` (`packages/spec/src/ui/view.zod.ts#VIEW_ITEM_OPTIONS_REFUSED`; the ADR-0087 semantic entry `view-item-options-bag-refused`).
473
+
> The flattened list overlay keeps its legacy `options` bag, judged key
0 commit comments