Skip to content

[finding] ADR-0087 台账没有「完备性」门禁:已发生的退役漏登记时全仓全绿,只有人工能发现(#6011 即如此) #6148

Description

@qq9340100

观察类发现,在做 #6011 台账半边(PR #6138)的反向验证时测出来的。不影响任何用户今天能碰到的行为,故只打 finding、不入 pm:queue,交 PM 分诊定级。

事实

ADR-0087 台账(packages/spec/src/migrations/registry.ts)有两道门禁:

  • check:spec-changes —— 比对 packages/spec/spec-changes.json
  • check:upgrade-guide —— 比对 docs/protocol-upgrade-guide.md

两道钉的都是台账 ↔ 生成物同步,即「registry 改了但没重生生成物」。实测方向(PR #6138 的反向验证,事前判定为红、结果一致):撤掉一条 registry 条目、生成物保持原样,两道立刻 exit 1 并报 stale。

但没有任何门禁钉「一次已经发生的退役必须有台账条目」。 因为生成物是 registry 的纯投影,当条目从一开始就不存在时,registry 与生成物是互相一致的 —— 两道门禁全绿,全仓其余门禁也全绿。

实证:#6011 就是这个形状

PR #6048(2026-08-07 合并)删除了 ActorUser 上的 roles 别名(ctx.user.roles / req.user.roles),退役已经发生。而 ADR-0087 台账里这一面一条都没有,同一次 ADR-0090 改名的另外三张面却都在册(data.hookContext.session.roles / ui.actionSession.roles / CEL/formula: current_user.roles)。

这个缺口存在期间 CI 全绿。它是分诊座位人工比对发现并立单的(#6011 正文那张对照表),不是任何门禁报出来的;补登记又要另派一轮(PR #6138)。也就是说:目前「退役有没有进台账」这件事,唯一的检测手段是人眼

为什么值得记一笔

后果不是运行时错误,而是升级路径上的静默缺失:台账条目是 objectstack migrate meta / spec-changes.json / 生成的升级指南三条渠道的唯一数据源。对于本来就没有 spec schema 的面(如 ctx.user,只有 runtime TS interface),既没有 retiredKey() tombstone 也没有 schema 拒绝,台账条目就是唯一的告知渠道 —— 漏登记等于该退役对升级者完全不可见,而 CI 不会有任何声音。

明确不主张具体做法

怎么补(或要不要补)是个设计问题,不是本条要裁的:候选思路各有代价 —— 例如把 changeset 的 major/breaking 与台账条目做交叉核对、或给 retiredKey() / 退役 PR 加一道清单式提示 —— 都要先回答「什么算一次需要登记的退役」,而跨包退役(本次退役发生在 packages/runtime,台账在 packages/spec)恰恰是最难自动判定的一类。按 startup focus,先把事实记在案,由维护者判断是否值得建这道门禁。

发现于 PR #6138(#6011 台账半边),该 PR 正文也如实写明了这一点、并刻意没有顺手新造门禁。


Generated by Claude Code

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions