首页
/ Ruff ty 生态系统报告的证据冻结:如何精确钉住并验证 PR 生态分析的 GitHub Actions 证据

Ruff ty 生态系统报告的证据冻结:如何精确钉住并验证 PR 生态分析的 GitHub Actions 证据

2026-09-05 21:23:55作者:侯霆垣

本文基于 Ruff 仓库中 summarise-ecosystem-results 技能包的参考文档 evidence-acquisition.md,系统讲解 ty 生态分析(ty ecosystem-analyzer)结果汇总流程中的第一步——“证据冻结”:如何优先保存用户显式提供的报告与 PR 评论、如何精确定位对应的 Actions run 与 attempt、如何验证并下载不可变的构建产物(artifact),以及为什么必须优先使用所选 attempt 的 diff.json 结构化差异。读完本文,你将掌握一套可复制的取证操作序列(含完整 bash 脚本),能独立复现一次历史生态分析的完整证据链,并理解其背后的产物替换机制与验证原则。

背景:为什么需要“冻结”生态系统证据

Ruff 项目对每次涉及 crates/ty*/** 等路径变更的 PR 都会触发 ty-ecosystem-analyzer.yaml 工作流:分别构建 merge base 与 PR 两个 revision 的 ty profiling 二进制,然后在数百个真实开源项目上运行两者并比对诊断输出,最终生成结构化的 diff.json 与人类可读的 diff.html 报告,写入 full-report artifact,并把摘要评论(ecosystem-results comment)回贴到 PR。周期性全量报告则由 ty-ecosystem-report.yaml 每周三生成并上传 full-report artifact(内含 dist/index.html)。

问题在于:PR 上“当前可见的报告”并不等于“你要分析的那一次运行产生的报告”。重新运行(rerun)会创建新的 attempt,新的 attempt 可以替换旧 attempt 的产物,而 Ruff 的 base/PR revision 甚至可能保持不变;PR 评论也可能被编辑或覆盖。因此,SKILL.md 定义的工作流把“Freeze the evidence(冻结证据)”列为第一步,而本文档 evidence-acquisition.md 正是这一步的操作细则:把所有证据钉死到唯一的 run、attempt、artifact 上,后续的复现、最小化与报告撰写(report-template.md)全部基于冻结下来的快照,而不是任何“实时”数据。

第一步:报告来源的优先级与 run/attempt 定位

文档开篇给出了三条来源裁决规则:

  1. 用户显式提供的报告/评论最高优先:在汇总请求开始时,立即保存用户在请求中明确给出的详细报告或 ecosystem-results 评论。只有当用户没有提供任何报告或评论时,才去查找 PR 当前的评论。
  2. 绝不替换:一旦选定某份已部署的报告,就要识别与其匹配的 PR、Actions run 与 attempt;永远不要用更新的评论、更新的报告、更新的 PR revision、更新的工作流运行或更新的 report 尝试来替换它。
  3. 不确定就如实报告:如果匹配用户显式报告的评论已不存在,继续使用该报告本身,并记录“匹配评论不可用”;如果无法唯一确定对应的 Actions run 或 attempt,应报告这一不确定性,而不是退回“当前 PR 报告”或从恰好匹配的 Ruff revision 进行猜测。

这套规则与 SKILL.md 中“忽略后续评论编辑、PR 更新与工作流运行”的要求一致:证据一经选定,后续分析对一切“更晚出现的同类证据”免疫。

第二步:创建唯一快照目录并保存元数据

定位到 run 与 attempt 后,文档给出一段可直接执行的 bash 序列,建立唯一的临时快照目录,并把三份关键元数据落盘:

set -euo pipefail

snapshot_dir="$(mktemp -d "${TMPDIR:-/tmp}/ty-ecosystem-report.XXXXXX")"
ecosystem_comment_id="<matching-comment-id-or-empty>"
if [[ -n "$ecosystem_comment_id" ]]; then
  GH_TELEMETRY=false gh api "repos/astral-sh/ruff/issues/comments/$ecosystem_comment_id" > "$snapshot_dir/comment.json"
fi
GH_TELEMETRY=false gh run view <actions-run> --repo astral-sh/ruff --attempt <actions-attempt> \
  --json attempt,headSha,jobs,startedAt,updatedAt,url > "$snapshot_dir/run.json"
GH_TELEMETRY=false gh api --paginate --slurp \
  "repos/astral-sh/ruff/actions/runs/<actions-run>/artifacts?per_page=100" |
  jq '{artifacts: [.[].artifacts[]]}' > "$snapshot_dir/artifacts.json"
printf 'TY_ECOSYSTEM_SNAPSHOT_DIR=%s\n' "$snapshot_dir"

逐段说明:

  • mktemp -d 保证每次取证都有唯一的快照目录,避免多次分析互相污染;目录路径通过 TY_ECOSYSTEM_SNAPSHOT_DIR 环境变量对外发布,供后续子代理(见 subagent-handoff.md)以只读方式共享。
  • 若存在匹配的 ecosystem-results 评论,用 gh api 按评论 ID 直接拉取 JSON 并保存为 comment.json。脚本中所有 gh 调用都带 GH_TELEMETRY=false 前缀——这是该技能包的硬性规定(SKILL.md 要求包括子代理在内的一切直接或间接触发 gh 的调用都必须加此前缀,且因为不同工具调用可能各自开 shell,export 不会自动继承)。
  • gh run view <actions-run> --attempt <actions-attempt> --json ... 固化该次运行在该 attempt 下的 attemptheadShajobs(有效作业图)、startedAtupdatedAturl。注意这里显式带 --attempt:不带该参数的 gh run view 默认取最新 attempt,正是需要规避的“取到新证据”陷阱。
  • gh api --paginate --slurp 分页拉全该 run 的全部 artifact 元数据(含不可变的 id 与创建时间 createdAt),供下一步做产物—作业的匹配验证。
  • 注意脚本中的 API 仓库名 astral-sh/ruff 是上游仓库地址;本地工作副本的绝对路径、二进制路径等由后续流程另行发布,快照本身只记录这些“指向性”元数据。

核心原则:下载前必须完成的两类产物验证

文档特别强调:gh run download 无法按 attempt 选择,而一次较新的 rerun 可以在 Ruff revision 完全不变的情况下,替换掉旧 attempt 的 artifact。因此,在真正下载任何 artifact 之前,必须基于**有效作业图(effective job graph)**完成验证,具体是:

  1. full-report 必须创建于所选 attempt 的 report 生成作业期间;
  2. 每一个 diagnostics shard 必须创建于其对应的、成功的 shard 作业期间。

这里有两个容易踩错的判断细节:

  • 以有效作业图为准,而不是 attempt 的启动时间。部分重跑(partial rerun)会合法地继承早期 attempt 中已成功的作业及其 artifact——某个 shard 的 createdAt 早于所选 attempt 的 startedAt 并不意味着它不属于这次运行,必须看它是由作业图里哪个作业产出的。
  • 保留每个已验证 artifact 的不可变 ID,下载时永不按可变的 artifact 名称重新解析。同名 artifact 在 rerun 后可能指向完全不同的 zip 包,只有 actions/artifacts/<id>/zip 这种按 ID 取件的方式才能保证拿到冻结时验证过的那一份。

第三步:按 artifact ID 下载已验证的产物

验证通过后,文档给出一个通用下载函数,直接以 artifact ID 拉取 zip 并解包:

download_validated_artifact() {
  local artifact_id="$1"
  local destination="$2"

  mkdir -p "$destination"
  GH_TELEMETRY=false gh api "repos/astral-sh/ruff/actions/artifacts/$artifact_id/zip" \
    > "$snapshot_dir/artifact-$artifact_id.zip"
  unzip -q "$snapshot_dir/artifact-$artifact_id.zip" -d "$destination"
}

download_validated_artifact <validated-full-report-id> "$snapshot_dir/full-report"
download_validated_artifact <validated-shard-id> \
  "$snapshot_dir/shards/diagnostics-shard-<number>"

full-report 解到 $snapshot_dir/full-report,每个 diagnostics shard 解到 $snapshot_dir/shards/diagnostics-shard-<number>。这些路径随后就是整个调查过程中唯一被信任的本地证据来源。

第四步:HTML 字节比对与精确运行元数据的收集

选定并下载的产物还要通过一道交叉校验:如果当时选定的已部署 HTML 报告是可得的话,在信任其相邻 JSON 之前,先把它与 artifact 里的 diff.html 做逐字节比较。只有两者一致,才能确认这份 artifact 就是你分析对象的那次运行生成的。

接下来调用仓库自带的元数据收集脚本 scripts/collect_ty_ecosystem_run_metadata.py

  • 该脚本通过 gh run view 拉取指定 run(可用 --attempt <actions-attempt> 指定 attempt),并且有严格的前置校验:工作流名必须是 ty ecosystem-analyzer、运行状态必须为 completed,否则直接以 MetadataError 失败退出(见 collect_ty_ecosystem_run_metadata.pyparse_run_referencecollect_metadata 的校验逻辑);
  • 它从该 attempt 的 Build ty (base) 作业日志中解析 Merge base:PR commit: 两个 SHA,从 shard 作业日志中解析 EXCLUDE_NEWER 时间戳与 ecosystem-analyzer revision,并交叉核对 build 与 shard 日志中的 merge base 是否一致(不一致即报错,见 collect_ty_ecosystem_run_metadata.py);
  • 按文档要求,--attempt <actions-attempt> 必须传入,且对所有需要复现的项目只运行一次——产出的不可变 manifest 随后作为共享只读输入分发给所有子代理(这是 subagent-handoff.md 中“主代理负责发布 TY_ECOSYSTEM_RUN_METADATA 等不可变路径”约定的数据来源)。

最后一步一致性检查:验证冻结的 HTML 报告中记录的 Ruff base revision 与 PR revision,和上述不可变 manifest 中的值一致;此后,整个调查过程只使用保存下来的报告、shards、run、attempt(以及可得时匹配的评论),不再接触任何“实时”证据。

优先使用精确 attempt 的结构化 diff

文档的第二个主题,是解释 diff.jsondiff.html 的权威性与出处(provenance)规则:

  • 首选来源:当已验证的 full-report artifact 内含 diff.json 时,$snapshot_dir/full-report/diff.json 就是权威的结构化变更清单(authoritative structured change inventory),而相邻的 diff.html 是它的人类可读对应物。
  • 内容本身不携带出处diff.json 里不包含任何 Ruff revision、Actions run ID 或 attempt 编号——它的可信出处来自“已验证的 artifact + 与其匹配的 HTML 报告”这一组合,而不是来自 JSON 内容,更不是来自恰好一致的 PR revision。
  • 已部署的相邻 diff.json 只能有条件使用:部署站点也会在详细 HTML 报告旁边暴露一个 diff.json。只有当这个已部署 JSON 与所选 HTML 报告冻结自同一个不可变部署,且该部署能绑定到你选定的 Actions run 与 attempt 时,才能使用它;“当前 PR 的部署”“更晚的 artifact”或“Ruff commit 恰好匹配”都不足以建立出处。
  • 不要假设字段稳定:必须在你所锁定的 ecosystem-analyzer 精确 revision 上检查结构化报告以及 schema/diff 生成代码;不同 revision 之间 JSON 字段名与分类方式可能变化。
  • 记录分析模式:记录每个受影响项目是 strict 还是 non-strict 分析模式,在适用的比较方法描述中同时给出两个 strict 分析标志。
  • 降级路径:如果所选 attempt 的 JSON 报告或 artifact 不可用、已被替换,或 artifact 中的 HTML 与冻结的已部署报告不一致,则回退使用冻结的 HTML 报告,明确披露不可用的 shard 及由此带来的验证局限,并且永远不用其他 attempt 的 artifact 顶替

与仓库内工作流的对应关系

这套取证流程的每个环节都能在仓库中找到产出端对应物。从 ty-ecosystem-analyzer.yaml 可以看到:build-ty 作业以 revision: [base, pr] 矩阵分别构建两个 revision 的 --profile profiling 二进制(base 侧的 merge base 取 GITHUB_SHA^1,即“第一父提交”,以兼容堆叠 PR),并上传 ty-build-base(含 ty-basemerge-base.txt)与 ty-build-pr(含 ty-prprojects_flaky.txtty-ecosystem.toml)artifact;后续的 shard 作业与 report 生成作业则产出各 diagnostics shard 与 full-reportty-ecosystem-report.yaml 中的全量报告同样以 name: full-report 上传 dist/ 目录——这也是文档中“验证 full-report 由 report 生成作业创建”所针对的产物。正因为 artifact 名称是可变的(rerun 可复用同名),而 collect_ty_ecosystem_run_metadata.py 这类脚本又能独立地从作业日志中恢复出 merge base、PR revision、EXCLUDE_NEWER 等地面真值,才有了文档中“按不可变 ID 取件 + 用 manifest 交叉验证”的完整闭环。

小结:一条可审计的证据链

evidence-acquisition.md 的规程串起来,得到一条每一步都可独立核验的证据链:

  1. 以用户显式提供的报告为锚点,唯一确定 PR、Actions run 与 attempt,不确定时如实上报而非猜测;
  2. 用唯一 mktemp 快照目录保存评论 JSON、指定 attempt 的运行 JSON 与全量 artifact 元数据;
  3. 以有效作业图(而非时间戳)验证 full-report 与各 shard 的作业归属,锁定不可变 artifact ID;
  4. 仅按 ID 下载,随后对 HTML 做逐字节比对,并用 collect_ty_ecosystem_run_metadata.py --attempt 生成唯一一份不可变 manifest,确认 base/PR revision 一致;
  5. 以该 attempt 的 full-report/diff.json 为权威结构化清单,diff.html 为可读对照;JSON 缺失或被替换时降级到冻结的 HTML 并披露局限。

这套做法的核心思想是:生态分析汇总的结论必须能够追溯到某一次运行、某一个 attempt、某一份按 ID 固定的产物,而不是“PR 上现在显示的东西”——这正是历史 PR 行为变化调查、复现与最小化工作能够并行且互不干扰的前提。

登录后查看全文
热门项目推荐
相关项目推荐