feat(runtime): GET /packages 与 GET /packages/:id 每行携带服务端自己的可写判定 writable(isWritablePackage),Studio 不再用 scope 启发式反推 #14375
Copy link
Copy link
Closed
Labels
Description
Activity
认领
- 会话:
session_01UHvF5hyiZjnCyExFnfQB8m(epic One artifact, N packages: let a release bundle carry co-owning packages so a product can be split into modules without renaming objects #14122 的 PM 席) - 分支:
claude/issue-14375-packages-writable-field - 派发:os-dev,单卡,工作树
objectstack-14375 - 文件面:
packages/runtime/src/domains/packages.ts(GET /packages、GET /packages/:id两处响应组装)+ 相邻测试 + changeset。⛔ 不动packages/metadata-protocol/src/package-writability.ts。 - 本卡的真实启动验收不依赖 feat(objectql): admit same-artifact co-owners at the install gate, and refuse two of them defining one object name (ADR-0130 D1+D3) #14354:module 子包用不同命名空间即可(ADR-0130 D5+D7: register an artifact's N packages in topological order, reusing resolvePluginOrder #14240 的装载路径已在
main),writable的判定与命名空间无关;共享命名空间的验证属 objectui#7177。
Generated by Claude Code
- 会话:
实施中:文件面扩到 metadata-protocol 的生产者——原因
派发的 dev agent 因账户会话额度触顶(05:40 UTC 重置)起步即失败,本卡由 PM 会话(
session_01UHvF5hyiZjnCyExFnfQB8m)直接实现,工作树objectstack-14375,分支不变。读实现时发现卡面只写了 runtime 的
handlePackagesRequest是不够的:GET /api/v1/packages有两条注册路(packages/client/src/index.ts:1736-1744早有记录),Studio 走的 REST 门不读 registry:门 行的来源 修法 runtime dispatcher handlePackagesRequestregistry.getAllPackages()/getPackage(id)卡面原计划: withWritableVerdict(qlService, row),list + detailREST packages/rest/src/package-routes.ts:664/805options.protocol.getMetaItems({ type: 'package' })的 item,再把PackageService.list()的持久行摊在其上REST 不依赖 metadata-protocol运行时(刻意,package-routes.ts:41),不能在 REST 里算 → 在生产者ObjectStackProtocolImplementation.getMetaItems的package分支给 item 加writable: this.isWritablePackage(id)(protocol.ts:12657已有的同一谓词封装)两处都是 spread 副本,registry 记录不动、不落库。REST 侧只加一条 pin:持久行摊在 registry item 上不能抹掉
writable(摊的顺序是 REST 唯一可能丢字段的地方);持久-only 行(本进程未注册)不带裁定——registry item 是唯一载体,写进 changeset。REST detail 门 DB-first 的行同样不带(改它要动 #11376 钉死的读拒绝顺序,不在本卡)。changeset:
@objectstack/runtime: patch+@objectstack/metadata-protocol: patch。
Generated by Claude Code
真实启动取证结果(PR #14430 正文已同步)
- app-todo(
os dev,全仓 dist 构建自bd0ee2fb):GET /api/v1/packages200、23 行全带布尔writable;com.example.todo(启动的代码包,scope:'project')false;22 个系统插件 false;POST …/duplicate出来的com.acme.dupbase无scope键、writable:true;detail 门同样 true,键集只多writable。卡面四态里的 1、3、4 在真实进程里各有一行;第 2 态(无 scope 的 module 子包)要等 feat(cli+spec): 从 N 个软件包的作者态编出一个packages[]发布物 ——os build认识项目级composeStacks('preserve'),装配后的包体有自己的声明(#14242 取 B) #14439 能编出packages[]发布物,其夹具负责追加两行writable:false断言。 - showcase 的三条包读门全 500(循环引用)——在
origin/main6aea1f5 逐字复现,dist 不含本卡任何改动 → 既有缺陷,另立GET /api/v1/packages//api/v1/packages/:id//api/v1/meta/package全部 500「Converting circular structure to JSON」—— 栈里带插件实例(showcase)时,包记录存的是含实例的原始摊平 bundle #14442(栈带插件实例时包记录存了含实例的原始摊平 bundle)。
PR 已 ready,随 PASS 自审评论 arm auto-merge。
Generated by Claude Code
- app-todo(
- added a commit that references this issue
on Sep 7, 2026 - added a commit that references this issue
on Sep 9, 2026
Part of #14122 · ADR-0130 Consequences 表第 6 行(Studio 按包分组)的服务端半边
Blocks: objectstack-ai/objectui#7177
业务需求
多包物(ADR-0130,#14240 装载路径 + #14354 安装闸已落地)把一个产品拆成
type: app+ 若干type: module子包。Studio 的包选择器要正确显示每个子包能不能在里面写元数据——这是 Studio 决定给作者哪一套控件(可写 / 🔒 只读 + "duplicate into a writable base")的唯一依据。今天这个判定在客户端用scope反推,而服务端的权威规则根本不看scope的这一维,两边在多包物上必然分叉。实测:两条规则不是同一条
服务端权威——
isWritablePackage(engine, id)(packages/metadata-protocol/src/package-writability.ts:73,ADR-0070 D2;requireWritablePackage与SysMetadataRepository.assertAllowed都引用它,#8146 维护者裁定"一个可写答案、徽标说真话"的落点):客户端启发式——objectui
packages/app-shell/src/views/studio-design/packages-io.ts:38-42:分叉点在 ①:服务端判"代码包"的信号是
engine.manifests(ObjectQL.registerApp→engine.ts:4784this.manifests.set(id, manifest);#14240 的装载路径对物内每个子包都调registerApp,所以每个子包都进这张表),客户端没有这张表,只能拿scope猜。链条(每环已对
origin/main核实):ManifestSchema.scope默认'project'只在 parse 时施加spec/src/kernel/manifest.zod.ts:263registerApp(D7 逐位相同)objectql/src/artifact-packages.ts模块头installPackage原样存InstalledPackage.manifestobjectql/src/registry.tsGET /packages直接返回registry.getAllPackages(),只有?status=/?type=过滤,没有可写判定runtime/src/domains/packages.ts:269-277scope键的 module 子包(常态——作者依赖默认值)→ 客户端scope=''→writable: true;服务端 ① → 只读结果:Studio 给一个服务端必拒的包画上"可写"徽标,作者写到
WRITABLE_PACKAGE_REQUIRED422 才知道。这正是 #8146 裁定要消灭的形状("两面回答同一个问题不一致,其中一面在说谎"),方向相反而已。⛔ 客户端不能自己修。 直觉修法"缺省 scope 视为 project → 只读"会把 Studio 自建的 DB base 一并翻成只读:它们同样缺省
scope(服务端 ③ 判可写),客户端靠scope === ''才把它们显示为可写。区分二者的唯一信号engine.manifests只在服务端。所以修法落在平台。方案
GET /packages与GET /packages/:id的每个包记录附加一个字段:requireWritablePackage同源,packages.ts:184已 import)。writable的副本,⛔ 不改写 registry 里的InstalledPackage对象,⛔ 不改isWritablePackage自身,⛔ 不动READ_ONLY_PACKAGE_SCOPES。engine参数就是现有路由已解析的qlService(与requireWritablePackage的传法一致)。Pins(runtime 域测试,四态一个不少)
registerApp注册、scope: 'project'的代码包 →writable: falseregisterApp注册、省略scope的代码包(模拟多包物 module 子包)→writable: false← 本卡的正题scope: 'system'/'cloud'→writable: falseinstallPackage(不经registerApp)、省略scope的 DB base →writable: true← 保护 Studio 自建 basewritable之外,每行其余字节与今日响应逐位相同(不改manifest、status、installedAt等既有键)manifests.has改成恒 false,pin 2 必须红(证明writable真的读了服务端的表,不是又一份scope启发式)边界
isWritablePackage的语义;有争议走 ADR-0070。@objectstack/runtime: patch(响应加法)。Clause-② 预期 NO:接受/拒绝面不动,只多一个只读字段;若 route ledger / 响应形状快照有门,按门的要求更新。验收
check:dispatcher-error-vocabulary等 dispatch-gates 重推导后全绿。main起一个含app+ 缺省scope的module两包物,curl /api/v1/packages读回两行的writable均为false;同一服务上经 Studio 复制出的 DB base 读回true。截 curl 输出为证。