Skip to content

静态扫描:job 级 if:needs.*.outputs.* 必须显式带状态函数(#4928 沉淀的规则 + publish-smoke.yml 唯一存量违规) #5343

Description

@os-zhuang

#4928 拆出来的第二半。#4928 的正文点名这条规则「值得沉淀成一条可检查的规则」,但它落地需要一个不属于 #4928 文件面的判断,所以单独立单。

规则

任何 job 级 if: 只要读了 needs.SOMEJOB.outputs.SOMEKEY,就必须显式点名一个状态函数(always() / !cancelled() / success() / failure())。

理由:读上游 输出值 来决策,说明这个决策是数据驱动的;而 GitHub 会给不含状态函数的 if: 隐式包一层 success(),于是「上游挂了」和「上游说不用跑」这两件完全不同的事被压成同一个 skipped。作者如果真的想要 success() 语义,也应当写出 success() && ... —— 声明即强制,而不是靠读者记得 GitHub 的隐式规则。

这条规则是可静态判定的:限定在 job 级 且读 needs.*.outputs.*,不涉及意图猜测。

为什么把范围收到「job 级 + needs.outputs」

我在 #4928 里跑过全仓审计(grep 全部 .github/workflows/*.ymlif: 行):

位置 层级 表达式 判定
ci.yml ×7 job needs.filter.outputs.* PR #4928 已修
release.yml:342 job needs.release.outputs.published 已带 !cancelled()(#4900)
publish-smoke.yml:98 job needs.resolve.outputs.run == 'true' 唯一存量违规
ci.yml:1091,1126 step steps.console-dist.outputs.cache-hit 规则不覆盖
release.yml:99,313 step steps.*.outputs.* 规则不覆盖
publish-smoke.yml:106,159 step needs.resolve.outputs.report-sha 见下

step 级读 本 job 自己steps.*.outputs.* 被排除是对的:那里的隐式 success()(前一步挂了就别继续)通常正是作者要的语义。把它们一起扫会立刻产生一批需要豁免的假阳性,而带着长豁免名单出生的门禁离 #4690 的反模式只有一步之遥。

存量违规需要一个决策(所以没有顺手在 #4928 里修)

publish-smoke.ymlresolve job 与 ci.ymlfilter 不同:它的 outputs 没有 || 'true' 兜底(run: ${{ steps.target.outputs.run }}),而且 ref 也是它算出来的。所以 resolve 失败时:

即「存疑就全跑」在这里不像在 ci.yml 那样无条件正确,要先决定 resolve 失败时的正确兜底(补 || 默认值?失败时显式报一个红状态而不是跑?)。这属于该 workflow 自己的取舍,不该由 #4928 顺手拍板。

另外未核实的一点,留给接手的人:publish-smoke / packed-tarballs 这个 commit status 在 resolve 挂掉时根本不会被写(连 pending 都没有)。如果它在 release PR 上是必需检查,后果与「skipped 算通过」不同,需要单独确认。

验收

  • scripts/check-workflow-status-functions.mjs(或等价物):解析 .github/workflows/*.yml,对每个 job 的 if: 检查是否读 needs.*.outputs.* 且未点名状态函数,违规则退出非零并打 ::error file=...,line=...
  • 输入缺失必须大声失败(check:react-declaration-parity 是唯一没接进任何 workflow 的源码审计门禁,且无 MANIFEST 时静默 skip 退出 0 —— 它现在永远不可能红 #4690 反模式):扫不到任何 workflow 文件、YAML 解析失败、.github/workflows/ 不存在 —— 一律退出非零,绝不 exit 0 静默放过。
  • 有测试:至少一个「违规样本判红」+ 一个「合规样本判绿」+ 一个「输入缺失判红」的 fixture。
  • 真的接进某个 workflow 跑(lint.yml 最自然),而不是只躺在 scripts/ 里。
  • publish-smoke.yml:98 按上面的决策修掉,或写进脚本的显式豁免名单并附理由注释。

参考

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions