Repository navigation
Commit 38d98d8
Fixes #23006
Clause-②: no
ADR-0005's present-tense "What delivers overlay uniqueness today" block
still spelled the overlay uniqueness key with four parts and quoted the
declared index with four fields and the retired bare `unique: true`.
Since `5677add93f` (PR #22954, #22877 ruling A) the runtime key and the
declared index both carry a fifth part, `bundle_member`. This PR
restates the current key from the tree and records the change as a dated
amendment. Docs and ADR-anchor text only; nothing publishes.
## What changed
`docs/adr/0005-metadata-customization-overlay.md` (898 → 908 lines, +19
/ −9, net +10):
- **Status line:** one entry appended in the record's existing format:
`· **Amended** (2026-10-12, #22877 ruling A — …)`. It names the fifth
part and what it is (a bundled type's member, such as an
`email_template`'s locale). It also states that NULL is the canonical
member, so name-only types are unchanged, and names the producer
`5677add93f` (PR #22954). The entry goes on the existing line, so no
line above the body moves.
- **The #6771 measurement** ("a plain `DatabaseLoader` boot already
stores `CREATE UNIQUE INDEX … (type, name, organization_id, package_id)`
— the declared index, with the current key"): kept verbatim, because it
records a measurement. A dated `2026-10-12 note` follows it, saying that
key was the four-part key of its day and pointing to the status line and
the two owners.
- **Owner 1 (runtime, `overlay-index.ts`):** `(type, name,
organization_id, COALESCE(package_id, ''), COALESCE(bundle_member, ''))`
`WHERE state = 'active'`. One sentence gives the reason, taken from the
module header: two members of one item are two rows. NULL is the
canonical member and every row of a name-only type, and folding it to
`''` constrains those rows exactly as the four-part key did.
- **Owner 2 (declared, `sys-metadata.object.ts`):** the quotation now
matches the tree: `fields: ['type', 'name', 'organization_id',
'package_id', 'bundle_member'], unique: 'global'`. A parenthetical notes
that `'global'` is protocol 18's spelling of the bare `true` it retired
(ADR-0120 D2). The trailing sentence about the coarse fallback is
restated from measurement (see Acceptance notes ②).
`scripts/adr-anchors/…overlay-index.ts.json` and
`…sys-metadata.object.ts.json` change one key span each, from the
four-part to the five-part spelling. This is a bounded in-place fix
(Acceptance notes ③).
The quoted code is taken from the tree at `edecd4485`:
`OVERLAY_INDEX_COLUMNS`
(`packages/metadata-protocol/src/migrations/overlay-index.ts:169`),
`buildOverlayIndexSql` (same file), and the declared index
(`packages/metadata-core/src/objects/sys-metadata.object.ts:250-254`).
## Gates (all on HEAD `a75f9f225`)
I derived the gate list with `node scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack`, passing no paths. It
found 3 paths against merge base `edecd4485` and listed 27 commands. All
27 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-scripts-symbol-anchors` (+
`--self-test`) · `@objectstack/lint check:doc-formula-expressions` ·
`check:adr-anchors` · `check:agent-test-spelling` · `check:bash32-floor`
· `check:cli-command-ids` · `check:cross-package-test-inputs` ·
`check:doc-authoring` · `check:driver-memory-census` ·
`check:entry-guard` · `check:gitlink-declared` · `check:nul-bytes` ·
`check:parse-guard` · `check:pm-governed-merges` ·
`check:pm-prior-rulings` · `check:pnpm-filter-targets` ·
`check:refd-timer-probe` · `check:watch-hint-literal`.
Key verdict lines:
- `check-adr-anchors: OK (68 anchored file(s), every governing ADR still
referenced; … 44138 citation(s) across 5846 file(s) resolve …)`
- `check-adr-links: 730 relative link destination(s) under docs/adr/
resolve`
- `check-nul-bytes: OK (scanned 11039 text file(s) …; no raw ASCII
control bytes)`
The first run of `check:doc-formula-expressions` exited 3 (PREREQUISITE
NOT MET: `@objectstack/formula` and `@objectstack/lint` were unbuilt). I
built them under the verify lock (`VERDICT command-exit 0`) and re-ran
it: exit 0, `22 record-scoped formula example(s) … judged clean`.
`dispatch-gates --ran` reconciliation: `27 derived famil(ies) accounted
for — 27 run, 0 NOT-MEASURED (a DERIVED zero — all 27 recorded an exit
code and none of them is 3)`.
I also ran some roster gates outside the 27:
- Exit 0: `check:overlay-whitelist-table`,
`check-published-list-mirrors`, `check:dts-closure`,
`check:engine-double-contract`, `check:i18n-stale-fill`.
- NOT MEASURED: `check:published-readme-exports` exited 3 because it
needs a full build; this diff touches no package and no README.
- NOT MEASURED: `check:console-injection` skipped itself because there
is no console dist.
No changeset: `docs/adr/**` and repo-root `scripts/adr-anchors/**`
publish nothing, so this PR takes `skip-changeset`.
## Acceptance notes
1. **Every present-tense four-part site, found by a full re-grep.** At
`edecd4485` there were three, not the two the card names: the #6771
measurement (`:835`), Owner 1 (`:867`) and the Owner 2 quotation
(`:878`, which the card does not name). After the edit, the only
four-part spelling left in present-tense context is the preserved `:835`
measurement, and the dated note now follows it. Every other four-part or
older spelling is in dated historical blocks that this record keeps
standing on purpose:
- the Addendum (d) YAML: `project_id`, `scope`, `unique: true`,
`partial`;
- the 2026-05-22 amendment;
- the 2026-08-09 amendment's statement that the decision "at most one
active customization of a given `(type, name)` per organization" is
unchanged.
2. **Owner 2's trailing sentence was false after `5677add93f` and is
restated.** The old text read "it still prevents duplicate active
overlays … and it stays NULL-distinct on `package_id`". The declared
index is a plain UNIQUE over five columns. It is NULL-distinct on
`bundle_member`, and that column is NULL for every canonical member, so
the fallback no longer holds any canonical row unique. I measured this
on an in-memory SQLite (better-sqlite3 12.11.1) with that exact
five-column plain `CREATE UNIQUE INDEX`:
- two identical packaged canonical rows (`bundle_member` NULL): both
INSERTED;
- two identical `zh-CN` member rows: the second REFUSED with
`SQLITE_CONSTRAINT_UNIQUE`.
The ADR now says "NULL-distinct on `package_id` and on `bundle_member` —
NULL for every canonical member, so it holds no canonical row unique".
The code comment beside the declaration
(`sys-metadata.object.ts:244-249`, "it still prevents duplicate ACTIVE
overlays") carries the same stale claim. It is outside this card's file
surface and is not edited. I have not measured whether any real
composition gets only the declared tier, so this stays a note, not a
card.
3. **Bounded in-place fix: two ADR anchors.** Both `invariant` strings
stated the current key in the present tense with four parts:
- `overlay-index.ts.json`: "`(type, name, organization_id,
COALESCE(package_id, ''))` `WHERE state = 'active'`";
- `sys-metadata.object.ts.json`: "the declared
`idx_sys_metadata_overlay_active` on `(type, name, organization_id,
package_id)`".
Each now spells the five-part key, and nothing else in either string
changed. All four conditions hold:
- same defect class (a present-tense four-part key after `5677add93f`);
- a mechanical one-span edit each;
- both files declared in the claim's file surface;
- `check:adr-anchors` is in the derived family, and it is green.
4. **The ADR line range another anchor cites did not move.**
`scripts/adr-anchors/packages__spec__src__kernel__metadata-plugin.zod.ts.json`
cites `docs/adr/0005-metadata-customization-overlay.md:53-64`. The
status entry was appended to the existing line, so lines 53-64 are
byte-identical between `edecd4485` and HEAD (`diff` exit 0). That anchor
file is not touched.
5. **The same four-part statement in other places, not edited here**
(other files or other governed surfaces; listed for whoever closes the
family):
-
`docs/adr/0029-kernel-object-ownership-and-platform-objects-decomposition.md:669`
- `docs/adr/0126-packaged-metadata-customization-model.md:313`
- `skills/objectstack-data/rules/indexing.md:165` (the published skill's
partial-index example quotes the platform's runtime SQL with four parts)
- `docs/qa/platform-checklist/areas/cli.json:1125` (a QA step recreates
"the declared NULL-distinct form" with four columns; the step still
seeds the duplicates it needs, so this is drift only)
6. **Pre-existing, not introduced here.** Both indexes keep
`organization_id` bare and NULL-distinct. The overlay-index module
header already names this as "a separate question with its own ADR-0120
D4 ceremony". Environment rows are written with `organization_id` NULL
(`sys-metadata-repository.ts`, the ADR-0131 D6 environment-rows read),
so the DB-level key does not constrain those rows on either tier. This
is a read-only observation, not filed.
## 维护者速读(草稿)
**改了什么**
- ADR-0005 状态行末尾追加一条 2026-10-12 修订:#22877 裁决 A,overlay 唯一键加入第五段
`bundle_member`。
- 「今天由谁保证 overlay 唯一」一节的两处现在时描述改为五段键,并附理由。
- 声明索引的引文与代码原文一致:五个字段,`unique: 'global'`。
- #6771 当时的实测原句保留,后面附一条带日期的注记。
- 两份 ADR 锚点 JSON 里的键写法同步为五段。
**为什么改**
- 自 `5677add93f`(PR #22954)起,运行时唯一索引和声明索引都多了
`bundle_member`。例如同一封邮件模板的不同语言版本,现在各存一行。
- ADR 仍写四段键,按 ADR 理解唯一性的 agent 和作者会被带偏。
- 另一处随之失真:声明索引对 `bundle_member` 是 NULL-distinct,而规范成员该列为
NULL,所以这个兜底索引已不再约束任何规范成员行。原句「仍能防止重复的 active overlay」不再成立,本 PR
按实测改写(SQLite 实测见 Acceptance notes 第 2 条)。
**风险与代价(含回滚)**
- 只改文档和锚点文字:不发布任何包,不改运行时行为。
- ADR-0005 净增 10 行(898 → 908)。
- 回滚:revert 本 PR 的单个提交即可。
**席位意见**
**你要做的**
- 审阅措辞,重点是声明索引那句新结论:「不再约束任何规范成员行」。
- 同意就给一个 APPROVED 评审,之后由席位落地;不同意就在 PR 上留言指出要改的句子。
---
_Generated by [Claude
Code](https://claude.ai/code/session_012dGTuJen61ECNHXYXKChoC)_
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2db2f8d commit 38d98d8
3 files changed
Lines changed: 21 additions & 11 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
| 8 | + | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| |||
838 | 838 | | |
839 | 839 | | |
840 | 840 | | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
841 | 845 | | |
842 | 846 | | |
843 | 847 | | |
| |||
864 | 868 | | |
865 | 869 | | |
866 | 870 | | |
867 | | - | |
| 871 | + | |
868 | 872 | | |
869 | | - | |
870 | | - | |
871 | | - | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
872 | 880 | | |
873 | 881 | | |
874 | 882 | | |
875 | 883 | | |
876 | 884 | | |
877 | 885 | | |
878 | | - | |
| 886 | + | |
| 887 | + | |
879 | 888 | | |
880 | | - | |
881 | | - | |
882 | | - | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
883 | 893 | | |
884 | 894 | | |
885 | 895 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
6 | | - | |
| 6 | + | |
7 | 7 | | |
0 commit comments