fix(spec): check:liveness 的 stale-evidence 判红,摘要行的数与词对齐 (#5623) - #6057
Open
os-zhuang wants to merge 2 commits into
Open
fix(spec): check:liveness 的 stale-evidence 判红,摘要行的数与词对齐 (#5623)#6057os-zhuang wants to merge 2 commits into
os-zhuang wants to merge 2 commits into
Conversation
`live` 判定的语义就是它的 evidence 指针。指针指向本仓已不存在的文件时, 这条声明不再可证伪 —— declared 有,enforced 无 —— 而一次目录重组或改名 就足以造成它。实测(origin/main,故意改坏 query.json 的 5 条路径):逐条 点名了,退出码仍是 0;摘要行的 330 也一动没动,因为它数的是 local 路径 总数,不是解析成功数。 - `live` 条目引用本仓缺失文件 → `✗` 判红(退出码 1),并给出三条修法。 边界不变:cross-repo attribution(objectui / cloud / service-ai,当前 101 条)只计数不解析,永远不判红 —— checkEvidence 只对 local 桶做存在 性检查,这是结构性的分界。 - 摘要行改成两个数:declared 与 resolved,绿的时候相等;有断链时追加 `, N MISSING`。保留 declared 是因为它是 #3857 留下的解析健康度信号。 - 新增 `--ledger-root=<dir>`,自测据此把真 gate 跑在只坏了一条指针的 ledger 副本上,不改动仓库任何文件。 以前是 ⚠ 不是对本仓路径的有意宽容,是解析器时代的遗留:#3857 之前 `evidence.split(':')[0]` 在 227 条里报 48 条、全是误报。同族的 check-empty-state 对 rotted-evidence 一直是 ✗ + exit 1。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
`packages/spec/liveness/**` 是发布内容(package.json 的 `files` 含 `liveness`;`npm pack --dry-run` 实测 29 个文件入包,含该目录的 README.md),所以本 PR 并非「releases nothing」,route 2 的 `skip-changeset` 与 route 3 的空 changeset 都不适用。 pr-automation.yml 也把空 frontmatter 明确降级为 LAST RESORT:它是 changesets/action 的真实输入,全空集合会走 "All changesets are empty; not creating PR" 分支,发布静默绿着卡住 (#4898 卡住 17.0.0-rc.2)。脚本本身(scripts/)不入包,是 dev-only。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014wsZeReNTqiceBfLb5Pyf5
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 112 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
os-zhuang
marked this pull request as ready for review
August 6, 2026 21:38
os-zhuang
enabled auto-merge
August 6, 2026 21:38
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5623
前提复现(先证伪,再动手)
在
origin/main(739f496)上把packages/spec/liveness/query.json的 5 条 evidence 路径故意改坏(指回迁移前的packages/plugins/driver-sql/...),跑真 gate:EXIT=0。issue 的两条实测逐条成立:五条断链全被点名却不判红,摘要行的330在改坏五条之后一动没动。代码位置:check-liveness.mts的failed表达式里没有report.staleEvidence,而摘要行印的是report.evidenceLocal(local 路径总数),头顶的注释还写着 "actually resolved against this checkout" —— 注释本身就是这个 bug。main当前 330 条本仓路径全部解析成功,所以本 PR 落地即绿,不需要修任何 ledger 数据(scope guardrail 里那条例外没有触发)。「注意的反面」:⚠/✗ 分级的原始意图查证结果
issue 要求先核对分级是不是有意设计。查证结论:这里的 ⚠ 不是对本仓路径的有意宽容,是解析器时代的遗留。四条证据:
evidence.mts的文件头记录了它自己的来历:fix(spec): liveness stale-evidence check was ~100% false positives — and was burying a real one #3857 之前的检查是evidence.split(':')[0],227 条里报 48 条、全是误报(prose 解析产物或有意的跨仓指针)。那种噪声比下,它当然不能判红。同一段还写明代价:被那 48 条埋掉的唯一一条真腐烂object.enable.clone(消费者从@objectstack/objectql搬到了@objectstack/metadata-protocol)一直没人看见。.changeset/liveness-register-orphan-proofs.md的收尾句:"the orphan list joins the stale-evidence list at empty, so both mean something again" —— 解析修好之后,这份清单的语义就是「有命中即信号」。check-empty-state.mts对rotted-evidence一直是✗+exit 1;liveness/README.md说它的执行点路径 "resolves likeevidenceabove, so a pointer that rots is reported rather than trusted"。同一族里,腐烂指针的既定处置就是判红。verifiedAt年龄("Age never fails CI — re-verification is a worklist")、undrilled 容器计数("a worklist, not a failure")、PENDING_GOVERNANCE("a worklist, not a merge gate"),连另一条 ⚠(orphan proof tags)都写了 "flag it (warning)"。唯独 stale-evidence 那一段,一个解释都没有。刻意的宽容在这个文件里是会写下来的;这条没写。所以按 issue 给的边界收紧,没有需要升级到
needs_decision的发现。改动
1.
live条目引用本仓缺失文件 →✗判红report.staleEvidence.length > 0进入failed,输出块从⚠改为✗并附三条修法(仓内搬家 → 改指针并顺手补verifiedAt;搬去别的仓 → 加 realm marker;消费者真没了 → 按 ADR-0049 重新判定,而不是随手指向一个看着像的幸存者)。边界:cross-repo attribution 完全不受影响。 这不是靠约定守住的,是结构性的 ——
checkEvidence只对local桶做存在性检查,foreign桶(realm markerobjectui:/cloud:/ee:,以及packages/services/service-ai/…前缀)从来不进missing。当前 101 条跨仓归属一条都不会判红,自测里有三个用例钉死这一点(含一个「同一条 evidence 里objectui:子句 + 仓内路径」的混合用例 —— 如果 realm 作用域不在子句边界结束,一个 marker 就能整条豁免 gate)。2. 摘要行:选「两个数都印」,而不是二选一
issue 给了两个选项(改成真实解析计数 / 把 "resolved" 改成 "declared")。两个都印才是对的,理由是这两个数各自有用途,丢掉任何一个都损失信息:
declared是 fix(spec): liveness stale-evidence check was ~100% false positives — and was burying a real one #3857 留下的解析健康度信号 —— 那次 changeset 明确写了 "the check degrading to 'extracts nothing' is visible rather than silently green (a unit test asserts it too)"。只印 resolved 的话,解析器退化成什么都提不出来时,0 resolved / 0 missing会读成通过。resolved是判定本身。绿的时候两者相等 —— 这恰恰是当初只印前一个会被读成「通过」的原因:
有断链时行尾追加
, N MISSING,数与词一一对应。JSON 报告同步新增evidenceMissing,evidenceLocal的注释从 "actually resolved" 改成 "DECLARED, not yet proven"。3. 新增
--ledger-root=路径参数让 gate 读
packages/spec/liveness的一份副本。它是为了让「判红」这件事本身可证明:判红的 gate 只值它那份「它确实会红」的证明,而这需要一条真的腐烂指针来红。往 shipped ledger 里提交一条不是选项(那会让其他每一次运行都红),测试中途改动被跟踪文件则会在崩溃时留下脏 worktree。所以自测把真 ledger 拷进临时目录、只坏一条指针、再跑这个脚本本身 —— CI 跑的同一条代码路径,仓库状态零改动;其余全部仍对真repoRoot解析,所以那个exit 1只有一个成因。反向验证(方向先判,再跑)
预判:红(标准方向)—— 同一批断链在改前 exit 0、改后 exit 1,且摘要行的数应当移动。
origin/main代码⚠ 5 …/ EXIT=0 /330 resolved✗ 5 …/ EXIT=1 /330 declared, 325 resolved, 5 MISSING330 declared, 330 resolved改前那一跑就是「新测试若在 main 上运行会红」的证明:测试断言
exit 1,main 给的是exit 0。测试
新增
packages/spec/scripts/liveness/check-liveness.test.ts(8 例),spawn 真脚本断言退出码,precedent 是scripts/check-generated-ledger.test.ts。这一层是必需的:这个 bug 从来不是「检查看不见」—— 它把五条全点名了还是 exit 0,所以只测checkEvidence的evidence.test.ts全程是绿的,再加一个同层单测也不会红。全量:
Changeset:为什么是
@objectstack/spec: patch,不是空 frontmatter,也不是skip-changeset派单给的默认是「dev-scripts/CI-only → 空 frontmatter」。实测后改了,因为这个 PR 不满足那个前提:
packages/spec/package.json的files里有liveness,npm pack --dry-run实测 29 个文件入包,含liveness/README.md—— 本 PR 改的那段 README 是发布内容。scripts/入包 0 个文件,那部分确实是 dev-only。skip-changeset(route 2)和空 changeset(route 3)都不适用,pr-automation.yml的 route 1 才是:it releases something → 具名包。pr-automation.yml已经把空 frontmatter 明确降级为 LAST RESORT:它是changesets/action的真实输入,全空集合会走"All changesets are empty; not creating PR"分支,发布静默地绿着卡住 —— 空 changeset 会静默卡死已 version 的发布:Release run 全绿,但 npm 和 Docker 什么都没发(17.0.0-rc.2 现在就卡着) #4898 卡住 17.0.0-rc.2 的正是这个。因此本 PR 不需要也不应该带
skip-changeset标签:它写了一个具名包的 changeset,Check Changeset走的是「有 changeset」那条路。一个新开始判红的 gate 必须同步它面向作者的文档,否则下一位作者只能靠撞红的 CI 才知道规则变了 —— README 那一段(新的失败语义 + 跨仓边界)因此是这次改动的一部分,而不是搭车。
与 #5475 的关系(packages/spec/scripts/** 的 tsconfig 覆盖)
实测答案:对错误数 无影响,对文件数轻微增加(而新增文件是干净的)。
tsconfig.test.json的文件头已经写明scripts/"is in no tsconfig at all",实测 16 files / 33 errors。我用一个把scripts/liveness/**纳入的探针 program 量了本 PR 之后的这个子目录:唯一 1 条,且在我没碰过的行(
v.stale.forEach((s) => …),verifiedAtworklist 那段)。新增的check-liveness.test.ts贡献 0 条错误。所以:#5475 的错误清单不因本 PR 变长(不会更难),但它的文件分母 +1(16 → 17)。既没变简单也没变得不必要 ——--ledger-root那点解析逻辑也没有引入新的类型面。与 #5837 的碰撞面
无。本 PR 只碰
scripts/liveness/check-liveness.mts+ 新增同目录自测 +liveness/README.md+ changeset;不读也不写authorable-surface.json/json-schema.manifest.json/api-surface.json,不碰build-schemas.ts/build-docs.ts/.gitattributes。