Files
DarkRoom/.gitea/workflows/traceability-check.yml
T
dtourolle 10216355c1 Render the manual as one HTML page the application can carry
The manual existed only as docs/manual/README.md, which the forge renders
and nothing else does. An installed copy of the application, on a laptop
with no network or on a tablet, had no manual it could open.

`traces manual` renders the README to docs/manual/index.html with
pulldown-cmark (already in the tree as Slint's Markdown parser, so this
adds a dependency edge and no crate). The page is one file with an inline
stylesheet that follows the system's light or dark preference, a
contents list of every section and subsection, and the pictures by their
relative media/ paths. Each heading carries the id the forge gives it, so
README.md#rating-and-flagging and index.html#rating-and-flagging are the
same link. A picture alone in its paragraph becomes a figure whose alt
text is shown as the caption, and every picture reserves its 16:11 box
before it loads, so a jump into the middle of the page lands where it
aimed rather than a screenful above. Links to design documents, which the
installed page has no copy of, point at the forge.

The page is committed rather than rendered at build time, as the gesture
book is: it is user-facing text reviewed in the diff, and the three
packagers then only copy it. `traces manual-check` fails in CI when the
committed page is not the render of the README, and the pre-commit hook
regenerates it when the README is staged.
2026-09-24 22:32:30 -04:00

138 lines
5.4 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%.
- 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.
- 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