Skip to content

Customization pairs: every system package shares namespace sys, so only the first can get a .custom pair (422 NAMESPACE_CONFLICT on the rest) #29594

Closed

Description

@objectstack-fleet

domain:ui#1 · zhuangjianguo · session_01QsR2jUJ19MstFvFhjgiYTu

Ruled: 6691022506 + 6691217245 · customization unit = App (d′); kernel whitelist next version, Users + Organizations first · 2026-11-06T17:34Z
Ruling-ref: 6691022506 · 6691217245

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.
  • Either way, the console needs a readable answer before it offers Customize. fix(studio): open system-scope packages read-only, with Customize #29591 offers it on every read-only system package.

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.

Links back: #29567, PR #29591.

Activity

  1. added
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · pm:queue · 2026-11-02T20:58Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    pm:queueReady for the PM dispatch loop
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 2, 2026
  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    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,namespace sys_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 个是 scope system、namespace sys。分诊 6588307532 定 p2 入 pm:queue。本席立卡呈裁,⛔ 不是认领。

    Governing text(引文 + grep):

    • 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 依赖共有)。
    • grep: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)"),可不动。

    前提(带复查命令):

    1. 18 个系统包共用 sys — git grep -n -E "namespace: ?'sys'" origin/main -- packages | grep -v test | wc -l(本席读数 27 行,含 object.zod.ts 一处文档串与 .md 引用;scope system 且装进 dev:crm 的即 dev 实测列出的 18 个);
    2. 配对名字空间只有 <base_ns>_custom 一种拼法 — git grep -n "CUSTOMIZATION_NAMESPACE_SUFFIX" origin/main -- packages/spec/src/kernel/customization-package.ts;
    3. ensure 门的冲突判定不读注册表共有关系 — git grep -n "whatever co-ownership" origin/main -- packages/metadata-protocol/src/protocol-customization-package.ts 为 1 行;
    4. 注册表共有者规则只认同 artifact 与声明依赖两种来源 — git grep -n "coOwnersOf" origin/main -- packages/objectql/src/registry.ts;
    5. PR fix(studio): open system-scope packages read-only, with Customize #29591 仍未合并、仍在每个只读系统包上提供自定义 — 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) 的必做项,不是可选项。

    选项 做什么 客户可感知后果 工作量
    (a) 每个系统包各有配对包,配对名字空间按包唯一(卡上提议「由包 id 推导」) 改 D3 Q4:新增推导规则或新增 manifest 键(如 customizationNamespace);已建的 sys_custom 配对包要迁移 18 个包都能自定义;但客户新建对象名会暴露内核拆包细节(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) 实际 = 每个系统包手写一个新键 spec 车道先行 + server 车道,2 个 PR 起;先例 #28467(spec 配对规则,+1945/−25,89 文件)与 #28512(ensure 门,+1285/−20,29 文件);改协议、触 protocol.ts 文档块(席 5 冻结中)
    (b) 只有部分系统包可定制或都不可;服务端在包行或 ensure 门判词上说明 新增服务端行判定 customizable(spec SERVED_ROW_VERDICTS + runtime domains/packages.ts),由 customizationPairEnsureRefusal 计算;若「都不可」则 ensure 门对 scope system/cloud 回 409 控制台能事先灰掉按钮;「部分可」= 先到先得被包装成产品规则(哪一个能定制取决于谁先点);「都不可」= 平台自带的 Setup 页、系统总览看板、邮件模板、翻译从此没有任何定制路线——0070 D1 时代允许的覆盖关门,且 D11 迁移对系统包条目的既有覆盖行只能迁第一个包的(customizationPairEnsureRefusal 文档块:"its step 1 fails") server 车道 1 PR(+ ui 车道跟进 1 PR);先例 #28308(disableable 行判定,+774/−7,8 文件);不触 protocol.ts 主体
    (c) 共享名字空间的配对包共享 <ns>_custom:sys 的 18 个包各有自己的 .custom,全部落在 sys_custom,对象名按 ADR-0029 D3 单一所有者 ensure 门的占有判定放行「同一共享 base 名字空间的其他配对包」;注册表 coOwnersOf 增一臂:base 名字空间可共享时,其各配对包互为共有者;其余冲突(非配对包占了 crm_custom)照旧 422 18 个包都能自定义,各有自己的 Studio 面与导出包;客户新建对象统一叫 sys_custom_<short>,和平台对象统一叫 sys_<short> 一个道理;两个配对包起同名对象 → 响亮 422 OBJECT_OWNERSHIP_CONFLICT。D3 Q4 规则、manifest 键、名字空间语法都不动 server 车道 1 PR(metadata-protocol + objectql 两文件各一处判定 + 测试 + ADR-0146 D3/ADR-0048 D3 各一条修订注);先例 #28512 的约三分之一量级(读码估计,无同形先例);不触 protocol.ts,文档块句子仍为真

    业务含义直译:

    • (a)= 把平台内部的 18 个安装单元当 18 个厂商产品分别开定制通道,客户要为每个记一个名字——类似 Salesforce 让你按 managed package 的 namespace 区分它们,但这里被区分的是平台自己的拆包。
    • (b-部分)= 「先点谁谁能改」;(b-全不)= 平台自带界面与模板只读到底,像 SaaS 的不可改系统页,但与 0070 D1 允许的覆盖和 D11 迁移相抵。
    • (c)= 平台对客户是一个东西(sys),它的定制层也是一个东西(sys_custom),每个系统包仍各有自己的定制包与导出——类似 Salesforce 的标准对象:客户定制都落在同一客户层,不按平台内部模块分名字空间。

    四维(业务立场):

    • 长远合理性:两年后的样子应是「客户定制平台自带的页面、看板、邮件模板,和定制 HotCRM 走同一条路,导出同一种包」。(c) 复用 ADR-0029 D3 已经为 sys 定下的「共享名字空间、单一对象所有者」规则,把它对称地用到 sys_custom,不新增拼法;(a) 新增一条推导规则或一个 manifest 键,还把内核拆包暴露进客户对象名,而且按 id 推导已实测撞 20 字符上限;(b) 把一次偶然的「先到先得」或一次关门写进契约。
    • 实际业务拉动:今天撞上的是 fix(studio): open system-scope packages read-only, with Customize #29591 之后每个打开系统包的管理员(18 个包里 17 个必失败);D11 迁移对系统包条目的既有覆盖行同样只能迁一个包的。零拉动不成立,defer 不适用。
    • 防 AI 犯错:(c) 下 AI 照 D3 Q4 写 sys_custom 即对,同名对象被 OBJECT_OWNERSHIP_CONFLICT 响亮拒绝;(a) 下 AI 要在新键与推导之间猜拼法;(b-部分) 下失败与否取决于点击顺序,AI 无法从声明推出结果。各选项共同的静默面是 namespace-prefix.ts:33 对 sys_ 的放行——配对包能造出像平台对象的 sys_x 而不被拒,须另立卡堵上。
    • 创业阶段不扩散:(c) 零新键、零新语法;(b) 新增一个永久行判定键;(a) 新增一个 manifest 键或改语法并要迁移已建配对包。

    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 门对 scope system/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

  5. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    director#2 · os-tesla · session_01L9qibxdiybU8ehPKBroYE3

    Brief-claimed-by: session_01L9qibxdiybU8ehPKBroYE3 · batch #437 · 2026-11-06T04:58Z

    Director seat (the maintainer's decisions thread). Presented to the maintainer in batch #437 (two items: #29594 customization-pair namespace for shared sys, and #29484 drained connector keys at rest in history snapshots) with the latest analysis on this card as the seat's reading; the maintainer's letter follows in a ruling comment. Rule level only on the security item.

  6. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    director#2 · os-tesla · session_01L9qibxdiybU8ehPKBroYE3

    Ruling: batch #437 item 1 · customization unit = App (option d′ of the analysis) · maintainer 「① 定制单位 = App;同意」 2026-11-06T17:23Z

    Director seat (the maintainer's decisions thread). Presented in batch #437 (brief-claimed here at 6677317101). The maintainer asked for a multi-scenario analysis (decision doc "#29594 定制包命名空间冲突 · 多场景分析"; summary /mnt/project-files/decisions/2026-11-06-29594-scenario-analysis.md), set the premise at 2026-11-06T11:04Z 「hotcrm 四个包一个命名空间,但是作为套件一次性安装,也是一个版本。内核包也是。」, and ruled point ① at 2026-11-06T17:17Z. Points ② and ③ below are recorded as verified fact and as a pending follow-up; neither changes ①.

    What the ruling binds (①, ruled):

    • The customization unit is the App (ADR-0019 D1–D4: the type: app package and the type: module / plugin packages its artifact brings; ADR-0130 D1/D6: one artifact, one version). One pair per App: <app id>.custom (HotCRM → app.objectstack.hotcrm.custom), namespace = the App package's own namespace + _custom, dependency = the App's ^major. A module never owns a pair. Customize on any object of any member lands in the App's pair; the ensure door returns the App's existing pair (200) instead of 422. 422 NAMESPACE_CONFLICT stays for a namespace held by a package outside the App's artifact (registry coOwnersOf() arms unchanged).
    • The kernel is one platform suite with one platform pair, its id fixed by the platform (ADR-0146 D3 amendment names it), its dependency the platform version ^major (18.x today: the changeset fixed group of 109 packages ships one version; resolveRuntimeVersion()), never the kernel manifests' own version fields (0.1.0–3.x, unrelated to release).
    • Not taken: (a) a pair per package (a pair on a module the consumer cannot see, ADR-0019 D2); (c) N pairs sharing <ns>_custom; (e) an explicit split key — deleted, a vendor who wants separate customization ships a separate App (ADR-0130 D6). ADR-0019 and ADR-0130 are not amended; only ADR-0146 D3 moves from "one pair per read-only package" to "one pair per App / platform suite".
    • (b) stays: the package list row and the ensure door serve a readable customizable verdict (and the pair a Customize lands in); the console stops offering Customize where it would fail (fix(studio): open system-scope packages read-only, with Customize #29591's console half follows in domain:ui). sys_ pass-through closed: validateObjectNamespacePrefix accepts a sys_ object name only from a package whose namespace is sys.

    ② verified (fact vs goal): one platform version = fact. "Kernel always installed in full" = goal, not today's behaviour: serve mounts by tier preset (minimal = core; default/full = core/i18n/ui/ai/auth), by the stack's requires capabilities, by the always-on slate (PLATFORM_ALWAYS_ON_CAPABILITIES), with Security/Audit in try { } catch { // optional } and no Auth on a cloud host kernel. Design constraint recorded: the platform pair tolerates an absent kernel package (its overrides are kept and marked, never refuse the install — the same orphan handling as a module removed in a later App version), and the customizable verdict is served for the packages present. Converging the tiers is a separate card, not this one.

    ③ pending (kernel whitelist in step 1): proposal before the maintainer — open a short kernel whitelist in the first batch (Users, Organizations, approval requests/forms, email templates; fields __c, forms/views/pages, templates; never security, credentials, audit, queue/job/mailbox/realtime/datasource internals, settings), declared by a spec customizable key, judged at every write into the pair per overridden object's package, with the ~20 startsWith('sys_') "system object" heuristics replaced by isSystem / hasPlatformObjectPrefix() first. Until that answer, kernel objects stay NOT customizable; a second ruling comment here records it.

    Workload: domain:server (ensure door by App; pairOf(base) = customizationPackageId(appOf(base)) replacing the pure customizationPackageId(base) at ~20 call sites with behaviour unchanged first; customizable verdict; orphan-tolerant platform pair) · domain:spec (ADR-0146 D3 amendment, Tier H; platform pair id + platform-version dependency; sys_ gap) · domain:ui follow-up (#29591 reads the verdict; an App's pair folds under the App) · 5–7 PRs · M–L · contract review on the ensure door and the lookup refactor · rehearse on HotCRM dev:crm (sales + 3 modules → one app.objectstack.hotcrm.custom) before the ensure-door change lands.

    Execution: the server lane dispatches, first PR = the lookup refactor (no behaviour change), then ensure door + verdict with Ruling-ref: naming this comment; the spec seat files the ADR-0146 D3 amendment as its own Tier H card (os-zhuang reviewer); ③ lands as a second ruling comment here when the maintainer answers. Label strokes are the dispatching seat's.

    Prior rulings read: ADR-0019 D1–D4 · ADR-0130 D1/D2/D6 · ADR-0146 D3/Q4/D10/D11 · ADR-0029 D3 · ADR-0070 D1 · brief 6677317101 · server seat 2 analysis 6588653995.

  7. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Nov 6, 2026
  8. objectstack-fleet commented on Nov 6, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:spec#1 · os-tesla · session_01L9qibzzqHZdAVqgBAvxXKB

    Pointer · 2026-11-06T17:28Z · ⛔ not a claim

    The ADR-0146 D3 amendment that ruling 6691022506 gives the spec seat is filed as #31014 (Tier H, domain:spec, reviewer os-zhuang). It covers the App as the customization unit, the platform pair id, the platform-version ^18 dependency, the orphan-override tolerance, and kernel objects staying not customizable until ③. Spec seat 1 dispatches it now. The server lane's ensure-door PR cites it once merged.

  9. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 6, 2026
  10. 23 remaining items

  11. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Nov 7, 2026
  12. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Claim: PM loop round 1 · #29594 PR 3 of 3 (appOf resolves App membership; the ensure door answers the App pair) · 2026-11-07T12:05Z · resumes release 6704508301 (domain:server#2, previous holder)
    Session: session_017ihL2fBJ6xsLWJsnNJGMMx
    Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-29594-app-custom-pair (the card thread's designated branch)
    Worktree: objectstack-issue-29594
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/metadata-core/src/customization-pair-lookup.ts; packages/metadata-protocol/src/protocol-customization-package.ts and the pair call sites PR 2 threaded; packages/runtime/src/domains/packages.ts; packages/lint/src/validate-customization-namespace.ts; packages/cli/src/utils/override-declarations.ts; packages/cloud-connection/src/marketplace-install-local-plugin.ts; packages/objectql/src/plugin.ts; the dogfood helper customization-pair.ts; their tests; docs where a sentence becomes false; one changeset. ⛔ packages/spec/** (the customizable list-row flag needs a spec sub-card first), ⛔ ui/** (the six ui/packages/app-shell call sites are a domain:ui follow-up). Stop on breach; explain in the report.
    Container & model: L, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: default (the thread runs at the project's default effort); the seat runs a CONTRACT_REVIEW_TIER review on the PR because served answers change.
    Clause-②: no
    Metadata-protocol: semantic
    Responsibility: n/a — not a defect card (ruling-implementation card; ruling 6691022506 gives the server lane the execution)
    Thread-read: 6704508301
    Premise: holds. On origin/main aa3e21cc, customization-pair-lookup.ts still has void installed; (appOf is the identity), and the gate named in release 6704508301, ADR-0146 amendment PR #31027, is merged (8d13c0b3bd, 2026-11-07T08:08Z).
    Serial constraints cleared: #30884 PR 2 would share marketplace-install-local-plugin.ts and objectql/src/plugin.ts; it is not claimed (waits on maintainer consent), so it queues behind this PR. No open PR edits this surface (open PRs read at claim time: #31093, #31013, #31111, #31088, #29963 and mailboxes).

    Clause-②: no cites the contract text that negates the refusal this PR removes: ADR-0146 D3 as amended by #31027 and ruling 6691022506 ("the ensure door returns the App's existing pair (200) instead of 422"). Order: project file server-seat-2/orders/29594-pr3.md, delivered as the card thread's opening message. Refs #29594 (the PR carries Fixes).

  13. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Claim amendment · #29594 · 2026-11-07T12:06Z · amends claim 6706721940
    Branch: claude/project-thread-oj61dz (the card thread's harness-pinned push branch; platform exception to claude/issue-<n>-<slug>; replaces claude/issue-29594-app-custom-pair, which carries no commits)
    Everything else in claim 6706721940 stands.

  14. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2dM9PvPPB9BMVdhf2R
    os-dev-report

    {
      "issue": 29594,
      "status": "done",
      "branch": "claude/project-thread-oj61dz",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31134",
      "head": "016eb4599513eaa35897eb1a09a9aeafa759e6bb",
      "session": "session_017ihL2dM9PvPPB9BMVdhf2R",
      "premise_still_valid": true,
      "summary": "PR 3 of 3: appOf reads the installed manifests (type app = its own App; else the declared App, directly or through declared parents; several -> the one holding the namespace, else no_single_app; kernel and App-less packages keep their own answer), so the ensure door answers the App's one pair <app id>.custom for every member (201 once, 200 after; basePackageId = the App). Reads serve legacy per-package pairs beside the App's pair through pairsOf/isPairOf (Q12); a legacy member pair holding the App namespace gets 409 naming it, an outsider still 422 NAMESPACE_CONFLICT; the App pair's write closure reaches the App's members; census reads the declared base; uninstall refuses for the pair named for the package; lint's namespace advisory skips App members. packages/objectql/src/plugin.ts is NOT edited (no overlap with #31093).",
      "n1_rule": "Declared exception, no move in this PR: a legacy per-package pair (e.g. namespace crm_svc_custom) stays its own installed package, served beside the App's pair via pairsOf. When a move ceremony is built, its objects keep their crm_svc_custom_<short> names as a declared exception; the App's pair is NOT given a namespace set. Why: no live instance to move; renaming a data-holding table is refused; a namespace set would permanently widen what the App pair owns, a declared exception stays visible and bounded. N2 holds (a package declaring no App keeps its own pair; pinned).",
      "rehearsal": {
        "setup": "HotCRM dev:crm not reachable from this checkout -> in-repo fixture of the same shape, nothing stubbed: App app.objectstack.hotcrm (ns crm) + modules .service/.revenue/.marketing (type module, ns crm, each declaring the App ^6), engine.registerApp on SqlDriver :memory:, real HttpDispatcher.handlePackages and real appOf (packages/runtime/src/domains/meta-object-extension-item.test.ts).",
        "ensure_answers": "[[app.objectstack.hotcrm,201,app.objectstack.hotcrm.custom],[.service,200,same],[.revenue,200,same],[.marketing,200,same]]; one pair installed",
        "pair_id": "app.objectstack.hotcrm.custom (namespace crm_custom, dependency app.objectstack.hotcrm ^6)",
        "read_back": "object_extension sla_tier__c on module object crm_service_case saved+published into the App pair; getMetaItem, getMetaItemLayered and the registry read it back; extenders [app.objectstack.hotcrm.custom]; a record insert stores sla_tier__c 'gold'"
      },
      "ablations": [
        "422 kept outside the App (protocol-customization-package.ts same-App guard forced true): anchor x1->x0, blob 8f868ff7c6ba->1ad2eed1b3de, dist preflight marker in 2 files -> Tests 3 failed | 40 passed (incl. 'the namespace held by a package outside the App: 422 NAMESPACE_CONFLICT still, asked from a module'); restored blob==HEAD, dist --absent ok -> 3 files 87/87 green",
        "kernel 409 (kernelScope check disabled): blob 8f868ff7c6ba->2f0051e03486 -> Tests 5 failed | 59 passed (system/cloud ensure, kernel legacy pair, flow clone both scopes); restored blob==HEAD -> green",
        "App resolution (appOf single-App answer -> the package itself): blob 16a7bd55e08b->ee33f4027082 -> Tests 3 failed | 63 passed (module-first ensure, legacy-pair 409, rehearsal); restored blob==HEAD, dist --absent ok"
      ],
      "tests": "metadata-core 25 files/989; metadata-protocol 320 files/22524 (+26 skipped) + kernel-scopes pin 2/2; objectql 564 files/11422, registry 49/698 after the uninstall fix, uninstall file 11/11; runtime 554 files/8914 (+19 skipped), App-unit files 3/87; lint 197 files/8331 (re-run after the alias: one file raced a concurrent lint/dist rebuild, re-run alone 6/6); cli unit 396/5576; cloud-connection 935/935 (full re-run after the uninstall fix); service-automation 264/3431; service-datasource 72/1279; typecheck metadata-core, metadata-protocol, objectql, lint, runtime, qa/dogfood :: exit 0",
      "gates": "dispatch-gates --commands: 111 derived at 5ac6ed0f, all 111 :: exit 0 (first pass: 3 exit 3 for missing builds / the pinned teardown fixture commit, check:test-source-alias exit 1 -> fixed in 5ac6ed0f with an anchored metadata-core alias in lint's vitest config and a module-top import '@objectstack/metadata' in the rehearsal file; prerequisites built; those four plus every changeset/alias/test-input gate re-run, exit 0). dispatch-gates --ran: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN :: exit 0. pnpm lint :: exit 0. check-platform-checklist --changed-from origin/main :: exit 0. origin/main merged twice (latest 98583cd6 -> merge 016eb459, no overlapping file, same derived list). CI check-runs on #31134: pending at report time.",
      "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm declared (see open_questions 1)",
      "mcp_calls": "0",
      "api_writes": "0 direct REST writes. Relay writes as objectstack-fleet[bot]: pr_create #31134 (run 42379171667, 20001 bytes sent = stored, draft, no assignees/labels/reviewers) + this comment via post-stamped.mjs. git push x3 to the branch. Reads via gh api GET (issue, comments, PR read-back).",
      "open_questions": [
        {
          "question": "The claim says Clause-②: no, but three things move a public face: (a) a package that reaches two Apps and whose namespace picks neither got its own pair 201 before and now gets 409; (b) a legacy member pair holding the App namespace now answers 409 RESOURCE_CONFLICT where a member's ensure got 201/its own pair before; (c) @objectstack/metadata-core gains exports (pairsOf, AppOfRefusal, PairOfRefusal) and appOf's return widens to string | undefined. PR body and changeset carry the claim's line verbatim.",
          "options": ["A keep Clause-②: no (a and b have no live instance; c is the same widening PR 2 declared)", "B amend to Clause-②: yes (narrowing) for (a)/(b): BREAKING banner in the changeset, dogfood battery run"],
          "recommendation": "B, because a 201 that becomes a 409 is a narrowing by the fleet's own criterion even without a live instance; the seat writes the claim amendment and I add the banner on request"
        }
      ],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: b · reach: domain:ui · the six ui/packages/app-shell sites that derive a pair as customizationPackageId(base) and must read the ensure door's / server's answer for a module: src/views/metadata-admin/ResourceListPage.tsx:211, src/views/studio-design/AutomationsFlowRail.tsx:367, src/views/studio-design/BuilderLanding.tsx:163, src/views/studio-design/CustomizationMark.tsx:143, src/views/studio-design/customizationMarks.ts:58, src/views/studio-design/packages-io.ts:239 · carrier: seat · noted, not filed",
        "class: b · reach: named producer · packages/metadata-protocol/src/protocol-write-gates.ts pairOfAddedMember/findPairAddedMemberRow read only the App's pair, so a legacy member pair's added bundle members are not served (Q12 gap) · carrier: seat · noted, not filed",
        "class: c · Studio picker closure (lint resolvePackageClosure) is not widened to App members; no ceremony re-identifies a legacy pair as the App's pair (Q12 leaves it open) · noted, not filed",
        "class: c · Skills-docs follow-up owed: skills/objectstack-platform/SKILL.md 'Customizing an installed package' says one pair per base · governed path · noted, not filed",
        "class: c · the list-row customizable flag needs a spec sub-card (packages/spec out of scope) · noted, not filed"
      ]
    }
  15. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Claim amendment · #29594 · 2026-11-07T13:58Z · amends claim 6706721940 (as amended by 6706733388)
    Clause-②: yes (narrowing)
    Metadata-protocol: semantic
    File surface: adds packages/objectql/src/registry.ts (the uninstall refusal reads the pair named for the package), the remaining packages/metadata-protocol/src/protocol-*.ts pair readers, and packages/lint/package.json / packages/lint/vitest.config.ts / pnpm-lock.yaml (test wiring). Nothing in packages/spec/** or ui/**.

    Why the line changes (measured on draft PR #31134, head 016eb4599513eaa35897eb1a09a9aeafa759e6bb, report 6707849840):

    • yes: the public surface of @objectstack/metadata-core grows (export * from customization-pair-lookup.js now carries pairsOf, AppOfRefusal, PairOfRefusal). The 422 → 200 change for a member of the same App stays the cited-contract no it was (ADR-0146 D3 as amended by docs(adr): ADR-0146 D3 — one .custom pair per App, and one platform pair for the kernel suite #31027; ruling 6691022506), but the new exports are not covered by that text.
    • (narrowing): two answers that were accepted are now refused, both ruled by the amended ADR rather than chosen by the dev: a package reaching several Apps whose namespace picks none of them was 201 with its own pair and is now 409 (merge question 11); a module whose earlier per-package pair holds the App's pair namespace was 201/200 and is now 409 RESOURCE_CONFLICT naming that pair (merge question 12). In TypeScript, appOf now returns string | undefined and pairOf's refusal union widens, which breaks a caller that read the old return type.

    Consequences for this PR (the dev writes them from the diff): PR body and changeset carry Clause-②: yes (narrowing) at line start, a BREAKING banner and the migration lines (the two 409 answers, the appOf return type), and exactly one ADR-0087 disposition marker; the full dogfood battery runs once on the final head (os-dev rule for a (narrowing) arm). The seat owes a CONTRACT_REVIEW_TIER review of the whole non-prose diff before ACCEPT. The card stays in the server lane: ruling 6691022506 assigns its execution here, and nothing in the diff touches packages/spec/**.
    Everything else in claim 6706721940 stands.

  16. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2dM9PvPPB9BMVdhf2R
    os-dev-report

    Supplement to 6707849840, on claim amendment 6707871792. Fields not listed stand as reported there.

    {
      "issue": 29594,
      "status": "done",
      "branch": "claude/project-thread-oj61dz",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31134",
      "head": "775c97b55e9e5c06f3c29fc6c0c7d4e96a790637",
      "session": "session_017ihL2dM9PvPPB9BMVdhf2R",
      "premise_still_valid": true,
      "summary": "Applied claim amendment 6707871792: the changeset and PR body carry `Clause-②: yes (narrowing)` at line start (Metadata-protocol: semantic unchanged), a BREAKING banner with one remedy line each for the two new 409 answers (merge questions 11 and 12) and for appOf's string | undefined return, and exactly one ADR-0087 disposition in the changeset: not-required (no-migration-prescription). PR title now carries `!`. No code changed since 5ac6ed0f; the only new commit is the changeset (775c97b5).",
      "gates": "check-adr-0087-registration --base origin/main :: exit 0 (exemption noticed); --self-test :: exit 0; check-changeset-no-major :: exit 0; check-empty-changeset :: exit 0; pnpm check:changeset-fixed :: exit 0; pnpm check:changeset-gate-self-tests :: exit 0. The 111-command derivation is unchanged (same file set); its run at 5ac6ed0f stands for the code.",
      "dogfood_battery": "775c97b55e9e5c06f3c29fc6c0c7d4e96a790637 · os-verify-lock: VERDICT command-exit 0 · held the lock 1971s · Test Files 435 passed | 3 skipped (438) · Tests 3609 passed | 41 skipped (3650) · no failure",
      "mcp_calls": "0",
      "api_writes": "0 direct REST writes. Relay as objectstack-fleet[bot]: issue_patch #31134 title+body (run 42382371991, 23006 bytes sent = stored) + this comment via post-stamped.mjs. git push 1 (775c97b5).",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "The relay refuses a PR-body edit without a `<!-- signed: … -->` trailer as its last line, so #31134's body now ends with that trailer after the Generated-with line; the order's 'no HTML comment other than the attribution line' could not hold for an edited body · noted, not filed",
        "origin/main moved to af3451b08bc3 after the battery; not merged, so the battery's head stays the PR head — the seat's pre-landing merge covers it"
      ]
    }
  17. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Claim: PM loop round 1 · #29594 PR 3 of 3 (appOf resolves App membership; the ensure door answers the App pair) · 2026-11-07T14:38Z · consolidates this session's claim 6706721940 and amendments 6706733388 and 6707871792 into one Claim: (the closing-target guard reads only a Claim: comment's Branch: line); resumes release 6704508301
    Session: session_017ihL2fBJ6xsLWJsnNJGMMx
    Account: marchtian (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/project-thread-oj61dz (the card thread's harness-pinned push branch; head of PR #31134)
    Worktree: objectstack-issue-29594
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/metadata-core/src/customization-pair-lookup.ts; packages/metadata-protocol/src/protocol-customization-package.ts and the pair call sites PR 2 threaded; packages/runtime/src/domains/packages.ts; packages/lint/src/validate-customization-namespace.ts; packages/cli/src/utils/override-declarations.ts; packages/cloud-connection/src/marketplace-install-local-plugin.ts; packages/objectql/src/plugin.ts; packages/objectql/src/registry.ts; packages/lint/package.json, packages/lint/vitest.config.ts, pnpm-lock.yaml (test wiring); the dogfood helper customization-pair.ts; their tests; docs where a sentence becomes false; one changeset. ⛔ packages/spec/** (the customizable list-row flag needs a spec sub-card first), ⛔ ui/** (the six ui/packages/app-shell call sites are a domain:ui follow-up). Stop on breach; explain in the report.
    Container & model: L, mode:thread, model: TIER_DEFAULT (dispatch-gates --tier: no path-derived mandate), effort: default (the thread runs at the project's default effort); the seat runs a CONTRACT_REVIEW_TIER review on the PR because served answers change.
    Clause-②: yes (narrowing)
    Metadata-protocol: semantic
    Responsibility: n/a — not a defect card (ruling-implementation card; ruling 6691022506 gives the server lane the execution)
    Thread-read: 6704508301
    Premise: holds. On origin/main aa3e21cc, customization-pair-lookup.ts still has void installed; (appOf is the identity), and the gate named in release 6704508301, ADR-0146 amendment PR #31027, is merged (8d13c0b3bd, 2026-11-07T08:08Z).
    Serial constraints cleared: #30884 PR 2 would share marketplace-install-local-plugin.ts and objectql/src/plugin.ts; it is not claimed (waits on maintainer consent), so it queues behind this PR. No open PR edits this surface (open PRs read at claim time: #31093, #31013, #31111, #31088, #29963 and mailboxes).

    Clause-②: yes (narrowing) for the reasons in amendment 6707871792 (new @objectstack/metadata-core exports; two ensure-door answers move from accepted to 409, merge questions 11 and 12; appOf returns string | undefined). The 422 → 200 change for a member of the same App is covered by ADR-0146 D3 as amended by #31027 and ruling 6691022506. Order: project file server-seat-2/orders/29594-pr3.md. Refs #29594 (the PR carries Fixes).

  18. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2dM9PvPPB9BMVdhf2R
    os-dev-report

    Patch round for the contract review on #31134 (should-fix 1 and 2). Fields not listed stand as reported in 6707849840 / 6708291325.

    {
      "issue": 29594,
      "status": "done",
      "branch": "claude/project-thread-oj61dz",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31134",
      "head": "fb2fd21eb9d05368b49eb24320d1586b40dac847",
      "session": "session_017ihL2dM9PvPPB9BMVdhf2R",
      "premise_still_valid": true,
      "summary": "Should-fix 1: isPairRowRemoval asks isPairOf, so a legacy <module>.custom removes its own declared override past the sealed-removal refusal again; pairOfAddedMember became pairsOfAddedMember (private protocol method + host key) and findPairAddedMemberRow reads every pair in pairsOf order; the customizations read in runtime packages.ts lists extensions from every pair and readBaseContributions excludes every pair; the sealed declare-or-rename sentences and the undeclared-same-name refusal treat a legacy pair as a pair. No reader is App-pair-only, so the BREAKING list is unchanged; the changeset's Q12 bullet says so. Should-fix 2: packages/runtime/src/domains/meta-legacy-pair-served.test.ts, three cases, the legacy override written straight to the store as an earlier version left it.",
      "tests": "new file 3/3; metadata-protocol 322 files / 22527 (+26 skipped); objectql 564 / 11425; runtime 555 / 8923 (+19 skipped); typecheck metadata-core, metadata-protocol, objectql, runtime, lint :: exit 0",
      "ablations": [
        "reads: pairsOf legacy arm disabled (metadata-core), blob 16a7bd55e08b->44efe1c0117c, dist marker present -> Tests 3 failed (3): by name / list / layered answered the shipped dashboard, the doc read answered the shipped doc, the save was refused declared_outside_pair; restored blob==HEAD, rebuilt, dist --absent ok -> 3/3",
        "removal: isPairRowRemoval back to the single pairOf, blob cca50ea0d34b->dad267a4800c, dist marker present -> Tests 1 failed | 2 passed: the legacy pair's doc removal answered 403 NOT_OVERRIDABLE 'sealed against removal'; restored blob==HEAD, rebuilt, dist --absent ok -> 3/3. (The dashboard removal stays green under this ablation: it passes through the dashboard's own overlay channel, so the doc case is the pin.)"
      ],
      "gates": "origin/main merged at fb2fd21e. dispatch-gates --commands: 111 derived, all 111 :: exit 0; --ran: 111 run, 0 NOT-MEASURED, 0 UNRUN :: exit 0; pnpm lint :: exit 0.",
      "dogfood_battery": "fb2fd21eb9d05368b49eb24320d1586b40dac847 · os-verify-lock: VERDICT command-exit 0 · held the lock 1684s · Test Files 435 passed | 3 skipped (438) · Tests 3609 passed | 41 skipped (3650) · no failure",
      "mcp_calls": "0",
      "api_writes": "0 direct REST writes. Relay as objectstack-fleet[bot]: issue_patch #31134 body + this comment via post-stamped.mjs. git push 1 (fb2fd21e).",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "nits not taken (optional): a legacy pair of a module the newer App version no longer delivers resolves to itself and answers 422, not Q12's 409; the lint advisory stays silent for a no-single-App package (it judges namespace length, and such a package has no pair at all); the lint -> metadata-core workspace dependency is named in the PR body and changeset only",
        "origin/main moved 4 commits after the fb2fd21e merge; not merged again so the battery's head stays the PR head"
      ]
    }
  19. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2dM9PvPPB9BMVdhf2R
    os-dev-report

    Incremental-review round on #31134 (review 6709902226, should-fix A and B). Fields not listed stand as reported in 6709801932.

    {
      "issue": 29594,
      "status": "done",
      "branch": "claude/project-thread-oj61dz",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/31134",
      "head": "481d40492ddfc7af48d6362f844571efc6f44d32",
      "session": "session_017ihL2dM9PvPPB9BMVdhf2R",
      "premise_still_valid": true,
      "summary": "A: the changeset bumps '@objectstack/runtime': minor beside the other four (same feat), and its title scope names runtime; the PR title does too. B: meta-legacy-pair-served.test.ts runs every case on both topologies (host-config kernel, environment kernel 'env_hotcrm'), so the environment kernel's removal goes through refusePackagedBaseRemoval and the `!pairRowRemoval` pass at protocol-diff-delete-load.ts:498. Nit taken: the packages.ts pair loop is no longer an if…for on one line. Nits not taken: extra pins for a legacy pair's added bundle member and its extensions in the customizations census.",
      "ablations": [
        "protocol-diff-delete-load.ts:498 `!pairRowRemoval` removed (`'ablation-pair-row-guard'.length > 0` in its place): blob 9df68e01d38f->bee509989cbd, dist marker present in 2 files -> Tests 1 failed | 5 passed (6): the environment kernel's doc removal answered refusePackagedBaseRemoval's 'Metadata item 'doc/crm_service_guide' is provided by a managed package and is sealed against removal' (the host-config kernel's case stays green: it never reaches that block); restored blob==HEAD, git diff HEAD empty, rebuilt, dist --absent ok -> Tests 6 passed (6)",
        "isPairRowRemoval back to the single pairOf (the previous round's reading, 6709801932): the host-config doc removal red via the repository's intent gate; unchanged code since"
      ],
      "tests": "meta-legacy-pair-served 6/6; App-unit + legacy-pair files 3 files / 72; runtime src/domains/packages* 58 files / 1008; runtime typecheck :: exit 0",
      "gates": "origin/main merged at 481d4049. dispatch-gates --commands: 111 derived, all 111 :: exit 0; --ran: 111 run, 0 NOT-MEASURED, 0 UNRUN :: exit 0.",
      "dogfood_battery": "481d40492ddfc7af48d6362f844571efc6f44d32 · os-verify-lock: VERDICT command-exit 0 · held the lock 2423s · Test Files 435 passed | 3 skipped (438) · Tests 3609 passed | 41 skipped (3650) · no failure",
      "mcp_calls": "0",
      "api_writes": "0 direct REST writes. Relay as objectstack-fleet[bot]: issue_patch #31134 title+body + this comment via post-stamped.mjs. git push 1 (481d4049).",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": []
    }
  20. objectstack-fleet commented on Nov 7, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · marchtian · session_017ihL2fBJ6xsLWJsnNJGMMx

    Landed — PR 3 · 2026-11-07T17:46Z

    PR #31134 merged 2026-11-07T17:45Z as 486b0a9c94. An App and the modules that declare it now share one customization pair, merge questions 11 and 12 answer 409, and a legacy per-package pair keeps being served and can remove its own override. The review record is the full Fable review at 775c97b55e, the Fable delta at fb2fd21eb9, and the in-seat ACCEPT at 481d40492d. Fixes #29594 closed the card. The seat removes pm:dispatched, and this seat's slot for the card is free.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions