diff --git a/.gitea/workflows/traceability-check.yml b/.gitea/workflows/traceability-check.yml new file mode 100644 index 0000000..b597b43 --- /dev/null +++ b/.gitea/workflows/traceability-check.yml @@ -0,0 +1,145 @@ +name: Traceability Validation + +# Mirrors JellyTau's .gitea/workflows/traceability-check.yml. The extractor is +# stdlib Python, so there is no toolchain install step and no jq. +# +# This workflow is component-agnostic: every repo-specific setting - which ID +# prefixes count, which file suffixes are source, which directories to scan, +# the threshold - lives in traceability.toml at the repo root, and the same +# extractor is shared by all three JRay components. Copying this file into +# another component needs no edits. +# +# NOTE: the runner here is an Intel N100 with no discrete GPU. This job is only +# ever static analysis of source comments plus markdown parsing, so it is cheap; +# the requirements it reports as "tagged but unexecuted" are the ones that need +# a GPU host, and they are deliberately never counted as covered. + +on: + push: + branches: + - main + - master + - develop + pull_request: + branches: + - main + - master + - develop + +jobs: + validate-traces: + runs-on: linux/amd64 + name: Check requirement traces + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: recursive + + - name: Check Python is available + run: | + set -e + command -v python3 >/dev/null 2>&1 || { + echo "python3 is missing from the runner image." + echo "The traceability tooling is stdlib-only Python;" + echo "3.9+ with CLI flags, 3.11+ to read traceability.toml." + exit 1 + } + python3 --version + + # The gate's own arithmetic is the thing being trusted, so its tests run + # before it does. JellyTau's gate was believed for months while it was + # dividing by frozen literals; untested gate logic is how that happens. + - name: Test the extractor + run: python3 scripts/vendor/jray-project/scripts/traceability/test_extract_traces.py + + # Threshold policy and every other repo-specific setting live in + # traceability.toml, not here, so local runs and CI runs cannot disagree + # about what "passing" means. Denominators come from docs/requirements.md + # at run time and are never hardcoded -- in this file or anywhere else. + # + # A misconfigured run (zero requirements parsed, zero files scanned) is a + # hard failure rather than a plausible-looking 0%. + - name: Traceability gate + run: sh scripts/vendor/jray-project/scripts/traceability/traceability-gate.sh + + - name: Check modified files for traces + if: github.event_name == 'pull_request' + run: | + set -e + echo "Checking modified sources for TRACES tags..." + + # The extensions come from the report the gate just wrote, which got + # them from traceability.toml. Restating them here would be a second + # place for the source-file definition to live, and the two would + # drift the first time a language is added. + PATTERN=$(python3 -c " + import json, re, sys + suffixes = json.load(open('traces-report.json'))['config']['sourceSuffixes'] + print('(' + '|'.join(re.escape(s) + '\$' for s in suffixes) + ')') + ") + echo "Source suffixes from traceability.toml: $PATTERN" + + CHANGED=$(git diff --name-only "origin/${{ github.base_ref }}...HEAD" \ + | grep -E "$PATTERN" || true) + + if [ -z "$CHANGED" ]; then + echo "No source files changed." + exit 0 + fi + + echo "Changed files:" + echo "$CHANGED" | sed 's/^/ /' + echo "" + + # Advisory by design: not every file implements a requirement, and a + # tag on every function is noise that rots faster than it helps + # (CLAUDE.md: tag the unit that decides). This step exists to prompt, + # not to block. The blocking checks are in the gate step above. + # + # Piped into the loop rather than a here-string, and `case` rather + # than `[[ == ]]`, so this works under dash as well as bash. The loop + # body runs in a subshell, so misses are recorded in a file. + MISSING=$(mktemp) + echo "$CHANGED" | while IFS= read -r file; do + case "$file" in + */test_*.py|*_test.py|*Tests.cs|tests/*|*/tests/*) continue ;; + esac + [ -f "$file" ] || continue + if ! grep -q 'TRACES:' "$file"; then + echo " no TRACES tag: $file" + echo "$file" >> "$MISSING" + fi + done + + COUNT=$(wc -l < "$MISSING" | tr -d ' ') + rm -f "$MISSING" + + if [ "$COUNT" -gt 0 ]; then + echo "" + echo "$COUNT changed file(s) carry no requirement tag." + echo "Format: // TRACES: AR-012, AR-013 | SR-002" + echo " (pipe separates requirement types, comma separates IDs)" + echo "A deliberate invariant exception is tagged separately:" + echo " // EXCEPTION: AR-024 " + echo "See CLAUDE.md and SPEC.md section 6." + fi + + - name: Report summary + if: always() + run: | + echo "Traceability matrix: docs/traceability.md" + echo "" + head -40 docs/traceability.md || true + + - name: Save reports + if: always() + uses: actions/upload-artifact@v3 + with: + name: traceability-reports + path: | + traces-report.json + docs/traceability.md + retention-days: 30 diff --git a/.gitmodules b/.gitmodules index 2649ab7..c526030 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,6 @@ path = external/KPN url = https://gitea.tourolle.paris/dtourolle/KPN.git branch = master +[submodule "jray-project"] + path = scripts/vendor/jray-project + url = git@gitea.tourolle.paris:dtourolle/jray-project.git diff --git a/scripts/vendor/jray-project b/scripts/vendor/jray-project new file mode 160000 index 0000000..17106f3 --- /dev/null +++ b/scripts/vendor/jray-project @@ -0,0 +1 @@ +Subproject commit 17106f337074753698454ccaf9c739cb6d0b4d79 diff --git a/traceability.toml b/traceability.toml new file mode 100644 index 0000000..e27448f --- /dev/null +++ b/traceability.toml @@ -0,0 +1,34 @@ +# Traceability configuration for scene-actor-extraction. +# +# Read by the shared extractor (scripts/traceability/extract_traces.py), which +# is the same implementation every JRay component uses. Everything repo-specific +# lives here rather than in the tool; run `extract_traces.py +# --print-example-config` for the annotated schema. +# +# This file's directory is taken as the repo root, so the gate works from any +# subdirectory. + +# The prefixes this repo's register defines. Nothing else enters the fraction: +# UT/IT are evidence for requirements, PR/SR belong to the system spec. +requirement_types = ["AR", "DP", "IR", "GR", "VR"] + +# C++ pipeline plus the Python tooling, optimizer and validation scripts. +languages = ["cpp", "python"] + +source_roots = ["src", "tests", "scripts", "experiments", "eval"] + +# CI is an Intel N100 with no discrete GPU. T4 is deliberately absent: a +# requirement verifiable only on GPU hardware is reported as tagged but +# unexecuted and never counted as covered, because counting a test that cannot +# run is the same failure mode as JellyTau's 158% coverage bug. +ci_executable_tiers = ["T1", "T2", "T3", "static"] + +# Threshold policy. 0 today because almost nothing is tagged yet - tags land as +# the pipeline is built. This is not a gate that cannot fail: orphan tags, a +# >100% ratio, a register that parses to nothing and an empty source scan are +# all hard failures already. Ratchet this up as tags land; never reset it down. +min_coverage = 0.0 + +# The system spec owning PR/SR is vendored per-component as a submodule. Point +# at it once that lands to turn on PR/SR orphan checking: +system_spec = "scripts/vendor/jray-project/SPEC.md"