diff --git a/ci/tests/touched-requirements.test.sh b/ci/tests/touched-requirements.test.sh index 9e906e1..5c0b028 100755 --- a/ci/tests/touched-requirements.test.sh +++ b/ci/tests/touched-requirements.test.sh @@ -136,6 +136,39 @@ printf 'touched set (clusters/ path map):\n%s\n---\n' "$OUT4" assert_present "R-ENV-02" "$OUT4" "clusters/* attributes R-ENV-02 (selects placeholder-guard-coupling HALF A)" assert_present "R-GITOPS-01" "$OUT4" "clusters/* attributes R-GITOPS-01" +# --------------------------------------------------------------------------------------- +# THE ID PATTERN MUST ALLOW A DIGIT IN THE WORKSTREAM SEGMENT (added 2026-09-13) +# +# The content scan's pattern was `R-[A-Z]+-[0-9]+`, which CANNOT match `R-C2-01` or +# `R-C2-02` — `[A-Z]+` consumes the `C` and then demands a `-` where the `2` is. Those are +# the only two registry IDs whose workstream segment carries a digit, and the scan was blind +# to BOTH, in the fail-OPEN direction: a change self-declaring a C2 requirement contributed +# it to nobody, so the C2 conformance tests were filtered out of the merge gate. Found while +# wiring conformance/tests/l0-static/c2-root-surface, which closes those two coverage gaps. +# +# `notes/` is outside path_requirements() on purpose, so the IDs below can only have come +# from the CONTENT SCAN. A neighbouring all-letter ID is asserted too, so a regex that +# somehow matched only the digit form would still fail here. +git -C "$TMP" checkout -q -b feature5 main +cat > "$TMP/notes/c2.yaml" <<'YAML' +kind: Test +metadata: + labels: + conformance.home.k3s/R-C2-01: "true" + conformance.home.k3s/R-C2-02: "true" + conformance.home.k3s/R-OBS-04: "true" +YAML +git -C "$TMP" add -A && git -C "$TMP" commit -qm "declare R-C2-01 / R-C2-02" +OUT5="$(REPO_ROOT="$TMP" BASE_REF=main "$SCRIPT" 2>/dev/null)" +printf 'touched set (digit in the workstream segment):\n%s\n---\n' "$OUT5" +assert_present "R-C2-01" "$OUT5" "an R-ID with a DIGIT in the workstream segment IS attributed (R-C2-01)" +assert_present "R-C2-02" "$OUT5" "the second such ID is attributed too (R-C2-02)" +assert_present "R-OBS-04" "$OUT5" "an all-letter workstream segment still works (control)" +# The pattern must not become so loose that a non-ID token is read as a requirement: a bare +# numeric workstream is not a registry shape, and attributing one would WIDEN the gate on +# noise (over-broad selection, never a false green, but noisy and misleading). +assert_absent "R-1-01" "$OUT5" "a purely numeric workstream segment is not invented from thin air" + echo if [ "$FAIL" -eq 0 ]; then echo "touched-requirements tests: PASS ($PASS assertions)"; exit 0; else echo "touched-requirements tests: FAIL ($FAIL failed, $PASS passed)"; exit 1; fi diff --git a/ci/touched-requirements.sh b/ci/touched-requirements.sh index 3f07374..8566f40 100755 --- a/ci/touched-requirements.sh +++ b/ci/touched-requirements.sh @@ -345,6 +345,21 @@ main() { done <<< "$files" # Self-declared R-IDs on the ADDED/REMOVED lines of the change (see changed_diff). + # + # THE ID PATTERN IS `R-[A-Z0-9]+-[0-9]+`, NOT `R-[A-Z]+-[0-9]+`. The WORKSTREAM segment may + # contain a DIGIT: `R-C2-01` and `R-C2-02` (command & control UI) are the only two such IDs + # in conformance/requirements/registry.yaml today, and the stricter pattern silently failed + # to match BOTH of them — `[A-Z]+` consumes the `C` and then demands a `-` where the `2` is. + # Every other consumer in the repo already allows the digit: test_requirements_for_path() + # twenty lines above uses `R-[A-Z0-9]+-[0-9]+`, as do conformance/lib/coverage.py, + # conformance/lib/status.py and conformance/lib/run_l1_offline.py. This grep was the lone + # dissenter, and it dissented by UNDER-attributing — the fail-OPEN direction. A change that + # retyped a `conformance.home.k3s/R-C2-01` tag, or a manifest self-declaring R-C2-02 from a + # path the map keys elsewhere, contributed those IDs to NOBODY, so the C2 tests were + # filtered out of the merge gate. Same defect class as the map's `*) : ;;` arm: a miss that + # reads as "nothing touched". path_requirements() has no arm for platform/homepage/* or + # platform/headlamp/* either, so before this fix the ONLY route to R-C2-01/02 was rule 3 + # (editing the C2 test itself). Regression-tested in ci/tests/touched-requirements.test.sh. # `^[+-]` keeps hunk headers (`@@`) and the `+++`/`---` file headers out of scope. # `sed 's/^[+-]//'` then strips the single-char diff marker (safe: -U0 emits one), so # what remains is the SOURCE line, and `grep -vE '^[[:space:]]*#'` drops whole-line @@ -358,7 +373,7 @@ main() { | grep -vE '^(\+\+\+|---)' \ | sed -E 's/^[+-]//' \ | grep -vE '^[[:space:]]*#' \ - | grep -hoE 'R-[A-Z]+-[0-9]+' \ + | grep -hoE 'R-[A-Z0-9]+-[0-9]+' \ | tr '\n' ' ' || true)" # Normalize: split, unique, sort. Emit to stdout. diff --git a/conformance/tests/l0-static/whole-repo-invariants-wired/check.sh b/conformance/tests/l0-static/whole-repo-invariants-wired/check.sh index 4d13f82..4f22a3a 100755 --- a/conformance/tests/l0-static/whole-repo-invariants-wired/check.sh +++ b/conformance/tests/l0-static/whole-repo-invariants-wired/check.sh @@ -85,7 +85,7 @@ done # second would fail. # Deliberately LOWERCASE: conf_normalize_reqs() upper-cases it, so the selection behaves # identically — but the literal here does not match ci/touched-requirements.sh's content-scan -# pattern `R-[A-Z]+-[0-9]+`, so editing this file does not attribute a nonexistent requirement +# pattern `R-[A-Z0-9]+-[0-9]+`, so editing this file does not attribute a nonexistent requirement # to the change. (Same reason the synthetic fixture below carries no requirement label at all.) BOGUS_REQ="r-nosuchreq-99" sel_without="$(bash "$RUN_SH" --layer l0 --requirements "$BOGUS_REQ" --print-selection 2>/dev/null || true)"