Skip to content

Commit f763615

Browse files
objectstack-fleet[bot]os-teslaclaude
authored
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

‎docs/adr/0048-cross-package-metadata-collision.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0048: Cross-package metadata collision — package-id identity, namespace install gate, package-scoped resolution
22

3-
**Status**: Revised (2026-06-13) — supersedes the original *per-item collision detection* framing. The runtime guard shipped under the original proposal is **retained as a same-package authoring backstop**; the strategic direction for the app-marketplace era is revised below. · **Superseded in part by [ADR-0146](./0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md)** (2026-10-31) — §3.3's sentence that a context-free read "is best-effort: it returns the first match": a read of a name a code package ships has an owner — that package, or the declared override a package in its dependency closure publishes — and a same-named item of a package outside that closure is served only under its own address (ADR-0146 D8); and §3.4's coexistence "by construction", for a base's dependency closure only: a dependent package's item bearing a base item's name is a declared override (one declarer per item) or is refused at publish — declare or rename (ADR-0146 D2, ruled 2026-10-31 on #28364). Packages with no dependency relation keep §3.3 and §3.4 as written; qualify-to-cross, the same-package overwrite and the 2026-10-08 addendum stand.
3+
**Status**: Revised (2026-06-13) — supersedes the original *per-item collision detection* framing. The runtime guard shipped under the original proposal is **retained as a same-package authoring backstop**; the strategic direction for the app-marketplace era is revised below. · **Superseded in part by [ADR-0146](./0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md)** (2026-10-31) — §3.3's sentence that a context-free read "is best-effort: it returns the first match": a read of a name a code package ships has an owner — that package, or the declared override its paired `<base>.custom` publishes — and a same-named item of a package outside that closure is served only under its own address (ADR-0146 D8); and §3.4's coexistence "by construction", for a base's dependency closure only: the paired `<base>.custom`'s item bearing a base item's name is a declared override or is refused at publish — declare or rename — and any other dependent package's item bearing a base item's name is refused at publish — rename; only the pair declares (ADR-0146 D2, ruled 2026-10-31 on #28364 and #28390). Packages with no dependency relation keep §3.3 and §3.4 as written; qualify-to-cross, the same-package overwrite and the 2026-10-08 addendum stand.
44
**Deciders**: ObjectStack Protocol Architects
55
**Builds on**: [ADR-0003](./0003-package-as-first-class-citizen.md) (package as first-class citizen), [ADR-0005](./0005-metadata-customization-overlay.md) (artifact vs runtime overlay precedence), [ADR-0008](./0008-metadata-repository-and-change-log.md) (metadata repository, `MetaRef` identity), [ADR-0010](./0010-metadata-protection-model.md) (package provenance / `_packageId` stamping)
66
**Consumers**: `@objectstack/objectql` (`SchemaRegistry.registerItem`, `ObjectQL.registerApp`, install path), `@objectstack/console` / `objectui` (routing + metadata resolution), package authors, CLI/CI install path, the app marketplace registry.

‎docs/adr/0126-packaged-metadata-customization-model.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ADR-0126: The platform customization model for packaged metadata — three regimes, one activation ledger
22

3-
**Status**: **Amended** (2026-09-04) by [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D6 — maintainer, verbatim: 「所以 ADR-0126 不能简单的关掉」「hotcrm 是标准的软件包,客户安装之后能根据业务需求实现具体的定制」「同意」. The three regimes stand at **environment scope only**: O's per-organization reading is retired (ADR-0005 amended), C's activation ledger stays as tenant-less deployment state (D3's reserved `organization_id`, written NULL, is withdrawn — ADR-0131 D1 forbids a nullable tenant column), E stands; the unreleased implementation (#12185, #12190, #12296, #12348, #12419, framework half of #12491) ships in 17.3 with that column dropped. The install mode ADR-0131 D6 adds (managed = sealed definitions + these regimes; template = copied once, fully editable, refused on shared-DB multi-tenant) is the frame the regimes now sit in. · **Amended** (2026-10-14, ADR-0131 D2/D3/D13): the permission projection §4 and §8 name is retired by #15204 stage 8 (`66949b7729`, 2026-10-13), which unwired the ADR-0094 projector and retired the `sys_permission_set` row object; see the notes in §4 and §8. · **Superseded in part by [ADR-0146](./0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md)** (2026-10-31) — D1 (§2 Regime C's "never an overlay read path", for a declared override), D4 wall 1 (§6 item 1's corollary against name-redirection, for the declared case only) and §7.1's "⛔ No same-name clone in a second package" (for a declared override from a package in the base's dependency closure only): customization of a packaged item is a declared override in the paired `<base>.custom` package (or in a dependent package that declares it), served at the base's name once real flow version pinning exists (ADR-0146 D2, D9); the lock, the ledger switch and the walled operator gate stand. §7.4's Setup surface moves to the item in Studio; of §9's first bullet the badge, the customization list and revert are chartered, diff-vs-base and the skip-report stay deferred. Original status line follows.
3+
**Status**: **Amended** (2026-09-04) by [ADR-0131](./0131-total-organization-ownership-no-null-organization-id.md) D6 — maintainer, verbatim: 「所以 ADR-0126 不能简单的关掉」「hotcrm 是标准的软件包,客户安装之后能根据业务需求实现具体的定制」「同意」. The three regimes stand at **environment scope only**: O's per-organization reading is retired (ADR-0005 amended), C's activation ledger stays as tenant-less deployment state (D3's reserved `organization_id`, written NULL, is withdrawn — ADR-0131 D1 forbids a nullable tenant column), E stands; the unreleased implementation (#12185, #12190, #12296, #12348, #12419, framework half of #12491) ships in 17.3 with that column dropped. The install mode ADR-0131 D6 adds (managed = sealed definitions + these regimes; template = copied once, fully editable, refused on shared-DB multi-tenant) is the frame the regimes now sit in. · **Amended** (2026-10-14, ADR-0131 D2/D3/D13): the permission projection §4 and §8 name is retired by #15204 stage 8 (`66949b7729`, 2026-10-13), which unwired the ADR-0094 projector and retired the `sys_permission_set` row object; see the notes in §4 and §8. · **Superseded in part by [ADR-0146](./0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md)** (2026-10-31) — D1 (§2 Regime C's "never an overlay read path", for a declared override), D4 wall 1 (§6 item 1's corollary against name-redirection, for the declared case only) and §7.1's "⛔ No same-name clone in a second package" (for a declared override from the paired `<base>.custom` only): customization of a packaged item is a declared override in the paired `<base>.custom` package, served at the base's name once real flow version pinning exists (ADR-0146 D2, D9); the lock, the ledger switch and the walled operator gate stand. §7.4's Setup surface moves to the item in Studio; of §9's first bullet the badge, the customization list and revert are chartered, diff-vs-base and the skip-report stay deferred. Original status line follows.
44
**Original status**: Proposed (2026-08-25) — awaiting the maintainer's hand-merge, which is itself the
55
acceptance act for a governed surface (Prime Directive #14) **and the ruling that settles the
66
tentative flow-instance directions in §7** (chartered on

0 commit comments

Comments
 (0)