Skip to content

Commit f4bed58

Browse files
docs(adr): ADR-0048 §3.4 narrowed — positions, permission sets and capabilities hold one name per deployment (#22198)
Part of #22135 Records the maintainer's ruling Q4 = A on #15196 (ruling record 6050490870, 「15196 Q3 A Q4 A」) in ADR-0048. The security catalog (positions, permission sets, capabilities) is taken out of §3.4's cross-package coexistence: each of the three types holds one name per deployment. This is the Tier H half of #22135, split from the code PR (#22197) so the code can land on its own record. It closes nothing: the card is closed by the code PR. #22135 is not addressed by this PR alone. ## What changed — `docs/adr/0048-cross-package-metadata-collision.md` only Additive. The original text is untouched; no existing line is edited. - **A dated note directly beneath §3.4:** "Narrowed (2026-10-08) — the security catalog is out of §3.4", with a link to the addendum. - **A new addendum at the end:** "Addendum (2026-10-08): the security catalog holds one name per deployment — §3.4 narrowed". It carries: - the ruled option's text, verbatim, and the options not taken (B, C); - N.1, why §3.4's premise (every caller carries its package id) does not hold for bare-name assignments, with the measurement that motivated the ruling; - N.2, what is refused and who the holders are; - N.3, what stays as §3.4 has it: same-package reload, every other type, an environment save, no `OS_METADATA_COLLISION=warn` downgrade; - N.4, where it is implemented. The header's `**Addenda**:` index line is deliberately not edited, to keep the original text untouched. The note under §3.4 carries the link. ## Gates (at c383221) `dispatch-gates --commands` derived 19 commands; all 19 were run with exit codes recorded, and `--ran` reconciles 19/19 with 0 NOT MEASURED. `check:doc-formula-expressions` first answered PREREQUISITE NOT MET (exit 3); it passed after `@objectstack/formula` and `@objectstack/lint` were built. ## 维护者速读(草稿) ### 改了什么 只改了一份架构决策记录 ADR-0048 的文字,没动任何代码。在 §3.4「跨包同名不再报错」那一节下面加了一段带日期的说明,并在文末加了一个附录。内容是把您在 #15196 上的裁决(Q4 选 A)写进去:职位、权限集、能力这三类安全目录,一个部署里一个名字只能有一个持有者。原文一个字都没改。 ### 为什么改 ADR-0048 §3.4 当初允许两个包用同一个名字,是因为界面类元数据被调用时总带着「我是哪个包」,系统能分清。但给用户分配职位、给职位挂权限集时,只记名字、不记包。两个包都带同名的「销售经理」,用户到底拿到哪一份权限,就取决于加载顺序。实测确实如此:同一套系统里,职位取后注册的那个包,权限集和能力取先注册的那个。一个应用自带一个叫 `admin_full_access` 的权限集,按名字查到的就是应用自己那份,而不是平台的管理员权限集。您裁定这三类单独收紧,这份 ADR 要跟着记下来,否则 ADR 写着「允许同名」,代码却在拒绝,两边对不上。 ### 风险与代价(含回滚) - 这份 PR 本身只是文档,没有运行时风险。 - 真正的行为变化在配套的代码 PR(#22197):装包或启动时遇到同名会直接报错。仓库里四个示例应用加平台内置名,一共 50 个声明,实测没有一处同名,所以现有示例都能照常启动。已部署环境和应用市场里的包没有测过。 - 回滚:撤销这份 PR 即可,ADR 回到原文;代码 PR 可以分开回滚。 ### 席位意见 ### 你要做的 请审阅附录的措辞是否准确反映您的裁决,同意就批准(Approve)。这份 PR 属于 Tier H,只能由您批准后落地。 --- _Generated by [Claude Code](https://claude.ai/code/session_01EUBvqtauTDmHi2ZgY759p2)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 3ae5966 commit f4bed58

1 file changed

Lines changed: 98 additions & 0 deletions

File tree

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,17 @@ unchanged), and **same-package re-registration** simply overwrites (idempotent
258258
reload). Authoring-time hygiene — an author shipping two `page/home` in one
259259
package — stays covered by the `naming/namespace-prefix` lint in `os lint`.
260260

261+
> **Narrowed (2026-10-08) — the security catalog is out of §3.4.** Positions,
262+
> permission sets and capabilities each hold ONE namespace per deployment: a
263+
> package whose position, permission set or capability bears a name an installed
264+
> package, the environment catalog or a built-in already holds is refused at
265+
> registration, and the error names both holders. §3.4 rests on every caller
266+
> carrying its package id; an assignment carries a bare position or
267+
> permission-set name with no package context (ADR-0131 D4), so for these three
268+
> types a shared name is a real ambiguity, not the disambiguable one described
269+
> above. Every other type keeps §3.4 as written. See
270+
> [Addendum (2026-10-08)](#addendum-2026-10-08-the-security-catalog-holds-one-name-per-deployment--34-narrowed).
271+
261272
### 3.5 Namespace rename-on-install is deferred (non-goal)
262273

263274
Renaming a colliding namespace on install would require rewriting **every**
@@ -749,3 +760,90 @@ Status legend as in §5.
749760
- **Open-source deployments lose nothing.** They keep the gate that prevents
750761
corruption; what they do not get is the global authority that would have told
751762
them earlier.
763+
764+
---
765+
766+
## Addendum (2026-10-08): the security catalog holds one name per deployment — §3.4 narrowed
767+
768+
> **Status of this addendum: Accepted** — it records a maintainer ruling. It
769+
> **narrows §3.4 for three metadata types and supersedes no other text**: §§1–6
770+
> and the 2026-08-08 addendum stand verbatim.
771+
>
772+
> **Ruling this addendum records** — Q4 on #15196, answered by the maintainer
773+
> 「15196 Q3 A Q4 A」 (ruling record 6050490870, 2026-10-08); the ruled option's
774+
> text, verbatim:
775+
>
776+
> > The three security catalog types (positions, permission sets, capabilities)
777+
> > each hold one namespace per deployment. Installing or registering a package
778+
> > whose position, permission set or capability bears a name an installed
779+
> > package, the environment catalog or a built-in already holds is refused, the
780+
> > error naming both holders. ADR-0048 §3.4's retirement of the cross-package
781+
> > throw is narrowed to leave these three types out: an assignment carries a
782+
> > bare name with no package context, so for the security catalog a shared name
783+
> > is a real ambiguity, not the disambiguable one §3.4 describes for UI metadata.
784+
>
785+
> Not taken: **B** (assignments carrying `package + name`, which rewrites the
786+
> ADR-0131 D4 reference syntax and puts a prefix on every assignment) and **C**
787+
> (resolution by registration order: who holds a permission would depend on load
788+
> order).
789+
790+
### N.1 Why §3.4's premise does not hold for these three types
791+
792+
§3.4 retires the cross-package throw because "prefer-local always disambiguates
793+
two different packages": every routed UI surface carries the package id of the
794+
caller, so two packages' `page/home` never compete for one caller. The security
795+
catalog has no such caller. A user holds the position `sales_manager`, a position
796+
binds the permission set `sales_user`, a permission set grants the capability
797+
`export_data` — each reference is a **bare name** (ADR-0131 D4), stored in
798+
assignment rows and junction rows with no package coordinate, and resolved by a
799+
read that takes no package context. Two holders of one name therefore leave the
800+
grant to whichever definition that read happens to reach first.
801+
802+
That is not hypothetical. Measured before the ruling, on a booted kernel with two
803+
packages sharing one name per type: the by-name catalog read resolved the
804+
permission set and the capability to the **first**-registered package and the
805+
position to the **last**-registered one — two different precedence rules inside
806+
one catalog. An app declaring a permission set named `admin_full_access` beside
807+
the platform's own was accepted, and the by-name read answered the app's set.
808+
809+
### N.2 What is refused
810+
811+
A package registration is refused when a position, permission set or capability
812+
it declares names something another holder already holds. The holders:
813+
814+
- **an installed package** — including a disabled one, which is still installed
815+
and still holds its names;
816+
- **the environment catalog** — an item authored in this environment rather than
817+
shipped by a package;
818+
- **a built-in** — the platform's built-in identity positions and audience
819+
anchors, and its curated capabilities. The platform's permission sets are
820+
declared by the security plugin's own package and are held by it like any
821+
other package's.
822+
823+
The error names both holders and carries an ADR-0112 envelope (`422`, with the
824+
code the §3.2 namespace gate already uses): the condition is the same one — a
825+
name in a deployment-wide namespace is already taken — and so is the remedy:
826+
rename, or uninstall the other holder.
827+
828+
### N.3 What stays as §3.4 has it
829+
830+
- **Same-package re-registration** — an idempotent reload, a re-install, a hot
831+
reload — is one holder, not two, and simply overwrites.
832+
- **Every other metadata type** keeps §3.4's coexistence: two packages' `page/home`
833+
still coexist under distinct composite keys.
834+
- **A write with no package provenance** — an environment save over a
835+
package-held name, which is how every `sys_metadata` hydration arrives — stays
836+
under ADR-0005 overlay precedence. The ruling covers installing or registering a
837+
package, not a metadata author's save; that path is unchanged by this addendum.
838+
- **No downgrade.** `OS_METADATA_COLLISION=warn` softens the §3.2 namespace gate
839+
only; no ruling extends it to this refusal.
840+
841+
### N.4 Where it is implemented
842+
843+
`@objectstack/objectql`: the rule and its holders in `security-catalog-namespace.ts`;
844+
the package door in `SchemaRegistry.installPackage` (ahead of every mutation, so a
845+
refused package leaves no record) and the item seam in `SchemaRegistry.registerItem`
846+
for a package-bound registration that reaches the registry directly. Every package
847+
registration reaches the package door before any in-memory registrar runs, so the
848+
boot of a conflicting composition — an artifact boot included — is refused in its
849+
first phase.

0 commit comments

Comments
 (0)