refactor(traceability): parameterise the extractor for all three components

The tool is moving into the jray-project submodule to be shared by
scene-actor-extraction (C++/Python), jRay (C#) and JRay-public-server
(Rust). Two constants blocked that: LOCAL_TYPES and SOURCE_SUFFIXES were
hardcoded to this repo, so either sibling parsed zero requirements and
scanned zero files. Both, plus the register path, scan roots, system-spec
path, exclude list and CI-executable tier set, are now configuration.

One implementation, parameterised. A second copy for "the other language"
is how two implementations start drifting apart, so there is exactly one -
the same code path now produces:

  scene-actor-extraction  AR/DP/IR/GR/VR   59 defined   0 tagged    0.0%
  jRay                    JR               46 defined  24 covered  52.2%
  JRay-public-server      UR/DR            32 defined  23 covered  71.9%

Configuration is traceability.toml at the component repo root, CLI flags,
or both (flags win). Its directory defines the repo root, so the gate works
from any subdirectory. `--print-example-config` emits the annotated schema.
The JSON report echoes the settings it ran with, since a shared tool's
output is otherwise ambiguous about which repo it describes.

The refusal behaviour is kept and sharpened, because parameterising is
exactly what makes it easy to point a repo at the wrong prefixes or the
wrong suffixes. Zero requirements parsed or zero files scanned is still a
hard failure, and the message now names the setting that is wrong rather
than printing a plausible 0%. Config errors exit 2, not 1: a broken config
is not a coverage failure, and conflating them makes CI logs lie about why
the job went red.

Also fixed while adapting to the sibling registers, which are read but not
modified here:

  * escaped `\|` inside a markdown cell no longer shifts every later column
    (the server's register contains `small-\|M\|`);
  * a tag above an attribute-decorated declaration attributes to the
    declaration, not to `[HttpGet(...)]` or `#[derive(...)]` - the gap
    jRay's register calls out;
  * Rust and C# declaration patterns for context extraction;
  * the missing-tier warning is suppressed for a register that assigns no
    tiers at all, rather than listing every requirement in it.

The workflow is now component-agnostic too: the changed-file check reads
its extension list out of the report the gate just wrote, so the definition
of "source file" lives in one place.

79 tests, still fixture-based, now including the cross-repo cases: the same
parser over JR and UR/DR registers, the same scanner over Rust and C#, and
both misconfigurations failing loudly.
This commit is contained in:
2026-07-30 18:35:56 +02:00
parent 054c4c8b8f
commit faaa71fa09
6 changed files with 1257 additions and 447 deletions
+32 -12
View File
@@ -1,8 +1,13 @@
name: Traceability Validation
# Mirrors JellyTau's .gitea/workflows/traceability-check.yml, adapted for a
# C++/Python repo: the extractor is Python and needs nothing but python3, so
# there is no toolchain install step and no jq.
# 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;
@@ -31,14 +36,15 @@ jobs:
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 3.9+;"
echo "no other dependency is needed."
echo "The traceability tooling is stdlib-only Python;"
echo "3.9+ with CLI flags, 3.11+ to read traceability.toml."
exit 1
}
python3 --version
@@ -49,10 +55,13 @@ jobs:
- name: Test the extractor
run: python3 scripts/traceability/test_extract_traces.py
# Threshold policy lives in traceability-gate.sh, 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.
# 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/traceability/traceability-gate.sh
@@ -62,11 +71,22 @@ jobs:
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 '\.(cpp|cc|cxx|hpp|hxx|h|cu|cuh|py)$' || true)
| grep -E "$PATTERN" || true)
if [ -z "$CHANGED" ]; then
echo "No C++/Python files changed."
echo "No source files changed."
exit 0
fi
@@ -85,7 +105,7 @@ jobs:
MISSING=$(mktemp)
echo "$CHANGED" | while IFS= read -r file; do
case "$file" in
*/test_*.py|*_test.py|tests/*|*/tests/*) continue ;;
*/test_*.py|*_test.py|*Tests.cs|tests/*|*/tests/*) continue ;;
esac
[ -f "$file" ] || continue
if ! grep -q 'TRACES:' "$file"; then