From a76ae3bdfa2d55c3edb3b3b7465e315e6dcaed39 Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Sun, 13 Sep 2026 16:25:42 -0500 Subject: [PATCH] =?UTF-8?q?fix(ci):=20the=20content=20scan=20could=20not?= =?UTF-8?q?=20match=20R-C2-01/02=20=E2=80=94=20a=20digit=20in=20the=20work?= =?UTF-8?q?stream=20segment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checked as part of closing the C2 coverage gaps, and it was live on main. The content scan's pattern was `R-[A-Z]+-[0-9]+`, which CANNOT match an ID whose workstream segment carries a digit: `[A-Z]+` consumes the `C` of `R-C2-01` and then demands a `-` where the `2` is. `R-C2-01` and `R-C2-02` are the only two such IDs in conformance/requirements/registry.yaml, and the scan was blind to BOTH. REPRODUCED before the fix, on a throwaway repo whose only change is a manifest declaring both IDs on real (non-comment) lines, in a path the map keys to nothing: $ REPO_ROOT=$T BASE_REF=main ci/touched-requirements.sh R-NET-06 # the all-letter control ID was the ONLY output It failed by UNDER-attributing, which is the fail-OPEN direction: a change that retyped a `conformance.home.k3s/R-C2-01` tag, or a manifest self-declaring R-C2-02, 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". Every other consumer in the repo already allowed the digit, which is why this was the lone dissenter: test_requirements_for_path() twenty lines above in this same script, conformance/lib/coverage.py, conformance/lib/status.py and conformance/lib/ run_l1_offline.py all use `R-[A-Z0-9]+-[0-9]+`. Aligned to that form, with the reason recorded at the line. SCOPE OF THE HOLE. path_requirements() has no arm for platform/homepage/* or platform/headlamp/* either, so before this the ONLY route to R-C2-01/02 was detection rule 3 (editing the C2 conformance test itself). No path-map arm is added here: c2-root-surface declares `scope: whole-repo`, so ci/l0.sh Stage 3b's --include-scope union selects it on EVERY gate invocation regardless of the touched set — which is the right mechanism for an invariant whose input surface is the whole tree, and strictly stronger than a two-component path arm would be. REGRESSION-TESTED, in ci/tests/touched-requirements.test.sh (the suite `make -C ci test` runs): a fixture declaring R-C2-01 / R-C2-02 / R-OBS-04 from `notes/` — a path outside path_requirements(), so the IDs can only come from the content scan — asserts both digit IDs ARE attributed, that the all-letter form still is (control), and that a purely numeric workstream is not invented (the pattern must not widen the gate on noise). PROVEN TO DETECT BREAKAGE: with the pattern reverted to `R-[A-Z]+-[0-9]+` the suite reports `FAIL (2 failed, 14 passed)` naming both C2 IDs as missing; restored, all three ci/ suites pass (16 assertions in this one). ALSO: conformance/tests/l0-static/whole-repo-invariants-wired/check.sh cites this pattern in the comment explaining why its BOGUS_REQ fixture is lowercase. Citation updated so the reasoning still reads true; the fixture is unaffected (grep -E is case-sensitive, and the check still passes 26/26). NOTE: another branch may be editing this same script concurrently. This is a one-line change to one statement plus its comment — expect a trivial conflict and keep the `[A-Z0-9]` form. Co-Authored-By: Claude Opus 5 --- ci/tests/touched-requirements.test.sh | 33 +++++++++++++++++++ ci/touched-requirements.sh | 17 +++++++++- .../whole-repo-invariants-wired/check.sh | 2 +- 3 files changed, 50 insertions(+), 2 deletions(-) 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)"