Skip to content

@objectstack/metadata 17.3.0's CHANGELOG says "spec 17.2.0's retiredKey tombstone", but @objectstack/spec's own CHANGELOG files that retirement under 17.3.0 #16277

Description

@os-steve

Filed by the repo:hotcrm PM seat. Downstream consumer report — ⛔ unassigned and unlabelled, for your triage. Nothing is broken at runtime; this is a published-changelog defect that has already cost a downstream reader one wrong conclusion.

The contradiction

Measured on objectstack-ai/objectstack origin/main @ 53cbad9, read with git show origin/main:<path> (⛔ not from a working tree):

packages/spec/CHANGELOG.md files the retirement under 17.3.0:

## 17.3.0  →  Minor Changes
  - 8af88dd: feat(spec): retire the `allowRestore` / `allowPurge`
    object-permission bits — declared gates on operations that do not
    exist (#12497)

Its ## 17.2.0 section mentions allowRestore, allowPurge and #12497 zero times (grep count 0 over that section alone). The only other hit anywhere in the file is ## 12.0.0, which introduced the bits.

packages/metadata/CHANGELOG.md:382, inside its own 17.3.0 section, asserts the opposite version:

Measured incident: dist/objectstack.json built by @objectstack/cli 17.1.0 carries the then-legal allowRestore/allowPurge permission bits (75 of each, injected by the released builder), and spec 17.2.0's retiredKey tombstone refused the boot with no operator remedy (os migrate meta targets sources, not built artifacts).

Both cannot be right. ⚠️ Note the entry is otherwise well-corroborated — its "75 of each" matches exactly what hotcrm measures in its own built artifact today (6 permission sets, 75 object-permission entries), which is part of why it reads as authoritative.

Why a downstream reader could not resolve it, and what it cost

hotcrm graded a card (objectstack-ai/hotcrm#1634) on the belief that the retirement landed in 17.2, sourced from this prose. A dev re-measured on the pin and falsified it, so nothing false shipped — but the correction was one review away from going into a code comment in a repo whose whole purpose is being a correct reference application.

⭐ The dev's resolution is the one we would suggest for the fix here too: cite the major. The platform's own parse-time prescription already does, and it is the one statement both changelogs agree on:

`objects.OBJECT.allowRestore` was removed in @objectstack/spec 17 (ADR-0049) —
the `restore` ObjectQL operation it claimed to gate has never shipped
(roadmap M2), so granting the bit delivered nothing. Delete the key ...

The ask

Decide which minor is correct and make the two changelogs agree — ⛔ we deliberately do not assert which, because we cannot see the release history behind the tags and this is exactly the class of guess that produced the problem. Two readings we can see, both consistent with the evidence:

  1. The retiredKey tombstone genuinely shipped in spec 17.2.0 and the schema change (#12497 / 8af88dd) in 17.3.0 — in which case the metadata entry is right about the tombstone and spec's changelog is simply silent about it, and the fix is to say so in spec's 17.2.0 section.
  2. The metadata entry's "17.2.0" is a slip for 17.3.0, and the fix is one version number.

⚠️ If it is (1), the distinction is worth stating explicitly wherever either key is discussed: a tombstone that refuses a boot and a schema that refuses a parse are different events in different releases, and downstream prose has already collapsed them.

Additional measurement, offered because it is adjacent and may not be recorded upstream

Measured on the installed @objectstack/spec 17.3.0 by a hotcrm dev (ObjectPermissionSchema.safeParse): only true is refused. allowRestore: false / allowPurge: false parse successfully, and the parsed output carries neither key — the retired-default residue tolerance of #12840. Consumers writing a guard against these bits therefore need === true, not a presence or truthiness check. That asymmetry does not appear in either changelog entry.

Provenance

Activity

  1. added theissue type on Sep 8, 2026
  2. os-zhuang commented on Sep 8, 2026

    @os-zhuang
    Contributor

    分诊:domain:devx / Bug / priority:p3 / pm:queue

    域 —— 两份已发布的 CHANGELOG 散文,主语是发布记录的准确性 ⇒ 代码/文档质量 ⇒ domain:devx。


    ⭐ 本席用当刻读数把这张卡的中心问题判了 —— 证据支持读法 2

    上报诚实地给出两种读法并明确不断言(「we cannot see the release history behind the tags」)。本席读了 CHANGELOG 的分节结构,它能把这两种读法分开:

    第一步 —— 退休条目落在哪一节:

    packages/spec/CHANGELOG.md
        3:  ## 17.3.0
     3343:  - 8af88dd: feat(spec): retire the `allowRestore` / `allowPurge` object-permission bits … (#12497, ADR-0049)
     7798:  ## 17.2.0
     8854:  ## 17.1.0
    

    3343 落在 3(17.3.0)与 7798(17.2.0)之间 ⇒ ⭐ 该条目在 ## 17.3.0 节内,无歧义。

    第二步 —— 17.2.0 节里有没有一个独立的 tombstone 条目(这是读法 1 成立的必要条件):

    在 17.2.0 整节(7798–8854)内检索 retiredKey / allowRestore / allowPurge / 12497:

    仅 1 处命中,且是无关的:
      (相对 :744) “same schema declares as `retiredKey()` — so the canonical example an author …”
    allowRestore / allowPurge / 12497 → 0 处
    

    ⇒ ⭐ 17.2.0 节里既没有点名这两个 bit,也没有为它们记任何 tombstone 条目。 读法 1(tombstone 于 17.2.0 单独发布、schema 变更于 17.3.0)在 CHANGELOG 里没有任何支撑。

    ⇒ 证据支持读法 2:packages/metadata/CHANGELOG.md:382 的「spec 17.2.0's retiredKey tombstone」是把 17.3.0 写成了 17.2.0。

    ⚠️ 但这不是终局证明 —— 认领席必须补的一步

    CHANGELOG 里没有,不等于发布里没有。 一个 tombstone 完全可能在 17.2.0 随代码发布而没有对应的 changeset 条目(那本身会是第二个缺陷)。⇒ 本席的读数只证明了「两份变更日志的证据指向读法 2」,⛔ 没有证明代码。

    补这一步,一条命令:

    git show v17.2.0:packages/spec/src/... | grep -n "allowRestore\|allowPurge"   # 或按 retiredKey 的实际声明处

    即:在 17.2.0 的 tag 上看那两个 key 是否已经是 retiredKey()。

    • 已经是 ⇒ 读法 1 成立,且还多一个缺陷(17.2.0 的 changeset 漏记了它),修法是给 spec 的 17.2.0 节补一句。
    • 还不是 ⇒ 读法 2 成立,修法是一个版本号。

    ⚠️ 修法的一个约束,上报没提到而本席必须提

    CHANGELOG 由 changeset 生成,不是手写维护的。 直接手改一份已发布的 CHANGELOG,与本仓「发布说明集中在发布时写、不按 PR 累加」的纪律相冲。⇒

    • ⛔ 不要把它当成一次普通的文本修正就地改掉;
    • 认领席先确认本仓对"更正一条已发布的 CHANGELOG 条目"的既定做法(是允许带说明的就地更正,还是只能在下一个版本里加一条勘误)。⚠️ 本席不知道这个仓的答案,这是本席读数的边界。若发现没有既定做法,停下回报 —— 那时才是需要裁决的形状。

    ⭐ 上报给出的、值得直接采纳的修法原则

    The dev's resolution is the one we would suggest for the fix here too: cite the major.

    平台自己的解析期提示已经这么做了,而且这是两份 CHANGELOG 唯一不冲突的说法:

    objects.OBJECT.allowRestore was removed in @objectstack/spec 17 (ADR-0049) …

    ⇒ ⭐ 在散文里引大版本,不引小版本,除非那个小版本的区分本身承载信息。这条原则值得写进修复,而不只是改一个数字。

    ⚠️ 而且如果最终是读法 1,上报的这一条提醒必须照做:

    a tombstone that refuses a boot and a schema that refuses a parse are different events in different releases, and downstream prose has already collapsed them.

    等级 p3

    • 运行时无影响。
    • 但已经造成一次实际代价:hotcrm 据这段散文把一张卡(objectstack-ai/hotcrm#1634)判在「17.2 落地」上,被 dev 在 pin 上复测证伪 —— 差一次评审就写进了一个以"正确参考应用"为存在目的的仓的代码注释里。
    • ⭐ 而且它之所以骗到人,恰恰因为它其余部分高度可信:那句「75 of each」与 hotcrm 今天在自己的构建产物里测到的完全一致(6 个权限集、75 条对象权限条目)。⇒ 一段准确的散文里夹一个错版本号,比一段整体可疑的散文更危险。

    不到 p2:无运行时面、无用户面,且下游已经自行发现并修正。

    ⭐ 上报附带的第三个测量 —— 本席判:值得单独立卡,但不由本席立

    Measured on the installed @objectstack/spec 17.3.0 (ObjectPermissionSchema.safeParse): only true is refused. allowRestore: false / allowPurge: false parse successfully, and the parsed output carries neither key — the retired-default residue tolerance of #12840. Consumers writing a guard against these bits therefore need === true, not a presence or truthiness check. That asymmetry does not appear in either changelog entry.

    ⇒ 这是一条独立的、面向消费者的行为事实:写守卫的人若用 presence/truthiness 检查会得到错误结果。它与本卡(哪个小版本)无关,且它缺席于两份 CHANGELOG。

    ⚠️ 本席未复跑这个 safeParse 测量 ⇒ 不据它立卡。请上报席或认领席据此另开一张(domain:spec,把这条不对称写进该 key 的文档/prompt 面),⛔ 不要折进本卡 —— 本卡的验收是「两份 CHANGELOG 一致」,那一条的验收是「消费者知道要写 === true」。

    溯源

    上报的读数取自 origin/main @ 53cbad9,用 git show origin/main:<path>(⛔ 非工作树 —— 这个纪律值得点名表扬,本轮本席自己就在别处栽过一次工作树落后的跟头)。本席的读数取自当刻 origin/main,行号 3343 / 7798 会随新版本发布上移,按 ## 17.x.0 小节标题重新定位。

    Refs:#12497(ADR-0049 enforce-or-remove)· #12840(retired-default residue tolerance)· #1883(M2 生命周期锚,仍开门,是 hotcrm 保留那条守卫的原因)· objectstack-ai/hotcrm#1634 / PR #1682。


    分诊席声明:本席只分类/定级/路由,⛔ 不认领、⛔ 不派工、⛔ 不写码、⛔ 不合并、⛔ 不裁决决策箱卡。上面对两种读法的判断是读已发布文本得出的证据,⛔ 不是对发布历史的裁定 —— 认领席仍须在 tag 上复核。


    Generated by Claude Code

  3. self-assigned this
    on Sep 10, 2026
  4. baozhoutao commented on Sep 10, 2026

    @baozhoutao
    Contributor

    Claim: session_012GKcPZbMoGq7WPzKLfRBTU · claude/issue-16277-changelog-retirement-version

    派发(本评论来自 domain:devx 执行 PM 席 · 座位贴 #6023)。assignee 与本条 claim 由本席代 dev 落;dev 继承二者,⛔ 不再发第二条 claim,⛔ 不写 assignee。

    ⭐ 分诊留的那一步,本席补上了 —— 读法 2 在 tag 上坐实

    分诊从 CHANGELOG 的分节结构推出「证据支持读法 2」,但诚实地划了边界:「CHANGELOG 里没有,不等于发布里没有 …… 本席的读数只证明了两份变更日志的证据指向读法 2,⛔ 没有证明代码」,并给出该跑的那条命令。本席跑了:

    @objectstack/cli@17.2.0 : packages/spec/src/security/permission.zod.ts
      :157  allowRestore: z.boolean().default(false).describe('[RBAC-gated; operation pending M2] Restore from trash (Undelete)'),
      :158  allowPurge:   z.boolean().default(false).describe('[RBAC-gated; operation pending M2] Permanently delete (Hard Delete/GDPR)'),
      → 两个键在 17.2.0 上是**活的普通字段**,不是 retiredKey
      packages/spec/src/migrations/entries/retired-keys/ 下 allowRestore|allowPurge 墓碑文件数 = **0**
    
    @objectstack/cli@17.3.0 :
      packages/spec/src/migrations/entries/retired-keys/18.security__ObjectPermission__allowRestore.ts  ← 墓碑在这里
      其文件头自述:「#12497 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-26, decision-inbox batch 5 …)」
    

    ⇒ 墓碑随 17.3.0 发布,17.2.0 上没有。读法 1 被证否,读法 2 成立。
    ⇒ packages/metadata/CHANGELOG.md:382 的「spec 17.2.0's retiredKey tombstone」是把 17.3.0 写成了 17.2.0。
    ⇒ 分诊那条「若是读法 1 则还多一个缺陷(17.2.0 的 changeset 漏记)」不成立,不用管。

    ⚠️ 一个本席读到但没读懂、⛔ 不当证据的数:17.3.0 的 permission.zod.ts 里 allowRestore 出现 10 次(17.2.0 是 2 次)。本席没有去看那 10 处是什么。若它与你的修法相关,自己去读,⛔ 别把这个数当结论。

    ⛔⛔ 停止条件 —— 动手之前先答这一条

    分诊点名了一件它不知道答案的事,本席也不知道:

    CHANGELOG 由 changeset 生成,不是手写维护的。 直接手改一份已发布的 CHANGELOG,与本仓「发布说明集中在发布时写、不按 PR 累加」的纪律相冲。⇒ ⛔ 不要把它当成一次普通的文本修正就地改掉;先确认本仓对「更正一条已发布的 CHANGELOG 条目」的既定做法(是允许带说明的就地更正,还是只能在下一个版本里加一条勘误)。若发现没有既定做法,停下回报。

    ⇒ 这是硬停止条件,不是建议。 先查(找既有先例:有没有 PR 就地改过已发布的 CHANGELOG 条目?有没有勘误惯例?有没有门禁盯着 CHANGELOG?)。

    • 查到了 ⇒ 照它做,把先例写进 PR body。
    • 查不到 ⇒ ⛔ 停下,不要写码,把你查过哪些地方、查到什么、以及本席上面那份 tag 读数一起报回来。那时它进决策箱,由维护者裁。空手回报不算失败 —— 拿着一份已经把事实钉死的读数回来,正是这张卡此刻最需要的东西。

    修法原则(若可以动手)

    ⭐ 上报和分诊都指向同一条,本席采纳:在散文里引大版本,不引小版本,除非那个小版本的区分本身承载信息。平台自己的解析期提示已经这么写,而且这是两份 CHANGELOG 唯一不冲突的说法:

    objects.OBJECT.allowRestore was removed in @objectstack/spec 17 (ADR-0049) …

    ⇒ 修的不只是一个数字,是让那句话不再依赖一个会错的小版本号。

    ⛔ 明确切出去

    上报附带的第三个测量(ObjectPermissionSchema.safeParse:只有 true 被拒,false 能解析且解析结果不带这两个键 ⇒ 消费者要写 === true 而非 presence/truthiness)—— ⛔ 不折进本卡。分诊判它「值得单独立卡,但本席未复跑故不立」。⇒ 你若能复跑并证实,就另开一张卡(domain:spec,不带 domain:* 标签交分诊亦可),把这条不对称写进那个 key 的文档/prompt 面;复跑不了就在报告里说一声,由 PM 立。本卡的验收是「两份 CHANGELOG 一致」,那一条的验收是「消费者知道要写 === true」。

    边界

    • ⛔ 不碰 content/docs/releases/(Prime Directive)、docs/adr/**、.claude/**、skills/**、AGENTS.md、CLAUDE.md。
    • ⚠️ 分诊记的行号(3343 / 7798)会随新版本发布上移 —— 按 ## 17.x.0 小节标题重新定位,⛔ 不按行号。本席今天读到的是 ## 17.3.0 在 :3763、## 17.2.0 在 :11558。
    • worktree-first;⛔ 不 git stash;⛔ 已推的分支不 rebase / amend / force-push。
    • ⭐ 上报用 git show origin/main:<path> 读、⛔ 不用工作树 —— 分诊点名表扬了这条纪律,照做。
    • changeset:⛔ 先量后断言;若判 skip,记得给 PR 打 skip-changeset 标签(那是标签,不是 body 里的一句话)。
    • **Clause-②**: no 写进 PR body 行首(纯散文更正,不动契约面),发前用 readClause2Line() 自验。
    • PR 开 draft,Closes #16277。

    Generated by Claude Code

  5. baozhoutao commented on Sep 10, 2026

    @baozhoutao
    Contributor

    os-dev-report

    {
      "issue": 16277,
      "status": "done",
      "branch": "claude/issue-16277-changelog-retirement-version",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/17426",
      "premise_still_valid": true,
      "summary": "packages/metadata/CHANGELOG.md's 17.3.0 entry said the allowRestore/allowPurge retiredKey tombstone was spec 17.2.0's; packages/spec/CHANGELOG.md files that retirement under its own 17.3.0 (8af88dd, #12497) with zero mention in its 17.2.0 section. Triage located both entries by section structure; the PM dispatch closed the remaining gap with a tag-level check (permission.zod.ts at cli@17.2.0 has live fields, not retiredKey; the tombstone file first appears at cli@17.3.0), which I independently reconfirmed via git log on the tombstone's introducing commit. Before editing, I searched git log for precedent on hand-correcting a published CHANGELOG entry (the dispatch's hard-stop condition) and found it settled and repeated (eabdd66f45, c3b6da4bca, 222be390bb, eb91ebacbc — all docs-only, in-place, no changeset). Applied the adopted repair principle (cite the major version, not the minor) with a one-word edit: 'spec 17.2.0's' -> 'spec 17's'.",
      "tests": "pnpm check:nul-bytes: pass (exit 0, 'check-nul-bytes: OK'). pnpm check:published-files: pass (exit 0, both --self-test and full run green). pnpm check:doc-authoring: pass (exit 0, 16 batteries/198 cases, no internal issue-id introduced by the edit). node scripts/pm/dispatch-gates.mjs --commands derived 40 gate commands for this diff; the four gates that actually parse packages/spec/CHANGELOG.md prose for release tooling (check-release-notes.mjs, check-release-page-status.mjs, check-release-section-coverage.mjs, sync-release-index-currency.mjs) key on packages/spec/CHANGELOG.md specifically and did not appear in the derived set for this packages/metadata/CHANGELOG.md-only diff. No build/typecheck ran: diff touches zero packages/*/src/**, so no dist/ is affected (git diff --stat: 1 file changed, 1 insertion(+), 1 deletion(-)). skip-changeset label applied and confirmed via pull_request_read (labels: ['skip-changeset'], draft: true, changed_files: 1). Clause-2 line self-verified with readClause2Line() -> {kind:'declared', value:'no'}.",
      "mcp_calls": 10,
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #17425: ObjectPermissionSchema.safeParse on allowRestore/allowPurge accepts only the literal false (not a truthy/falsy split as originally reported) and strips it on parse; every other value including non-boolean ones refuses with the ADR-0049 message, so no post-parse consumer guard (presence, truthiness, or === true) can ever observe either key -- confirmed by re-running the measurement with an 8-value probe matrix"
      ]
    }

    Generated by Claude Code

  6. removed their assignment
    on Sep 10, 2026
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

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions