1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
name: CI
# This binary sits in front of every shell command a coding agent runs, and
# it can refuse one. A broken build is felt the moment somebody opens a
# session, so everything that can be checked before a release is checked here.
on:
push:
branches:
pull_request:
# For `audit` above all. A new advisory against a crate in this tree
# appears without anybody pushing anything, so a push-only workflow learns
# about it whenever the next commit happens to land. Monday morning, so a
# finding is read at the start of a week rather than on a Friday.
schedule:
- cron: "17 6 * * 1"
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
# GitHub's DEFAULT shell is `bash -e {0}` — no pipefail. Only naming bash
# explicitly gets `bash -eo pipefail {0}`. Without this, a piped command
# reports the exit status of the LAST stage and a failing suite goes GREEN.
#
# Which is, with some irony, the exact mistake `pipe-to-tail` exists to
# refuse. It would be a poor look to ship it from a workflow that makes it.
defaults:
run:
shell: bash
permissions:
contents: read
jobs:
rust:
name: rust
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
# The behavioural coverage lives in `tests/`; these two keep the source
# honest. `make lint` runs EXACTLY these, so a contributor can reproduce
# a red `rust` job without pushing.
- run: cargo fmt --check
- run: cargo clippy --all-targets -- -D warnings
# The floor `rust-version` claims, compiled against.
#
# In the amont workspace this crate's floor was inherited from the
# dependency-free commit path, and its own manifest admitted CI never
# checked it: "this number has no guard and will drift the first time a
# dependency raises its own." It had drifted — every published version up
# to 1.18.2 claimed 1.74, which cargo 1.74 could not build. This job is why
# that cannot happen again.
#
# `--locked`, emphatically. amont's msrv job deleted Cargo.lock first,
# which was safe THERE because the crates it checked had no dependencies at
# all. Here a fresh resolve would silently pick whatever the registry offers
# today and prove nothing about what we ship.
msrv:
name: msrv (builds on 1.85.0)
runs-on: ubuntu-latest
timeout-minutes: 15
env:
# Keep in step with `rust-version` in Cargo.toml.
MSRV: "1.85.0"
steps:
- uses: actions/checkout@v4
- run: rustup toolchain install "$MSRV" --profile minimal --no-self-update
- run: cargo "+$MSRV" check --locked --all-targets
# NON-BLOCKING, deliberately: an advisory is information about a dependency,
# not a defect in the change under review, and a scheduled run that goes red
# on somebody else's disclosure trains people to ignore red. Findings surface
# as ::warning:: annotations. The RELEASE workflow's twin blocks instead —
# same parsing, opposite stakes.
#
# `cargo install --locked cargo-audit`, NOT a marketplace action. Pulling an
# unaudited third-party action — which runs with this workflow's token — into
# the job whose purpose is supply-chain hygiene is self-defeating.
audit:
name: cargo audit (advisory, non-blocking)
runs-on: ubuntu-latest
timeout-minutes: 20
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
~/.cargo/bin/cargo-audit
key: audit-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
- name: Install cargo-audit
run: command -v cargo-audit >/dev/null || cargo install --locked cargo-audit
- name: Audit
run: |
set -euo pipefail
cargo audit --json > audit.json || true
python3 - <<'PY'
import json, pathlib
d = json.loads(pathlib.Path("audit.json").read_text() or "{}")
vulns = d.get("vulnerabilities", {}).get("list", [])
for v in vulns:
adv = v.get("advisory", {})
print(f"::warning::{adv.get('id')} {adv.get('package')}: {adv.get('title')}")
warns = d.get("warnings", {})
for kind, items in warns.items():
for w in items:
adv = (w.get("advisory") or {})
print(f"::warning::{kind}: {adv.get('package') or w.get('package')}")
print(f"{len(vulns)} vulnerability advisories, {sum(len(v) for v in warns.values())} warnings")
PY
test:
name: tests (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
# All three, because this crate's answers are platform-shaped in ways a
# single runner would hide: `atomic.rs` carries file modes on unix and
# deliberately does not on Windows, the settings path differs, and the
# `guidance` shell-out resolves a program on PATH — which on Windows
# means `.exe` resolution rules that have broken this project before.
matrix:
os:
steps:
- uses: actions/checkout@v4
- id: attest
uses: fredericrous/attest@v1
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: cargo-test-${{ runner.os }}-${{ hashFiles('Cargo.lock') }}
# `git` is present on every GitHub runner, and the tests need it: the
# stale-base rules build real repositories to ask real questions. They
# set GIT_CONFIG_GLOBAL=/dev/null themselves so a runner's config cannot
# change an answer.
- name: git identity for the fixtures
run: |
git config --global user.email ci@example.com
git config --global user.name CI
git config --global init.defaultBranch main
- if: ${{ !contains(fromJSON(steps.attest.outputs.gates), 'pre-push-cargo-test') }}
run: cargo test --locked