🏗️ Build and Test JellyTau / Run Tests (push) Successful in 15m52s
🏗️ Build and Test JellyTau / Supply Chain (push) Failing after 29s
Publish Documentation / Build & publish docs to gitea-pages (push) Successful in 5m35s
Traceability Validation / Check Requirement Traces (push) Successful in 11s
Build & Release / Run Tests (push) Successful in 14m53s
🏗️ Build and Test JellyTau / Android Compile Check (push) Successful in 4m22s
Build & Release / Build Linux (push) Successful in 20m53s
Build & Release / Build Windows (push) Successful in 15m41s
Build & Release / Build Android (push) Successful in 30m46s
Build & Release / Create Release (push) Successful in 38s
The v0.10.0 release build failed in Build Linux after 16 minutes: failed to bundle project: xdg-open binary not found /usr/bin/xdg-open: No such file or directory linuxdeploy embeds xdg-open into the AppImage and aborts the whole bundle when it is absent. deb and rpm had already bundled fine; only AppImage was affected. This is the one failure tonight that building locally could not have caught, and the reason is worth writing down: a developer machine is a desktop and always has xdg-utils, so the AppImage builds there and fails on a minimal server image. The asymmetry is the bug. Every other release defect this evening was found by building locally first; this one needed the runner. xdg-utils, desktop-file-utils and zsync are added together rather than one at a time. Each round trip costs an image rebuild plus a failed release build, and those three are what linuxdeploy commonly reaches for (xdg-open, desktop-file-validate, and zsync for delta updates). Workflows move to jellytau-builder:2026.08.1, built and pushed with all three verified present inside it before this commit. ci-operations.md gains two things learned here: that an apt addition invalidates the layer above the cargo-install steps, so it is a ~20 minute rebuild rather than the ~2 minutes the trailing layer normally gives; and that Tauri's AppImage bundler downloads linuxdeploy, AppRun and two plugin scripts from GitHub during the build, so an AppImage build depends on GitHub being reachable from the runner.
188 lines
6.7 KiB
YAML
188 lines
6.7 KiB
YAML
name: Traceability Validation
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- master
|
|
- main
|
|
- develop
|
|
pull_request:
|
|
branches:
|
|
- master
|
|
- main
|
|
- develop
|
|
|
|
jobs:
|
|
validate-traces:
|
|
runs-on: linux/amd64
|
|
name: Check Requirement Traces
|
|
container:
|
|
image: gitea.tourolle.paris/dtourolle/jellytau-builder:2026.08.1
|
|
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
# bun is baked into jellytau-builder (see Dockerfile.builder); no setup-bun
|
|
# action needed — fetching it stalls on this Gitea runner.
|
|
- name: Install dependencies
|
|
run: bun install
|
|
|
|
- name: Extract traces
|
|
run: |
|
|
echo "🔍 Extracting requirement traces..."
|
|
bun run traces:json > traces-report.json
|
|
|
|
- name: Validate traces
|
|
run: |
|
|
set -e
|
|
|
|
echo "📊 Validating requirement traceability..."
|
|
echo ""
|
|
|
|
# Denominators come from docs/requirements.md at run time — NEVER
|
|
# hardcode them here. This step previously divided by frozen literals
|
|
# (UR/39, IR/24, DR/48, JA/3, total 114) while the file had grown to
|
|
# 211 requirements, so it reported 158% coverage and the threshold
|
|
# below could never trip. See docs/traceability-ci.md.
|
|
TOTAL_TRACES=$(jq '.totalTraces' traces-report.json)
|
|
COVERED=$(jq '.coverage.covered' traces-report.json)
|
|
TOTAL_REQS=$(jq '.coverage.total' traces-report.json)
|
|
COVERAGE=$(jq '.coverage.percent' traces-report.json)
|
|
|
|
echo "✅ TRACES Found: $TOTAL_TRACES"
|
|
echo ""
|
|
echo "📋 Coverage Summary (traced / defined):"
|
|
for T in UR IR DR JA; do
|
|
TRACED=$(jq --arg t "$T" '[.byType[$t][] | select(. != null)] | length' traces-report.json)
|
|
DEFINED=$(jq --arg t "$T" '.defined[$t]' traces-report.json)
|
|
echo " $T: $TRACED / $DEFINED"
|
|
done
|
|
echo ""
|
|
|
|
echo "📈 Overall Coverage: $COVERED / $TOTAL_REQS ($COVERAGE%)"
|
|
echo ""
|
|
|
|
# Traced IDs that requirements.md does not define (typo, or a deleted
|
|
# requirement). These do not count toward coverage.
|
|
ORPHANED=$(jq -c '.coverage.orphaned' traces-report.json)
|
|
if [ "$ORPHANED" != "[]" ]; then
|
|
echo "⚠️ Traced but not defined in requirements.md: $ORPHANED"
|
|
echo ""
|
|
fi
|
|
|
|
# A ratio above 100% means the computation is broken — the exact
|
|
# condition that hid the stale-denominator bug. Fail loudly.
|
|
if [ "$COVERAGE" -gt 100 ]; then
|
|
echo "❌ ERROR: Coverage ($COVERAGE%) exceeds 100% — the gate is miscomputing."
|
|
echo " Orphaned IDs: $ORPHANED"
|
|
exit 1
|
|
fi
|
|
|
|
# Minimum coverage. RATCHET POLICY: this number only ever goes UP.
|
|
#
|
|
# It sits a few points under the coverage actually achieved, so a real
|
|
# regression trips it. It was 50 while true coverage was 86%, which
|
|
# meant nearly half the matrix could rot before CI said a word — a
|
|
# gate that cannot fail is not a gate.
|
|
#
|
|
# When coverage rises durably, raise this to just under the new figure
|
|
# (`bun run traces:coverage` prints it). Never lower it to make a red
|
|
# build pass — add the missing TRACES comments instead.
|
|
#
|
|
# Keep in sync with MIN_COVERAGE_PERCENT in scripts/extract-traces.ts;
|
|
# scripts/extract-traces.test.ts fails if the two drift apart.
|
|
MIN_THRESHOLD=89
|
|
if [ "$COVERAGE" -lt "$MIN_THRESHOLD" ]; then
|
|
echo "❌ ERROR: Coverage ($COVERAGE%) is below minimum threshold ($MIN_THRESHOLD%)"
|
|
exit 1
|
|
fi
|
|
|
|
echo "✅ Coverage is acceptable ($COVERAGE% >= $MIN_THRESHOLD%)"
|
|
|
|
# Every ID named by a TRACES comment must be defined as a table row in
|
|
# docs/requirements.md. The extractor used to accept any well-formed ID
|
|
# silently, so a typo or a rename that missed a call site passed CI
|
|
# unnoticed (DR-189 and UT-188 lived in three source files, defined
|
|
# nowhere, for months). This covers UT/IT too, which the coverage
|
|
# orphan list above deliberately ignores.
|
|
- name: Validate requirement IDs
|
|
run: bun run traces:validate
|
|
|
|
- name: Check modified files
|
|
if: github.event_name == 'pull_request'
|
|
run: |
|
|
echo "🔍 Checking modified files for traces..."
|
|
echo ""
|
|
|
|
# Get changed files
|
|
CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep -E '\.(ts|tsx|svelte|rs)$' || echo "")
|
|
|
|
if [ -z "$CHANGED" ]; then
|
|
echo "✅ No TypeScript/Rust files changed"
|
|
exit 0
|
|
fi
|
|
|
|
echo "📝 Changed files:"
|
|
echo "$CHANGED" | sed 's/^/ /'
|
|
echo ""
|
|
|
|
# Check each file
|
|
# Pipe into the loop instead of a here-string (<<<) so this step works
|
|
# under POSIX sh/dash, not just bash. Use `case` instead of `[[ == ]]`
|
|
# for the same reason. The loop runs in a subshell (so a counter var
|
|
# wouldn't survive), so we record warnings in a temp file and count it
|
|
# afterwards.
|
|
MISSING_FILE=$(mktemp)
|
|
echo "$CHANGED" | while IFS= read -r file; do
|
|
# Skip test files
|
|
case "$file" in
|
|
*.test.*) continue ;;
|
|
esac
|
|
|
|
if [ -f "$file" ]; then
|
|
if ! grep -q "TRACES:" "$file"; then
|
|
echo "⚠️ Missing TRACES: $file"
|
|
echo "$file" >> "$MISSING_FILE"
|
|
fi
|
|
fi
|
|
done
|
|
|
|
MISSING_TRACES=$(wc -l < "$MISSING_FILE" | tr -d ' ')
|
|
rm -f "$MISSING_FILE"
|
|
|
|
if [ "$MISSING_TRACES" -gt 0 ]; then
|
|
echo ""
|
|
echo "📝 Recommendation: Add TRACES comments to new/modified code"
|
|
echo " Format: // TRACES: UR-001, UR-002 | DR-003"
|
|
echo ""
|
|
echo "💡 For more info, see: scripts/README.md"
|
|
fi
|
|
|
|
- name: Generate full report
|
|
if: always()
|
|
run: |
|
|
echo "📄 Generating full traceability report..."
|
|
bun run traces:markdown
|
|
|
|
- name: Display report summary
|
|
if: always()
|
|
run: |
|
|
echo ""
|
|
echo "📊 Full Report Generated"
|
|
echo "📁 Location: docs/traceability.md"
|
|
echo ""
|
|
head -50 docs/traceability.md || true
|
|
|
|
- name: Save artifacts
|
|
if: always()
|
|
uses: actions/upload-artifact@v3
|
|
with:
|
|
name: traceability-reports
|
|
path: |
|
|
traces-report.json
|
|
docs/traceability.md
|
|
retention-days: 30
|