Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -170,16 +170,18 @@ jobs:
- name: task changelog:verify
run: task changelog:verify

# SonarCloud: needs repo secret SONAR_TOKEN. Automatic Analysis must stay off
# (mutually exclusive with CI analysis + Go coverage). Non-blocking per OD-3 —
# quality gate is ERROR until E4-S02 clears duplication; continue-on-error so
# that does not block the remediating lanes.
# SonarCloud: needs repo secret SONAR_TOKEN. Automatic Analysis must stay OFF —
# it is mutually exclusive with CI analysis (the scanner exits 3) and it ignores
# sonar-project.properties, so the E4-S02 test exclusions silently would not
# apply. Blocking as of 2026-08-06 (OD-3 revisit): the continue-on-error escape
# hatch is gone AND sonar.qualitygate.wait=true is set, so both a scanner
# failure and an ERROR quality gate now fail this job. Fork PRs receive no
# secrets, so the scan step self-skips rather than failing.
sonarqube:
name: sonarqube
runs-on: ubuntu-latest
timeout-minutes: 20
needs: [check]
continue-on-error: true
steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
Expand Down
19 changes: 18 additions & 1 deletion Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,22 @@ tasks:
cmds:
- bash hack/pinned-actions.sh

guards:
desc: Run the hack/test/*.sh config regression guards
cmds:
- |
fail=0
for t in hack/test/*.sh; do
if bash "$t" >/dev/null 2>&1; then
echo "ok: $t"
else
echo "FAIL: $t" >&2
bash "$t" || true
fail=1
fi
done
exit "$fail"

tools:git-cliff:
desc: Download pinned git-cliff binary ({{.GIT_CLIFF_VERSION}})
status:
Expand Down Expand Up @@ -135,7 +151,7 @@ tasks:
- bash hack/verify-changelog.sh

check:
desc: Local gate — fmt, lint, arch, coverage≥90%, gitleaks, build, REWE-trace, pinned-actions
desc: Local gate — fmt, lint, arch, coverage≥90%, gitleaks, build, REWE-trace, pinned-actions, guards
cmds:
- task: fmt-check
- task: vet
Expand All @@ -146,6 +162,7 @@ tasks:
- task: build
- task: rewe-trace
- task: pinned-actions
- task: guards

docs:install:
desc: Install the locked MkDocs toolchain into .venv-docs
Expand Down
66 changes: 66 additions & 0 deletions hack/test/sonar_blocking_gate_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Assert the SonarCloud job is a BLOCKING gate (OD-3 revisit, 2026-08-06).
#
# OD-3 made `sonarqube` non-blocking so the quality gate could not veto the very
# lanes (E4-S01/E4-S02) that were fixing it. That gate went OK once Automatic
# Analysis was disabled and CI analysis actually ran, so the escape hatch came
# out. Blocking here means BOTH halves, because either alone is a false sense of
# safety:
#
# 1. no `continue-on-error` — a scanner failure fails the job. Without this a
# hard exit 3 ("running CI analysis while Automatic Analysis is enabled")
# was swallowed for a day while the workflow reported success.
# 2. `sonar.qualitygate.wait=true` — the scanner polls for the gate verdict
# instead of fire-and-forget. Without this the job goes green the moment the
# report is uploaded, so a gate that flips to ERROR is never noticed.
#
# Run: bash hack/test/sonar_blocking_gate_test.sh
set -euo pipefail

root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
wf="${root}/.github/workflows/ci.yaml"
props="${root}/sonar-project.properties"

fail=0

# 1. No job or step in ci.yaml may opt out of failing the workflow. Deliberately
# whole-file: nothing in this workflow has a legitimate use for it today.
# Matches bare, quoted and expression forms.
if grep -Eq "^[[:space:]]*continue-on-error:[[:space:]]*(\"?true\"?|'true'|\\\$\\{\\{)" "$wf"; then
echo "FAIL: continue-on-error opt-out still present in ci.yaml" >&2
grep -nE '^[[:space:]]*continue-on-error:' "$wf" >&2
fail=1
else
echo "ok: no continue-on-error opt-out in ci.yaml"

Check warning on line 34 in hack/test/sonar_blocking_gate_test.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Redirect this error message to stderr (>&2).

See more on https://sonarcloud.io/project/issues?id=PlatformRelay_kollect-render&issues=AZ_XfxtRzt7YJQR6t1rm&open=AZ_XfxtRzt7YJQR6t1rm&pullRequest=20
fi

# 2. The sonarqube job must exist and keep needing `check` — the scan consumes
# coverage.out via the artifact that `check` uploads.
if grep -Eq '^[[:space:]]{2}sonarqube:' "$wf"; then
echo "ok: sonarqube job present"
else
echo "FAIL: sonarqube job missing from ci.yaml" >&2
fail=1
fi

if grep -Eq '^[[:space:]]*needs:[[:space:]]*\[[[:space:]]*check[[:space:]]*\]' "$wf"; then
echo "ok: sonarqube needs [check] (coverage artifact producer)"
else
echo "FAIL: sonarqube no longer declares needs: [check]" >&2
fail=1
fi

# 3. The scanner must wait for the quality gate verdict, not merely upload.
if grep -Eq '^sonar\.qualitygate\.wait[[:space:]]*=[[:space:]]*true' "$props"; then
echo "ok: sonar.qualitygate.wait=true"
else
echo "FAIL: sonar.qualitygate.wait=true missing from sonar-project.properties" >&2
echo " without it the job passes on upload and never sees an ERROR gate" >&2
fail=1
fi

if [[ "${fail}" -ne 0 ]]; then
echo "sonar_blocking_gate_test: RED" >&2
exit 1
fi
echo "sonar_blocking_gate_test: GREEN"
5 changes: 5 additions & 0 deletions sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ sonar.tests=.
sonar.test.inclusions=**/*_test.go
sonar.go.coverage.reportPaths=coverage.out

# Make the CI job actually ENFORCE the gate rather than fire-and-forget: without
# this the scanner uploads and exits 0, so a quality gate that flips to ERROR
# leaves the workflow green. Guarded by hack/test/sonar_blocking_gate_test.sh.
sonar.qualitygate.wait=true

# Belt-and-suspenders with source exclusion above (cpd.exclusions alone was
# insufficient when tests were dual-indexed as sources).
sonar.cpd.exclusions=**/*_test.go
Expand Down
Loading