Build and test / Desktop (Linux) (push) Failing after 1h18m38s
Build and test / Layer separation (push) Successful in 48s
🐳 Android image / Build and push (push) Successful in 9m41s
Build and test / android-image (push) Successful in 9m41s
Traceability / Requirement traces (push) Successful in 48s
Build and test / Android (aarch64) (push) Successful in 21m54s
VS Code's rust-analyzer extension ships a server newer than 1.92.0 supports and prompts, on every window, to add one. Listing the component in rust-toolchain.toml makes rustup supply the server matching the pin — the same thing the pin buys everywhere else. The cost lands outside the editor, which is why this is four files rather than one. rustup reconciles that component list against the installed toolchain on the first cargo call in the work tree and downloads what is missing, inside whatever job happens to be running. An unasked-for fetch in the middle of a build step is nobody's line item and hard to find in a log. So every environment that builds this repo names it too: baked into the Android image, and in the install step of each CI job that rolls its own toolchain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
118 lines
4.3 KiB
YAML
118 lines
4.3 KiB
YAML
name: Traceability
|
|
|
|
# Mirrors JellyTau's traceability gate, including the reason it exists.
|
|
#
|
|
# That gate divided a traced count by frozen literal denominators while the
|
|
# requirements file grew past them, reported 158% coverage, and so could never
|
|
# fail its own threshold. Two rules follow, and the extractor's own tests
|
|
# enforce both:
|
|
#
|
|
# 1. Denominators are parsed from docs/requirements.md at run time.
|
|
# 2. Coverage is |traced ∩ defined| / |defined|, never a raw traced count.
|
|
#
|
|
# This job is static analysis of source comments plus markdown parsing, so it
|
|
# needs no GPU and no Android SDK — only the Rust toolchain.
|
|
|
|
on:
|
|
push:
|
|
branches: [main, master, develop]
|
|
pull_request:
|
|
branches: [main, master, develop]
|
|
|
|
jobs:
|
|
traceability:
|
|
runs-on: linux/amd64
|
|
name: Requirement traces
|
|
# Node for actions/checkout and actions/cache, which the bare runner image
|
|
# cannot execute. Rust is installed below.
|
|
container:
|
|
image: catthehacker/ubuntu:act-latest
|
|
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Cache cargo
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: |
|
|
~/.cargo/registry
|
|
~/.cargo/git
|
|
target
|
|
key: traces-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
|
|
|
|
# Source-comment and markdown parsing only, so the minimal profile is
|
|
# enough — no system libraries and nothing this job itself needs beyond
|
|
# cargo. rust-analyzer is here anyway because rust-toolchain.toml lists
|
|
# it: rustup installs that file's components on the first cargo call in
|
|
# the work tree regardless, and a download named in the install step
|
|
# beats the same download appearing unannounced inside the gate.
|
|
- name: Install Rust 1.92.0
|
|
run: |
|
|
set -e
|
|
curl -fsSL https://sh.rustup.rs | sh -s -- \
|
|
-y --no-modify-path --profile minimal --default-toolchain 1.92.0 \
|
|
--component rust-analyzer
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
|
|
# The gate's own arithmetic is the thing being trusted, so its tests run
|
|
# before it does. Untested gate logic is exactly how JellyTau's 158% went
|
|
# unnoticed for months.
|
|
- name: Test the extractor
|
|
run: cargo test -p traceability
|
|
|
|
# Structural failures are unconditional and do not depend on the coverage
|
|
# threshold: zero requirements parsed, zero files scanned, a ratio above
|
|
# 100%, or any orphan tag all fail the build. A misconfigured run must not
|
|
# report a plausible-looking 0%.
|
|
- name: Traceability gate
|
|
run: cargo run -q -p traceability -- check
|
|
|
|
- name: Regenerate matrix and check it is committed
|
|
run: |
|
|
set -e
|
|
cargo run -q -p traceability -- report
|
|
if ! git diff --quiet docs/traceability.md; then
|
|
echo ""
|
|
echo "docs/traceability.md is out of date."
|
|
echo "Run: cargo run -p traceability -- report"
|
|
git diff --stat docs/traceability.md
|
|
exit 1
|
|
fi
|
|
|
|
# Advisory, not blocking: not every file implements a requirement, and a
|
|
# tag on every function is noise that rots faster than it helps. Tag the
|
|
# unit that decides.
|
|
- name: Check changed files for tags
|
|
if: github.event_name == 'pull_request'
|
|
run: |
|
|
set -e
|
|
CHANGED=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" \
|
|
| grep -E '\.(rs|slint|wgsl)$' || true)
|
|
[ -z "$CHANGED" ] && { echo "No source files changed."; exit 0; }
|
|
|
|
MISSING=0
|
|
for file in $CHANGED; do
|
|
case "$file" in
|
|
*/tests/*|*/test_*|tools/*) continue ;;
|
|
esac
|
|
[ -f "$file" ] || continue
|
|
if ! grep -q 'TRACES:' "$file"; then
|
|
echo " no TRACES tag: $file"
|
|
MISSING=$((MISSING + 1))
|
|
fi
|
|
done
|
|
|
|
if [ "$MISSING" -gt 0 ]; then
|
|
echo ""
|
|
echo "$MISSING changed file(s) carry no requirement tag."
|
|
echo "Format: /// TRACES: FR-CAT-1, FR-CAT-2 | NFR-P1"
|
|
echo " (comma separates IDs, pipe groups types)"
|
|
fi
|
|
|
|
- name: Summary
|
|
if: always()
|
|
run: head -30 docs/traceability.md || true
|