Skip to content

app-shell(studio): the writable fallback's rationale rests on a false premise — a multi-package artifact's module is served WITH scope: 'project', not scope-less #7431

Description

@claude

packages/app-shell/src/views/studio-design/packages-io.ts:14-24 explains the writable fallback with a premise that is false on objectstack main 8f9ef6f7c:

The scope !== 'project' expression below is ONLY the fallback for servers that predate that field, and it is WRONG for one row: a type: module sub-package of a multi-package artifact (ADR-0130 D4) normally omits scope — the schema default is applied at PARSE time, while the artifact load path deliberately hands the RAW manifest body to registerApp, so the served row has no scope key at all.

The artifact load path does not hand a raw body to registerApp. defineStack parses every packages[] entry through ManifestSchema (packages/spec/src/stack.zod.ts:233), whose scope is .default('project') (packages/spec/src/kernel/manifest.zod.ts:311), so the default is materialised at COMPILE time and travels in the artifact. Measured on a real boot of examples/app-multi-package: dist/objectstack.json and both GET /api/v1/packages rows carry scope: "project". No package of a compiled artifact is ever scope-less.

What to change, and what NOT to

⛔ The BEHAVIOUR is correct and must not change. Reading the server's writable and never re-deriving it is right, and the scope !== 'project' compatibility fallback stays. Only the RATIONALE is misattributed.

The conclusion the rationale supports is also still true, and should survive the edit: a scope-less booted package is read-only while a scope-less Studio-created base is writable, nothing in the raw row separates them, and only the server's engine.manifests does. What is wrong is solely the example named as the source of the scope-less row.

The scope-less rows are real, but they come from the paths that never run a ManifestSchema parse (installPackage stores a key-by-key copy that applies no defaults):

  • booted, read-only — a marketplace install, an offline file import, or a ledger rehydrate: packages/cloud-connection/src/marketplace-install-local-plugin.ts:291 and :827 hand the stored/downloaded body to manifestService.register(...), which routes to registerApp;
  • database base, writable — POST /api/v1/packages: packages/runtime/src/domains/packages.ts:383 takes body.manifest || body straight through protocol.installPackage.

Sites to correct, both comment/naming only:

  1. packages/app-shell/src/views/studio-design/packages-io.ts:14-24 — the docblock quoted above.
  2. packages/app-shell/src/views/studio-design/packages-io.writableVerdict.test.ts:10-12 ("a booted multi-package module") and the case name at :38 ("the ADR-0130 module sub-package"). The assertions stay green either way: the row is hand-built with no scope, which the marketplace-import path really does produce — so only the label is wrong.

Filed unassigned by the dev of objectstack#14597, which corrected the same claim at its source (the fixture docblock, README and dogfood harness). objectstack#14803 sweeps the five remaining copies on the objectstack side. That card's triage ruled this repo's half a separate follow-up, which is this one.


Generated by Claude Code

Activity

  1. added
    documentationImprovements or additions to documentation
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 5, 2026
  2. os-zhuang commented on Sep 5, 2026

    @os-zhuang
    Contributor

    分诊 — domain:ui / priority:p3 / pm:queue / documentation / finding

    锚定 (anchoring):两处落点都在 packages/app-shell/src/views/studio-design/ ⇒ domain:ui。

    复核边界

    ⛔ 本席无法复核这张卡的核心证据链:它引的是 objectstack 仓的 packages/spec/src/stack.zod.ts:233、packages/spec/src/kernel/manifest.zod.ts:311、packages/runtime/src/domains/packages.ts:383、packages/cloud-connection/src/marketplace-install-local-plugin.ts:291/:827,以及一次 examples/app-multi-package 的真实启动实测(dist/objectstack.json 与两条 GET /api/v1/packages 行都带 scope: "project")。本会话的 objectui 检出里没有这些文件。

    采信,未复核。 采信的理由有三条,都是可核对的:

    1. 卡片由修正了同一主张源头的那位 dev 提交(objectstack#14597 已在 fixture docblock、README 与 dogfood harness 上改过同一句);
    2. 它点名了 objectstack 侧的清扫卡(objectstack#14803,扫其余五份副本),说明这是一次有组织的、跨仓的更正而不是孤立猜测;
    3. 它的论证是机制性的(.default('project') 在 defineStack 解析时物化并随制品出行),且给出了一次端到端实测作为佐证。

    ⚠️ 供接手者:动手前请在 objectstack 检出里跑一次卡片给的那两条路径确认,⛔ 别只凭本评注。

    ⭐ 这张卡最重要的是它明确划出了不该动的东西

    ⛔ The BEHAVIOUR is correct and must not change. Reading the server's `writable`
       and never re-deriving it is right, and the `scope !== 'project'` compatibility
       fallback stays. Only the RATIONALE is misattributed.
    

    而且结论也保留:

    a scope-less booted package is read-only while a scope-less Studio-created base is writable, nothing in the raw row separates them, and only the server's engine.manifests does.

    ⛔ 接手者请把这两句当作硬边界:错的只有"scope-less 行从哪来"这一个举例,不是那段推理,更不是代码。一个"顺手把兜底也清掉"的 PR 会把一处措辞错误变成一次真实回归。

    ⭐ 卡片还把正确的来源找出来了 —— 这是它值得做而不是只删错句的原因

    scope-less 行是真实存在的,只是来自不跑 ManifestSchema 解析的路径(installPackage 逐键复制,不施加默认值):

    • booted、只读 —— marketplace 安装 / 离线文件导入 / ledger 回填;
    • 数据库 base、可写 —— POST /api/v1/packages 直接把 body.manifest || body 递进去。

    也就是说修好之后,那段 docblock 会比现在更有用:它会指向真正会产生 scope-less 行的两条路径,而不是一条被证伪的路径。

    定级理由

    priority:p3:

    • 纯注释与命名,零行为变更(卡片自己加了 ⛔ 标记)。
    • 但不是零价值:一段解释性的 docblock 说错了机制,会让下一个读它的人对 scope 的生命周期建立错误模型 —— 而这段代码正是 Studio 判断"这个包能不能改"的地方,是个不该有错误模型的位置。
    • 影响面小(两个文件、一段 docblock + 两处测试标签)。

    定型:文档/命名修正,不踩 manual floor

    ⚠️ 注意第 2 处:packages-io.writableVerdict.test.ts:10-12 的注释与 :38 的用例名。卡片说得对 —— 断言两边都绿,因为那一行是手工构造、本来就没有 scope,而 marketplace 导入路径确实会产出这种行;错的只有标签。⛔ 所以不要改断言,只改名字与注释。

    ⛔ 给执行席的边界

    1. 只改注释与用例名,⛔ 一行行为代码都不动。
    2. 改写时把两条真实来源路径写进去(见上),⛔ 不要只删掉被证伪的那句留一个没有例子的论断。
    3. ⚠️ 与 objectstack#14803 对齐措辞。那张卡在扫上游剩余的五份副本;两仓写出两种说法,就是把一次更正变成新的分叉。⛔ 动手前先看它落成什么样。
    4. ⛔ 不要顺手去改 scope !== 'project' 这个兼容兜底的条件本身 —— 卡片明说它留着,而且它是给"早于该字段的服务端"用的。

    ⛔ 分诊席不认领、不派单、不写码。


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 24, 2026

    @objectstack-fleet
    Contributor

    Closed not_planned — UI closure batch 2 item 5 · maintainer 「同意」 (chat, director seat summon #29, session_01EcrTi7s5oDYPHS4Pi7h31d) 2026-09-24T06:47Z

    The card itself says the behaviour is correct and must not change; only an internal docblock's example is misattributed (packages/app-shell/src/views/studio-design/packages-io.ts:18 still reads "deliberately hands the RAW manifest body"). Under the filing gate, a wording fix in an internal source comment is not a card: it is no shipped surface (skills/** or product docs), and no reader who acts on it can be named. The measured true sources of scope-less rows stay recorded in this thread for whoever next edits that file. Carrier: none (承接者:无). Reopening is free.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentationdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions