Skip to content

v4.39.0

Choose a tag to compare

@github-actions github-actions released this 09 Sep 16:24
· 8 commits to main since this release

[4.39.0] - 2026-09-09

Phase 54「声明 / 求值一致性」。本仓的签名缺陷是「声明了机制,却没有代码求值它」 —— 4.36.0 失效的 eval trap、4.37.0 chrome-devtools 的散文承诺、4.38.0 verify 缺回退边,三次都是事故或审计倒推发现的。本轮把这条病族成建制清了一遍,其中一面有真实行为后果。

立项前提在执行中被实测推翻五条(112→108 计数 / 「14 个悬空事实」/ 断点行形态 / eval git fixture / DRY 抽取),全部显式更正未静默改写 —— 详 .planning/phases/54-declaration-evaluation-parity/SPEC.md

Added

  • verify/second-opinion —— 跨模型第二意见第一次成为会被求值的编排单元(Phase 54 T2+T3)capabilities.yaml 里的 codex 能力(第二意见 / cross-AI peer review)此前被锁在一条 fires_when 后面,而它引用的 phase.requires_second_opinion 全仓只出现那一次 —— 连事实都没声明过。
    • 判据机械派生,不是判断题:harnessed factsgit diff --name-only <上一个 release tag>ORCHESTRATION SURFACE(workflows/judgments/ · capabilities.yaml · role-prompts · disciplines/ · defaults.yaml · phaseFactContext.ts · judgmentResolver.ts · exprBuilder.ts · facts.ts · ledger.ts · 任一 workflows/**/SKILL*.md)的交集。集合从代码推导,不是从样本拟合 —— 早先草案用的是「最近三次发版恰好碰过的四条路径」,那是在缺陷被发现的地方过拟合,既误报(一行 ADR 拼写修正命中)又漏报(200 行改注入语义不命中),而且把 capabilities.yaml / facts.ts 这两个缺陷出生地排除在外。
    • 基准是 release tag,不是 merge-base origin/main:后者在 commit-即-push-main 的纪律下塌缩成 HEAD,只看得见未提交改动,会漏掉多-commit milestone 的早期 commit。
    • 条件性交付要求 = 条件性 fire 的 sub(零新机制)。artifacts_expected 是 phase 级静态列表,直接声明会让第二意见记录无条件必交、判据没命中也卡住,--force 随即变成肌肉记忆。改为新 leaf workflows/verify/second-opinion/(serial order 90,在 code-review/paranoid 之后、simplify 之前),由 delegates_to.gate 控制它跑不跑;跑了就必交 second-opinion.md(evidence guard 已 fail-closed),没跑则 ledger 记 status: skipped + 原因。跳过从隐形变成台账上一行。
    • requires_second_opinion 为 root-flat 裸标识符(sister chrome_devtools_available),buildDefaultGateContextfalse —— 与 chrome-devtools 种 true 方向相反:那边未知不可能力,这边未知不可活。算不出来时 harnessed factssource 直接写「criterion unavailable — <原因>」。

Changed

  • capabilities.yamlfires_when 改名 routing_note,并从 schema 删掉旧名(Phase 54 T1)。judgment 文件里的 fires_when真求值的(judgmentResolvertriggers/rules);capabilities.yaml 里同名的 108 条没有任何消费者 —— gate 引擎不读它,prompt.ts:72-96 只渲染 cmd/impl/aliases。同名异义就是误读的根源:读的人(包括未来的实施 subagent)会把它当生效路由,而项目 SPEC 检查表的 grep 恰恰会放行它们(检查表验「存在」,不验「被消费」)。
    • 改名把 108 条一次性变得自明,并让 additionalProperties: false 承担守卫:写回 fires_when 直接是构建期错误,不是静默无效。门从「维护一张百条豁免表」缩成「schema 就是门」。
    • 两处 schema 同步(src/workflow/schema/capabilities.ts + scripts/check-workflow-schema.mjs 的镜像);该 .mjs 里另一处 fires_when: Type.String()judgment trigger 的真字段,未动。

Fixed

  • 21 处 per-phase 迭代上限第一次真的生效(Phase 54 T0)workflows/**/workflow.yamlphases[].max_iterations 全部写成 {{ defaults.ralph_max_iterations.<workflow>.<phase> }},而没有任何一处被解析过:loadPhases 的插值只覆盖 ph.invokes(实测 v3 下该字段恒为 undefined,分支从不触发),且 interpolateSTRICT = /\{\{\s*(\w+)\s*\}\}/g 不认 dot-path、残留即抛错 —— 接进去只会把 21 处全变成异常。21 处以字面串抵达 resolveMaxIterations,parseInt 得 NaN,统统落回 RALPH_DEFAULT_MAX_ITER = 20。声明 5 的 phase 实跑 20,四倍于写下的意图。
    • 修法不是补模板层,而是删掉 yaml 里的字段,让值只走 workflows/defaults.yaml 查表 —— 与 resolveAttemptBudget 同一条已知能工作的路径,单一数据源。resolveMaxIterations 新增可选第三参数,优先级 CLI flag > yaml 字面量(向后兼容手写数字)> defaults 查表 > 20。
    • 边界:checkpoint 的 attempt budget(budget.ts 按 sub 直读 defaults)一直是好的;坏的是 run 引擎的 per-phase 上限。两个不同的天花板,不要混。
    • 顺带修 7 处键名漂移:5 处 workflow 的 phase id 与 defaults 键早已分家(如 verify-simplify01-simplify vs defaults 的 01-code-simplifier);research 的单个 01-fan-out 前缀键展开成 5 个真实 lane id(精确匹配下前缀键匹配不到任何 phase);verify-progress.03-persist 对齐为真实的 03-progress-update
    • 新增 tests/workflow/template-ref-resolvable.test.ts:defaults 表里的每个 <workflow>.<phase> 必须对应一个真实存在的 phase,且不得有 workflow.yaml 再写回不可解析的 {{ defaults… }}。零正则抓源码 —— 直接读两个 yaml 拿真数据。