# CI workflow generated by `autumn new {{project_name}}`.
#
# Runs on every push and pull request:
# - cargo fmt --check
# - cargo clippy -D warnings
# - cargo build
# - cargo test
#
# Toolchain is pinned to the project MSRV ({{rust_version}}) so local and CI
# builds stay in sync.
#
# A Postgres 16 service container is provisioned and DATABASE_URL is set so
# DB-dependent tests can opt in. Tests decorated `#[ignore]` are skipped by
# default; run `cargo test -- --ignored` to include them.
#
# Also enforced: `autumn a11y verify` (accessibility), `autumn routes audit`
# (route auth-coverage security manifest) — both run against the prebuilt
# `autumn` CLI installed mid-job — and `cargo deny check`, which fails the
# build when any crate in this app's dependency tree carries a known RustSec
# security advisory. That audit reads `deny.toml` at the repository root and
# checks whatever that file declares: advisories always, plus `[licenses]`,
# `[bans]` or `[sources]` once you uncomment them. `autumn doctor` runs the
# same policy locally. Waive a finding in `deny.toml`, never by weakening or
# deleting the step.
#
# The companion `.github/workflows/posture-gate.yml` takes the next step on
# pull requests: it diffs this app's security posture against the base branch
# and blocks a change that widens it until a reviewer acknowledges it on the
# pull request. See docs/guide/posture-gate.md.
#
# Optional extensions (not required for green CI on a fresh scaffold):
# - Agent authority (issue #1691): once any handler carries
# `#[agent_operable]` or the app mounts MCP, commit a manifest with
# `autumn agents manifest --manifest agent-authority-manifest.json` and add
# a `autumn agents manifest --check agent-authority-manifest.json` step
# here. `--check` is the gate: it is the only invocation that fails on
# drift, on a mutating MCP tool with no envelope, and on a binary that can
# act irreversibly with no audit sink. It is not enabled by default because
# it needs a committed baseline, which a freshly scaffolded app does not
# have yet.
# - Tailwind: add `autumn setup --tailwind` and run the downloaded binary
# before `cargo build` to compile CSS in CI.
# - Coverage: install cargo-llvm-cov and upload to Codecov.
name: CI
on:
# Run on every branch push and pull request so CI fires on the first push
# regardless of the repository's default branch name. Narrow this to specific
# branches (e.g. `branches: [main]`) once your branching model settles.
push:
pull_request:
env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1
DATABASE_URL: postgres://autumn:autumn@localhost:5432/{{crate_name}}_test
jobs:
ci:
name: CI
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: autumn
POSTGRES_PASSWORD: autumn
POSTGRES_DB: {{crate_name}}_test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@{{rust_version}}
with:
components: rustfmt, clippy
# `cache-bin` is off deliberately: it would cache $CARGO_HOME/bin, and the
# cargo-deny install below skips its verified download when the pinned
# version is already on PATH. The auditor this app's security gate trusts
# must come from that pin, not from a restored cache entry. Everything
# else (registry, git deps, target dir) is still cached.
- uses: Swatinem/rust-cache@v2
with:
cache-bin: false
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
# Dependency advisory gate: every crate in this app's tree — transitive
# included — is checked against the RustSec advisory database, and a known
# advisory that `deny.toml` does not explicitly waive fails the build. See
# `deny.toml` for how to read a finding and how to waive one.
- name: Install cargo-deny
uses: taiki-e/install-action@v2.85.5
with:
tool: cargo-deny@0.20.2
# The advisory database is the one part of this gate that needs the
# network, so fetching it is a separate, retried step. If it is still
# unreachable after the retries the job FAILS — the gate is fail closed:
# a dependency tree nobody could verify is never reported as clean.
- name: Fetch the RustSec advisory database
run: |
# cargo-deny falls back to a built-in default when it finds no config,
# which would audit this app under a policy with no waivers and fail on
# an advisory the scaffold already triaged. An app upgraded from a
# release before this gate existed has this workflow but not the
# policy file, so say so plainly instead of letting that happen.
if [ ! -f deny.toml ]; then
echo "::error::deny.toml is missing. It is this app's dependency advisory policy, and the audit below reads it. Copy the one \`autumn new\` generates (see docs/guide/supply-chain.md in the Autumn repository) into the project root and commit it."
exit 1
fi
cargo fetch
fetched=""
for attempt in 1 2 3; do
if output=$(cargo deny fetch db 2>&1); then
fetched="yes"
break
fi
echo "$output"
# cargo-deny loads deny.toml before it touches the network, so a
# malformed policy fails here on every attempt. Retrying that wastes
# a minute and blames the network for your typo.
if echo "$output" | grep -q 'deny\.toml'; then
echo "::error::deny.toml could not be loaded (see above) - a configuration error, not a network failure."
exit 1
fi
if [ "$attempt" -lt 3 ]; then
echo "advisory database unreachable (attempt $attempt/3); retrying in $((attempt * 10))s"
sleep "$((attempt * 10))"
fi
done
if [ -z "$fetched" ]; then
echo "::error::RustSec advisory database unreachable after 3 attempts - failing closed."
exit 1
fi
# Runs against the database fetched above, so a failure here is a real
# advisory rather than a network blip.
#
# The check list is derived from `deny.toml`: advisories always, plus
# every optional section the policy declares. `autumn doctor` derives it
# the same way, so uncommenting `[licenses]`, `[bans]` or `[sources]`
# widens the local check and this gate together — a lockfile cannot pass
# locally and fail here for a dependency reason.
- name: Audit dependencies (RustSec advisories and declared policy)
run: |
# Matches every TOML spelling of a section header or key: [bans],
# [ bans ], ["bans"], ['bans'], [bans.build], [[bans.deny]],
# bans.deny = …, bans = { … }. `autumn doctor` parses the file as
# TOML and reaches the same answer; grep cannot, so the two are held
# together by a test that runs this loop and doctor over the same
# policies. A commented line never matches.
checks="advisories"
for section in bans licenses sources; do
if grep -qE "^[[:space:]]*(\[\[?[[:space:]]*)?[\"']?$section[\"']?[[:space:]]*[].=]" deny.toml; then
checks="$checks $section"
fi
done
echo "policy sections in force: $checks"
cargo deny --offline check $checks
# Accessibility gate: install the release pinned to this app's own
# autumn-web version and scan raw html! markup for a11y violations.
# This is an enforcing gate: an a11y violation fails the job. Unlike
# posture-gate.yml's verdict job (issue #2495), this step and `routes
# audit` below get no "latest release" fallback: both compile and
# introspect the pull request's own code, the same as posture-gate.yml's
# `manifest` job, so — like that job — nothing here may run under a CLI
# this project's own compatibility check (`autumn doctor`) would call
# incompatible with this app's autumn-web version.
- name: Accessibility (a11y) verify
run: |
curl -fsSL "https://raw.githubusercontent.com/autumn-foundation/autumn/v{{autumn_version}}/scripts/install.sh" \
| sh -s -- --version "v{{autumn_version}}"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
# `a11y verify` and `routes audit` (used by the next step) are
# assumed to exist in the release pinned to this app's autumn-web
# version. Say so and fail — never skip — if either hasn't shipped
# in that release yet, rather than failing later with an
# unknown-subcommand error: a gate that waves a pull request
# through because its own tooling is too old is worse than one
# that is red.
if ! "$HOME/.local/bin/autumn" a11y verify --help >/dev/null 2>&1; then
echo "::error::the autumn CLI pinned to this app's autumn-web version (v{{autumn_version}}) has no \`a11y verify\` command yet, so this gate cannot run. Raise this app's autumn-web version and this workflow's pin to the release that introduced it."
exit 1
fi
if ! "$HOME/.local/bin/autumn" routes audit --help >/dev/null 2>&1; then
echo "::error::the autumn CLI pinned to this app's autumn-web version (v{{autumn_version}}) has no \`routes audit\` command yet, so the next step cannot run. Raise this app's autumn-web version and this workflow's pin to the release that introduced it."
exit 1
fi
"$HOME/.local/bin/autumn" a11y verify .
# Route auth-coverage gate (default-deny posture, issue #1604): every
# mounted route must carry a proven security classification (`gated`,
# `public`, or framework-owned) before it merges. A route someone forgot
# to annotate fails the build here instead of shipping silently. Reuses
# the CLI installed and probed by the a11y step above.
- name: Route auth coverage (security manifest)
run: autumn routes audit
- name: Build
run: cargo build
- name: Test
run: cargo test