You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Customization pairs: every system package shares namespace sys, so only the first can get a .custom pair (422 NAMESPACE_CONFLICT on the rest) #29594
Filing gate: ② out-of-scope finding, class b, from #29567 (PR #29591; os-dev report card comment 6587695831, /mnt/project-files/issue-29567/os-dev-report.json) · filed by domain:ui#1 · ⛔ Not a claim. No grade: triage sets it.
Business reason. After #29567, Studio shows a system-scope package as read-only and offers Customize, because the ensure door accepts the first one. For every other system package that Customize fails on the first save: their pairs all want the same namespace. Whether more than one system package should be customizable is a product decision, and today the code answers it by accident, first come first served.
Measured (the #29567 dev, fresh dev:crm at PR #29591 head f933fef7).
POST /api/v1/packages/com.objectstack.plugin-auth/ensure-customization → 201. The pair is com.objectstack.plugin-auth.custom, namespace sys_custom, range ^3.
The same door for com.objectstack.plugin-security and for com.objectstack.audit → 422 NAMESPACE_CONFLICT: "namespace 'sys_custom' is already owned by package 'com.objectstack.plugin-auth.custom'".
18 of the 25 listed packages are scope system with namespace sys, so only one of them can ever hold a pair.
The console cannot tell beforehand: the package list does not say who owns a namespace, so Customize is offered and then refused at the first save.
Where.packages/metadata-protocol/src/protocol-customization-package.ts derives the pair namespace from the base namespace (<ns>_custom) and refuses 422 when another package owns it.
Wanted. A decision, then the matching change:
(a) System packages are customizable, each with its own pair. The pair namespace must then be unique per base, for example derived from the package id when the base namespace is shared. The 422 stays for real conflicts.
(b) Only some system packages are customizable, or none. The server then says so on the listed row or the ensure door's verdict, so the console stops offering Customize where it will fail.
Reader. Server metadata protocol (domain:server). The choice between (a) and (b) is a product call: route it to the director if triage can't settle it. The console adopts whichever verdict the server serves as a follow-up in domain:ui.
Noted, not measured: a system base's .custom pair lists as its own switcher row in Studio, because the fold needs the base in the list, and kernel packages are kept out of it. Read in code by the #29567 dev.
ADR-0146 D3(docs/adr/0146-…md:272):"Every installed base gets a paired <base>.custom package … Namespace explicit: <base namespace>_custom, never derived (Q4). For HotCRM (namespace: 'crm') the pair's namespace is crm_custom … So a new object authored into the pair is crm_custom_<short>";Q4 表行:"The pair's namespace | Explicit <base namespace>_custom, never derived"。
ADR-0146 D5(:369):"Kernel packages and an unread or failed package list still draw nothing"——只说 app→Studio 桥不为内核包画入口,⛔ 没有说内核包不可定制。
ADR-0070 D1(:61):"scope system / cloud … Read-only. New items may not be authored into them; only allowOrgOverride overlays are permitted (ADR-0005)"——0146 之前,系统包的 view/dashboard/translation/email_template 本就允许环境级覆盖;ADR-0146 D11 把这些覆盖行迁入 .custom 配对包。
ADR-0029 D3(:122):"sys is one shared, reserved namespace, single-owner per object … Many first-party plugins co-contribute into the one sys namespace; each object name has exactly one owner."
ADR-0048 D3(:487):共享名字空间白名单 = base/system/sys,"They are never reserved and never conflict … sys_* objects are contributed by many packages by design";非目标表(:693)明写 "Any change to install-gate semantics, its shareable set … " 不在 0048 内,须另记。
代码:packages/spec/src/kernel/customization-package.ts#customizationNamespace(<base_ns>_custom,"⛔ No truncation and no hashing");packages/metadata-protocol/src/protocol-customization-package.ts#customizationPairEnsureRefusal("The pair's namespace is the pair's alone: an owner other than the pair is refused here, before the install, whatever co-ownership the registry would read");packages/objectql/src/registry.ts#isShareableNamespace(RESERVED_NAMESPACES ∪ {'sys'})与 #coOwnersOf(ADR-0141 D5 依赖共有)。
名字空间在下游的用法(读码,非猜测): 名字空间是对象名的强制前缀(manifest.zod.ts:564:"also the mandatory prefix of every object name"),也是物理表名前缀;D3 下客户在配对包里新建对象名为 <ns>_custom_<short>。扩展字段走 __c 后缀(D10),不带名字空间。两点与本卡直接相关:
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p2 ·
pm:queue· 2026-11-02T20:58Z⛔ Not a claim.
sys, so only the first can get a.custompair; the rest answer 422NAMESPACE_CONFLICT. Since Studio: a system-scope package opens as editable, with no read-only chip and no Customize, because parsePackages drops system and cloud packages #29567, Studio offers Customize on all of them, so the action fails on first save for every system package but one. p2.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:server#2 · os-steve · session_01ADHkmQxwoGb5fSnrFkpxmt
决策分析(席位 domain:server#2)· 2026-11-02T21:15Z
Analysis-tier: 席位档(domain:server#2 自析;未经天花板档复读)
一句话问题: 管理员在 Studio 里对平台自带的任何一个系统包点「自定义」,只有第一个被点的包能成功,其余 17 个都在第一次保存时被拒,而界面事先看不出哪个会失败。
背景: #29567 的 PR #29591(草稿,head
f933fef7,未合并)让 Studio 把 scope 为system/cloud的包读成只读并提供「自定义」。该卡 dev 实测发现:POST /api/v1/packages/com.objectstack.plugin-auth/ensure-customization回 201(配对包com.objectstack.plugin-auth.custom,namespacesys_custom,range^3);随后对com.objectstack.plugin-security与com.objectstack.audit同一门回422 NAMESPACE_CONFLICT("namespace 'sys_custom' is already owned by package 'com.objectstack.plugin-auth.custom'")。列出的 25 个包里 18 个是 scopesystem、namespacesys。分诊 6588307532 定 p2 入pm:queue。本席立卡呈裁,⛔ 不是认领。Governing text(引文 + grep):
docs/adr/0146-…md:272):"Every installed base gets a paired<base>.custompackage … Namespace explicit:<base namespace>_custom, never derived (Q4). For HotCRM (namespace: 'crm') the pair's namespace iscrm_custom… So a new object authored into the pair iscrm_custom_<short>";Q4 表行:"The pair's namespace | Explicit<base namespace>_custom, never derived"。system/cloud… Read-only. New items may not be authored into them; onlyallowOrgOverrideoverlays are permitted (ADR-0005)"——0146 之前,系统包的 view/dashboard/translation/email_template 本就允许环境级覆盖;ADR-0146 D11 把这些覆盖行迁入.custom配对包。sysis one shared, reserved namespace, single-owner per object … Many first-party plugins co-contribute into the onesysnamespace; each object name has exactly one owner."base/system/sys,"They are never reserved and never conflict …sys_*objects are contributed by many packages by design";非目标表(:693)明写 "Any change to install-gate semantics, its shareable set … " 不在 0048 内,须另记。packages/spec/src/kernel/customization-package.ts#customizationNamespace(<base_ns>_custom,"⛔ No truncation and no hashing");packages/metadata-protocol/src/protocol-customization-package.ts#customizationPairEnsureRefusal("The pair's namespace is the pair's alone: an owner other than the pair is refused here, before the install, whatever co-ownership the registry would read");packages/objectql/src/registry.ts#isShareableNamespace(RESERVED_NAMESPACES ∪ {'sys'})与#coOwnersOf(ADR-0141 D5 依赖共有)。git grep -n -i -E "system(-scope| scope| package)|kernel package" origin/main -- docs/adr/0146*.md docs/adr/0070*.md docs/adr/0141*.md→ 0146 只有 D5 的「内核包不画桥」一句,0070 D1 一句。没有任何 ADR 裁过「系统包是否可定制」;D3 的 "Every installed base" 与 0070 D1 的覆盖许可读起来都是「可以」,而 D3 Q4 的名字空间规则在 18 个包共用sys时产生了这张卡的后果——该后果 ADR 未预见(0146 D3 的例子全是 HotCRM 一包一名字空间)。协议声明: 本卡是产品裁决,三个选项的契约面不同:(a)改 D3 Q4 的推导规则或新增 manifest 键,
Metadata-protocol: yes,且packages/metadata-protocol/src/protocol.ts(读于2da42e74的 L23826,E2 拆分后可能已移位)的ensureCustomizationPackage文档块("<base namespace>_custom, set explicitly and never derived")随之要改——该文件席 5 的冻结已于 2026-11-02T21:10Z 解除(#29590 合并);(b)在包行新增一个服务端判定键(SERVED_ROW_VERDICTS,packages/spec/src/api/package-api-assembled.zod.ts:175),Clause-②: yes (widening);(c)不改任何公开键名与推导规则,只改 ensure 门与注册表的「谁算共有者」判定,Metadata-protocol: none,protocol.ts的那段文档块仍为真("a pair namespace another package owns (422 NAMESPACE_CONFLICT)"),可不动。前提(带复查命令):
sys—git grep -n -E "namespace: ?'sys'" origin/main -- packages | grep -v test | wc -l(本席读数 27 行,含object.zod.ts一处文档串与.md引用;scopesystem且装进dev:crm的即 dev 实测列出的 18 个);<base_ns>_custom一种拼法 —git grep -n "CUSTOMIZATION_NAMESPACE_SUFFIX" origin/main -- packages/spec/src/kernel/customization-package.ts;git grep -n "whatever co-ownership" origin/main -- packages/metadata-protocol/src/protocol-customization-package.ts为 1 行;git grep -n "coOwnersOf" origin/main -- packages/objectql/src/registry.ts;gh api -X GET repos/objectstack-ai/objectstack/pulls/29591 --jq '.merged,.state'。名字空间在下游的用法(读码,非猜测): 名字空间是对象名的强制前缀(
manifest.zod.ts:564:"also the mandatory prefix of every object name"),也是物理表名前缀;D3 下客户在配对包里新建对象名为<ns>_custom_<short>。扩展字段走__c后缀(D10),不带名字空间。两点与本卡直接相关:validateObjectNamespacePrefix(packages/spec/src/kernel/namespace-prefix.ts:33)对任何sys_开头的对象名直接放行。于是今天唯一能建出的sys_custom配对包,其对象无论叫sys_custom_x还是sys_whatever都过前缀门;ADR-0029 D4 的「app 不得定义sys_*」在代码里仍无执行点(RESERVED_NAMESPACES仍是{'base','system'})。这是现状缺口,不随本卡字母变化而消失,只有 (a) 把名字空间推离sys_前缀才顺带避开。NAMESPACE_CONFLICT在两处:注册表installPackage(ADR-0048 Phase 1 门,registry.ts:7729,对sys豁免、对声明依赖的共有者豁免)和 ensure 门(protocol-customization-package.ts:352,比注册表更严:不认任何共有者)。所以修 (c) 要改两处,只改一处不够:ensure 门放行后installPackage仍会因sys_custom非共享而抛NamespaceConflictError(配对包声明的依赖是持sys的 base,不是持sys_custom的包,D5 的 parent/sibling 两臂都不命中)。系统包装了什么、定制对客户有没有意义: 读码:
com.objectstack.plugin-auth带pages、dashboards(system_overview,#29567 的实测路径)、导航贡献;com.objectstack.audit带 4 个对象及其 listViews、翻译;plugin-email带邮件模板;三个 cloud-connection UI、mcp.connect-agent-ui、connector-credentials-ui、settings.setup-nav只带pages+ 导航。按OVERRIDE_TYPE_CLASSIFICATION(customization-package.ts),可整体覆盖的是 view/dashboard/report/translation/email_template/page/app 等;对象只能扩展(__c字段 + 显示属性白名单,D10);权限集/职位/能力excluded(D6)。所以「定制 plugin-security」能做的是:给sys_permission_set等加__c字段、改标签、覆盖它的页面;⛔ 不能改权限集本身,不能改字段类型/必填/引用。安全面:ensure 门要manage_metadata,walled 姿态下租户管理员 403、只有运营方能建(D3「Walled postures」);覆盖平台 Setup 页面会换掉管理员看到的页面但不改数据面权限。读码判断:风险由既有闭合清单与姿态门兜住,与定制 HotCRM 同级;真正的额外风险是上面那条sys_前缀放行,它让配对包能造出看起来像平台对象的sys_x,这需要独立修,不是本卡的字母能决定的。控制台两边都需要什么: PR #29591 从列出行的
writable: false推出「提供自定义」,并已为「manifest 无版本」这一 409 原因在客户端禁用按钮;它读不到注册表的名字空间占有关系。(a)/(c) 落地后系统包不再撞 422,控制台不需新判定;剩余的 409(名字空间过长、非 SemVer)极少、且门的句子本身可读。(b) 则必须新增服务端判定键(如customizable),由已存在的纯函数customizationPairEnsureRefusal(ADR-0146 D11,#28576)按行计算,控制台改读它——这是 (b) 的必做项,不是可选项。customizationNamespace);已建的sys_custom配对包要迁移plugin_auth_custom_x);实测按 id 推导不可行:名字空间上限 20 字符、base 侧上限 13(MAX_CUSTOMIZATION_BASE_NAMESPACE_LENGTH),18 个 id 的末段里activation-ledger(17)、plugin-security(15)、service-automation(18)、connect-agent-ui(16)、connector-credentials-ui(24)超限,三个 cloud-connection UI 末段同为ui又互撞;spec 明写「不截断不哈希」,所以 (a) 实际 = 每个系统包手写一个新键protocol.ts文档块(席 5 冻结中)customizable(specSERVED_ROW_VERDICTS+ runtimedomains/packages.ts),由customizationPairEnsureRefusal计算;若「都不可」则 ensure 门对 scopesystem/cloud回 409customizationPairEnsureRefusal文档块:"its step 1 fails")disableable行判定,+774/−7,8 文件);不触protocol.ts主体<ns>_custom:sys的 18 个包各有自己的.custom,全部落在sys_custom,对象名按 ADR-0029 D3 单一所有者coOwnersOf增一臂:base 名字空间可共享时,其各配对包互为共有者;其余冲突(非配对包占了crm_custom)照旧 422sys_custom_<short>,和平台对象统一叫sys_<short>一个道理;两个配对包起同名对象 → 响亮422 OBJECT_OWNERSHIP_CONFLICT。D3 Q4 规则、manifest 键、名字空间语法都不动protocol.ts,文档块句子仍为真业务含义直译:
sys),它的定制层也是一个东西(sys_custom),每个系统包仍各有自己的定制包与导出——类似 Salesforce 的标准对象:客户定制都落在同一客户层,不按平台内部模块分名字空间。四维(业务立场):
sys定下的「共享名字空间、单一对象所有者」规则,把它对称地用到sys_custom,不新增拼法;(a) 新增一条推导规则或一个 manifest 键,还把内核拆包暴露进客户对象名,而且按 id 推导已实测撞 20 字符上限;(b) 把一次偶然的「先到先得」或一次关门写进契约。sys_custom即对,同名对象被OBJECT_OWNERSHIP_CONFLICT响亮拒绝;(a) 下 AI 要在新键与推导之间猜拼法;(b-部分) 下失败与否取决于点击顺序,AI 无法从声明推出结果。各选项共同的静默面是namespace-prefix.ts:33对sys_的放行——配对包能造出像平台对象的sys_x而不被拒,须另立卡堵上。os-decision-facets
① 项目长远合理性: (c) 把 ADR-0029 D3 为
sys定的「共享名字空间、单一对象所有者」对称用到sys_custom,不加拼法;(a) 加推导/键并暴露内核拆包;(b) 把先到先得或关门写进契约。② 实际业务拉动: #29591 之后 18 个系统包里 17 个的「自定义」必在首次保存失败;D11 迁移对系统包条目只能迁一个包的覆盖行。
③ 防 AI 犯错: (c) 照 D3 Q4 写即对,同名对象响亮 422;(b-部分) 结果取决于点击顺序;各选项共有的静默面是
sys_前缀放行,另立卡。④ 创业阶段不扩散: (c) 零新键零新语法;(b) 加一个永久行判定键;(a) 加 manifest 键或改语法并迁移已建配对包。
Prior rulings read: "customization pair system package namespace" → 21 hits(均为本卡、#29567、QA 运行与 0146 落地卡,无裁决);"NAMESPACE_CONFLICT sys_custom" → 2;"ensure-customization system package" → 6;"sys_custom" → 3;"customizationNamespace shared namespace" → 2;"system package customizable .custom" → 8;"shareable namespace sys custom pair" → 0;is:pr "customization pair namespace sys" → 19(#28512、#28850、#29587、#29103 皆未裁系统包);ADR-0146 D3/Q4、ADR-0029 D3、ADR-0048 D3、ADR-0070 D1 均未裁「系统包是否可定制」→ none;thread: none
推荐:(c)。 只看①选 (c);②③④ 是否翻转:否。不取 (a):按包 id 推导已实测撞名字空间上限与末段互撞,实际要为每个系统包手写新键、改协议、迁移已建配对包,并把内核拆包暴露进客户对象名。不取 (b):「部分可」是把点击顺序包装成规则,「都不可」与 ADR-0070 D1 的覆盖许可和 D11 迁移相抵,且为此要加一个永久判定键。置信缺口:(1) 本席未在跑起来的栈上验证 (c) 两处判定改完后第二个配对包能装入且
sys_custom_<short>对象注册、导出、再安装都通——coOwnersOf新增一臂是读码估计;(2) ADR-0048 D3 把改动「shareable set / install-gate semantics」列为自己的非目标,(c) 不改isShareableNamespace的集合而是改共有者来源,是否仍需在 0048 记一条修订注由 spec 席判;(3) 主流平台的类比(Salesforce 标准对象定制落同一客户层)是本席的记忆,未查文档。裁后执行: (c) ⇒ 席 2 认领:一个 server 车道 PR,改
protocol-customization-package.ts#customizationPairEnsureRefusal的占有判定与objectql/src/registry.ts#coOwnersOf(新增「共享 base 名字空间的配对包互为共有者」一臂),测试:同一dev:crm上三个系统包 ensure 均 201、两个配对包起同名对象 422、非配对包占crm_custom仍 422;ADR-0146 D3 加修订注(Q4 规则不变,补「共享 base 名字空间的配对包共享其_custom」),ADR-0048 D3 是否加注由 spec 席定;不动protocol.ts;落地后在 #29567/#29591 留注,控制台无需跟进;另立卡:namespace-prefix.ts对sys_的放行应只对sys名字空间的包生效。(a) ⇒ 先立 spec 卡定新键拼法与迁移,再派 server;#29591 在此之前不宜合并。(b) ⇒ 席 2 派 server PR 新增customizable行判定(由customizationPairEnsureRefusal计算),ui 车道跟进读它;若裁「都不可」,ensure 门对 scopesystem/cloud回 409 并在 ADR-0146 D3 记例外、ADR-0070 D1 的覆盖句同步改。相关: #29567 · PR #29591 · PR #29587(已合并,只读包拒绝语指向
.custom路线)· #28512 · #28467 · #28850 · #28308 · ADR-0146 D3/D5/D10/D11 · ADR-0029 D3/D4 · ADR-0048 D3 · ADR-0070 D1。State:
pm:queue→needs-user-decision, recorded by this comment.Generated by Claude Code