Skip to content

三个 ADR 编号各自指向两个不同的已接受决策(0010 / 0019 / 0057)—— adr-anchors.json 与 328 处代码注释按裸编号引用,无法消歧 #5992

Closed

Description

@hotlong

在 #4672(知识 reap guard)实现中顺带发现,不在该单范围内,按 Prime Directive #10 立单记录。

事实

docs/adr/ 下有三个编号各自被两个主题完全无关的已接受 ADR 占用:

编号 文件 A 文件 B
0010 0010-metadata-protection-model.md 0010-nl-to-flow-authoring.md
0019 0019-app-as-consumer-unit.md 0019-approval-as-flow-node.md
0057 0057-erp-authorization-core-business-units-and-scope-depth.md 0057-system-data-lifecycle-and-retention.md

(0006 有三个文件,但那是同一决策的 .v2 / .v4 版本,不算冲突。)

为什么这不只是文件名洁癖

AGENTS.md Prime Directive #13 把「按编号找到决策」定为硬要求:「在改动 docs/adr/ 治理的行为前,grep the ADRs for the surface you are touching」,以及*「当你实现某个 ADR 的决策时,leave its id in the code」*。这两条都以「编号 ⇒ 唯一决策」为前提,而上面三个编号不满足它。

实测规模:

  • packages/**/*.ts 中 328 处裸 ADR-0057 引用。以 ADR-0057 为例,两个候选是 ERP 授权内核(安全) 与 系统数据生命周期(保留期回收) —— 域相差最远的一对,读者无法从编号本身判断。
  • scripts/adr-anchors.json 的锚点条目按裸字符串记录,例如
    { "file": "packages/objectql/src/lifecycle/lifecycle-service.ts", "adrs": ["ADR-0057"], … }。
    这个文件存在的目的正是告诉下一位作者「你正站在哪个决策上」(PD [WIP] Add Chinese version of the documentation #13 原话:a decision nobody can find is a decision that will be reversed),而它给出的 id 恰好指向两个决策。

顺带一处断链:0057-system-data-lifecycle-and-retention.md 头部写
[ADR-0010](./0010-metadata-protection.md),该文件不存在(实际是 0010-metadata-protection-model.md)—— 而 0010 本身就是冲突编号之一。

观察类,不是今天会咬人的缺陷

没有用户路径受影响,pnpm check:adr-anchors 今天是绿的(它校验锚点与文件的对应关系,不校验编号唯一性)。代价落在agent 导航上:按编号 grep 会同时命中两个无关决策,或者更糟 —— 只读到先命中的那个,并据此推理。

因此标 finding、不入 pm:queue,严重性交 PM 分诊。

可能的处置方向(未做取舍,留给裁决)

  • A. 给后到的那份重新编号,原文件留 tombstone 指向新号;
  • B. 保留编号,强制引用带 slug(ADR-0057-lifecycle),并让 check:adr-anchors 校验编号唯一性 + 拒绝裸的冲突编号;
  • C. 只加一道唯一性检查止血(不再产生新冲突),存量三处按 B 逐步消歧。

无论选哪条,建议同时给 check:adr-anchors 加编号唯一性断言 —— 这正是本仓反复付费的「declared ≠ enforced」形状:PD #13 声明了「编号可定位决策」,但没有任何门校验它。

Activity

  1. claude commented on Aug 6, 2026

    @claude
    Contributor

    发现分诊:转维护者决策箱 —— 摘 finding,改 needs-user-decision + domain:devx(并摘掉无消费方的 repo:objectstack)

    判级 — 不晋级入队,升决策箱。缺陷本身已复核成立,但三条处置路线的分歧点是治理取舍,不是实现细节:

    • A. 给后到的那份重新编号 + 原文件留 tombstone —— 动的是已接受 ADR 的身份。仓内有 328 处裸 ADR-0057 引用,重编号意味着一次全仓引用迁移,且「已发布的决策编号可以改」本身就是一条需要维护者认可的先例;
    • B. 保留编号,强制带 slug 引用(ADR-0057-lifecycle)+ 门禁校验唯一性 —— 动的是引用惯例(AGENTS.md PD [WIP] Add Chinese version of the documentation #13 的原文措辞要跟着改),存量 328 处逐步消歧;
    • C. 只加唯一性检查止血,存量按 B 逐步消 —— 成本最低,但把存量歧义长期留在盘上。

    三条的代价落在不同的人身上(A 落在一次性迁移,B 落在此后每一次引用,C 落在此后每一个读者),这类取舍归维护者。

    域 — docs/adr/(编号现状)+ scripts/adr-anchors.json + pnpm check:adr-anchors(门禁)⇒ domain:devx(域表:scripts/ 门禁类)。

    过时前提检查(origin/main 44106d9)—— 三处冲突逐一复核命中,git ls-tree docs/adr/ 输出:

    docs/adr/0010-metadata-protection-model.md      docs/adr/0010-nl-to-flow-authoring.md
    docs/adr/0019-app-as-consumer-unit.md           docs/adr/0019-approval-as-flow-node.md
    docs/adr/0057-erp-authorization-core-...md      docs/adr/0057-system-data-lifecycle-and-retention.md
    

    立单人对 0006 的排除(同一决策的 .v2 / .v4 版本,不算冲突)也复核采信。

    PM 倾向(供参考,维护者可直接否决) — C 先行,B 收尾:唯一性断言是那条止血且各方向都要的公共前置(本仓反复付费的「declared ≠ enforced」形状 —— PD #13 声明了「编号可定位决策」却无门校验),可以先单独落地而不锁死后续路线;A 的全仓 328 处迁移在没有独立拉力时性价比最低。⚠️ 若裁 A,建议同时明确「已接受 ADR 可否重编号」的一般规则,否则下次同样的问题还要再裁一次。

    顺带一处断链(与主裁决可脱钩,任何路线下都该修):0057-system-data-lifecycle-and-retention.md 头部的 [ADR-0010](./0010-metadata-protection.md) 指向不存在的文件(实际是 0010-metadata-protection-model.md)—— 而 0010 恰是冲突编号之一。

    标签清理说明 — 摘掉 repo:objectstack:主 backlog 仓的 issue 不需要回指本仓的路由标签(repo:* 只用于把落点路由到姊妹仓),该标签无任何查询消费,留着会被后续 sweep 反复当作路由信息读。同型清理前例:#5535。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 7, 2026

    @claude
    Contributor

    Ruling (maintainer, 2026-08-07): C first, B to finish. A is rejected.

    C (uniqueness assertion in check:adr-anchors, with the three existing collisions on an explicit allowlist) lands immediately: it stops new collisions and is the shared prerequisite of every other route, so it is un-regrettable. B (slug-qualified references for the three existing pairs) follows and can be amortised as those files are touched. The broken ./0010-metadata-protection.md link is fixed regardless of route.

    A (renumbering the later ADR of each pair with tombstones) is rejected: 296+ bare references would have to migrate, and it would set the precedent that an accepted ADR's number can move — a bigger cost than the navigation tax it removes.

    This is pure agent-navigation debt with no user path, but it is paid daily: PD #13 assumes number ⇒ unique decision, and an agent grepping ADR-0057 today gets two unrelated accepted decisions with no way to tell them apart. A gate remembers that; a person doesn't.

    Operator: PM session session_01GcjbQLUQKysMU9uXB34iyv; maintainer ruling 2026-08-07 (decision-inbox round 2). Veto window open — comment or reopen to overturn.


    Generated by Claude Code

  3. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    Claim: PM loop round 1 (devx seat, new tenure)
    Session: session_01F8q5J1MQyocgtNspb15fSn
    Branch: claude/issue-5992-adr-number-uniqueness
    Worktree: objectstack-issue-5992
    Domain: domain:devx
    File surface: scripts/check-adr-anchors.mjs, docs/adr/0057-erp-authorization-core-business-units-and-scope-depth.md, plus a changeset. scripts/adr-anchors.json only if the allowlist lands there.
    Container: S — mode:subagent, shared container.
    Serial constraints cleared: scripts/check-adr-anchors.mjs last touched by 82bf47b (#6471, merged); scripts/adr-anchors.json by 94e749b (#6516, merged). No open in-lane claim on either. Dispatched scope is route C only per the maintainer ruling below.

    Dispatching on the maintainer ruling of 2026-08-07 in this thread: C first (uniqueness assertion + explicit allowlist of the three existing collisions), B to finish, A rejected. The dev implements C and does not re-open the route choice.

    ⚠️ Correction to this card, measured at origin/main a36db28. The body says the broken ./0010-metadata-protection.md link is in 0057-system-data-lifecycle-and-retention.md, and the triage comment repeats that attribution. It is actually in 0057-erp-authorization-core-business-units-and-scope-depth.md:5:

    **Builds on**: [ADR-0010](./0010-metadata-protection.md) (metadata protection / object ownership),
    

    0057-system-data-lifecycle-and-retention.md contains no 0010 reference at all. Both sources agree because the second inherited the first's reading — agreement between them is not corroboration. The dev verifies the location itself before editing.


    Generated by Claude Code

  4. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    Collaborator

    ACCEPT — PR #6593 (devx seat review, round 1).

    What shipped, route C exactly as ruled: check:adr-anchors now audits number uniqueness over docs/adr/ — a fourth collision goes red with a message that computes the next free number; the three existing pairs (0010/0019/0057) sit on a shrink-only allowlist whose stale entries also go red, so a resolved collision cannot return under its own grandfather clause. A third record joining an allowlisted pair is a distinct failure. Route B untouched, nothing renumbered, all 328 bare references untouched — the ruling's boundaries held.

    Review notes:

    • 0006 is handled structurally (same stem + .vN = version, different stem = collision), not special-cased by name — pinned by self-test cases in both directions, including .vN-on-a-different-stem laundering.
    • Red path proven three ways: a real probe collision in the working tree (red, then green after removal), allowlist ablation against the live tree (predicted red, observed red — the entries are load-bearing), and a permanent --self-test with named assertions and a guarded body wired into the pnpm script, so CI exercises the red path on every PR.
    • The gate's own inventory blind spot closed: unparseable filenames under docs/adr/ are now errors (with NON_RECORD_FILES for declared non-records) instead of being silently invisible.
    • Broken link fixed at the corrected location (0057-erp-authorization…md:5) — the dev independently confirmed the card + triage comment shared a wrong attribution, and additionally measured that the issue body's "已接受" description holds only for the 0057 pair (recorded, behaviour-neutral).
    • CI wiring verified, not assumed: lint.yml:186, ESLint job, required set since 红的 ESLint job 没有拦住合并:PR #5584 在 ESLint 已红 19 分钟的情况下过队合入 —— main 的 required-status-check 集需要维护者核查 #5617.
    • Out-of-scope finding filed as docs/adr/ is outside the Check Links scan surface — ADR cross-links are unverified, and the obvious widening goes red on ADR-0046's illustrative links #6592 (docs/adr/ outside the Check Links scan surface, with the measured reason a naive widening goes red) — verified it exists.
    • skip-changeset justified: private script + wiring + one docs link, nothing published.

    Ready + auto-merge armed 06:17Z. Route B remains open on this issue per the ruling ("B to finish, amortised as those files are touched") — the gate's success-path note prints the 4 ambiguous anchor refs, keeping that debt visible.


    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