Files
DarkRoom/.gitea/workflows/traceability-check.yml
T
dtourolleandClaude Opus 5 091306f736
🐳 Android image / Build and push (push) Successful in 3s
Build and test / android-image (push) Successful in 3s
Build and test / Desktop (Linux) (push) Failing after 1h16m50s
Build and test / Layer separation (push) Successful in 39s
Traceability / Requirement traces (push) Successful in 50s
Build and test / Android (aarch64) (push) Successful in 22m27s
Gate the gesture vocabulary the way the matrix is gated
Three holes, all found by the gate catching itself out.

**Prose that mentions the tag was read as a tag.** `dr-ui`'s module list
carries a comment saying where the generated table comes from, and it
names `GESTURE:` in passing; the scan extracted that sentence fragment as
a gesture with no place and no way to perform it. A tag must now *open*
its comment. A line that merely mentions it is describing the mechanism,
not declaring a member of it, and position is the only thing that tells
the two apart — which also makes the string-literal guard fall out for
free rather than being a special case.

**Neither artefact was regenerated on commit.** They cite line numbers,
so they go stale on anything that moves a line — the sheet commit made
the document wrong about every gesture in `library.slint` without
touching a single one. The pre-commit hook that already keeps the matrix
in step now keeps these too, and unlike the matrix it *fails* rather than
shrugging when the scan does: a matrix that will not build leaves a stale
one in place, where a malformed gesture block means a user about to be
told the wrong thing.

**CI did not check them at all.** It does now, blocking. The matrix is
read; the gesture table is *shown to somebody using the application*, and
a stale one tells them to perform a gesture that no longer exists — from
which they will conclude the application is broken rather than the page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-30 00:51:13 +02:00

131 lines
5.0 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
# 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
# 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