Skip to content

bug(example-showcase): 启动日志暴露的 4 个 showcase 自身问题 —— 夜间 job 永不运行、权限集引用不存在的 capability、退役的 retry 字段、非法 cover 种子值 #4774

Description

@os-zhuang

背景

排查 pnpm dev 控制台刷屏(#4765)时,把 showcase 冷启的 24 条启动告警逐条查证了一遍。其中平台侧的问题已分别开在 #4769 / #4770 / #4771 / #4772 / #4773。剩下这 4 条是 showcase 自己的问题,都在 examples/app-showcase/,可以一个 PR 收掉。

1. sweepProjectHealth handler 根本不存在 —— 夜间 job 永不运行

WARN [AppPlugin] job handler not found in bundle.functions — skipping
     {"appId":"com.example.showcase","job":"showcase_health_sweep","handler":"sweepProjectHealth"}

src/automation/jobs/index.ts:11 声明了 handler: 'sweepProjectHealth',注释还写着 "Handler is registered in defineStack({ functions })"。但全仓搜 sweepProjectHealth 只有这一处引用 —— 函数从来没被定义,也没进 functions。

所以这个 "Nightly Project Health Sweep" 从来没跑过。要么补上实现,要么删掉这个 job(showcase 的定位是"每种能力至少出现一次",倾向补实现)。

2. showcase.export_data capability 没有 owning package,永不 materialize

WARN [security] capability has no owning package — not materialized {"name":"showcase.export_data"}

×3(启动过程中重复 3 次)。

  • 声明处:src/security/capabilities.ts:32
  • 引用处:src/security/permission-sets.ts:190 — OpsPermissionSet.systemPermissions: ['setup.access', 'showcase.export_data']

结果是 OpsPermissionSet 引用了一个不会存在的权限。需要确认 capability 要怎样才算有 owning package(大概是要挂到 app/package 声明上),然后补齐 —— 或者如果这是平台侧的归属推断漏了 app 内声明的 capability,那就要拆一个平台 issue 出去。这一点我没查到底。

3. retryDelayMs → backoffMs:用的是会在 protocol 18 失效的写法

defineStack: flows[24].nodes[1].config.retry.backoffMs: 'retryDelayMs' → 'backoffMs'
  (converted at load; conversion 'retry-policy-converged', retires in protocol 18).
  Update the source to the canonical shape — the conversion stops running then.

两处:src/automation/flows/index.ts:984、src/automation/flows/index.ts:1176

retry: { maxRetries: 3, retryDelayMs: 1000, backoffMultiplier: 2, maxRetryDelayMs: 10000 }
retry: { maxRetries: 2, retryDelayMs: 500, backoffMultiplier: 2 }

日志已经把该做的事写在脸上了:改成 backoffMs。showcase 是给人抄的样板,留着退役写法尤其不合适。顺带确认 maxRetryDelayMs 是不是也在同一次 retry-policy-converged 收敛里改了名(#4661 / C8)。

4. cover 种子值不是 opaque sys_file id

[value-shape] cover has an invalid image value: Expected an opaque sys_file id
  — accepted for now (ADR-0104 warn-first; run `os migrate files-to-references --apply` ...)

src/data/seed/index.ts 的 10 条 showcase_task 种子用 placeholderCover(n, color) 生成 cover,不是 opaque sys_file id。

⚠️ 注意与 #4769 的关系:这份数据是 #4769 的诱因 —— 它让"第二次 pnpm dev 起 10 条 ERROR"这个平台 bug 显形。把种子数据改干净会让这个部署不再触发它,但不修复 #4769 本身(部署在同一次启动里证明了一个它随即违反的契约)。

所以顺序上建议 #4769 先定方案:如果那边决定"让 seed 走同一套契约",这里的种子值就必须改成真正的 sys_file 引用(需要先建 file 记录);如果决定"推迟 attestation",这里改不改就只是 showcase 数据质量问题。别在 #4769 定案前先改这条,否则可能白做或做反。

复现

rm -rf examples/app-showcase/.objectstack && pnpm dev

1/2 在启动摘要的 ⚠ Boot diagnostics 段落;3/4 在 Loading objectstack.config.ts... 之后的行内输出。

Activity

  1. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    分诊:入队 pm:queue,但本轮不派发。

    Blocked-by: #4769

    理由:本 issue 第 4 条(cover 种子值)的正确改法取决于 #4769 的裁定 —— issue 作者自己已经写明了这个顺序依赖。#4769 已在第 3 轮派发,PM 裁定取「推迟 attestation 到首启 seed 之后」,也就是说 seed 侧不会被要求改成真正的 sys_file 引用,这里的种子值问题降级为纯 showcase 数据质量问题。等 #4769 的 PR 合并后本 issue 解锁,届时 4 条可以一个 PR 收掉。

    另外第 2 条(showcase.export_data 没有 owning package)带一个未查到底的岔路:这到底是 app 侧漏了声明,还是平台侧的归属推断看不见 app 内声明的 capability? 接手的 dev 请先判定这一点 —— 如果是后者,那是平台缺陷,要拆一个独立 issue 出去,不要在 showcase 里绕过去。「app 引用了一个不会存在的权限」而启动只给一条 warn,本身也值得看一眼是否该更响亮(#4632 规则)。


    Generated by Claude Code

  2. os-zhuang commented on Aug 3, 2026

    @os-zhuang
    ContributorAuthor

    Blocked-by: #4769 已解除 —— PR #4794 已合并。本条可派发。

    第 4 条(cover 种子值)的处理方式现在确定了:降级为纯 showcase 数据质量问题。 #4769 取的是「推迟 attestation 到首启 seed 之后」,没有采用「让 seed 走同一套契约」,所以这里的种子值不需要改成真正的 sys_file 引用(那会要求先建 file 记录,成本高得多)。把 placeholderCover(...) 改干净是好事(showcase 是给人抄的样板),但它不再是任何平台缺陷的前提。

    顺带一提 #4794 落地后这条的现场会变:首启不再自证成功,所以第二次启动也不会拒掉这批行 —— 也就是说改不改种子值,那 10 条 ERROR 都不会再出现了。留着不合形状的 cover 只剩一个后果:每次启动一条 warn-first 提示。仍建议改,但优先级下调。

    其余三条不受影响,原样处理:

    1. sweepProjectHealth handler 不存在 —— 倾向补实现(showcase 定位是「每种能力至少出现一次」);
    2. showcase.export_data 没有 owning package —— 这条仍需先判定归属:是 app 侧漏了声明,还是平台侧的归属推断看不见 app 内声明的 capability?若是后者,拆平台 issue 出去,不要在 showcase 里绕过。另外「app 引用了一个不会存在的权限」而启动只给一条 warn,值得一并看是否该更响亮([convention] best-effort 降级导致"看起来正常、实则不持久"时不应记 warn——把 #4460 的点状修复定成规则 #4632 规则);
    3. retryDelayMs → backoffMs —— 顺带确认 maxRetryDelayMs 是否同在 retry-policy-converged 收敛里改名(spec 双源清账 C8:RetryPolicy / RetryPolicySchema(./automation ≠ ./system)—— 2 条 #4661 / C8)。

    Generated by Claude Code

  3. claude commented on Aug 3, 2026

    @claude
    Contributor

    🔒 认领:PM 循环第 1 轮扩批(维护者要求加速,batch 扩至 5)
    会话:session_01NrmBxj8rK2uGCnh9aipjwX(账号 xuyushun441-sys)
    分支:claude/issue-4774-showcase-inert-wirings
    Worktree:objectstack-issue-4774
    域:domain:services
    文件面:examples/app-showcase/src/**(automation/jobs、security、seed、flows retry 字段)

    ⛔ 硬约束:packages/spec/** 及生成物零改动;metadata-protocol/src/protocol.ts 零改动(在飞 #4721 占用)。
    全局在飞不相交已核:#4721、#4636、#4889(rule-validator)、#4821(dataset-executor)、#4884(driver-sql)均与本文件面不交。


    Generated by Claude Code

  4. claude commented on Aug 3, 2026

    @claude
    Contributor

    ✅ 验收通过 —— PR #4975(draft,等 CI 绿后入队);一单关三号(#4774 / #4888 / #4891)

    对照实际 diff 复核(10 文件,+794/-23,全部 examples/app-showcase/** + changeset;平台包零触碰,⛔ 三禁区零触碰,逐一核实):

    1. 四条全修,且每条先在 origin/main 复现、修后消失(STALE-PREMISE 纪律执行到位);
    2. sweepProjectHealth 补了真实现(预算燃烧 vs 交付进度重算 project.health,经 onEnable 捕获的引擎句柄 —— job handler 无 data engine,这是唯一正规接缝);
    3. 第 2 条的岔路按裁定走了平台缺陷路线:A refused capability declaration still suppresses the back-compat derivation, so the capability exists nowhere — and an app-declared capability can never get registry provenance #4967 立案(三段:REFUSED 声明反而压制 back-compat 派生 / capabilities 缺 _packageId 盖章通道 / [convention] best-effort 降级导致"看起来正常、实则不持久"时不应记 warn——把 #4460 的点状修复定成规则 #4632 响亮度评估),showcase 侧补 ADR-0086 D3 packageId 而非绕过;
    4. cover 修法拒绝了造假 id 的诱惑:ADR-0104 自己的对账会把捏造的 sys_file id 判成 unowned_reference(blocking),且参考应用不能教读者发明文件 id —— 删掉 data: URI,新库冷启 8 条告警 → 2 条(剩两条与本单无关且已立案 OS_STORAGE_ROOT 对任何非默认值不生效:CLI 与设置服务的 env 通道错位,schema 默认值覆盖宿主构造配置(原报「swap 假警告」归因已被 #4096 时间线推翻,警告本身是准确的) #4968),ADR-0104 闸门在新库上正常 attest(app-showcase seed writes data: URIs into an image field, so ADR-0104's migration gate can never attest on a fresh datastore #4891 的验收判据);
    5. 测试防空转证明齐全:四条守卫逐一重新引入 bug 验红;其中 retry 守卫第一版被 conversion 的加载期改写骗成恒绿,dev 自己识破并改为扫源码文本 —— 这是我们要的测试纪律;
    6. 期中并 main 撞出 functions: { fn: { handler, effect: 'writes' } } cannot survive objectstack build — lowering emits a shape FlowFunctionEntrySchema rejects #4976(声明形 functions 过不了 build,script 的 config 契约要接入 #4277 的执行期 parse,先得有判别式(actionType)形态 #4343 不对称的下一形),按规程立案 + showcase 用裸 callable 形绕行,守卫注明「functions: { fn: { handler, effect: 'writes' } } cannot survive objectstack build — lowering emits a shape FlowFunctionEntrySchema rejects #4976 落地时应删除本守卫」。

    跨仓衍生:objectui#3317(Gallery 把 coverField 当 URL 读,spec-正确的 image 值反而不渲染 —— 也解释了为何第 4 条的造 id 路线救不回封面)已在 objectui 立案,归该分片 PM 清扫视野,本会话不越界。

    CI 绿后转 ready 入合并队列。


    Generated by Claude Code

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions