From c165f92b49a7c42e1dd2a731bd364b070174a2e2 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 12 Aug 2026 12:20:55 +0200 Subject: [PATCH 1/3] =?UTF-8?q?fix(shellcheck):=20SC2294=20=E2=80=94=20the?= =?UTF-8?q?=20probe=20helper=20never=20had=20an=20array=20to=20lose?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ShellCheck has been red on main since 2026-08-03 on a single finding, and because the required check runs against the PR MERGE ref it fails every open PR — including six queued gate fixes that never touched this file. All 15 probe callers pass their planted defect as ONE quoted shell string ('rm .php-cs-fixer.dist.php', "sed -i 's|...|...|' composer.json"), so the string form is what the helper always meant; eval "$@" only looked array-shaped. Verified both arms of the suite: 23 passed / 0 failed before and after. That is a real control, not a tautology — had eval stopped executing, the defect would go unplanted and each probe would report 'did not detect'. --- .../scripts/lib/test_check_coding_standard_adoption.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hydra-gates/scripts/lib/test_check_coding_standard_adoption.sh b/hydra-gates/scripts/lib/test_check_coding_standard_adoption.sh index 31e889f7..7c236bdf 100755 --- a/hydra-gates/scripts/lib/test_check_coding_standard_adoption.sh +++ b/hydra-gates/scripts/lib/test_check_coding_standard_adoption.sh @@ -132,7 +132,10 @@ probe() { local name="$1" expect="$2"; shift 2 local d="$WORK/$name" rm -rf "$d"; scaffold "$d" - ( cd "$d" && eval "$@" ) + # Every probe below passes its defect as ONE shell string (quoted sed/rm + # commands), so the string form is what this has always meant. eval "$@" + # only looked array-shaped — SC2294. + ( cd "$d" && eval "$*" ) local o; o="$(run "$d")" if printf '%s' "$o" | grep -q "FAIL ${expect}:"; then ok "detects ${name} (${expect})" From a208556f54708d08f0394cce8f2526ee46eaafbf Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 12 Aug 2026 13:09:46 +0200 Subject: [PATCH 2/3] fix(gate-65): a whole-repo gate fires inside every other suite's fixture MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Package invariants` — a REQUIRED check — has been red on main since 09:38 today, so the six queued gate PRs cannot merge. Both failures trace to gate-65 (coding-standard-adoption), added in #382. gate-65 is deliberately not diff-scoped: it judges the repo's standing configuration. Its only NOT-APPLICABLE path is "no composer.json and no phpcs.xml", and every synthetic fixture in the corpus HAS a composer.json, because the gate under test needs one. So it fires inside other suites' fixtures and fails runs those suites require to be green. A whole-repo gate is a global mutation of every other gate's test environment. test_gate_license_triangle_scope.sh — the fixture now adopts the standard the same way the compliant scaffold in test_check_coding_standard_adoption.sh does (require-dev, cs:check/cs:fix, .php-cs-fixer.dist.php, and an .editorconfig with indent_style = tab). Arm 6 needed the same treatment separately: it rewrites composer.json down to a bare name to drop the licence field, which also un-adopts the standard — that arm is the CONTROL that makes assertion 5 mean anything, so it silently proving nothing was the worse of the two failures. test_gate_acceptance_matrix.sh — gate-65 gets its COVERED-ELSEWHERE.md row. It is not uncovered: test_check_coding_standard_adoption.sh plants 15 single-defect probes, one per rule, plus a negative control and a wiring assertion. That is what the ELSEWHERE list is for. Verified: license-triangle 11 PASS / 0 FAIL, acceptance matrix green (13 gates counted as covered elsewhere, was 12), and the full helper suite on this branch is 76 passed / 2 quarantined / 0 failed. --- .../lib/test_gate_license_triangle_scope.sh | 19 +++++++++++++++++-- .../gate-acceptance/COVERED-ELSEWHERE.md | 1 + 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/hydra-gates/scripts/lib/test_gate_license_triangle_scope.sh b/hydra-gates/scripts/lib/test_gate_license_triangle_scope.sh index 94bd07ba..5dd9cc93 100755 --- a/hydra-gates/scripts/lib/test_gate_license_triangle_scope.sh +++ b/hydra-gates/scripts/lib/test_gate_license_triangle_scope.sh @@ -97,7 +97,16 @@ _mkrepo() { git config user.email "ci@example.invalid" git config user.name "gate28 test" printf '\nfixture1.0.0\n' > appinfo/info.xml - printf '{\n "name": "conduction/fixture",\n "license": "EUPL-1.2"\n}\n' > composer.json + # gate-65 (coding-standard-adoption) is deliberately NOT diff-scoped: + # it judges the repo's standing configuration, so it fires on this + # fixture too. Two assertions below require the whole run to end + # GREEN, and a fixture that has not adopted the shared standard cannot + # produce that — the arm would be measuring gate-65 instead of gate-28. + # So the fixture adopts it, mirroring the compliant scaffold in + # test_check_coding_standard_adoption.sh. + printf '{\n "name": "conduction/fixture",\n "license": "EUPL-1.2",\n "require-dev": {\n "conduction/coding-standard": "^1.0"\n },\n "scripts": {\n "cs:check": "php-cs-fixer fix --dry-run --diff",\n "cs:fix": "php-cs-fixer fix"\n }\n}\n' > composer.json + printf 'getFinder()->in(__DIR__ . %s/lib%s);\nreturn $config;\n' "'" "'" "'" "'" > .php-cs-fixer.dist.php + printf 'root = true\n\n[*]\nindent_style = tab\n' > .editorconfig printf 'name: ci\non: push\njobs:\n a:\n runs-on: ubuntu-latest\n steps:\n - run: echo base\n' > .github/workflows/ci.yml if [ "${_withlib}" = "yes" ]; then mkdir -p lib @@ -379,8 +388,14 @@ fi # (composer-audit) applicable, and it fails on a fixture with no vendor # tree — `_FAILED` becomes 1 and the coverage sentence is unreachable # again. The diff must contain the lib PHP file and nothing else. +# For the same reason the rewrite below drops ONLY the licence field and +# keeps the coding-standard wiring: rewriting composer.json down to a +# bare name also un-adopts the shared standard, gate-65 fails, `_FAILED` +# is 1 again and the coverage sentence this control reads is never +# reached — the control would report the coverage requirement inert when +# it is simply never consulted. _mkrepo scope-nolicense-fullcov yes -printf '{\n "name": "conduction/fixture"\n}\n' > "${_REPO}/composer.json" +printf '{\n "name": "conduction/fixture",\n "require-dev": {\n "conduction/coding-standard": "^1.0"\n },\n "scripts": {\n "cs:check": "php-cs-fixer fix --dry-run --diff",\n "cs:fix": "php-cs-fixer fix"\n }\n}\n' > "${_REPO}/composer.json" git -C "${_REPO}" add -A >/dev/null 2>&1 git -C "${_REPO}" commit -qm "drop the composer license field" >/dev/null 2>&1 _BASE="$(git -C "${_REPO}" rev-parse HEAD)" diff --git a/hydra-gates/scripts/test-fixtures/gate-acceptance/COVERED-ELSEWHERE.md b/hydra-gates/scripts/test-fixtures/gate-acceptance/COVERED-ELSEWHERE.md index eec51705..c0bc3f2e 100644 --- a/hydra-gates/scripts/test-fixtures/gate-acceptance/COVERED-ELSEWHERE.md +++ b/hydra-gates/scripts/test-fixtures/gate-acceptance/COVERED-ELSEWHERE.md @@ -32,3 +32,4 @@ identically to `UNCOVERED.md`. | gate-32 | semantic-controls | `test_gates_23_33_never_green_over_nothing.sh` | `test-fixtures/gates-23-33/{planted,clean}` | | gate-33 | axe-core | `test_gates_23_33_never_green_over_nothing.sh` | `test-fixtures/gates-23-33/{planted,clean}` | | gate-61 | listener-work-placement | `test_gate_scope_matrix.sh` | `test-fixtures/scope-matrix/app` — push / full / diff matrix, `.github#347` | +| gate-65 | coding-standard-adoption | `test_check_coding_standard_adoption.sh` | scaffolded in-suite — 15 planted single-defect probes (one rule each) plus a negative control that fails if a broadened rule starts matching the compliant scaffold, and a wiring assertion that the checker printed its terminal `checked N rule(s)` summary | From fa88ab6e7612d940d7fdfad1a8709c23533a9b26 Mon Sep 17 00:00:00 2001 From: Ruben van der Linde Date: Wed, 12 Aug 2026 13:18:15 +0200 Subject: [PATCH 3/3] =?UTF-8?q?fix(shellcheck):=20SC2016=20=E2=80=94=20the?= =?UTF-8?q?=20fixture's=20PHP=20body=20belongs=20in=20a=20quoted=20heredoc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit My own previous commit reintroduced a ShellCheck finding while fixing one. The .php-cs-fixer.dist.php body is PHP and contains $config; written as printf with single quotes, ShellCheck reads it as a shell expansion that will not expand. A <<'PHP' heredoc says verbatim to the shell and to the reader at once, and matches how the sibling scaffold already writes the same file. Re-verified: license-triangle 11 PASS / 0 FAIL. --- .../scripts/lib/test_gate_license_triangle_scope.sh | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/hydra-gates/scripts/lib/test_gate_license_triangle_scope.sh b/hydra-gates/scripts/lib/test_gate_license_triangle_scope.sh index 5dd9cc93..edbb71c1 100755 --- a/hydra-gates/scripts/lib/test_gate_license_triangle_scope.sh +++ b/hydra-gates/scripts/lib/test_gate_license_triangle_scope.sh @@ -105,7 +105,17 @@ _mkrepo() { # So the fixture adopts it, mirroring the compliant scaffold in # test_check_coding_standard_adoption.sh. printf '{\n "name": "conduction/fixture",\n "license": "EUPL-1.2",\n "require-dev": {\n "conduction/coding-standard": "^1.0"\n },\n "scripts": {\n "cs:check": "php-cs-fixer fix --dry-run --diff",\n "cs:fix": "php-cs-fixer fix"\n }\n}\n' > composer.json - printf 'getFinder()->in(__DIR__ . %s/lib%s);\nreturn $config;\n' "'" "'" "'" "'" > .php-cs-fixer.dist.php + # Quoted heredoc, not printf: the body is PHP and contains `$config`, + # which printf-with-single-quotes makes ShellCheck read as a shell + # expansion that will not expand (SC2016). <<'PHP' says "verbatim" to + # the shell and to the reader at once. + cat > .php-cs-fixer.dist.php <<'PHP' +getFinder()->in(__DIR__ . '/lib'); +return $config; +PHP printf 'root = true\n\n[*]\nindent_style = tab\n' > .editorconfig printf 'name: ci\non: push\njobs:\n a:\n runs-on: ubuntu-latest\n steps:\n - run: echo base\n' > .github/workflows/ci.yml if [ "${_withlib}" = "yes" ]; then