Node 20 reached end of life on 2026-04-30. check-frontend.yml, audit.yml and release.yml now install Node.js 24 through NodeSource's setup_24.x, as check-rust.yml has done since #375. Each step still prints `node --version` in the job log. The concurrency comment of check-rust.yml claimed the key cancels obsolete runs on force-push. la-compagnie-maximus docs/forgejo-ops.md measured the opposite on Forgejo 8.0.3: no pull_request run was cancelled by a later one, and push runs are cancelled without the key. The comment now states that measurement; the keys stay (harmless, meaningful where honoured), and check-frontend.yml and audit.yml point to it. release.yml names the trust it places in NodeSource: the script runs as root in the job container, whose Node.js then runs `npx tauri build` with TAURI_SIGNING_PRIVATE_KEY. Kept on purpose, also documented in docs/architecture.md, whose release.yml section described two jobs (windows-latest, ubuntu-22.04) where there is one containerised job cross-compiling Windows with cargo-xwin. release.yml only runs on a tag, so Node 24 there is proven at the next release: the /release skill now reads `node --version` from the release run's log (step 9), and STATE.md lists it under the checks due at the next release. No CHANGELOG entry: CI only. Resolves #376 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
210 lines
10 KiB
YAML
210 lines
10 KiB
YAML
name: PR Check — Rust
|
|
|
|
# Rust half of the former check.yml (split in #232).
|
|
#
|
|
# Runs on PRs that change Rust, plus once a week on main to refresh the shared
|
|
# cache (see the note on the triggers). The runner has capacity 1 — jobs queue
|
|
# instead of running in parallel, so every minute spent here is a minute the
|
|
# next PR waits.
|
|
#
|
|
# No `branches:` filter on purpose. `branches: [main]` never matched a PR
|
|
# stacked on top of another feature branch, which is what /autopilot produces:
|
|
# 4 of the 5 PRs in the feature-gating milestone ran no CI at all.
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- 'src-tauri/**'
|
|
- '.forgejo/workflows/check-rust.yml'
|
|
# Whole directory, not just audit.toml: a later .cargo/config.toml
|
|
# (rustflags, linker, target dir) would change the Rust build, and an
|
|
# exact-path entry would let it skip Rust CI unnoticed.
|
|
- '.cargo/**'
|
|
# The weekly run is what feeds the cache to pull requests (#234). The runner
|
|
# (forgejo-runner v12.7.0) isolates what a pull_request run writes to that
|
|
# PR's ref: later pushes to the same PR read it back, other PRs never see it.
|
|
# A PR run falls back to the shared, non-isolated entries only when nothing
|
|
# matches in its own scope. Scheduled and dispatched runs write to the shared
|
|
# scope, which makes them this workflow's only writers of it (a merged PR's
|
|
# `closed` event would be another, but this workflow does not subscribe to
|
|
# it).
|
|
#
|
|
# The runner deletes an entry unused for 7 days or created more than 30 days
|
|
# ago, and keeps only the newest entry per key, whatever its scope; the next
|
|
# Monday run saves a fresh one. Cold runs that do not mean #234 is back (a
|
|
# timeout would):
|
|
# - a new stable Rust (the toolchain floats on `stable`) or a change to this
|
|
# job's `env:`: the key hashes rustc and the CARGO*, RUST*, CC*, CFLAGS*,
|
|
# CXX* and CMAKE* variables. Cold until the next Monday;
|
|
# - a PR run that saves under the shared entry's key evicts it for every PR
|
|
# until the next Monday. It saves when its own scope holds an older entry
|
|
# for another Cargo.lock, served before the shared one: a PR that bumped a
|
|
# dependency, then went back to main's lockfile. Two open PRs on the same
|
|
# lockfile, different from main's, evict each other the same way;
|
|
# - PR runs once an entry that only got exact hits, hence never re-saved,
|
|
# reaches 30 days;
|
|
# - after a week without any cache request, the Monday restore can fail with
|
|
# a 404: the lookup does not refresh the entry, and the cleanup it triggers
|
|
# may delete it (forgejo/runner#1741). The run then saves a fresh entry.
|
|
# workflow_dispatch re-seeds by hand.
|
|
schedule:
|
|
# Mondays 06:30 UTC, after audit.yml (06:00 UTC).
|
|
- cron: '30 6 * * 1'
|
|
workflow_dispatch:
|
|
|
|
# What this key does on Forgejo 8.0.3 / runner v12.7.0, as measured on
|
|
# 2026-10-02 (la-compagnie-maximus docs/forgejo-ops.md, sections "Le schéma du
|
|
# workflow est validé par le runner" and "Un push annule le run du push
|
|
# précédent"):
|
|
# - it passes the runner's schema validation, but what it adds has not been
|
|
# isolated;
|
|
# - without it, Forgejo cancels a `push` run when a newer one of the same
|
|
# workflow is created on the same ref, whether the older run's job was still
|
|
# queued or already running;
|
|
# - no pull_request (or pull_request_sync) job has been cancelled by a later
|
|
# run: on a PR, a force-push does not cancel the obsolete run.
|
|
# Kept anyway (#376): harmless here, and it keeps its GitHub Actions meaning
|
|
# should these workflows run where it is honoured (GitHub, a later Forgejo).
|
|
# There, a group shared with the frontend workflow would make the two cancel
|
|
# each other on a PR that touches both, hence the distinct group.
|
|
concurrency:
|
|
group: ci-rust-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
rust:
|
|
runs-on: ubuntu
|
|
container: ubuntu:22.04
|
|
env:
|
|
PATH: /root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
CARGO_TERM_COLOR: always
|
|
# rust-cache keeps the dependencies and drops this crate's own artifacts
|
|
# before saving, so incremental data would never be reused — pure
|
|
# overhead — and test debug info would only make the cached target/
|
|
# bigger.
|
|
CARGO_INCREMENTAL: 0
|
|
CARGO_PROFILE_TEST_DEBUG: 0
|
|
steps:
|
|
- name: Install system dependencies, Node.js and Rust
|
|
run: |
|
|
apt-get update
|
|
# zstd: the cache archives are compressed with it when present,
|
|
# faster and smaller than the gzip fallback.
|
|
apt-get install -y --no-install-recommends \
|
|
curl wget git ca-certificates build-essential pkg-config \
|
|
libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev libssl-dev \
|
|
libdbus-1-dev zstd
|
|
# Node.js runs the JavaScript actions: actions/checkout (node20) and
|
|
# Swatinem/rust-cache (node24 since v2.9). The runner starts them
|
|
# with whatever `node` the container has, so install the newest
|
|
# runtime they declare. taiki-e/install-action is a composite action.
|
|
curl -fsSL https://deb.nodesource.com/setup_24.x | bash -
|
|
apt-get install -y nodejs
|
|
# Rust toolchain
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal
|
|
node --version
|
|
rustc --version
|
|
cargo --version
|
|
|
|
- name: Checkout
|
|
uses: https://github.com/actions/checkout@v4
|
|
|
|
# Caches ~/.cargo (registry, git, bin) and src-tauri/target, keyed on the
|
|
# rustc version, the job's CARGO*/RUST* environment, the dependencies
|
|
# declared in the Cargo manifests and lockfile (not the app's own
|
|
# version), and this job. Saved only when the job succeeds and the key
|
|
# was not an exact hit. Served by
|
|
# the runner's cache proxy on port 28100, which ufw opens to the Docker
|
|
# job networks only; until 2026-10-03 every restore and save timed out
|
|
# there (#234, la-compagnie-maximus docs/forgejo-ops.md). What a PR run
|
|
# saves stays private to that PR: see the note on the triggers.
|
|
- name: Rust cache
|
|
uses: https://github.com/Swatinem/rust-cache@v2
|
|
with:
|
|
workspaces: "src-tauri -> target"
|
|
|
|
- name: cargo check
|
|
run: cargo check --manifest-path src-tauri/Cargo.toml --all-targets
|
|
|
|
# Anti-rot guard for the suppressions in .cargo/audit.toml (#310). Each
|
|
# entry there is justified by its crate being absent from every shipped
|
|
# target's graph — a property of today's resolved graph, not a permanent
|
|
# one. rsa is currently reachable from nothing: its only parent in the
|
|
# lockfile is sqlx-mysql, which this SQLite project never compiles. If a
|
|
# dependency change ever pulls it into a shipped target, the suppression
|
|
# would silently hide a live advisory and the daily audit would stay
|
|
# green: the inverse of the permanent red #310 exists to kill.
|
|
#
|
|
# Such a change would itself touch src-tauri, which is exactly what
|
|
# triggers this workflow. Runs after cargo check so the registry index is
|
|
# already warm, and --locked so cargo tree cannot rewrite the lockfile the
|
|
# audit was taken against.
|
|
#
|
|
# CRATES must mirror the crates named in .cargo/audit.toml. Adding an
|
|
# entry there without adding its crate here leaves it unguarded.
|
|
#
|
|
# Every check echoes its result, including the passing ones. A guard that
|
|
# is silent on success cannot be told apart in the log from a guard that
|
|
# never ran — which is the same silent-skip failure mode this step exists
|
|
# to catch, one level up.
|
|
- name: Verify suppressed advisories are still unreachable
|
|
run: |
|
|
set -u
|
|
CRATES="rsa"
|
|
TARGETS="x86_64-unknown-linux-gnu x86_64-pc-windows-msvc"
|
|
rc=0
|
|
checks=0
|
|
for crate in $CRATES; do
|
|
for target in $TARGETS; do
|
|
# An absent crate exits 0 with empty stdout ("nothing to print"
|
|
# goes to stderr). A non-zero exit means cargo tree itself failed
|
|
# — treat that as a failure rather than as proof of absence.
|
|
out=$(cargo tree --manifest-path src-tauri/Cargo.toml --locked \
|
|
-i "$crate" --target "$target" 2>/dev/null) || {
|
|
echo "cargo tree failed for $crate / $target — cannot verify the suppression"
|
|
rc=1
|
|
continue
|
|
}
|
|
if [ -n "$out" ]; then
|
|
echo "FAIL: $crate is now compiled for $target — its .cargo/audit.toml suppression is no longer justified (see #310)."
|
|
rc=1
|
|
else
|
|
echo "ok: $crate absent from $target"
|
|
fi
|
|
checks=$((checks + 1))
|
|
done
|
|
done
|
|
# Canary: a crate known to be present. If this stops being found, the
|
|
# loop above is broken and its silence means nothing.
|
|
canary=$(cargo tree --manifest-path src-tauri/Cargo.toml --locked \
|
|
-i tar --target x86_64-unknown-linux-gnu 2>/dev/null) || true
|
|
if [ -z "$canary" ]; then
|
|
echo "FAIL: canary 'tar' was not found although it is a known dependency. The guard is not proving anything."
|
|
rc=1
|
|
else
|
|
echo "ok: canary 'tar' found for x86_64-unknown-linux-gnu"
|
|
fi
|
|
echo "Suppression guard: $checks checks, exit $rc"
|
|
exit $rc
|
|
|
|
- name: cargo test
|
|
run: cargo test --manifest-path src-tauri/Cargo.toml --all-targets
|
|
|
|
# Prebuilt binary. `cargo install --locked cargo-audit` recompiled the
|
|
# tool from source on every single run (~4m40s). No continue-on-error:
|
|
# a tooling failure should fail the job rather than be swallowed.
|
|
- name: Install cargo-audit
|
|
uses: https://github.com/taiki-e/install-action@v2
|
|
with:
|
|
tool: cargo-audit
|
|
|
|
# Advisories are informational — they can land on unrelated crates and
|
|
# would otherwise stall unrelated work — so the step is non-blocking.
|
|
# It no longer hides real failures behind `|| true` though. Daily
|
|
# RustSec coverage outside Rust PRs lives in audit.yml.
|
|
- name: cargo audit
|
|
continue-on-error: true
|
|
run: cargo audit --file src-tauri/Cargo.lock
|