From e2ea84337f32b28d8db92df1f9220f2c8e9a73b9 Mon Sep 17 00:00:00 2001 From: modusensus Date: Thu, 24 Sep 2026 13:10:28 +0800 Subject: [PATCH] docs(ci): fix both Sourcery findings on the two-step gate and artifact recipe --- docs/ci-integration-zh.md | 30 +++++++++++++++++++++++++----- docs/ci-integration.md | 34 ++++++++++++++++++++++++++++------ 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/docs/ci-integration-zh.md b/docs/ci-integration-zh.md index 5465258..76b6b1d 100644 --- a/docs/ci-integration-zh.md +++ b/docs/ci-integration-zh.md @@ -52,16 +52,31 @@ GitHub 把任何非零退出当作步骤失败,所以 `0` 显示绿,`10`、` npx --yes euthyna@latest audit --base "origin/${{ github.base_ref }}" --head HEAD continue-on-error: true id: audit - - name: 执行契约(10 = 留给人工审阅,其余非零失败) + - name: 执行契约(0 和 10 通过——10 留给人工审阅;1 和 2 失败) if: always() run: | - code=${{ steps.audit.outcome == 'success' && 0 || 1 }} - if [ "$code" -eq 0 ]; then exit 0; fi - echo "euthyna audit 未测成干净(或发现了 security 分类的历史);见上一步。" >&2 + code=${{ steps.audit.outcome == 'success' && 0 || steps.audit.outputs.exit_code }} + if [ "$code" -eq 0 ] || [ "$code" -eq 10 ]; then exit 0; fi + echo "euthyna audit 失败:退出码 $code(1 = 用法错误,修 workflow;2 = 无法测量,绝不当成干净)。见上一步。" >&2 exit 1 ``` -`ponytail:` 两步版用 shell 重述了一遍退出码,把 `10` 和 `2` 混在一起;一步版才是诚实的 +审计步骤必须自己公布退出码,否则执行步骤分不清 `10` 和 `1`/`2`——`outcome` 把所有 +非零结果压成一个「失败」布尔。公布形式: + +```yaml + - name: 变更面审计(删除代码来源 + 依赖锁定版本) + run: | + npx --yes euthyna@latest audit --base "origin/${{ github.base_ref }}" --head HEAD + echo "exit_code=$?" >> "$GITHUB_OUTPUT" + id: audit +``` + +(`continue-on-error` 在这里什么都没吞——步骤先把退出码记下来,失败 outcome 加 +`outputs.exit_code=10` 正是「有发现但不挡合并」的情形。`run:` 步骤 `exit 10` 会让步骤 +失败但退出码仍可从 `outputs` 读到,没有信息丢失。) + +`ponytail:` 两步版用 shell 重述了一遍退出契约;一步版才是诚实的 门禁。只有当 `10` 不该挡合并时才用两步版。 ## 把报告挂到 PR 上 @@ -71,6 +86,7 @@ GitHub 把任何非零退出当作步骤失败,所以 `0` 显示绿,`10`、` ```yaml - name: 变更面审计 run: | + set -o pipefail npx --yes euthyna@latest audit --base "origin/${{ github.base_ref }}" --head HEAD \ | tee audit-report.txt continue-on-error: true @@ -82,6 +98,10 @@ GitHub 把任何非零退出当作步骤失败,所以 `0` 显示绿,`10`、` path: audit-report.txt ``` +没有 `pipefail` 时,步骤的状态是 `tee` 的(永远 0),audit 的 `1`/`2`/`10` 会被吞掉、 +门禁假绿。Actions 的默认 shell 是 `bash -e`,**不**隐含 `pipefail`,所以要显式写 +`set -o pipefail`。 + 下游消费方(评论机器人、裁定 agent)要读 fact 而不是散文时,加 `--json` 并把 artifact 指向 JSON 文件。 diff --git a/docs/ci-integration.md b/docs/ci-integration.md index c42fd3d..03f5ea3 100644 --- a/docs/ci-integration.md +++ b/docs/ci-integration.md @@ -54,17 +54,34 @@ translate: npx --yes euthyna@latest audit --base "origin/${{ github.base_ref }}" --head HEAD continue-on-error: true id: audit - - name: Enforce the contract (10 = review, anything else non-zero fails) + - name: Enforce the contract (0 and 10 pass — 10 is for human review; 1 and 2 fail) if: always() run: | - code=${{ steps.audit.outcome == 'success' && 0 || 1 }} - if [ "$code" -eq 0 ]; then exit 0; fi - echo "euthyna audit did not measure clean (or found security-classified history); see the step above." >&2 + code=${{ steps.audit.outcome == 'success' && 0 || steps.audit.outputs.exit_code }} + if [ "$code" -eq 0 ] || [ "$code" -eq 10 ]; then exit 0; fi + echo "euthyna audit failed: exit $code (1 = usage error, fix the workflow; 2 = could not measure, never treat as clean). See the step above." >&2 exit 1 ``` -`ponytail:` the two-step form re-implements the exit code in shell to blur `10` vs `2`; the -one-step form is the honest gate. Use the two-step form only when `10` must not block merges. +The audit step must publish its own exit code or the enforcement step cannot tell `10` +from `1`/`2`; `outcome` collapses all non-zero results into one "failed" boolean. The +publishing form: + +```yaml + - name: Change-surface audit (deleted-code provenance + dependency pins) + run: | + npx --yes euthyna@latest audit --base "origin/${{ github.base_ref }}" --head HEAD + echo "exit_code=$?" >> "$GITHUB_OUTPUT" + id: audit +``` + +(`continue-on-error` still swallows nothing here — the step records the code first, and a +failure outcome with `outputs.exit_code=10` is exactly the "flagged, don't block" case.) +Note `exit 10` from a `run:` step fails the step but keeps the code readable in +`outputs`, so nothing is lost. + +`ponytail:` the two-step form re-implements the exit contract in shell; the one-step form +is the honest gate. Use the two-step form only when `10` must not block merges. ## Reading the report in the PR @@ -73,6 +90,7 @@ The run prints the human-readable report to the step log. To attach it to the PR ```yaml - name: Change-surface audit run: | + set -o pipefail npx --yes euthyna@latest audit --base "origin/${{ github.base_ref }}" --head HEAD \ | tee audit-report.txt continue-on-error: true @@ -84,6 +102,10 @@ The run prints the human-readable report to the step log. To attach it to the PR path: audit-report.txt ``` +Without `pipefail`, the step's status is `tee`'s (always 0), so a `1`/`2`/`10` from the +audit would turn the gate green. Actions' default shell is `bash -e`, which does **not** +imply `pipefail`, hence the explicit `set -o pipefail` line. + Add `--json` and point the artifact at the JSON file when a downstream consumer (a comment bot, an adjudicator agent) should read facts rather than prose.