Repository navigation
Commit f763615
docs(adr): ADR-0146 amendment — only the paired .custom declares an override; basedOn records the base package version (ruling A) (#28486)
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao8kpZNh2WHEhMcxiXxy3HZi)_
Fixes #28390
Clause-②: no
Metadata-protocol: none
## 维护者速读(草稿)
- **改了什么**:ADR-0146 原地修订,落实 #28390 的裁决 A——只有配对的 `<base>.custom`
包能声明覆盖;依赖包(伙伴/厂商扩展包)里与基础包同名的项只有一条出路:改名;依赖包若带 `overrides` 声明,在发布、`os
validate`、安装三处都拒绝。D2、D8.2、D9.1、替代表、Q1 行、Consequences、N-09 行,以及 ADR-0048
/ ADR-0126 第 3 行的指针同步收窄。追加三项已同意的范围:D9 `action` 段改为「开关已落地」时态并写明依赖包同名
action 只能改名;Consequences 新增缺口三(基础包升级新增同名项会顶掉客户 `.custom` 里的项);`basedOn`
改为记录基础包的 `manifest.version`,与 PR #28467 的 schema 文案一致。
- **为什么改**:原文 rider 3 让「谁能覆盖基础项」由发布先后决定——伙伴包先发,客户的 `.custom`
就成了「第二个声明者」被拒,失去对该项的定制权,与 ADR「客户定制都进 `.custom`」的前提冲突;您在 #28390 上 「同意」
A。N-09 写入关(#28410)等这份文本定稿后才按 A 实现。
- **风险与回滚**:纯 ADR 文本,不改 spec 键、不改任何代码路径;回滚即 revert 本 PR。风险在措辞:哪一句把 A
写宽或写窄,N-09 / N-10 就会照着实现——请重点看「先看」三处。
- **先看**:① `docs/adr/0146-….md` :219–:229,D2 的拒绝段(声明只许
`.custom`;依赖包三处拒绝;按角色不按先后);② :598–:606,Consequences
的两条代价(伙伴只能改名、升级顶替成孤儿);③ :478–:485,D9 的 `action` 段。
- **两分钟验证**:`grep -nE 'any package in the closure|closure
package|dependent that declares|one declarer' docs/adr/0146-*.md
docs/adr/0048-*.md docs/adr/0126-*.md`,预期只剩 0146 的 :19 与 :223 两处,且都是记录「该
rider 已撤回」的句子;没有任何一处仍把声明权开放给依赖包。
- **需要你做的**:受管面(Tier H):席位跑完 Fable 契约复审、写好席位核对后,由您亲自合并本 PR;合并后
N-09(#28410)摘掉 `Blocked-by: #28390`。
## What changes
Three ADR files, text only. Every ruled item of #28390 (ruling A,
6542177429; scope 6543281223; scope 6550188803) mapped to the line it
now lives on, at head `8ca6f396`:
| Ruled item (card line, source) | Edit | Line(s) at head |
|:---|:---|:---|
| D2 :188–189 — the partner sentence (6542177429) | "A dependent package
a vendor or partner ships may carry the same declaration…" → "…ships
adds items under its own names; it never declares an override (the
refusal paragraph below)." | 0146 :196–197 |
| D2 :191–192 — the declaring item | "An item of a package in the base's
dependency closure — the customer's `.custom`, or a dependent package
that ships one —" → "An item of the base's paired `.custom` package
([D3])" | 0146 :199–200 |
| D2 :212–214 — the closure paragraph; :210 "the refusal names both
fixes" for `.custom` only | Both fixes are named for the pair, the one
fix (rename) for any other dependent; "**The declaration is admitted
from the paired `.custom` alone**" (ruled A, rider withdrawn); a
dependent that carries `overrides` is refused at publish, at `os
validate` and at the install door (D10's three points for `__c`); the
overrider is fixed by role, never by publish order; the cost on record:
a partner ships a different base view under its own name via its
navigation contribution (ADR-0029 D7), and a real partner need reopens
#28390 as option B with that evidence | 0146 :219–229 |
| D2 :218–219 — the cost sentence of the #28364 ruling | "…when the
dependent is not overriding the base's" → "…and under ruling A renaming
is also a dependent's only route to a different version of a base item"
| 0146 :233–234 |
| D8.2 :408 | "whichever closure package declared it" → "the `.custom`
item that declares it"; the next sentence re-lists what is refused: the
pair's undeclared item, and any other dependent's item declared or not
(it still read "without the declaration … the pair and every other
dependent alike", rider 3's shape) | 0146 :424–426 |
| D9.1 :433–434 | "from `.custom` or from any package in the closure" →
"from `.custom`"; a dependent's row, declared or not, is refused at
publish | 0146 :450–451 |
| Supersede table :524 / :526 / :530 | ADR-0126 D1 row, gate column:
`refusePackagedBaseOverride` "admits a declared override from the base's
paired `.custom`"; ADR-0126 §7.1 row, rule column: "the **declared**
override from the base's paired `.custom`, the one package admitted to
declare"; ADR-0048 §3.4 row: the pair's item is admitted only as a
declared override or refused — declare or rename; any other dependent's
is refused, declared or not — rename; gate column "from the paired
`.custom` alone" | 0146 :542 / :544 / :548 |
| Q1 row :553 | "declare or rename" → "the paired `.custom` declares or
renames, any other dependent renames (scope ruled 2026-10-31, #28364;
the declarer ruled A on #28390)" | 0146 :571 |
| Consequences :580 | "renames it or declares the override (#28364)" →
renames it; only the paired `.custom` declares; a partner ships under
its own name; a real need reopens #28390 as option B | 0146 :598–601 |
| N-09 row :603 | "admit declared (from any closure package) … / second
declarer" → "admit declared (from `.custom`) … / a dependent's
declaration" | 0146 :628–629 |
| ADR-0048 :3 pointer line | "or the declared override a package in its
dependency closure publishes" → "or the declared override its paired
`<base>.custom` publishes" (narrowed, see Acceptance notes); its §3.4
half, which still read "a dependent package's item … is a declared
override (one declarer per item) or is refused", now: the pair's item is
a declared override or refused — declare or rename — and any other
dependent's is refused — rename; "ruled … on #28364 and #28390" | 0048
:3 |
| ADR-0126 :3 pointer line | "(or in a dependent package that declares
it)" dropped; "(for a declared override from a package in the base's
dependency closure only)" → "(for a declared override from the paired
`<base>.custom` only)" | 0126 :3 |
| Scope 6543281223 item 1 — D9's action paragraph | "An undeclared
same-name `action` in a dependent is refused at publish like any other
overridable kind ([D2]); until the action override opens, the only fix
is rename." | 0146 :478–485 |
| Scope 6543281223 item 2 — D9's tense | "is admitted once the action
ledger switch lands (#27842, #27504 …)" → "Its precondition has landed:
the action ledger switch is on `main` (#27842 and #27504, both landed
2026-10-30 …). A declared override of a base's `action` is admitted,
once its door is cut, under conditions 1–3…"; "the switch comes first" →
"came first"; D2's type list :231 "after its ledger switch" → "its
ledger switch landed 2026-10-30" | 0146 :478–485; :247 |
| Scope 6543281223 item 3 — gap 3 in Consequences | New bullet: a base
upgrade that adds a name a `.custom` item already holds displaces that
item — the base's name has an owner and only a declared override is
served over it ([D8] items 1–2), so the pair's item (authored when no
base item bore the name, hence undeclared) stops being served and
becomes an orphan; no door judges a base upgrade today (ADR-0141 D3
deferred); N-09 makes the install and upgrade door name each collision,
N-10 surfaces the orphan in the layered read | 0146 :602–606 |
| Scope 6550188803 — `basedOn` (L195, L201, L499) | Shape: `basedOn:
'<the base package's manifest.version the override was taken from>'`;
prose: "which version of the base **package** — its `manifest.version`,
not a history version of the item — the author started from (ruling
record 6549129352 on #28404, aligned here)"; migration: "`basedOn` set
to the installed base package's `manifest.version` at migration" | 0146
:203; :209–210; :517 |
| Status line | "· **Amended in place** (2026-10-31, ruling **A** on
decision card #28390, ruling record 6542177429, maintainer, verbatim:
「同意」)…" naming each change and its ruling record (Prime Directive #13: a
reversed clause needs an amended status line) | 0146 :15–23 |
| Refs | decision card #28390 added beside #28364 | 0146 :699–700 |
Re-read and left as they are (6550188803 asked for the re-read): :298
(the `^MAJOR` range "is where `basedOn` is read by a human"), :589
("`basedOn` tells a human what the override was taken from") and
:669–671 ("recorded and never judged") say nothing about an item
version. The D2 heading and the ADR-0048 §3.3 table row (0146 :547, "or
its declared override in `.custom`") are true as written under A — the
residues 6542177429 says need no edit. Options B and C of #28390 are not
taken and nothing here re-argues the ruling.
Alignment with PR #28467 (N-08): the schema's `basedOn` `.describe()`
there reads "the base package's `manifest.version` this override was
taken from …, not a history version of the item"; D2 :209–210 now says
the same, and the ADR's `.custom`-only declarer matches
`isCustomizationPairOf`'s docblock ("ruling A: only the paired
`<base>.custom` declares").
## Why
Under rider 3 of the #28364 ruling, who overrides a base item was
decided by publish order: a partner package that declared first made the
customer's `.custom` a refused "second declarer", and a customer who
customized first blocked the partner's install. The maintainer ruled A
on #28390 (「同意」, 6542177429): the overrider is fixed by role — `.custom`
is the customer's layer, a dependent is a contribution layer — so the
customer always has the last word and the layered read keeps its two
layers. N-09 (#28410, `Blocked-by: #28390`) implements the write gate
from this text, so the record has to say A everywhere before that card
is cut.
## Tests
None owed: text only, no test or runtime behaviour changes. The 22
replacements were applied by an anchored script that asserts exactly one
hit per anchor, so no edit missed or doubled silently; the two rider-3
phrases that remain (0146 :19, :223) are the sentences recording the
rider's withdrawal.
## Acceptance notes
- Two consistency edits sit beyond the numbered list so the record reads
A throughout, both named in the table: D8.2's second sentence (0146
:424–426) and the §3.4 half of ADR-0048 :3. Both still carried rider 3's
shape ("without the declaration … every other dependent alike"; "a
dependent package's item … is a declared override (one declarer per
item)").
- The ADR-0048 pointer's clause "or the declared override a package in
its dependency closure publishes" was narrowed to "or the declared
override its paired `<base>.custom` publishes" rather than dropped
outright: dropping it leaves the pointer saying the only owner of a
shipped name is the shipping package, which contradicts D8 (the pair's
declared override is served within that address). Reverting to a plain
drop is a one-clause change if the maintainer prefers the literal
reading.
- `node scripts/adr-index.mjs --check` exit 0: `docs/adr/README.md` is
unchanged, because the index reads only the first `**Status**:` line and
the amendment pointer sits at the end of the Status paragraph.
- The attribution comment on line 1 of this body is harness-written and
kept as the os-dev exception allows; nothing else in this PR is an HTML
comment.
- The harness's assignee/reviewer step is skipped: this card writes to
GitHub only through the relay, and the PR is Tier H — the seat runs the
Fable contract review and hands it to the approver.
- Out-of-scope findings: none.
## Verification
Head `8ca6f396`, worktree `objectstack-issue-28390`, branched from
`origin/main` at `82b29fe5`.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives 21 commands for this diff. Every one
ran with `</dev/null`, exit code captured before any pipe; **all 21 exit
0** — `check:adr-links` (+ self-test), `check:adr-anchors`,
`check:adr-index`, `check:adr-symbol-anchors`, `check:doc-authoring`,
`check:closed-ceremony-text`, `check:pm-prior-rulings`,
`check:pm-governed-merges`, `check:nul-bytes`, `check:gitlink-declared`,
`check:closing-keyword-parity` (+ self-test),
`check:comment-mask-corpus`, `check:ci-filter-parity`,
`check:cross-package-test-inputs`, `check:driver-memory-census`,
`check:refd-timer-probe`, `check:watch-hint-literal`,
`plan-test-core-shards --self-test`, and `check:doc-formula-expressions`
after `pnpm --filter '@objectstack/lint...' build` under the verify lock
(VERDICT command-exit 0, 113 s).
- `dispatch-gates.mjs --repo objectstack-ai/objectstack --ran
gates-ran.list`: **21 derived, 21 run, 0 NOT-MEASURED, 0 UNRUN**;
changed lines 99 (+63 / −36), under the 3000 human-merge threshold.
- `node scripts/adr-index.mjs --check`: exit 0.
- The repo-wide farm (`pnpm lint`, Test Core, the type-check lanes) is
CI's run; this PR's check-runs are its record.
<details><summary>gates-ran.list (21 lines, head 8ca6f39)</summary>
```text
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
pnpm check:adr-anchors :: exit 0
pnpm check:adr-index :: exit 0
pnpm check:adr-symbol-anchors :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:pm-governed-merges :: exit 0
pnpm check:pm-prior-rulings :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
```
</details>
## Human Minimum
- [x] 1. n/a — no schema or runtime code touched.
- [x] 2. n/a — no names added.
- [ ] 3. No formatter ran; `pnpm lint` is CI's repo-wide run and was not
run locally (the 21 derived doc gates were).
- [x] 4. One issue (#28390), one branch off `main` at `82b29fe5`, one
pull request.
- [x] 5. Publishes nothing (`docs/adr/**` only) — `skip-changeset`
declared on the create; both declaration lines are above.
- [x] 6. n/a — `packages/spec` untouched.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments in English; the 维护者速读 block is Chinese
by AGENTS.md's Communication rule.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done as far as
a docs-only PR reaches.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L9qibzxSvCxtE6cpabwBDy)_
Co-authored-by: os-tesla <yinlianghui@steedos.com>
Co-authored-by: Claude <noreply@anthropic.com>1 parent f116f38 commit f763615
3 files changed
Lines changed: 63 additions & 36 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
0 commit comments