libmpv on Windows (DR-237): - The builder image carries zhongfly's LGPL libmpv-2.dll, pinned by asset name and sha256, plus an MSVC mpv.lib generated from the DLL's own mpv_* exports (the archive ships only a MinGW .dll.a). LGPL, not the GPL builds: no x264/x265, mpv -Dgpl=false; FFmpeg is version3, so LGPL-3.0. THIRD_PARTY_NOTICES.md records it. - build-windows-cross.sh stages both files into src-tauri/windows-libs/; build.rs links mpv.lib from there and tauri.windows.conf.json bundles the DLL beside jellytau.exe from there, with the licence texts under licenses/. One directory, so the DLL shipped is the one linked. - Workflows move to builder image 2026.09.1. Windows audio: - MpvBackend replaces WebviewAudioBackend on Windows (ao=wasapi), so volume, EQ, normalization and gapless work there as on Linux. Local files are passed to mpv as native paths, not file:// URLs. Fixes found on the way: - player_play_item decided "does the backend render video" with cfg!(not(linux)), true on Windows, while get_player_status sent Windows video to the <video> element. With mpv as the backend that would decode every film's soundtrack twice. All three callers now ask video_renders_natively() (UT-273). - confine_queued_path rebuilt paths with PathBuf::push, so on Windows a queued `downloads/x` was stored as `downloads\x`, no longer the spelling the app built. It now keeps the caller's separator (UT-205, which only ever ran on Linux, failed under Windows). Verified: jellytau.exe imports libmpv-2.dll; the full unit suite cross-compiled for Windows passes under wine against the shipped DLL (943/943, including the mpv injection and TLS tests); the NSIS installer contains the DLL and licence texts. Not yet run on real Windows hardware.
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.09.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
|