Repository navigation
Description
epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
Part of #28236 · blocks #28237 (ADR-0146 rewrite, draft PR #28295) · lane: domain:skills (docs/adr/**) · opened at the maintainer's request in a direct desktop session, verbatim: 「开一个决策卡」. ⛔ Not a claim.
一句话问题
客户装了基础包「项目管理」,又装了一个依赖它的普通包「移动外勤」(不是配对的 .custom)。「移动外勤」里存了一个和基础包同名的视图,又没有声明它要覆盖谁。这条记录发布时该被拒,还是允许它作为「移动外勤」自己的视图存在?ADR-0146 改写稿对这一点给了三个互相矛盾的答案,写发布门禁的卡(N-09)没法照着实现,所以 #28237 合不进去。第二问顺带一起裁:动作(action)在本 epic 里能不能被覆盖。
背景与 Governing text
- epic 裁定 Q1(2026-10-30,「等我拍板的 8 个问题,全部同意」):未声明的同名项,发布时拒绝。范围没说是「所有依赖包」还是「只有配对包」。
- 改写稿三处矛盾(PR docs(adr): rewrite ADR-0146 as the package-level override record — paired
<base>.custom, one declared override shared by code and Studio; dated pointers on six Status lines #28295 headaf70e268;契约复核 round 1 FAIL,记录 6538795712,Must-fix 1):- D2(:206–209):「
.custom的项,或任何依赖包的项」,同名且未声明的,发布时拒绝。 - D8 第 2 条(:396–399):依赖包里同名未声明的项「是那个包自己的项,按它自己的地址读取」,只有配对包的才在发布时拒绝。
- D9 第 1 条(:419–421):流程这边继续「遮蔽并告警」未声明的同名运行时记录,跟现在一样。
- D2(:206–209):「
- ADR-0048 §3.3(
main:236–242):两个包合法地各出品page/home,各自按自己的包读取。⚠️ 那里说的是没有依赖关系的两个包。本卡只问依赖闭包内的同名,即一个包用了它所依赖的包里已有的名字。 - ADR-0126 §7.1(
main:362):「⛔ No same-name clone in a second package」,流程的同名克隆本来就禁止。 - metadata-protocol: a same-named item in a dependent package replaces the base package's item for package-less reads and in the base app #28238 的实测缺陷(showcase,
d6c0a75f1a)正是非配对依赖包com.default.acme-custom存了基础包的同名视图showcase_task.default,导致基础应用显示 2 列。读取一侧维护者已裁 A(6540076460):基础应用不再列出那条。 - 第二问:改写稿 D2「Which types」(:217–220)把
action列为可覆盖类型,没有附加条件。ADR-0126 §3 把action归在行为类 Regime C(停用加克隆),维护者原话「动作 可能是需要开关的,因为有的 action 我不想启用。」(ADR-0126 :20)。epic 另裁「动作开关不入 epic」。同为行为类的flow在 D9 有四个条件,action一个都没有(复核 Should-fix 9)。
协议声明: 本卡只定 ADR 文本,不改 spec 键。门禁由 N-09 实现,类型清单由 N-08 收口。
前提(每条带 re-check)
- 三处矛盾文本仍在 PR 当前 head。re-check:
git fetch origin claude/project-thread-j6cjuw && git show FETCH_HEAD:docs/adr/0146-studio-designs-setup-administers-one-editor-per-item-paired-customize-package.md | sed -n '206,209p;396,399p;419,421p' - 拒绝只针对依赖闭包内的同名,无依赖关系的两个包照旧各自共存。re-check:同一文件
grep -n "bears a",D2 用词是 "bears a base's item name"。 - 现网有多少「依赖包存了基础包同名项」的记录,未实测(只在 showcase 上人为复现过)。N-09 动手前先清点(见裁后执行)。
第一问:非配对依赖包的未声明同名项
| 选项 | 做什么 | 客户可感知后果 | 工作量 |
|---|---|---|---|
| A 一律拒绝 | 依赖闭包里任何包(不只 .custom)用基础包已有的名字又没声明,发布时拒绝。提示两条出路:要改基础包,就到 .custom 声明覆盖;不是要改,就换个名字。D8.2「按自己地址读取」和 D9.1「遮蔽并告警」两句删掉 |
「移动外勤」的作者发布时当场看到拒绝和出路。基础应用永远不会被别的包悄悄顶替 | ADR 改三句 · 同一个 PR · N-09 实现时多一个判断条件 |
| B 只拒配对包 | 只有 .custom 的未声明同名被拒。其他依赖包可以合法用同名,算它自己的项,只在点名它的包时读到。D2 里「或任何依赖包的项」删掉 |
「移动外勤」发布成功,但基础应用不受影响(靠 #28238 A 的读取规则)。作者以为自己改了基础视图,其实只是新建了一个同名视图,没有任何提示 | ADR 改两句 · 同一个 PR · 读取规则长期承担区分责任 |
业务含义直译
- A:公司里一个工号只对应一个人。分公司想换掉总部的某个流程,必须走正式的定制申请(
.custom声明);不是要换,就另起名字,不许冒用总部的工号。 - B:允许分公司用总部的工号登记自己的人。总部名册不受影响,但同一个工号在不同名册里指不同的人,查起来要先问「你看的是哪本名册」。
四维分析(业务立场)
- ① 长远合理性(领起):两年后,依赖链上一个名字只对应一个有效答案:要么是出品它的包,要么是
.custom里声明的覆盖。这正是改写稿 D8 自己写的「一个读取有主人」。参照 Salesforce:托管包和订阅方的同名组件能并存(接近 B),是因为每个名字都带命名空间前缀,每处引用都写明是谁的。ObjectStack 的界面按名字读、不带包(metadata-protocol: a same-named item in a dependent package replaces the base package's item for package-less reads and in the base app #28238 实测:控制台读不点名包的清单,按名合并),所以在 B 下,依赖包那条同名视图合法存在,却在界面上读不到,成了声明了却不生效的东西。A 把特例收到一个:唯一合法的同名就是.custom里的声明。B 永久保留「闭包内同名但不同物」这一类,以后每种读取、引用、导出都要回答「这个名字指哪个包」,特例越积越多。 - ② 业务拉动:今天撞上的就是 metadata-protocol: a same-named item in a dependent package replaces the base package's item for package-less reads and in the base app #28238 的形状。依赖包存了同名视图,基础应用的用户看到被顶替的视图。读取一侧的修复(metadata-protocol: a same-named item in a dependent package replaces the base package's item for package-less reads and in the base app #28238 A)只挡住了症状,没挡住这类记录继续产生。有没有人有意在依赖包里复用基础包的名字,未实测。
- ③ 防 AI 犯错:AI 写依赖包时最常见的错,就是照着基础包的视图改几列、原名保存,以为自己在定制。A 的结果是发布时响亮拒绝,提示里直接给出「去
.custom声明」。B 的结果是发布成功、什么都没生效(基础应用按 metadata-protocol: a same-named item in a dependent package replaces the base package's item for package-less reads and in the base app #28238 A 列基础包那条,依赖包那条界面上读不到),作者和用户看到的不一样,没人报错,正是静默泄露。 - ④ 不扩散:A 不新增能力,只多一条拒绝,用的是 Q1 已裁的同一个门禁。B 要长期维护「闭包内同名共存」的读取语义,而且它是新增的。
推荐:A。 只看 ① 选 A;②③④ 是否翻转:否。不取 B:它让 #28238 那类记录合法化,AI 写错时静默通过,而且依赖链上的名字从此要靠「看哪本名册」才能说清。
置信缺口:现网存量未清点。如果有客户有意在依赖包里复用了基础包的名字,A 会让这些包下次发布时被拒,需要改名或改到 .custom 里声明。先清点再上线可以兜住这一点,见裁后执行。
第二问:action 在本 epic 里能否覆盖
| 选项 | 做什么 | 客户可感知后果 | 工作量 |
|---|---|---|---|
| A 本 epic 不开放 | action 从 D2 的可覆盖清单移到「不在本 epic」,和动作开关一样延后。客户需要新按钮时,在 .custom 里新增一个 |
不能替换厂商的按钮,只能新增自己的 | ADR 改一句 |
| B 开放,附条件 | 和 flow 一样写清条件:替换后点击只触发一个定义,引用它的页面在一次重载内切换,等等 |
能替换厂商按钮的行为 | ADR 加一节,N-11 多一类行为替换的实现和测试,无先例 |
推荐:A。 只看 ① 选 A;②③④ 是否翻转:否。理由有三:
- 没有实测需求:本 epic 里替换动作的拉动没量过。
- 它是行为类:替换它就是改「点了之后发生什么」,
flow为此需要四个条件,action没有理由更宽。 - 和已有裁定一致:「动作开关不入 epic」已经把动作这条线整体推后。
不取 B:在没有拉动的地方先付出一套行为替换规则,属于扩散。
os-decision-facets
① 长远:A 把依赖链上的同名收成唯一合法形状(.custom 声明),与改写稿 D8「一个读取有主人」一致;B 永久保留闭包内同名不同物;action 延后不增特例
② 拉动:#28238 实测即非配对依赖包顶替基础视图;有意复用基础包名字的存量未实测;action 替换无实测拉动
③ 防错:A 发布时响亮拒绝并给出 .custom 声明的出路;B 让「以为定制了其实没生效」静默通过
④ 不扩散:A 复用 Q1 已裁门禁不加能力;B 新增长期共存语义;action 开放需新增一套行为替换条件
Prior rulings read: undeclared same-name dependent package refused publish / non-pair dependent same-named item own address → 2 hits (#28237 review 6538795712, #28238 letter 6538867716); ADR-0048 §3.3, ADR-0126 §3 / §7.1; thread: fable director "总监决裁 7" (not readable from here; recommendation recorded there as "C for both" — content unknown to this card)
裁后执行
- 第一问裁 A:docs(adr): rewrite ADR-0146 for package-level override — customizations live in <base>.custom through a declared override (folds #28145) #28237 的承接者把 D2、D8.2、D9.1 和 Q1 表行改成同一条规则(闭包内一律拒绝,出路是
.custom声明或改名),ADR-0048 §3.3 的指针行写明「仅限无依赖关系的包」。然后做一次增量契约复核,交维护者合并。N-09 派发令加一条前提:先清点各示例和测试部署里闭包内同名的存量记录,零条就直接上;非零就把清单报给维护者再定迁移方式。⛔ 不许悄悄放宽成只拒配对包。 - 第一问裁 B:D2 删「或任何依赖包的项」,D9.1 改成与 D8.2 同义。metadata-protocol: a same-named item in a dependent package replaces the base package's item for package-less reads and in the base app #28238 A 的读取规则改为长期规则,ADR 里写明。
- 第二问裁 A:D2 清单删
action,D6「不在本 epic」加一行。裁 B:docs(adr): rewrite ADR-0146 for package-level override — customizations live in <base>.custom through a declared override (folds #28145) #28237 补 D9 式条件节,N-11 卡面加上action。 - 两问裁定后本卡关闭,docs(adr): rewrite ADR-0146 for package-level override — customizations live in <base>.custom through a declared override (folds #28145) #28237 按裁定续做。metadata-protocol: a same-named item in a dependent package replaces the base package's item for package-less reads and in the base app #28238 A 的读取规则两种结果下都成立,不受影响。
Analysis-tier: opus, desktop session (no fable re-read)
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 31, 2026
Ruling (maintainer os-elon-musk, fable 决裁 thread, 2026-10-31 06:48–06:56Z) — both questions decided; this card closes with #28237 carrying the text.
Maintainer, verbatim: 「没写这行声明、又恰好同名,是否建议统一规则,要么申明覆盖,要么禁止。这样会有什么坏处吗」 → fable listed the costs → 「同意」; and on this card: 「还有一个 28364 的决裁,是不是同一件事」 → confirmed the same matter, 「28364 你也回复」.
Question 1 — undeclared same-name item of a non-pair dependent: A, uniform
Within the base's dependency closure, ANY dependent package's item (every overridable type, not only
.custom) that bears a base item's name without theoverrides: { package, basedOn }declaration is refused at publish. Riders the maintainer agreed to:.custom; one declarer per base item — the second publish is refused naming the first.Fable's independent read, for the record: views, pages, reports and dashboards are read by address, so a same-name item in a dependent would coexist harmlessly (ADR-0048 §3.3); flows and actions are found by name at trigger time, so a same-name item is a collision (ADR-0126 §7.1, #11665). Fable therefore offered a type split (declarative = refuse only
.custom; behavioural = refuse every dependent). The maintainer chose one uniform rule over the split, with the measured cost accepted: a dependent that ships a generic name the base also ships (HotCRM shipspage/home,hotcrm/src/sales/pages/home.page.ts) must rename or declare. #28238's read-side ruling A stands either way.Question 2 —
actionoverridable in this epic: C (Q3-shaped), which supersedes this card's A/Bactionstays in D2's type list with a condition, asflowis with D9: overridable only after the action ledger switch lands (#27842 / #27504), under D9's conditions 1–3 (served over the code package, rebound in one reload, ledger identity inherited); no card in this epic. Effect this epic is the same as this card's option A (no action override), but the ADR records the direction and its precondition instead of a bare exclusion, so a later card does not reopen the question. Fable's reasoning: admittingactionnow would ship an override with no switch (D4 keeps the ledger out of this epic) — a half; dropping it loses the direction.Seven "decided where silent" items of the draft
Confirmed as written: 2 (
basedOnrecorded, never judged), 3 (uninstall refusal 409, extender family), 5 (one effective read, base via the layeredcodelayer), 6 (migration as an idempotent per-base CLI ceremony). Changed: 1 — keep^<major>but replace the rationale (ADR-0141 D2 runs only when installing the dependent; name what reads the range today); 4 — the criterion stays, and the whitelist IS the N-08 closed constant the write gate reads; 7 — add the install door (installPackage/ marketplace local install) as the third__cenforcement point, so "a vendor's next version can never take that name" holds.Execution: the #28237 holder (card thread "Rewrite ADR-0146") folds the above into PR #28295, runs the delta contract review, then governed approvers and maintainer hand-merge. Fable analysis file: project files
fable-director/28237-adr0146-decision-batch.md.Analysis-tier: ceiling
Generated by Claude Code
docs(adr): ADR-0146 folds the 2026-10-31 ruling — one refusal rule ac…
objectstack-fleet commentedon Oct 31, 2026
epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
Ruled: Q1 A (uniform, five riders) · Q2 C — ruling record 6540351677 (maintainer os-elon-musk, fable 决裁 thread, 2026-10-31 06:48–06:56Z). Recorded 2026-10-31T08:15Z.
<base>.custom, one declared override shared by code and Studio; dated pointers on six Status lines #28295 at head04aa72e7(contract review PASS 6540597637, seat ACCEPT 6540551597); the PR is in the merge queue, enqueued byos-zhuangat 08:07Z.needs-user-decisionremoved in this write — the decision is made. This card closes when docs(adr): rewrite ADR-0146 as the package-level override record — paired<base>.custom, one declared override shared by code and Studio; dated pointers on six Status lines #28295 merges (Fixes #28237), as the ruling record says.Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
epic:#28130#1 · hotlong · session_040dfd80c4c9411b895c698e785a6848
2026-10-31T09:36Z
Closing as completed: the ruling (Q1 A with five riders · Q2 C, record 6540351677) is in ADR-0146 as merged by #28295 (head
04aa72e7). Closure was pre-announced in 6541041334 ("closes when #28295 merges"). Its execution riders now live on the epic (#28236 comment 6541767586) and travel with N-09. A follow-on question this ruling surfaced — rider 3's "any closure package may declare" × "one declarer per base item" — goes to its own decision card, not back into this one.