Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions docs/ci-integration-zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The echo "exit_code=$?" line is never reached when audit exits with 10, 1, or 2 because Actions runs the script with bash -e; the shell exits immediately after the failing audit command. Consequently steps.audit.outputs.exit_code is unset and the enforcement step cannot distinguish flagged audits from measurement or usage failures, so exit 10 is rejected instead of passing.

Triggers: When the audit returns any non-zero exit code, including the documented 10 review result.

Suggested fix: Disable errexit around the audit, capture $?, publish it, and then exit with the captured code so continue-on-error preserves the result for the enforcement step.

```

(`continue-on-error` 在这里什么都没吞——步骤先把退出码记下来,失败 outcome 加
`outputs.exit_code=10` 正是「有发现但不挡合并」的情形。`run:` 步骤 `exit 10` 会让步骤
失败但退出码仍可从 `outputs` 读到,没有信息丢失。)

`ponytail:` 两步版用 shell 重述了一遍退出契约;一步版才是诚实的
门禁。只有当 `10` 不该挡合并时才用两步版。

## 把报告挂到 PR 上
Expand All @@ -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
Expand All @@ -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 文件。

Expand Down
34 changes: 28 additions & 6 deletions docs/ci-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand All @@ -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.

Expand Down
Loading