-
Notifications
You must be signed in to change notification settings - Fork 0
307 lines (299 loc) · 19.1 KB
/
Copy pathdev.yml
File metadata and controls
307 lines (299 loc) · 19.1 KB
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# Fast gate for the oauth-as repository. Runs on every push to dev and on every pull
# request, so it is deliberately kept quick: fmt, clippy, and the workspace test suite only.
# The full suite (MSRV, conformance, packaging, third-party tooling) lives in qa.yml and runs
# only on the qa branch, where speed matters less than thoroughness.
#
# NO continue-on-error anywhere: every step is load-bearing and a failure is RED.
#
# HOW THIRD-PARTY ACTIONS ARE PINNED HERE, and why the two classes are treated differently.
#
# A `uses:` reference is arbitrary code running with this job's token and this job's checkout. A
# TAG is not an identity: the owner of the repository can move it to a different commit at any
# time and every workflow that names it silently starts running the new code. A BRANCH (which is
# what `@stable` and `@nightly` are on dtolnay/rust-toolchain) is the same thing without even the
# pretence of stability. So every action NOT under the `actions/` organisation is pinned to a
# full-length commit SHA, with the human-readable ref in a trailing comment so the next person can
# see what it was meant to be and diff it.
#
# `actions/*` stay on major tags. They are published by GitHub itself, from the same trust root
# that runs the workflow at all: if that account is compromised, a pinned SHA in this file buys
# nothing, because the runner, the token service and the tag are all downstream of it. Pinning
# them would cost a dependency-bump treadmill for no threat it removes.
#
# dtolnay/rust-toolchain NEEDS `toolchain:` spelled out once it is pinned by SHA: the action reads
# the wanted toolchain from the REF it was invoked by, so `@<sha>` alone silently installs its
# default (stable) whatever the job intended. The SHA below is a master commit, which that
# action's README requires: its `stable`/`nightly`/`1.75` branch tips are outside master's history
# and get garbage collected.
#
# When a pin needs moving: `gh api repos/<owner>/<repo>/commits/<ref> --jq .sha`.
name: dev fast gate
on:
push:
branches: [dev]
pull_request:
concurrency:
group: dev-${{ github.ref }}
cancel-in-progress: true
# LEAST PRIVILEGE FOR THE JOB TOKEN, at the workflow level so a new job inherits it.
#
# With no `permissions:` block at all, every job gets whatever the REPOSITORY SETTINGS say the
# default GITHUB_TOKEN scope is, which is a value nobody reviewing this file can see and which
# GitHub has historically defaulted to read/write on older repositories. Nothing in this workflow
# writes anything: it lints, tests and measures. Stating that here means a reviewer can read the
# token's power out of the reviewed tree instead of out of a settings page, and it means a step
# that later needs more has to ask for it in a diff.
permissions:
contents: read
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: "-D warnings"
jobs:
lint-and-test:
name: fmt, clippy, test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
# THE FEATURE LIST HAS FOUR HAND-MAINTAINED MIRRORS, and this is what stops them drifting.
# The rule stated in the comment on the no-backend step below (anything added to [features]
# in crates/oauth-as/Cargo.toml belongs there too, unless it pulls an ES256 backend in) was
# enforced by nothing, and neither was the same list in qa.yml, the same list AGAIN in
# publish.yml, or the `f-*` mirrors in scripts/size-probe/Cargo.toml. A feature added to the
# crate and to none of them escapes the no-backend job, the size gate, or both, silently.
# That has already happened twice here; see the script's header for both escapes.
#
# The script has since grown past the feature lists themselves, because each of these was a
# way for the same class of miss to survive: it reads the no-rar list as well as the
# no-backend one in all three workflows, the ALL_FEATURES line in scripts/size-report.sh, a
# sixteen-entry table of which exerciser reaches each size-probe row, and the three lockfiles
# that must all name this version (every gate runs `--locked`, so a stale satellite lockfile
# fails the job before it starts rather than on its merits).
#
# The script derives the exclusion rather than listing it: a feature is excluded from the
# no-backend list exactly when its closure reaches `jwt-p256`. A fourth hand-maintained list
# would have made this worse rather than better.
#
# FIRST STEP, like qa.yml's workflow drift check: it needs no toolchain and no cache, so a
# drifted list is named in seconds rather than after the compile it was supposed to gate.
- name: feature lists must match the crate manifest (feature drift)
run: python3 scripts/feature-mirrors.py
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master, 2026-08-05
with:
toolchain: stable
components: rustfmt, clippy
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2, 2026-08-06
- name: rustfmt
run: cargo fmt --all --check
- name: clippy (deny warnings)
run: cargo clippy --workspace --all-targets --locked -- -D warnings
- name: tests
run: cargo test --workspace --locked
# ALL FEATURES TOO, and this is not belt and braces. Thirteen of this crate's capabilities
# are behind off-by-default cargo features, so a default-features run exercises a minority
# of the code: MEASURED on the 0.9.2 tree with `cargo test -p oauth-as` against the same
# command plus `--all-features`, that is 494 tests against 1335. A regression in
# DPoP, mTLS, PAR, RAR, token exchange or consent would otherwise reach `qa` unnoticed,
# which defeats the point of having a fast gate at all.
- name: clippy (all features)
run: cargo clippy --workspace --all-targets --locked --all-features -- -D warnings
- name: tests (all features)
run: cargo test -p oauth-as --locked --all-features
# THE NO-BACKEND BUILD, which --all-features cannot reach. With `jwt-p256` compiled in,
# `P256Verifier` is installed as the default and "no ES256 backend" is not a state the server
# can be in. This step is the only place it is, so it is the only place `client-assertion`
# without a backend, `dpop` without a backend, and `jar` without a backend are ever executed.
#
# THE FEATURE SET IS "EVERYTHING EXCEPT THE BACKEND", and it is written that way on purpose
# rather than as a hand-picked list. It used to read `dpop,jar,client-assertion,par,test-util`
# and the omission that mattered was `http`: `tests/wire_reachability.rs` is
# `#![cfg(feature = "http")]`, so the ONE suite whose job is to prove that every capability
# the RFC 8414 document advertises is reachable over the wire was compiled out of the ONE
# configuration where the advertisement can be false. `client_secret_jwt` then shipped broken
# in exactly that configuration, refused by a server whose own metadata invited clients to
# use it, and the gate built to catch that class could not see the class.
#
# So the rule for this line is now a rule and not a list: it is `--all-features` minus
# `jwt-p256` and minus `jwt-pkcs8` (which requires it). Anything added to [features] in
# crates/oauth-as/Cargo.toml belongs here too unless it pulls an ES256 backend in.
# `axum` rather than `http` because it implies `http` and adds the router adapter's own
# tests, which are gated `#![cfg(feature = "axum")]` and were compiled out here for the same
# reason.
- name: tests (every feature EXCEPT the ES256 backend)
run: |
cargo test -p oauth-as --locked --features \
axum,jwt,mtls,par,jar,client-assertion,dpop,resource-metadata,token-exchange,consent,rar,cimd,test-util
# EVERY FEATURE EXCEPT `rar`, and this line exists because two test files had never run.
#
# RFC 9396 section 5 makes refusal a MUST for a detail type the server does not support, and
# the build supporting NONE of them is the build without `rar` -- which is `default = []`,
# the shipped default. The audit found the parameter accepted and ignored at three doors,
# fixed all three, and wrote the proofs into two files gated `not(feature = "rar")`.
#
# Neither ever ran. Every `cargo test` above is one of three feature sets: the default
# (`http` off, so the first file compiles to nothing), `--all-features` (has `rar`), and the
# no-backend line (has `rar`). The second file needs `jar` AND `jwt-p256` without `rar`, a
# combination no step names, so it was never even COMPILED. The clippy feature loop
# type-checks the first file but does not execute it, and a test that is type-checked is a
# test nobody has watched pass.
#
# The rule for this line: `--all-features` minus `rar`. Anything added to [features] belongs
# here too, unless it is `rar` itself.
- name: tests (every feature EXCEPT rar, so the RFC 9396 s5 refusals actually run)
run: |
cargo test -p oauth-as --locked --features \
axum,jwt,jwt-p256,jwt-pkcs8,mtls,par,jar,client-assertion,dpop,resource-metadata,token-exchange,consent,cimd,test-util
# THE MUTUALLY-EXCLUSIVE PAIRS, which no other step in any workflow REACHES.
#
# Four tests were gated on feature combinations that every running configuration
# contradicted: `all(dpop, not(mtls))` and `all(mtls, not(dpop))` in
# introspection_feature_bounds.rs, and `not(jwt)` under `#![cfg(http)]` in
# http_advertised_reachability.rs. Default, --all-features, no-backend and no-rar all have
# dpop and mtls together or neither, and every set with http also has jwt. The clippy
# combination loop COMPILES them, which is why they type-checked forever; clippy does not RUN
# tests, so nothing ever executed them. Proven by making each panic and watching all four
# configurations stay green.
#
# They are not decoration. They are the cases the top-level guards CANNOT reach: a
# certificate-bound token read as a plain bearer by a dpop-only build, the mirror image for
# mtls, and a jwks_uri advertised by a build that cannot serve one. Each is a silent
# downgrade, which is the failure those files exist to prevent.
#
# Scoped to the two files rather than a fourth full matrix: what is missing is REACH, not
# coverage, and a full run of each would cost minutes to execute tests the other steps
# already ran.
- name: the changelog must date the version being built
run: python3 scripts/changelog-dated.py
- name: tests (the feature pairs no other step reaches)
run: |
cargo test -p oauth-as --locked --features dpop --test introspection_feature_bounds
cargo test -p oauth-as --locked --features mtls --test introspection_feature_bounds
cargo test -p oauth-as --locked --features http --test http_advertised_reachability
# RUSTDOC, and it is a gate rather than a nicety. Nothing here ran rustdoc, and the cost of
# that was measurable: 23 warnings at 0.9.0, including public documentation pointing readers
# at pub(crate) items they cannot see, and two links to a type (`RouterBuilder`) renamed in
# the 0.9 rewrite and therefore dead since then. docs.rs is where most people meet this
# crate, and a rustdoc warning is the compiler saying the published documentation is wrong.
#
# --all-features because that is exactly what docs.rs builds (see the
# [package.metadata.docs.rs] table in crates/oauth-as/Cargo.toml), so this is the same
# rustdoc invocation modulo --cfg docsrs. --no-deps because a warning in somebody else's
# crate is not this gate's business.
- name: rustdoc (deny warnings)
env:
RUSTDOCFLAGS: "-D warnings"
run: cargo doc -p oauth-as --locked --all-features --no-deps
# The doc examples must COMPILE. They are `no_run`, so nothing here binds a socket, but a
# quickstart that does not compile is worse than no quickstart: it is a wrong answer with
# the authority of documentation behind it.
- name: doc tests (all features)
run: cargo test -p oauth-as --locked --all-features --doc
# FEATURE COMBINATIONS, and this job exists because of a real escape rather than on
# principle. Thirteen optional features means default and --all-features are two points in a
# large space, and code gated `#[cfg(feature = "a")]` inside a function only compiled under
# feature "b" is invisible to both. A `details` parameter used only under `rar` went out in
# a `http,jwt` build (exactly what the conformance server compiles) and neither matrix saw
# it; the conformance job caught it after the push.
#
# These are the combinations a real consumer plausibly picks, not an exhaustive product.
#
# CLIPPY --all-targets, NOT `cargo build`, and the difference was worth seven live errors.
# This loop ran `cargo build` until 2026-08-12, which compiles the LIBRARY only: no test
# target, no example, no bench. So the one matrix whose entire job is to visit the corners of
# the feature space never compiled the files that hold most of the `#[cfg(feature = ...)]`
# code, and `-D warnings` at any point in it was a promise about a third of the crate. When
# this line was changed to clippy, `--no-default-features --features http` failed immediately
# with six dead-code errors in tests/mutation_gaps_http_credentials.rs and one in
# tests/wire_reachability.rs, all of which had been sitting on `dev` green. That is the third
# escape of this exact shape here: an MSRV violation and a per-feature `-D warnings` failure
# both got through earlier for the same reason, a gate compiling less than it appeared to.
- name: clippy feature combinations (all targets)
run: |
set -e
# BOTH sides of the ES256 seam appear here on purpose: `jwt` is the trait surface with
# no crypto dependency, `jwt-p256` is the built-in backend, and a combination that
# compiles with one can fail with the other. `test-util,jwt` is listed because the
# signer conformance harness only exists when both are on.
for f in "" "http" "jwt" "jwt-p256" "http,jwt" "http,jwt-p256" "jwt,rar" \
"http,jwt,dpop" "jwt,mtls" "dpop,jar,client-assertion" \
"jwt,client-assertion" "http,jwt,par,jar" "jwt,token-exchange" \
"consent" "resource-metadata" "cimd" "test-util" "test-util,jwt"; do
echo "::group::features: ${f:-<default>}"
if [ -z "$f" ]; then
cargo clippy -p oauth-as --locked --all-targets --no-default-features -- -D warnings
else
cargo clippy -p oauth-as --locked --all-targets --features "$f" -- -D warnings
fi
echo "::endgroup::"
done
# The independent conformance harness, on EVERY push rather than only at `qa`.
#
# It was left out of the fast gate on the assumption it was slow. Measured: 13 seconds warm,
# and GitHub runs jobs in parallel, so it costs this gate no wall clock at all. Leaving the
# crate's central piece of evidence to a promotion branch meant a change could break the one
# thing an outsider is asked to judge this library by, and nobody would know until later.
#
# BOTH halves run, in this order, because a green from a gate nobody has watched go red is
# worth nothing: `--selftest` proves the suite fails against a deliberately nonconformant stub
# server, and only then does `--check` mean anything.
conformance:
name: independent conformance harness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master, 2026-08-05
with:
toolchain: stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2, 2026-08-06
- name: gate self-test (must go RED against a nonconformant server)
run: scripts/oauth-conformance.sh --selftest
- name: live black-box conformance
run: scripts/oauth-conformance.sh --check
# LINKED SIZE, gated rather than asserted, in the same spirit as tests/allocation.rs.
#
# WHAT IT MEASURES: scripts/size-report.sh links scripts/size-probe twice per feature set, once
# with oauth-as and once without, and compares byte-exact SECTION sizes. The probe EXERCISES
# every surface it measures, because `lto = "fat"` deletes anything nothing calls and a probe
# that merely enables a feature measures it at close to zero. The script's header has the whole
# method and the reasoning for every budget.
#
# WHY macOS AND NOT ubuntu, which is what every other job here runs on: code size is a property
# of the target's instruction encoding, so a budget measured on arm64 does not describe x86-64.
# The recorded budgets were measured on aarch64-apple-darwin, and the script REFUSES to run
# `--check` on a platform it has no budgets for rather than passing vacuously. Running it
# anywhere else would need a second set of budgets measured there, which is a real option and
# not what was done. It is a separate job, so it costs the rest of this gate no wall clock.
#
# THE HOUSE RULE, and it is the point of having this at all: when a change blows a budget, the
# CHANGE gets fixed. Raising a number here is allowed only with what bought it written down
# beside it, in scripts/size-report.sh.
size:
name: linked size budgets
runs-on: macos-latest
steps:
- uses: actions/checkout@v7
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master, 2026-08-05
with:
toolchain: stable
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2, 2026-08-06
with:
# The probe is its own workspace (see scripts/size-probe/Cargo.toml on why), and its
# per-configuration target directories live under target/size-report. Neither is what
# this action caches by default, and without both every run is a cold fat-LTO link of
# nine binaries.
workspaces: |
.
scripts/size-probe
cache-directories: target/size-report
# RED FIRST, exactly like the conformance, interop and MCP-scanner gates in qa.yml, and this
# was the one shell gate in the repository without it. --selftest re-runs --check with the
# budget forced to 1 byte and requires exit 1: if the row set is empty, the probe did not
# build, the section parser did not understand the object format, or the comparison is not
# wired to the exit status, that run exits 0 and this step is RED. Only then does the green
# below mean the budgets were checked. It costs one extra link of the `default` row.
- name: prove the size gate can go RED
run: scripts/size-report.sh --selftest
- name: linked size within budget
run: scripts/size-report.sh --check