Skip to content

docs(adr): ADR-0146 amendment — only the paired <base>.custom declares an override; a dependent package adds under its own names (ruled A, withdraws rider 3) #28390

Closed

Description

@objectstack-fleet

epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848

Part of #28236 · blocks N-09 (write gate, not yet cut — it will carry Blocked-by: this card) · lane: domain:skills (docs/adr/**) · opened at the maintainer's direction in a direct desktop session, verbatim: 「落卡:缺口一和三写进 N-09 的卡面(合并后切卡时),缺口二开决策卡」, then 「缺口二开一个 fable 综合评估」. Riders for gaps 1 and 3: #28236 comment 6541767586. ⛔ Not a claim.

一句话问题

伙伴的扩展包(依赖 HotCRM)先声明覆盖了 HotCRM 的「客户列表」视图,之后客户想在自己的 hotcrm.custom 里改这个视图,会被当成「第二个声明者」拒绝;客户也没法在伙伴的覆盖上再改一层。反过来,客户先改了,伙伴包就发布不了,或者按缺口一绕过检查装进来。谁说了算由谁先发布决定,这和 ADR「客户的定制都进 .custom」的前提冲突。

背景与 Governing text

ADR-0146 已合入(#28295,docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md,main):

协议声明: 本卡只定 ADR 文本,不改 spec 键。overrides 键由 N-08 落地,门禁由 N-09 实现,分层读取由 N-10 实现。

前提(每条带 re-check)

  1. 上面引的五处原文仍在 main。re-check:git fetch origin main && git show origin/main:docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md | sed -n '188,189p;212,214p;404,408p;433,434p'
  2. 今天没有任何代码路径能让非 .custom 的依赖包覆盖基础项。re-check:git show origin/main:packages/metadata-protocol/src/protocol-environment-rows.ts | sed -n '200,211p'(fix(metadata-protocol): a dependent package's same-named row no longer replaces the base package's item on the read naming no package #28304 后,唯一出品该名的代码包就是这个地址的主人,「a row bound to another package … is never served」);grep -rn "overrides\|basedOn" packages/spec/src 零命中。
  3. 仓内没有伙伴或 ISV 包。唯一的 manifest.dependencies 示例是同一厂商的模块(examples/app-multi-package/src/packages/orders/index.ts:65),而且没有同名项;「伙伴」只出现在枚举(content/docs/references/marketplace/package.mdx:60)和设计稿(docs/design/marketplace-publishing.md:143)里。re-check:git grep -n "dependencies" origin/main -- 'examples/**/index.ts' 'examples/**/objectstack.config.ts'
  4. 安装入口只查 DEPENDENCY_MISSING,不查声明槽位。re-check:git show origin/main:packages/metadata-protocol/src/protocol-package-install.ts | sed -n '258,268p'

选项

选项 做法 业务效果(HotCRM / 伙伴扩展包 / hotcrm.custom) 工作量
A 只有 .custom 能声明覆盖(撤回 rider 3) 依赖包里和基础包同名的项只有一条出路:改名。依赖包带 overrides 声明,在发布、os validate、安装三处都拒绝 伙伴只能加新名字的视图,再通过导航贡献挂进去(ADR-0029 D7)。客户永远有最后一句话。厂商要改自己模块的视图,发新版即可 ADR 收窄约 9 处(清单见裁后执行)· N-09 少一个「naming the first」分支 · N-10 保持两层 · 小
B 保留 rider 3,.custom 永远在最上层 加一句「配对包的声明压过任何依赖包的声明;依赖包之间仍只许一个声明者」 伙伴先覆盖了,客户在 .custom 里照样能改,并且最终生效;伙伴那份被整份遮住,不做合并。伙伴之间仍然是先到者独占 D2、D8.2、D9.1 各加一级先后 · 分层读取多一层并标出来源包 · N-11 流程排序多一档 · 中
C 维持现状,把代价写进 ADR 原样,代价清单加一段 伙伴先到,客户只能去求伙伴改;客户先改了,伙伴包装不上,或者绕过检查装上后环境里有两个声明者,而 D8 没规定听谁的 ADR 加一段 · 零

不列:「按闭包深度排序、多层叠加」。这要永久维护一套通用排序,而且没有需求。

推荐:A(若坚持 rider 3,则取 B;⛔ 不取 C)

  • 长远:一个基础项的覆盖者由角色决定,不由先后决定。.custom 是客户层,依赖包是贡献层,两者不争同一个名字。分层读取保持草稿的两层,以后做升级扫描(缺口三)也只需比对 .custom 一处。
  • 拉动:伙伴覆盖基础项的实测拉动是 0,仓内连一个伙伴包都没有。真实拉动只有客户定制,这一点 HotCRM 走查已有记录。
  • 防错:依赖包只有「改名」一条出路,AI 不会把客户定制错写进伙伴包。选 C 的话,客户的 .custom 被拒,AI 会去想办法绕。
  • 不扩散:A 收窄一项零拉动能力,少一个分支,不加门禁。
  • 重开 rider 3 的理由是新事实:裁决记录上的代价只有「改通用名」,没有「客户失去定制权」。Salesforce 托管包的做法与 A 同形:扩展包只能在自己的命名空间里新增,订阅方才是覆盖层。这一点未验证,凭通用知识,仓内没有资料。

os-decision-facets
① 长远:A 由角色定覆盖者(.custom 独占),读取两层不变;B 客户层在上但伙伴之间仍先到先得;C 语义权利由安装先后决定,违背 D2「customer's own changes still live in .custom」
② 拉动:伙伴/ISV 依赖包仓内为 0,无代码路径让非 .custom 依赖包覆盖;客户定制拉动已实测(HotCRM 走查)
③ 防错:A 依赖包同名唯一出路改名,三点响亮拒;B 伙伴覆盖被静默遮住难察觉;C 客户 .custom 被拒诱导绕行
④ 不扩散:A 收窄零拉动能力、少分支、不加门;B 多一档先后;C 不加东西但留洞
Prior rulings read: closure any-package declare / one declarer per base item → 1 hit (#28364 ruling 6540351677 rider 3); ADR-0048 §3.3/§3.4, ADR-0126 §7.1, ADR-0141 D3/D5; ADR-0146 :217–219 cost-on-record

与缺口一/三的关系

  • 缺口一(检查只在发布时,安装和 os validate 不查;已作为 rider 记在 N-09):在 rider 3 下,伙伴包带着声明安装进来时,安装入口不知道 .custom 是否已经占了这个槽位,环境里会出现两个声明者,而 D8 没规定听谁的。这是本缺口伤得最深的地方。选 A,安装入口的检查就退化成一条与类型无关的规则:「依赖包工件不得携带 overrides,不得与基础包同名」,照 D10 __c 三处执法的先例落进 N-09,不另开新门。
  • 缺口三(基础包升级新增名字,客户的项静默变成孤儿):选项不改变缺口三本身。但选 A 后,合法持有基础包名字的只剩 .custom,升级扫描只需比对一处;选 B 还要重新判断每个伙伴声明和配对包的先后。

裁后执行

  • 裁 A:开一个 ADR-0146 修订 PR(Tier H,维护者亲合),收窄以下各处:

    • :188–189 删伙伴那句。
    • :191–192 改为「An item of the base's paired .custom package」。
    • :212–214 改为「The declaration is admitted from the paired .custom alone; a dependent's same-named item has one fix, rename; a dependent that carries the declaration is refused at publish, at os validate and at the install door」,:210「the refusal names both fixes」只对 .custom 成立。
    • :408 改为「the .custom item that declares it」;:433–434 改为「from .custom」。
    • 替代表 :524 / :526 / :530 收窄。
    • Q1 行 :553 和 Consequences :580 同步。
    • N-09 行 :603 改为「admit declared (from .custom), refuse undeclared / out-of-closure / excluded type / a dependent's declaration」。
    • ADR-0048 :3、ADR-0126 :3 的指针行删去「or the declared override a package in its dependency closure publishes」。

    N-09 卡面按 A 写。

  • 裁 B:同样开修订 PR,在 :214 后加「The pair outranks every other declarer: .custom's declaration is served over a dependent's and is never refused as a second declarer; among dependents, one declarer per base item.」D8.2 写明三级先后 code < dependent override < .custom override,分层读取的 override 标出来源包,D9.1 同步。N-10、N-11 卡面加上这一档。

  • 裁 C:ADR 代价清单加一段「伙伴先声明则客户失去该项的定制权,反之亦然」。N-09 照现文实现,安装入口也要拒第二个声明者。

  • 裁定后本卡关闭,N-09 摘掉 Blocked-by:,按裁定切卡派发。

Analysis-tier: fable comprehensive assessment (desktop session subagent), seat spot-checked ADR :186–219, :404–408 and protocol-package-install.ts:258–268 on main


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
    2026-10-31T10:22Z

    Ruled: A — only the paired <base>.custom declares an override; rider 3 of 6540351677 ("any package in the closure may declare") is withdrawn.

    Maintainer, verbatim, in the desktop PM session, replying 「同意」 to the seat's business explanation that recommended A (partner packages only add items under their own names; the customer's .custom is the one layer that changes a base item; owner fixed by role, not by publish order). Freshness gate: thread had 0 comments at recording; body unedited since filing.

    What this card now carries (execution card, pm:queue, lane domain:skills): one ADR amendment PR to docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md (Tier H — maintainer merges), applying the "裁 A" list in this card's body verbatim in intent:

    • D2: delete the partner sentence (:188–189); the declaring item is "an item of the base's paired .custom package" (:191–192); the closure paragraph (:212–214) becomes — a dependent's same-named item has one fix, rename; a dependent that carries overrides is refused at publish, at os validate and at the install door; "the refusal names both fixes" holds for .custom only.
    • D8.2 (:408) "the .custom item that declares it"; D9.1 (:433–434) "from .custom".
    • Supersede table (:524 / :526 / :530), Q1 row (:553), Consequences (:580), N-09 row (:603) narrowed to match.
    • ADR-0048 :3 and ADR-0126 :3 pointer lines: drop "or the declared override a package in its dependency closure publishes" / "(or in a dependent package that declares it)".
    • Cost on record: a partner that wants a different base view ships it under its own name; if a real partner need to replace a base item appears later, it reopens as option B with that evidence.

    Line numbers are readings of main at filing; the PR re-reads them. PR body: Fixes #28390, ## 维护者速读(草稿) per the governed-surface rule.

    Effect on the N-09 riders (#28236 comment 6541767586): under A the Supersede-table residue and the D2-heading residue become true as written and need no edit; gap 1's install-door check reduces to "a dependent artifact carries no overrides and no base item's name". N-09 carries Blocked-by: #28390 and implements A.

  2. changed the title [-][Decision] ADR-0146 D2: when a partner's dependent package declares an override of a base item, can the customer's `.custom` still customize that item — or does the first declarer hold it[/-] [+]docs(adr): ADR-0146 amendment — only the paired `<base>.custom` declares an override; a dependent package adds under its own names (ruled A, withdraws rider 3)[/+] on Oct 31, 2026
  3. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
    2026-10-31T12:20Z

    Scope added to this card's amendment PR. Maintainer, verbatim, in the desktop PM session: 「同意」 to the seat's recommendation that N-09's ADR edits ride in this PR so N-09 (#28410) stays code-only. Add to the "裁 A" list in 6542177429:

    1. D9's action paragraph: an undeclared same-name action in a dependent is refused like any other kind; until action override opens, the only fix is rename.
    2. D9's tense: the action ledger switch is no longer pending — meta: GET /meta/object serves which packaged actions are switched off beside the item, and the disabled-action refusal reads as a plain sentence #27842 and Setup › Packaged automation: a packaged action switched off keeps its button on the record page, and clicking it shows a toast naming sys_metadata_activation and ADR-0126 #27504 closed on 2026-10-30.
    3. ADR-0146's cost list (Consequences) gains gap 3: a base upgrade that adds a name a .custom item already holds displaces that item (D8 item 2) — no door judges a base upgrade (ADR-0141 D3 deferred); N-09 makes the install/upgrade door name each collision, N-10 surfaces the orphan in the layered read.
      Source: N-09 riders on the epic (6541767586 item 3 and item 2(b), amended by 6542181562). The Supersede-table and D2-heading residues need no edit under A.
  4. added
    priority:p1High: required for production / M2
    and removed on Oct 31, 2026
  5. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
    2026-10-31T22:49Z

    Notify: scope · #28390 · one more ADR-0146 text item, from the N-08 ruling

    The maintainer's ruling on PR #28467 (recorded on #28404 as 6549129352: 田騏, verbatim 「同意」, 2026-10-31 20:44Z) carries follow-up (c): basedOn records the base package's manifest.version the override was taken from, not a history version of the item; "the ADR-0146 D2 wording is to be aligned in the #28390 text amendment". PR #28467 already changed the schema's .describe() to this meaning.

    Added to this card's amendment, beside the items in 6543281223. Lines read on origin/main today:

    • D2 L195: basedOn: '<the base item's version the override was taken from>' → the base package's manifest.version.
    • D2 L201: "basedOn records which version of the base's item the author started from" → which version of the base package.
    • Migration paragraph L499: "basedOn set to the base item's version at migration" → the installed base package's version at migration.
    • L282, L571 and L644–646 talk about basedOn being recorded and read by a human, and need no change of meaning. Re-read them when editing, in case a sentence there says "item version".

    Nothing else changes: basedOn stays recorded and never judged, which is the epic's Not in this epic line.

  6. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-tesla · session_01L9qibzCtJt8Q2Z63fyRNix

    Claim: skills seat 1 round 1 (single card) · card thread · 2026-10-31T22:59Z
    Session: session_01L9qibzCtJt8Q2Z63fyRNix (the seat); card thread session_01L9qibzxSvCxtE6cpabwBDy
    Account: os-tesla (the seat's linked user; the card's assignee)
    Branch: claude/project-thread-i8f3y5
    Worktree: the card thread's own container checkout
    Domain: domain:skills
    Seat: domain:skills#1
    File surface: docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md, the :3 pointer lines of ADR-0048 and ADR-0126. Stop on breach.
    Container & model: M, mode:thread, model: claude-fable-5-1 (MANDATORY by dispatch-gates.mjs --tier for this surface), effort xhigh; contract-review tier before the queue; Tier H
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6550188803
    Premise: holds (PR #28295 merged the ADR; the ruled lines still carry rider 3's wording; the dev re-reads each named line on main; the dev re-checks first)
    Ruling-ref: 6542177429 (A, maintainer 「同意」), scope 6543281223, scope 6550188803 (from N-08 ruling 6549129352)
    Serial constraints cleared: open PR #28467 (another lane) touches skills/*/references/_index.md only, outside this surface; no other open PR or claim touches it
    Resume-from: none (fresh from origin/main)
    Order: /mnt/project-files/skills-seat-1/r2026-10-31/dispatch-28390.md (project file)

    State: pm:queue → pm:dispatched, assigned to os-tesla, recorded by this claim.

  7. objectstack-fleet commented on Oct 31, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-tesla · session_01L9qibzxSvCxtE6cpabwBDy
    os-dev-report
    Posted 2026-10-31T23:26Z · CI check-runs read 2026-10-31T23:24Z

    {
      "issue": 28390,
      "status": "done",
      "branch": "claude/project-thread-i8f3y5",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/28486",
      "session": "session_01L9qibzxSvCxtE6cpabwBDy",
      "premise_still_valid": true,
      "summary": "ADR-0146 amended in place on ruling A (6542177429): the override declaration is admitted from the paired `.custom` alone, a dependent's same-named item has one fix (rename), and a dependent that carries `overrides` is refused at publish, at `os validate` and at the install door; D2, D8.2, D9.1, the Supersede table rows (ADR-0126 D1, ADR-0126 §7.1, ADR-0048 §3.4), the Q1 row, the Consequences, the N-09 row, the Status line and Refs are narrowed to match, and the ADR-0048 :3 / ADR-0126 :3 pointer lines drop the closure-wide declarer. Added scope landed too: D9's action paragraph reads the landed ledger switch (#27842 and #27504 closed 2026-10-30) and refuses a dependent's undeclared same-name action with rename as the only fix (6543281223 items 1–2); the Consequences gain gap 3, the base-upgrade displacement of a `.custom` item (item 3); `basedOn` records the base package's `manifest.version` in the D2 shape, the D2 prose and the D11 migration paragraph, matching PR #28467's describe() (6550188803). Every card-quoted line still said what the card quoted on main at 82b29fe5; 22 anchored replacements, each asserting one hit. Draft PR #28486 (head 8ca6f396, 3 files, +63/−36) carries the ruled-item → line table and `skip-changeset` (docs/adr only). Options B and C were not taken.",
      "tests": "None owed — docs/adr text only, no test or runtime behaviour changed, no ablation. Residue check at head: grep -nE 'any package in the closure|closure package|dependent that declares|one declarer' over the three files leaves exactly two hits (0146 :19 and :223), both the sentences recording the rider's withdrawal.",
      "gates": "Local subset (head 8ca6f396): `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 21 commands; all 21 ran with </dev/null, exit codes captured before any pipe, all 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 @objectstack/lint check:doc-formula-expressions after `pnpm --filter '@objectstack/lint...' build` under os-verify-lock, VERDICT command-exit 0, 113 s). `--ran` reconciliation: 21 derived, 21 run, 0 NOT-MEASURED, 0 UNRUN; changed lines 99 under the 3000 threshold. Also `node scripts/adr-index.mjs --check` exit 0 (README index unchanged). CI on PR #28486 at head 8ca6f396, read before convergence (the CI-read stamp is the header line above): success — Check Changeset, Governed Surface Queue Guard, PR-body guards (claim · duplicate · Part-of · single-writer path), Check Documentation Links, Check PR Size, filter; in_progress — the Lint & Repo Gates lanes (ESLint; docs & code guards; repo & tooling gates; PM dispatch-gates; CI, release & engine gates; scoped ×3; tooling self-tests), Type Check (workspace, source gates, consumer gates, debt ledger), Plan Test Core shards, Test UI, Dogfood Regression Gate, Auto Label; skipped by path filter — Build Core, Build Docs, Build UI Deps, Type Check UI, Lint UI, Test UI shards, Build & E2E UI, Live E2E UI, Dogfood Verify CLI, Temporal Conformance, UI Published Artifacts, the opt-in harnesses. The PR's check-runs are the record; the dev does not wait for CI.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
      "mcp_calls": "9 — GitHub MCP READ tools only: issue_read ×5 (get #28390; get_comments #28390 ×2; get #27842; get #27504), pull_request_read ×3 (get #28467; get #28486; get_check_runs #28486), get_file_contents ×1 (override-declaration.zod.ts at refs/pull/28467/head). 0 write tools. The harness's assignee/reviewer MCP step was skipped on purpose (relay-only writes; Tier H hand-off is the seat's).",
      "api_writes": "0 direct REST writes. Relay strokes: pr_create via fleet-write (run 41252295991 → PR #28486, 15285 bytes sent, 15285 stored, identical; labels [skip-changeset]; no assignees) and this os-dev-report comment via post-stamped. git push ×1 (claude/project-thread-i8f3y5 @ 8ca6f396, after the seat's Claim: 6550291768 named the branch).",
      "open_questions": [
        {
          "question": "ADR-0048 :3'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 literally (6542177429 says 'drop'). A literal drop 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).",
          "options": ["A keep the narrowing (ADR-0048 :3 stays consistent with D8 and with the §3.4 half of the same line)", "B drop the clause literally (one-clause change on ADR-0048 :3)"],
          "recommendation": "A, because the ruling's intent is to remove the closure-wide declarer, not the pair's override, and D8 still names the pair's override as the owner within the base's address; reversible in one clause if the maintainer prefers B."
        }
      ],
      "out_of_scope_findings": []
    }
  8. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:skills#1 · os-tesla · session_01L9qibzCtJt8Q2Z63fyRNix

    Landed · 2026-11-01T03:53Z

    probe reading
    PR #28486 merged through the queue at 2026-11-01T03:48Z, merge commit f7636153 (enqueued by os-zhuang, Tier H; seat ACCEPT 6550678929, contract review PASS 6550660808)
    this card closed completed by the PR's Fixes line

    State: pm:dispatched removed (closed card).

  9. objectstack-fleet commented on Nov 1, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-project-manager · session_01FEkQ5pFHjQjFRsVAurERSJ

    Evidence for option B at locale grain (from #28779, measured on main 8f294e04)

    The ADR-0146 D2 text says a dependent package "adds items under its own names". Since PR #28777 an email template is judged by name and locale, so a dependent may now publish a base template's name at a locale the base does not ship. #28779 measured what the base's reads then answer.

    Setup on the showcase. The showcase ships its "task done" email in English only. A partner-style package that depends on the showcase publishes the same template name in French. It is accepted (its English version under that name is still refused).

    What a reader of the showcase's template sees in French:

    • Opening the showcase's template in French, either by naming the showcase or by naming no package, shows the partner's French text. The response labels it as the partner's package, not the showcase's.
    • The template lists (all templates, or the showcase's templates) do not show a French version, and an actual French email for the showcase's template is sent in English, from the showcase's own text.

    So the partner's French body is answered on the showcase's address when read one at a time, but never listed and never sent. No read labels it as the showcase's, so this is not the full silent-replacement shape of Context §5; it is a contribution answered on the base's address by the by-name reads only.

    The customer's own French version (in com.example.showcase.custom, without overrides) behaves the same way: shown when opened by name, missing from the lists, and never sent.

    Where it comes from: the by-name read for the showcase finds no showcase or customization row in French and falls back to the registry's cross-package "first match" (ADR-0048 §3.3); the lists keep a name the showcase ships to the showcase's items at name grain.

    The behaviour is pinned as a shape test (named SHAPE) in PR #28800; the readings are in the PR body. #28779 stays open for the seat's decision: whether a dependent's (or the pair's) added locale should be served and sent for the base's template, and on which reads.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portaldomain:skillspriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions