The traceability job now runs `tools/manual/record.sh --check`: every picture docs/manual/README.md shows must be made by a scene in tools/manual/scenes.py, and every picture a scene makes must be shown. It reads the two files and nothing else, so it needs no app, display or LFS pull. --changed now dates a scene by the newest commit among its pictures rather than each picture alone. A scene that also makes a picture which re-records byte for byte (panorama-aligned beside panorama.gif) no longer stays listed for ever. A scene all of whose pictures come out identical (launch) stays listed until one differs, which costs one harmless re-run.
146 lines
5.8 KiB
YAML
146 lines
5.8 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/dev/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%.
|
|
# Every picture the manual shows is made by a scene in
|
|
# tools/manual/scenes.py, and every picture a scene makes is shown.
|
|
# Two files read; no app, no display.
|
|
- name: Manual pictures have scenes
|
|
run: tools/manual/record.sh --check
|
|
|
|
- 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/dev/traceability.md; then
|
|
echo ""
|
|
echo "docs/dev/traceability.md is out of date."
|
|
echo "Run: cargo run -p traceability -- report"
|
|
git diff --stat docs/dev/traceability.md
|
|
exit 1
|
|
fi
|
|
|
|
# The gesture vocabulary, from the same scanner and under the same rule.
|
|
#
|
|
# Blocking, and for a sharper reason than the matrix: these two artefacts
|
|
# are not only read, one of them is *shown to the user*. A stale
|
|
# `gesture_book.rs` is a help sheet in the application telling somebody to
|
|
# perform a gesture that was removed — worse than no help sheet, because
|
|
# they will conclude the application is broken rather than the page.
|
|
#
|
|
# This also fails on a malformed tag, so a typo costs a gesture its
|
|
# desktop half loudly rather than silently — and on a key a Slint
|
|
# handler binds that no tag names, or a key a tag names that no handler
|
|
# binds (tools/traceability/src/keymap.rs).
|
|
- name: Regenerate the gesture vocabulary and check it is committed
|
|
run: cargo run -q -p traceability -- gestures-check
|
|
|
|
# The manual's page, which the packages carry and the help sheet links
|
|
# into. Blocking for the gesture book's reason: it is shown to the user,
|
|
# and a page that disagrees with the README is a manual describing an
|
|
# application that no longer exists.
|
|
- name: Regenerate the manual page and check it is committed
|
|
run: cargo run -q -p traceability -- manual-check
|
|
|
|
# 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/dev/traceability.md || true
|