refactor(build,ci): 현재 상태 검증을 걷어내고 불변조건만 남기는 검증 표면 축소

외부 리뷰("현재 상태를 유지하기 위한 검증이 너무 많고, 그 검증 자체를
다시 검증하는 구조까지 생겼다")를 설계 문서로 정리하고 코드로 반영한다.
설계·판단 근거는 docs/superpowers/specs/2026-09-16-verification-surface-reduction-design.md.

삭제
- .github/ci-gate-matrix.yml(1,025줄) + verify-gate-matrix.sh(568줄):
  Gradle task graph와 workflow graph에 이미 있는 정보의 3중 복제
- verify-gradle-wrapper.sh(799줄): workflow 바이트 해시 잠금.
  wrapper 검증은 gradle/actions/wrapper-validation(full SHA 핀)에 위임
- DeveloperExperienceContractTest 등의 CI YAML mutation 테스트:
  애플리케이션 test suite가 GitHub Actions YAML 파서를 검증하던 계층 역전
- 문서 drift 파서: verifyReadmeCommands, verifyRunbookReferences,
  verifyDocumentedLeafCount, verifyTestSourceSetRegistry
- 빈 레지스트리를 지키던 커스텀 YAML 파서: verifyTrivyignore,
  verifyQuarantineSunset, flaky-quarantine.yaml
- verifyConfigurationPropertiesProcessor, verifyOneTypePerFile:
  각각 ca.spring-config convention과 Checkstyle OneTopLevelClass가 대체
- 정상 입력으로도 성공할 수 없던 messaging always-fail task
- ModuleRegistry의 JSON 필드 집합 정확 일치, sample-portfolio negative guard

이동
- java/quality/spring 공통 설정을 configure(subprojects) 블록에서
  ca.java-conventions / ca.quality-conventions / ca.java-library /
  ca.spring-library convention plugin으로
- 아키텍처 검증을 ca.architecture로, JPA·messaging qualification을
  gradle/qualification/ 아래로, verifyEnvKeys를 :app-bootstrap 소유로

완화
- Git revision은 releaseCheck·아카이브 생성에서만 요구. 일반 빌드는 SNAPSHOT
- SpotBugs/FindSecBugs는 로컬 check에서 빼고 qualityCheck 레인으로

task 계층
- leaf check는 그 leaf만. architectureCheck / qualityCheck /
  configContractCheck / integrationCheck / ci / releaseCheck로 이름 분리

CI
- _reusable-gradle.yml 신규. checkout + wrapper validation + JDK/캐시 공통화
- fileserver-release.yml -> fileserver-certification.yml (CD가 아니라 certification)
- GitHub Actions = CI + artifact, Argo CD = CD 경계를 docs/ci-cd/boundary.md로 고정

순증감 +3,274 / -7,483.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-16 20:33:19 +09:00
co-authored by Claude Opus 5
parent d00c76241c
commit ef947e5bb0
95 changed files with 3284 additions and 7493 deletions
+28 -24
View File
@@ -1,27 +1,30 @@
name: Set up Java and the Gradle cache
name: Set up Java and Gradle
description: >-
Installs the repository's pinned Temurin JDK and restores the Gradle cache keyed on this
repository's build files. Every Gradle job used to carry this block verbatim, so the JDK patch
level and the cache key lived in fifty-nine places and could drift in any one of them.
Installs the repository's pinned Temurin JDK, then configures Gradle through the official
setup-gradle action — which validates every checked-in wrapper jar and manages the Gradle cache.
Every Gradle job used to carry the JDK block verbatim, so the JDK patch level lived in fifty-nine
places; every job also carried a separate three-line wrapper-validation step, so the pinned action
SHA lived in forty.
# Deliberately NOT in this action: `actions/checkout` and the Gradle wrapper validation step.
# Wrapper validation is INSIDE this action now.
#
# Neither can move here, and the reasons are different:
# It could not be before, and the reason was not a GitHub limitation: .github/scripts/
# verify-gradle-wrapper.sh read every workflow job and required it to contain, literally and in this
# order, an `actions/checkout@` step, the exact three-field pinned wrapper-validation step, and then
# the Gradle invocation. That literalness was the whole guard — "this job validated the wrapper" had
# to be answerable from the workflow file alone — and it is what made the step uninlineable.
#
# * checkout — a `./.github/actions/...` reference is resolved from the checked-out working
# copy, so the action file does not exist until checkout has already run. A composite action
# cannot contain the step that makes itself readable.
# * wrapper validation — .github/scripts/verify-gradle-wrapper.sh reads each workflow job and
# requires it to contain, literally and in this order, an `actions/checkout@` step, the exact
# three-field pinned wrapper-validation step, and then the Gradle invocation. That literalness
# is the guard: it is what makes "this job validated the wrapper before running it" checkable
# from the workflow file alone. Hiding the step behind an action would also break the guarded
# `if: ${{ always() && steps.gradle-wrapper-validation.outcome == 'success' }}` form the same
# script enforces, because a composite action's step ids are not visible to its caller — the
# condition would silently evaluate to false and skip the step it was protecting.
# That script is gone (it also byte-hashed all twelve workflow files, so a comment change needed a
# hash update, while an attacker with write access would simply have updated both). The guarantee it
# was protecting is now the official action's own: `gradle/actions/setup-gradle` validates all
# wrapper jars by default (`validate-wrappers`, default true), and the action is pinned to a full
# commit SHA here — which GitHub's own hardening guide calls the only immutable action reference.
#
# So a Gradle job is four lines of preamble (checkout, the three-line validation step) plus one
# line for this action, instead of thirteen.
# `actions/checkout` still cannot move here: a `./.github/actions/...` reference is resolved from the
# checked-out working copy, so this file does not exist until checkout has already run. A composite
# action cannot contain the step that makes itself readable.
#
# So a Gradle job is two lines — checkout, then this action.
runs:
using: composite
@@ -30,8 +33,9 @@ runs:
with:
distribution: temurin
java-version: "21.0.11+10"
cache: gradle
cache-dependency-path: |
src/**/*.gradle
src/**/gradle-wrapper.properties
src/**/gradle.lockfile
# Gradle's own caching, not setup-java's `cache: gradle`. The two cache the same directory with
# different keys, and running both is how a job restores one cache and saves the other.
- uses: gradle/actions/setup-gradle@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
with:
build-scan-publish: false
cache-read-only: ${{ github.ref != 'refs/heads/main' }}
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -3,7 +3,7 @@
This policy is enforced by
[`dependency-vulnerability.yml`](workflows/dependency-vulnerability.yml),
[`dependency-review-config.yml`](dependency-review-config.yml),
[`../.trivyignore.yaml`](../.trivyignore.yaml), `verifyTrivyignore`, CODEOWNERS, and
[`../.trivyignore.yaml`](../.trivyignore.yaml), CODEOWNERS, and
[`../renovate.json`](../renovate.json).
## Execution and platform boundary
@@ -73,7 +73,7 @@ dependencies; stale mirrors can delay detection.
The only suppression source is repository-root `.trivyignore.yaml`. Every Trivy scan passes it
explicitly with `--ignorefile .trivyignore.yaml`. Each future entry must contain an identifier, a
non-empty rationale, and a future expiry no more than 90 days away. `verifyTrivyignore` validates
non-empty rationale, and a future expiry no more than 90 days away. A CODEOWNERS reviewer validates
the shape and expiry; CODEOWNERS plus branch protection controls who may approve the change.
Neither control substitutes for the other.
-568
View File
@@ -1,568 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
if (( $# > 1 )); then
printf '::error::gate-matrix-lint: expected zero arguments or one repository root\n' >&2
exit 2
fi
if (( $# == 1 )); then
if [[ ! -d "$1" ]]; then
printf '::error::gate-matrix-lint: repository root is not a directory: %s\n' "$1" >&2
exit 2
fi
REPO_ROOT="$(cd -- "$1" && pwd -P)"
else
REPO_ROOT="$(git -C "${SCRIPT_DIR}" rev-parse --show-toplevel)"
EXPECTED_SCRIPT_DIR="$(cd -- "${REPO_ROOT}/.github/scripts" && pwd -P)"
if [[ "${SCRIPT_DIR}" != "${EXPECTED_SCRIPT_DIR}" ]]; then
printf '::error::gate-matrix-lint: script location must be repository .github/scripts directory\n' >&2
exit 1
fi
fi
readonly REPO_ROOT
readonly MATRIX="${REPO_ROOT}/.github/ci-gate-matrix.yml"
# There is deliberately no expected gate count here. A hand-edited integer made the matrix
# un-editable: no control could be registered without editing the guard whose purpose was to stop
# the matrix changing, and the guard caught nothing a per-row rule does not already catch — a row
# whose task, workflow or job does not exist fails below regardless of how many rows there are.
# What replaces it is the per-row invariant set: required fields, valid enums, a workflow and job
# that exist, a registered and actually-executed mechanism, unique ids, and the release-blocking
# rule below. Those hold at any count.
#
# The one property the count did carry is kept explicitly: a matrix with no gates at all is drift,
# not a clean run.
# The release gate every pull request and push to main passes through. Named rather than inferred:
# `release_blocking: true` is checked against what this job waits on, so the field means something a
# machine can verify instead of being an enum nobody reads.
readonly RELEASE_GATE_WORKFLOW='ci-quality-gates.yml'
readonly RELEASE_GATE_JOB='release-gate'
if [[ ! -f "${MATRIX}" ]]; then
printf '::error::gate-matrix-lint: missing %s\n' "${MATRIX}" >&2
exit 1
fi
records="$(
awk '
function flush() {
if (id != "") {
printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", id, blocking, mechanism, ref, workflow, job, execution
}
}
/^[[:space:]]*-[[:space:]]+id:[[:space:]]*/ {
flush()
id=$0
sub(/^[[:space:]]*-[[:space:]]+id:[[:space:]]*/, "", id)
blocking=mechanism=ref=workflow=job=execution=""
next
}
/^[[:space:]]+release_blocking:[[:space:]]*/ {
blocking=$0
sub(/^[[:space:]]+release_blocking:[[:space:]]*/, "", blocking)
next
}
/^[[:space:]]+mechanism:[[:space:]]*/ {
mechanism=$0
sub(/^[[:space:]]+mechanism:[[:space:]]*/, "", mechanism)
next
}
/^[[:space:]]+ref:[[:space:]]*/ {
ref=$0
sub(/^[[:space:]]+ref:[[:space:]]*/, "", ref)
next
}
/^[[:space:]]+workflow:[[:space:]]*/ {
workflow=$0
sub(/^[[:space:]]+workflow:[[:space:]]*/, "", workflow)
next
}
/^[[:space:]]+job:[[:space:]]*/ {
job=$0
sub(/^[[:space:]]+job:[[:space:]]*/, "", job)
next
}
/^[[:space:]]+execution:[[:space:]]*/ {
execution=$0
sub(/^[[:space:]]+execution:[[:space:]]*/, "", execution)
next
}
END { flush() }
' "${MATRIX}"
)"
declare -A seen_ids=()
declare -a failures=()
total=0
verified=0
delegated=0
job_body() {
local workflow_file="$1"
local job_id="$2"
awk -v target="${job_id}" '
$0 ~ "^ " target ":[[:space:]]*$" { inside=1; print; next }
inside && $0 ~ "^ [A-Za-z0-9_-]+:[[:space:]]*$" { exit }
inside { print }
' "${workflow_file}"
}
gradle_command_has_safe_literal_grammar() {
local command="$1"
[[ "${command}" =~ ^\./gradlew([[:space:]]+[A-Za-z0-9_.:/@=,+-]+)+[[:space:]]*$ ]]
}
gradle_token_suppresses_execution() {
local token="$1"
case "${token}" in
'--dry-run'|'--dry-run='*|'-m'|'-x'|'-x'*|'--exclude-task'|'--exclude-task='*) return 0 ;;
*) return 1 ;;
esac
}
gradle_token_is_allowed_gate_argument() {
local token="$1"
case "${token}" in
'--no-daemon'|'--stacktrace'|'--warning-mode=fail') return 0 ;;
esac
[[ "${token}" =~ ^:?[A-Za-z0-9_][A-Za-z0-9_.-]*(:[A-Za-z0-9_][A-Za-z0-9_.-]*)*$ ]]
}
gradle_plugin_is_applied() {
local plugin_id="$1"
grep -RqsF --include='build.gradle' -- "id '${plugin_id}'" "${REPO_ROOT}/src" \
|| grep -RqsF --include='build.gradle' -- "id \"${plugin_id}\"" "${REPO_ROOT}/src" \
|| grep -RqsF --include='build.gradle' -- "apply plugin: '${plugin_id}'" "${REPO_ROOT}/src" \
|| grep -RqsF --include='build.gradle' -- "apply plugin: \"${plugin_id}\"" "${REPO_ROOT}/src"
}
gradle_custom_task_is_registered_in_build_file() {
local task_name="$1"
local build_file="$2"
if grep -qsE -- "tasks\\.register\\(['\"]${task_name}['\"]" "${build_file}"; then
return 0
fi
# A lane declared through the `ca.strict-test-lane` convention. The convention exists because the
# five lines every lane used to repeat were copied per lane and per leaf, and two copies had
# already lost `failOnNoDiscoveredTests`; registering through it is still registering, so this lint
# has to recognise the declaration or it reports every converted lane as missing.
if grep -qsE -- "lane\\(['\"]${task_name}['\"]\\)" "${build_file}"; then
return 0
fi
# An API surface gate declared through the `ca.api-surface` convention, which derives every task
# name from one label so a leaf cannot verify one surface while telling the reader about another.
# The name is computed, so there is no literal `tasks.register('verifyMongoApiSurface')` anywhere;
# what the build file says is `apiSurface { label = 'Mongo' }`.
if [[ "${task_name}" =~ ^verify(.+)ApiSurface$ ]]; then
local surface_label="${BASH_REMATCH[1]}"
if grep -qsE -- "label[[:space:]]*=[[:space:]]*['\"]${surface_label}['\"]" "${build_file}"; then
return 0
fi
fi
awk -v required_task="${task_name}" '
index($0, "registerStrictQualificationTest(") > 0 { inside_registration=1 }
inside_registration && /^[[:space:]]*name:[[:space:]]*/ {
candidate=$0
sub(/^[[:space:]]*name:[[:space:]]*/, "", candidate)
quote=substr(candidate, 1, 1)
if (quote != "\"" && quote != sprintf("%c", 39)) {
next
}
candidate=substr(candidate, 2)
closing_quote=index(candidate, quote)
if (closing_quote == 0) {
next
}
candidate=substr(candidate, 1, closing_quote - 1)
if (candidate == required_task) {
found=1
}
}
inside_registration && /\)[[:space:]]*$/ { inside_registration=0 }
END { exit found ? 0 : 1 }
' "${build_file}"
}
# Every `dependsOn ... named('x')` in the build, collected once.
#
# This used to be one recursive grep per gate. That was affordable at 38 gates and stopped being so
# at 48: the whole lint crossed the ten-second budget its own contract test asserts, and the first
# symptom was that test failing rather than anything about gate coverage. One pass, then membership
# tests against the result.
CHECK_WIRING_CACHE=""
load_check_wiring() {
[[ -n "${CHECK_WIRING_CACHE}" ]] && return 0
CHECK_WIRING_CACHE="$(grep -RhoE -- "dependsOn[^\n]*named\((['\"])[A-Za-z0-9_.-]+\1\)" \
"${REPO_ROOT}/src" --include='build.gradle' --include='ca.*.gradle' 2>/dev/null \
| grep -oE "(['\"])[A-Za-z0-9_.-]+\1" | tr -d "\"'" | sort -u)"
# A build with no such wiring at all would leave this empty and make every membership test pass by
# vacuity, so an empty result is a marker rather than an answer.
[[ -z "${CHECK_WIRING_CACHE}" ]] && CHECK_WIRING_CACHE="<none>"
return 0
}
gradle_custom_task_wired_into_check() {
local task_name="$1"
load_check_wiring
if printf '%s\n' "${CHECK_WIRING_CACHE}" | grep -qxF -- "${task_name}"; then
return 0
fi
# `ca.api-surface` wires check as `dependsOn tasks.named(verifyName())`, where verifyName() is
# derived from the leaf's label. The declaration that makes the gate real is the label, so that is
# what proves the wiring — the convention has exactly one check wiring and it is unconditional.
if [[ "${task_name}" =~ ^verify(.+)ApiSurface$ ]]; then
local surface_label="${BASH_REMATCH[1]}"
if grep -RqsE -- "label[[:space:]]*=[[:space:]]*['\"]${surface_label}['\"]" "${REPO_ROOT}/src" \
--include='build.gradle' \
&& grep -qsE -- "dependsOn tasks\.named\(verifyName\(\)\)" \
"${REPO_ROOT}/src/build-logic/src/main/groovy/ca.api-surface.gradle"; then
return 0
fi
fi
return 1
}
# The build files, found once rather than once per gate. Same reason as the wiring cache above: the
# per-gate `find` was a fixed cost multiplied by a number that grew.
GRADLE_FILE_CACHE=""
load_gradle_files() {
[[ -n "${GRADLE_FILE_CACHE}" ]] && return 0
GRADLE_FILE_CACHE="$(find "${REPO_ROOT}/src" -type f -name '*.gradle' | sort)"
return 0
}
gradle_custom_task_is_registered() {
local task_name="$1"
local build_file
load_gradle_files
while IFS= read -r build_file; do
[[ -z "${build_file}" ]] && continue
if gradle_custom_task_is_registered_in_build_file "${task_name}" "${build_file}"; then
return 0
fi
done <<< "${GRADLE_FILE_CACHE}"
return 1
}
gradle_token_matches_registered_task() {
local token="$1"
local required_task="$2"
local project_path build_file
if [[ "${token}" == "${required_task}" || "${token}" == ":${required_task}" ]]; then
return 0
fi
if [[ "${token}" != :* || "${token}" != *:"${required_task}" ]]; then
return 1
fi
project_path="${token%:"${required_task}"}"
project_path="${project_path#:}"
project_path="${project_path%:}"
build_file="${REPO_ROOT}/src/${project_path//:/\/}/build.gradle"
[[ -f "${build_file}" ]] \
&& gradle_custom_task_is_registered_in_build_file "${required_task}" "${build_file}"
}
job_runs_gradle_task() {
local workflow_file="$1"
local job_id="$2"
local required_task="$3"
local command token
local found_task suppressed
local -a tokens=()
while IFS= read -r command; do
if ! gradle_command_has_safe_literal_grammar "${command}"; then
continue
fi
read -r -a tokens <<< "${command}"
if (( ${#tokens[@]} < 2 )) || [[ "${tokens[0]}" != './gradlew' ]]; then
continue
fi
found_task=0
suppressed=0
for token in "${tokens[@]:1}"; do
case "${token}" in
'&&'|'||'|';'|'|'|'#'*) break ;;
esac
if gradle_token_suppresses_execution "${token}"; then
suppressed=1
break
fi
if ! gradle_token_is_allowed_gate_argument "${token}"; then
suppressed=1
break
fi
if gradle_token_matches_registered_task "${token}" "${required_task}"; then
found_task=1
fi
done
if (( found_task == 1 && suppressed == 0 )); then
return 0
fi
done < <(
job_body "${workflow_file}" "${job_id}" | awk '
/^[[:space:]]+(-[[:space:]]+)?run:[[:space:]]+/ {
command=$0
sub(/^[[:space:]]+(-[[:space:]]+)?run:[[:space:]]+/, "", command)
if (command !~ /^(\||>)/) {
print command
}
}
'
)
return 1
}
# A workflow that only runs for a release tag. Its jobs need no separate release gate: the workflow
# run *is* the release, so a failing job fails it. Detected from the `on:` block rather than from a
# filename, because "release" in a filename is a naming convention and `on: push: tags:` is not.
workflow_is_release_tag_triggered() {
local workflow_file="$1"
[[ -f "${workflow_file}" ]] || return 1
awk '
/^on:[[:space:]]*$/ { in_on=1; next }
/^[^[:space:]#]/ { in_on=0 }
in_on && /^[[:space:]]+tags:/ { found=1 }
END { exit found ? 0 : 1 }
' "${workflow_file}"
}
# Jobs the release gate actually waits on: its `needs:` inside its own workflow, plus the job names
# in REQUIRED_CHECKS, which is how it requires a check run produced by a different workflow.
RELEASE_GATE_NEEDS=""
RELEASE_GATE_REQUIRED_CHECKS=""
load_release_gate_requirements() {
[[ -n "${RELEASE_GATE_NEEDS}" ]] && return 0
RELEASE_GATE_NEEDS="<none>"
RELEASE_GATE_REQUIRED_CHECKS="<none>"
local workflow_file="${REPO_ROOT}/.github/workflows/${RELEASE_GATE_WORKFLOW}"
[[ -f "${workflow_file}" ]] || return 0
grep -Eqs -- "^[[:space:]]{2}${RELEASE_GATE_JOB}:[[:space:]]*$" "${workflow_file}" || return 0
local entry kind value
local -a needs=()
local -a checks=()
while IFS= read -r entry; do
[[ "${entry}" =~ ^(need|check)\ [A-Za-z0-9_-]+$ ]] || continue
kind="${entry%% *}"
value="${entry#* }"
if [[ "${kind}" == "need" ]]; then
needs+=("${value}")
else
checks+=("${value}")
fi
done < <(
job_body "${workflow_file}" "${RELEASE_GATE_JOB}" | awk '
/^[[:space:]]+needs:[[:space:]]*\[/ {
value=$0
sub(/^[[:space:]]+needs:[[:space:]]*\[/, "", value)
sub(/\].*$/, "", value)
count=split(value, parts, /[[:space:]]*,[[:space:]]*/)
for (index_value = 1; index_value <= count; index_value++) {
gsub(/[[:space:]]/, "", parts[index_value])
if (parts[index_value] != "") { print "need " parts[index_value] }
}
next
}
/^[[:space:]]+needs:[[:space:]]*[A-Za-z0-9_-]+[[:space:]]*$/ {
value=$0
sub(/^[[:space:]]+needs:[[:space:]]*/, "", value)
sub(/[[:space:]]+$/, "", value)
print "need " value
next
}
/^[[:space:]]+needs:[[:space:]]*$/ { in_needs=1; next }
in_needs && /^[[:space:]]+-[[:space:]]+/ {
value=$0
sub(/^[[:space:]]+-[[:space:]]+/, "", value)
sub(/[[:space:]]+$/, "", value)
print "need " value
next
}
in_needs { in_needs=0 }
/^[[:space:]]+REQUIRED_CHECKS:[[:space:]]*/ {
value=$0
sub(/^[[:space:]]+REQUIRED_CHECKS:[[:space:]]*/, "", value)
count=split(value, entries, /[[:space:]]+/)
for (index_value = 1; index_value <= count; index_value++) {
if (entries[index_value] != "") { print "check " entries[index_value] }
}
}
'
)
(( ${#needs[@]} > 0 )) && RELEASE_GATE_NEEDS="$(printf '%s\n' "${needs[@]}" | sort -u)"
(( ${#checks[@]} > 0 )) && RELEASE_GATE_REQUIRED_CHECKS="$(printf '%s\n' "${checks[@]}" | sort -u)"
return 0
}
# `release_blocking: true` used to be read by nothing but an enum test, so a gate could claim to
# block a release that no job anywhere waited on — filesystem-vulnerability-scan was red while
# release-gate was green and nothing in the repository joined the two. A gate earns `true` by being
# required on a path a release actually takes:
# - it is the release gate job itself, or one of that job's `needs:` in the same workflow;
# - its job name is listed in the release gate's REQUIRED_CHECKS (the cross-workflow hook);
# - its workflow only runs for a release tag, so the job failing fails that release run.
# A control that is real but reachable by none of those is `conditional`, which is the honest value
# and is what the enum is for.
gate_is_enforced_by_a_release_gate() {
local gate_workflow="$1"
local gate_job="$2"
load_release_gate_requirements
if [[ "${gate_workflow}" == "${RELEASE_GATE_WORKFLOW}" ]]; then
if [[ "${gate_job}" == "${RELEASE_GATE_JOB}" ]]; then
return 0
fi
if printf '%s\n' "${RELEASE_GATE_NEEDS}" | grep -qxF -- "${gate_job}"; then
return 0
fi
fi
if printf '%s\n' "${RELEASE_GATE_REQUIRED_CHECKS}" | grep -qxF -- "${gate_job}"; then
return 0
fi
workflow_is_release_tag_triggered "${REPO_ROOT}/.github/workflows/${gate_workflow}"
}
while IFS=$'\t' read -r id blocking mechanism ref workflow job execution; do
[[ -z "${id}" ]] && continue
total=$((total + 1))
if [[ -n "${seen_ids[${id}]:-}" ]]; then
failures+=("duplicate gate id '${id}'")
fi
seen_ids["${id}"]=1
if [[ -z "${blocking}" || -z "${mechanism}" || -z "${ref}" || -z "${workflow}" \
|| -z "${job}" || -z "${execution}" ]]; then
failures+=("gate '${id}' has an empty required field")
continue
fi
if [[ ! "${blocking}" =~ ^(true|false|conditional)$ ]]; then
failures+=("gate '${id}' has invalid release_blocking '${blocking}'")
fi
if [[ ! "${workflow}" =~ ^[A-Za-z0-9._-]+\.ya?ml$ || ! "${job}" =~ ^[A-Za-z0-9_-]+$ ]]; then
failures+=("gate '${id}' has an unsafe workflow or job identifier")
continue
fi
workflow_file="${REPO_ROOT}/.github/workflows/${workflow}"
if [[ ! -f "${workflow_file}" ]]; then
failures+=("gate '${id}' references missing workflow '.github/workflows/${workflow}'")
continue
fi
if ! grep -Eqs -- "^[[:space:]]{2}${job}:[[:space:]]*$" "${workflow_file}"; then
failures+=("gate '${id}' references missing job '${job}' in '${workflow}'")
continue
fi
if [[ "${blocking}" == "true" ]] \
&& ! gate_is_enforced_by_a_release_gate "${workflow}" "${job}"; then
failures+=("gate '${id}' is release_blocking: true but no release gate requires job '${job}' in '${workflow}'")
continue
fi
case "${mechanism}" in
gradle-custom-task)
if [[ ! "${ref}" =~ ^[A-Za-z_][A-Za-z0-9_-]*$ ]]; then
failures+=("gate '${id}' has unsafe Gradle custom task ref '${ref}'")
continue
fi
if ! gradle_custom_task_is_registered "${ref}"; then
failures+=("gate '${id}' references unregistered Gradle task '${ref}'")
continue
fi
;;
gradle-plugin-task)
plugin="${ref%@*}"
task="${ref#*@}"
if [[ "${plugin}" == "${ref}" \
|| ! "${plugin}" =~ ^[A-Za-z][A-Za-z0-9.-]*$ \
|| ! "${task}" =~ ^[A-Za-z_][A-Za-z0-9_-]*$ ]]; then
failures+=("gate '${id}' has unsafe Gradle plugin task ref '${ref}'")
continue
fi
if ! gradle_plugin_is_applied "${plugin}"; then
failures+=("gate '${id}' references unapplied Gradle plugin '${plugin}'")
continue
fi
;;
contract-test)
if [[ "${ref}" == /* || "${ref}" == *".."* || ! -f "${REPO_ROOT}/src/${ref}" ]]; then
failures+=("gate '${id}' references missing or unsafe contract test 'src/${ref}'")
continue
fi
;;
workflow-job)
if [[ "${ref}" != "${job}" ]]; then
failures+=("gate '${id}' workflow-job ref '${ref}' must equal job '${job}'")
continue
fi
;;
delegated-pending)
delegated=$((delegated + 1))
printf "gate '%s': explicitly delegated-pending\n" "${id}"
continue
;;
*)
failures+=("gate '${id}' has unknown mechanism '${mechanism}'")
continue
;;
esac
case "${execution}" in
check)
if ! job_runs_gradle_task "${workflow_file}" "${job}" 'check'; then
failures+=("gate '${id}' expects Gradle check in job '${job}'")
continue
fi
# Build files *and* convention plugins. A gate can now be wired into check from an included
# build's convention rather than from a leaf's build.gradle, and a lint that only reads
# build.gradle would call such a gate unwired while it runs on every leaf — a false failure
# that teaches the next author to delete the matrix row instead of trusting it.
#
# A convention that derives the task name from a label wires check by that derived name, so
# there is no literal to grep for either; `gradle_custom_task_wired_into_check` handles both
# the literal and the derived form.
if [[ "${mechanism}" == "gradle-custom-task" ]] \
&& ! gradle_custom_task_wired_into_check "${ref}"; then
failures+=("gate '${id}' task '${ref}' exists but is not wired into Gradle check")
continue
fi
;;
explicit)
if ! job_runs_gradle_task "${workflow_file}" "${job}" "${ref}"; then
failures+=("gate '${id}' task '${ref}' is not explicit in job '${job}'")
continue
fi
;;
job)
;;
*)
failures+=("gate '${id}' has unknown execution '${execution}'")
continue
;;
esac
verified=$((verified + 1))
done <<< "${records}"
if (( total == 0 )); then
failures+=("matrix declares no gates")
fi
printf 'gate-matrix-lint: %d gates, %d verified, %d delegated-pending\n' \
"${total}" "${verified}" "${delegated}"
if (( ${#failures[@]} > 0 )); then
printf '::error::gate-matrix-lint: %d drift(s) found\n' "${#failures[@]}" >&2
for failure in "${failures[@]}"; do
printf ' - %s\n' "${failure}" >&2
done
exit 1
fi
printf 'gate-matrix-lint: OK\n'
-799
View File
@@ -1,799 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
readonly EXPECTED_DISTRIBUTION_SUFFIX='/gradle-9.0.0-bin.zip'
readonly EXPECTED_DISTRIBUTION_SHA256='8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b'
readonly EXPECTED_WRAPPER_JAR_SHA256='76805e32c009c0cf0dd5d206bddc9fb22ea42e84db904b764f3047de095493f3'
readonly EXPECTED_VALIDATION_ACTION='gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6'
readonly EXPECTED_DEPENDENCY_SUBMISSION_ACTION='gradle/actions/dependency-submission@748248ddd2a24f49513d8f472f81c3a07d4d50e1'
readonly EXPECTED_GUARDED_GRADLE_IF="\${{ always() && steps.gradle-wrapper-validation.outcome == 'success' }}"
# Lock update procedure (only after intentional review of the complete .github diff):
# find .github/workflows -mindepth 1 -maxdepth 1 \
# \( -name '*.yml' -o -name '*.yaml' \) ! -type f -print # must print nothing
# find .github/workflows -mindepth 1 -maxdepth 1 -type f \
# \( -name '*.yml' -o -name '*.yaml' \) -print0 \
# | LC_ALL=C sort -z | xargs -0 sha256sum # EXPECTED_WORKFLOW_LOCK
# find .github/actions -mindepth 2 -maxdepth 2 \
# \( -name 'action.yml' -o -name 'action.yaml' \) ! -type f -print # must print nothing
# find .github/actions -mindepth 2 -maxdepth 2 -type f \
# \( -name 'action.yml' -o -name 'action.yaml' \) -print0 \
# | LC_ALL=C sort -z | xargs -0 sha256sum # EXPECTED_COMPOSITE_ACTION_LOCK
# Replace an entire sorted array in the same reviewed change. Never refresh a single digest merely
# to make this verifier pass.
#
# Composite actions are locked alongside the workflows, and for the same reason. A job's Java
# toolchain and Gradle cache configuration used to be written out in every workflow that needed it,
# so the pinned actions/setup-java commit sat inside the locked bytes fifty-nine times over.
# .github/actions/setup-gradle-java/action.yml now holds the single copy: leaving it out of this
# lock would mean one unreviewed edit could change what every Gradle job in the repository installs
# and runs, while this verifier still said PASS. The two arrays are compared separately so that a
# drifting action does not shift every workflow's expected position and bury the real message.
readonly EXPECTED_WORKFLOW_LOCK=(
'444bb0da12f631fa20f492d3dc37e93b762d144640e4f86b81b7bdd3d4c81312 .github/workflows/ci-quality-gates.yml'
'e7f355c7eb81a72e0f1d2892843621bf11384ca2a4bf36f1daf3900b82ae46e7 .github/workflows/dependency-vulnerability.yml'
'2fa9c8081df1679c1feb9aa101aff47d7d2c24995c155aff6d1e4799eaad8f21 .github/workflows/fileserver-nightly.yml'
'1686b7b637611c8cd5eb87b2cc759f5cd2c6b878154363fc336c16b93c635ada .github/workflows/fileserver-pr.yml'
'b47932200c9ac9db57070b43bc70c40c89c152e9235d7a1325baab407df215e9 .github/workflows/fileserver-release.yml'
'a18a0f08982b393177a843c1bdd03a881d9d12491819cebb44b6891a87ff2a6d .github/workflows/integration-main.yml'
'4345d5cfb5a139a11cf3647c58fff61ab08397ace186919cdc7a769cdfc4d4b7 .github/workflows/jpa-next-hibernate8.yml'
'726b3d91603a2529205d1d5568253b57d85fcbb9d10d3efe182491c9da744d78 .github/workflows/jpa-next-jpa4.yml'
'3c073a928dfb266051a1a52f4d66bf6d6903b9dbd2cdb6459fab661228f27e88 .github/workflows/jpa-next-postgresql19.yml'
'c098946cfa7ba9c2959a6f8217f20af1ced28a45f22d088bc7ee4df661d45e84 .github/workflows/jpa-nightly.yml'
'b73314359be3391f8b569bb2ea0a5757927c4bbbd42d84c242e0e15e494320cd .github/workflows/jpa-r2-evidence.yml'
'43c565aa2709bc4d72cfcedf56816c6442bb63a23cc1db011e425ae0181d0bcd .github/workflows/jpa-release.yml'
'5be7e931db749029d89787da042d6d7cf8e683d60698bd8a2993c29db26355fb .github/workflows/link-check.yml'
'62a852157481e89c778c0498067a7443bde22bf421995ade8714a89e4eca347c .github/workflows/messaging-certification.yml'
'ee9f247297559077c7766f7f0f8b5e39538b496922f6b2cc2621aa04593f320a .github/workflows/notification-platform.yml'
'e685bc846108503ee2cf1e06b6cec040174d49348bd205400f891828f24dda68 .github/workflows/object-storage-qualification.yml'
'67ef53adb80551629a482e2610a0753dd0fadf85f523e985c4693354df543748 .github/workflows/pr-adapters.yml'
'376a71f7a2b9990e1e96937ad3dd46a33f266cc742ca499b208bc909897b67f3 .github/workflows/redis-sdk-topology.yml'
'42b57385c1f87170ba6d882345c709c11dff019f1860e72ad989b0c5c1a67ece .github/workflows/release.yml'
)
readonly EXPECTED_COMPOSITE_ACTION_LOCK=(
'7ec6591f26a1bd76658c55472e16b195b80db2c4792b429efda5a0dcbde61a45 .github/actions/setup-gradle-java/action.yml'
)
readonly EXPECTED_WRAPPER_PROPERTIES=(
'distributionBase=GRADLE_USER_HOME'
'distributionPath=wrapper/dists'
"distributionUrl=https\://services.gradle.org/distributions${EXPECTED_DISTRIBUTION_SUFFIX}"
"distributionSha256Sum=${EXPECTED_DISTRIBUTION_SHA256}"
'networkTimeout=10000'
'validateDistributionUrl=true'
'zipStoreBase=GRADLE_USER_HOME'
'zipStorePath=wrapper/dists'
)
fail() {
printf 'gradle-wrapper-contract: FAIL: %s\n' "$1" >&2
exit 1
}
if [[ $# -ne 1 ]]; then
fail 'expected exactly one repository-root argument'
fi
readonly REPOSITORY_ROOT=$1
[[ -d "${REPOSITORY_ROOT}" ]] || fail "repository root is not a directory: ${REPOSITORY_ROOT}"
readonly WRAPPER_PROPERTIES="${REPOSITORY_ROOT}/src/gradle/wrapper/gradle-wrapper.properties"
readonly WRAPPER_JAR="${REPOSITORY_ROOT}/src/gradle/wrapper/gradle-wrapper.jar"
readonly WORKFLOWS_DIRECTORY="${REPOSITORY_ROOT}/.github/workflows"
# Not asserted to exist here, deliberately. The structural and wrapper-validation diagnostics below
# are what a reader needs first; a missing composite action surfaces as a lock mismatch at the end,
# which is still fail-closed.
readonly ACTIONS_DIRECTORY="${REPOSITORY_ROOT}/.github/actions"
[[ -f "${WRAPPER_PROPERTIES}" ]] || fail "missing wrapper properties: ${WRAPPER_PROPERTIES}"
[[ -f "${WRAPPER_JAR}" ]] || fail "missing wrapper JAR: ${WRAPPER_JAR}"
[[ -d "${WORKFLOWS_DIRECTORY}" ]] || fail "missing workflows directory: ${WORKFLOWS_DIRECTORY}"
if ! printf '%s\n' "${EXPECTED_WRAPPER_PROPERTIES[@]}" | cmp -s - "${WRAPPER_PROPERTIES}"; then
fail 'wrapper properties must match the exact canonical Gradle 9.0.0 eight-line contract'
fi
readonly actual_wrapper_jar_sha256=$(sha256sum "${WRAPPER_JAR}" | awk '{print $1}')
[[ "${actual_wrapper_jar_sha256}" == "${EXPECTED_WRAPPER_JAR_SHA256}" ]] \
|| fail "wrapper JAR SHA-256 mismatch: ${actual_wrapper_jar_sha256}"
workflow_lock_valid=1
# One digest line per locked file, in the same LC_ALL=C order the update procedure prints. A symlink
# or a non-regular file is reported as such rather than followed: a workflow replaced by a link to
# another workflow is exactly the substitution this lock exists to catch.
collect_actual_lock() {
local locked_file locked_file_relative locked_file_sha256
while IFS= read -r -d '' locked_file; do
locked_file_relative=${locked_file#"${REPOSITORY_ROOT}"/}
if [[ -L "${locked_file}" || ! -f "${locked_file}" ]]; then
locked_file_sha256='<invalid-file-type>'
else
locked_file_sha256=$(sha256sum -- "${locked_file}" | awk '{print $1}')
fi
printf '%s %s\n' "${locked_file_sha256}" "${locked_file_relative}"
done
}
# Compared position by position rather than as a set, so an added, removed, renamed or reordered
# entry is a mismatch and the message names both sides.
compare_lock() {
local label=$1
shift
local -a expected=("$@")
local entry_count=${#expected[@]}
if ((${#actual_lock[@]} > entry_count)); then
entry_count=${#actual_lock[@]}
fi
local index expected_entry actual_entry
for ((index = 0; index < entry_count; index++)); do
expected_entry=${expected[index]-<missing>}
actual_entry=${actual_lock[index]-<missing>}
if [[ "${actual_entry}" != "${expected_entry}" ]]; then
printf 'gradle-wrapper-contract: %s lock mismatch: expected %q; actual %q\n' \
"${label}" "${expected_entry}" "${actual_entry}" >&2
workflow_lock_valid=0
fi
done
}
mapfile -t actual_lock < <(
find "${WORKFLOWS_DIRECTORY}" -mindepth 1 -maxdepth 1 \
\( -name '*.yml' -o -name '*.yaml' \) -print0 \
| LC_ALL=C sort -z \
| collect_actual_lock
)
compare_lock 'workflow' ${EXPECTED_WORKFLOW_LOCK[@]+"${EXPECTED_WORKFLOW_LOCK[@]}"}
# A missing .github/actions directory yields an empty list, which mismatches every expected entry.
# That is the fail-closed answer: a composite action every Gradle job uses cannot be absent.
actual_lock=()
if [[ -d "${ACTIONS_DIRECTORY}" ]]; then
mapfile -t actual_lock < <(
find "${ACTIONS_DIRECTORY}" -mindepth 2 -maxdepth 2 \
\( -name 'action.yml' -o -name 'action.yaml' \) -print0 \
| LC_ALL=C sort -z \
| collect_actual_lock
)
fi
compare_lock 'composite action' \
${EXPECTED_COMPOSITE_ACTION_LOCK[@]+"${EXPECTED_COMPOSITE_ACTION_LOCK[@]}"}
workflow_count=0
gradle_job_count=0
while IFS= read -r -d '' workflow; do
if ! awk -v workflow="${workflow#"${REPOSITORY_ROOT}"/}" '
function reset_step(known_field) {
step_active = 0
run_block = 0
for (known_field in step_fields) {
delete step_fields[known_field]
}
}
function reset_job() {
job = ""
in_steps = 0
steps_count = 0
reset_step()
}
function indentation(line, first_non_space) {
if (line ~ /^ *$/) {
return length(line)
}
first_non_space = match(line, /[^ ]/)
return first_non_space - 1
}
function trim(value) {
sub(/^[[:space:]]+/, "", value)
sub(/[[:space:]]+$/, "", value)
return value
}
function grammar_error(message) {
printf "%s: job %s %s\n", workflow, job == "" ? "<unknown>" : job, message > "/dev/stderr"
invalid = 1
}
function workflow_grammar_error(message) {
printf "%s: %s\n", workflow, message > "/dev/stderr"
invalid = 1
}
function validate_job_shape() {
if (job != "" && steps_count != 1) {
grammar_error("must contain exactly one canonical steps block")
}
}
function is_allowed_step_field(field) {
return field == "name" \
|| field == "id" \
|| field == "uses" \
|| field == "run" \
|| field == "if" \
|| field == "shell" \
|| field == "with" \
|| field == "env" \
|| field == "working-directory" \
|| field == "continue-on-error" \
|| field == "timeout-minutes"
}
function validate_uses_scalar(value, first, quote, closing, index_value, suffix, action, single_quote) {
value = trim(value)
if (value == "" || index(value, "\\") != 0) {
grammar_error("has unsupported uses scalar")
return
}
first = substr(value, 1, 1)
single_quote = sprintf("%c", 39)
if (first == "\"" || first == single_quote) {
quote = first
closing = 0
for (index_value = 2; index_value <= length(value); index_value++) {
if (substr(value, index_value, 1) == quote) {
closing = index_value
break
}
}
if (closing == 0) {
grammar_error("has unsupported uses scalar")
return
}
suffix = substr(value, closing + 1)
if (suffix !~ /^[[:space:]]*(#.*)?$/) {
grammar_error("has unsupported uses scalar")
return
}
action = substr(value, 2, closing - 2)
if (index(action, quote) != 0) {
grammar_error("has unsupported uses scalar")
return
}
} else {
action = value
sub(/[[:space:]]+#.*$/, "", action)
action = trim(action)
if (action ~ /["'"'"'\\]/ || action ~ /^[*!&|>]/) {
grammar_error("has unsupported uses scalar")
return
}
}
if (action !~ /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(\/[A-Za-z0-9_.-]+)*@[A-Za-z0-9_.\/-]+$/ \
&& action !~ /^\.\/[A-Za-z0-9_.\/-]+$/ \
&& action !~ /^docker:\/\/[^[:space:]]+$/) {
grammar_error("has unsupported uses scalar")
}
}
function validate_run_scalar(value, first) {
value = trim(value)
if (value ~ /^(\||>)[+-]?([[:space:]]+#.*)?$/) {
run_block = 1
return
}
first = substr(value, 1, 1)
if (value == "" || first == "\"" || first == sprintf("%c", 39) \
|| first ~ /[*&!|>]/ || index(value, "\\") != 0) {
grammar_error("has unsupported run scalar")
}
}
function validate_step_field(content, field, value, separator) {
content = trim(content)
if (content ~ /^[{[]/) {
grammar_error("contains unsupported flow-style step syntax")
return
}
if (content ~ /^<</) {
grammar_error("contains a forbidden step merge key")
return
}
if (content ~ /^[*&!]/) {
grammar_error("contains unsupported step anchor, alias, or tag syntax")
return
}
if (content !~ /^[A-Za-z][A-Za-z0-9-]*:/) {
grammar_error("contains unsupported step field syntax")
return
}
separator = index(content, ":")
field = substr(content, 1, separator - 1)
value = substr(content, separator + 1)
sub(/^[[:space:]]*/, "", value)
if (!is_allowed_step_field(field)) {
grammar_error("contains unsupported step field: " field)
return
}
if (field in step_fields) {
grammar_error("contains duplicate step field: " field)
return
}
step_fields[field] = 1
if (field == "uses") {
validate_uses_scalar(value)
} else if (field == "run") {
validate_run_scalar(value)
}
}
BEGIN {
in_jobs = 0
invalid = 0
jobs_count = 0
single_quote = sprintf("%c", 39)
reset_job()
}
/^jobs:/ {
if ($0 !~ /^jobs:[[:space:]]*(#.*)?$/) {
workflow_grammar_error("jobs container must use a canonical block mapping")
next
}
jobs_count++
if (jobs_count != 1) {
workflow_grammar_error("workflow must contain exactly one canonical jobs block")
}
in_jobs = 1
next
}
/^"jobs":/ {
workflow_grammar_error("jobs container must use a canonical block mapping")
next
}
substr($0, 1, 7) == single_quote "jobs" single_quote ":" {
workflow_grammar_error("jobs container must use a canonical block mapping")
next
}
run_block == 0 && /^<<:/ {
workflow_grammar_error("workflow contains a forbidden merge key")
next
}
in_jobs && /^[^[:space:]#]/ {
validate_job_shape()
reset_job()
in_jobs = 0
}
in_jobs && /^ [^[:space:]#]/ {
if ($0 !~ /^ [A-Za-z0-9_.-]+:[[:space:]]*(#.*)?$/) {
grammar_error("job declaration must use a canonical block mapping")
next
}
validate_job_shape()
reset_job()
job = $0
sub(/^ /, "", job)
sub(/:.*/, "", job)
next
}
in_jobs && job != "" {
raw = $0
line_indent = indentation(raw)
if (run_block != 0) {
if (raw ~ /^ *$/ || line_indent > 8) {
next
}
run_block = 0
}
if (raw ~ /^ *#/) {
next
}
if (raw ~ /^ steps:/ || raw ~ /^ "steps":/ \
|| substr(raw, 1, 11) == " " single_quote "steps" single_quote ":") {
if (raw != " steps:") {
grammar_error("steps container must use a canonical block sequence")
next
}
steps_count++
if (steps_count != 1) {
grammar_error("must contain exactly one canonical steps block")
}
in_steps = 1
reset_step()
next
}
if (in_steps != 0 && line_indent == 4) {
in_steps = 0
reset_step()
}
if (raw ~ /^ *<<:/) {
grammar_error("contains a forbidden merge key")
next
}
if (in_steps != 0 && raw ~ /^ - /) {
reset_step()
step_active = 1
content = substr(raw, 9)
validate_step_field(content)
next
}
if (in_steps != 0 && raw ~ /^ -[[:space:]]*$/) {
grammar_error("contains unsupported empty step syntax")
next
}
if (in_steps != 0 && step_active != 0 && line_indent == 8) {
content = substr(raw, 9)
validate_step_field(content)
next
}
if (in_steps != 0 && line_indent == 6 && raw !~ /^ *$/) {
grammar_error("contains unsupported step-list syntax")
}
}
END {
validate_job_shape()
if (jobs_count != 1) {
workflow_grammar_error("workflow must contain exactly one canonical jobs block")
}
if (invalid) {
exit 1
}
}
' "${workflow}"; then
fail "workflow structural validation failed: ${workflow#"${REPOSITORY_ROOT}"/}"
fi
if ! grep -Fq -- './gradlew' "${workflow}" \
&& ! grep -Fq -- 'gradle/actions/dependency-submission@' "${workflow}"; then
continue
fi
((workflow_count += 1))
if ! jobs_in_workflow=$(
awk -v workflow="${workflow#"${REPOSITORY_ROOT}"/}" \
-v validation_action="${EXPECTED_VALIDATION_ACTION}" \
-v dependency_action="${EXPECTED_DEPENDENCY_SUBMISSION_ACTION}" \
-v guarded_gradle_if="${EXPECTED_GUARDED_GRADLE_IF}" '
function reset_step(known_field) {
step_active = 0
run_block = 0
step_kind = ""
step_name = ""
step_id = ""
step_uses = ""
step_uses_action = ""
step_if = ""
step_if_present = 0
step_continue_on_error = 0
step_gradle = 0
step_gradle_line = 0
step_unsupported_gradle = 0
step_field_count = 0
step_name_line = 0
step_id_line = 0
step_uses_line = 0
step_extra_field = ""
for (known_field in step_fields) {
delete step_fields[known_field]
delete step_field_raw[known_field]
}
}
function reset_job() {
job = ""
checkout_line = 0
validation_line = 0
gradle_line = 0
in_steps = 0
unsupported_gradle = 0
reset_step()
}
function indentation(line, first_non_space) {
if (line ~ /^ *$/) {
return length(line)
}
first_non_space = match(line, /[^ ]/)
return first_non_space - 1
}
function has_gradle_reference(line) {
return index(line, "./gradlew") != 0 \
|| index(line, "gradle/actions/dependency-submission@") != 0
}
function trim(value) {
sub(/^[[:space:]]+/, "", value)
sub(/[[:space:]]+$/, "", value)
return value
}
function normalize_action(value, scalar, first, quote, closing, index_value) {
scalar = trim(value)
first = substr(scalar, 1, 1)
if (first == "\"" || first == single_quote) {
quote = first
closing = index(substr(scalar, 2), quote)
if (closing == 0) {
return ""
}
return substr(scalar, 2, closing - 1)
}
sub(/[[:space:]]+#.*$/, "", scalar)
return trim(scalar)
}
function record_gradle(line_number) {
step_gradle = 1
if (step_gradle_line == 0) {
step_gradle_line = line_number
}
if (gradle_line == 0) {
gradle_line = line_number
}
}
function record_uses(value, line_number, action) {
if (step_kind == "run") {
if (index(value, "gradle/actions/dependency-submission@") != 0) {
step_unsupported_gradle = 1
}
return
}
step_kind = "uses"
action = normalize_action(value)
step_uses = trim(value)
step_uses_action = action
step_uses_line = line_number
if (checkout_line == 0 && action ~ /^actions\/checkout@/) {
checkout_line = line_number
}
if (action == dependency_action) {
record_gradle(line_number)
} else if (index(action, "gradle/actions/dependency-submission@") != 0) {
record_gradle(line_number)
step_unsupported_gradle = 1
}
}
function record_run(value, line_number) {
if (step_kind == "uses") {
if (index(value, "./gradlew") != 0) {
step_unsupported_gradle = 1
}
return
}
step_kind = "run"
if (value ~ /^(\||>)[+-]?([[:space:]]+#.*)?$/) {
run_block = 1
} else if (index(value, "./gradlew") != 0) {
record_gradle(line_number)
}
}
function record_step_field(content, line_number, separator, field, value) {
separator = index(content, ":")
field = substr(content, 1, separator - 1)
value = substr(content, separator + 1)
sub(/^[[:space:]]*/, "", value)
step_fields[field] = 1
step_field_raw[field] = trim(content)
step_field_count++
if (field == "name") {
step_name = trim(value)
step_name_line = line_number
} else if (field == "id") {
step_id = trim(value)
step_id_line = line_number
} else if (field == "uses") {
record_uses(value, line_number)
} else if (field == "run") {
record_run(trim(value), line_number)
} else if (field == "if") {
step_if_present = 1
step_if = trim(value)
} else if (field == "continue-on-error") {
step_continue_on_error = 1
}
if (field != "name" && field != "id" && field != "uses" && step_extra_field == "") {
step_extra_field = step_field_raw[field]
}
}
function validate_wrapper_step() {
if (step_uses_action != validation_reference) {
return
}
if (step_extra_field != "") {
printf "%s: job %s wrapper validation step contains unsupported field: %s\n", workflow, job, step_extra_field > "/dev/stderr"
invalid = 1
return
}
if (step_field_count != 3 \
|| step_name != "Validate Gradle wrapper" \
|| step_id != "gradle-wrapper-validation" \
|| step_uses != validation_action \
|| !(step_name_line < step_id_line && step_id_line < step_uses_line)) {
printf "%s: job %s wrapper validation step must contain exact name, id, and uses fields only\n", workflow, job > "/dev/stderr"
invalid = 1
return
}
if (validation_line == 0) {
validation_line = step_uses_line
}
}
function validate_gradle_step() {
if (step_gradle == 0 && step_unsupported_gradle == 0) {
return
}
if (step_unsupported_gradle != 0 || ("uses" in step_fields && "run" in step_fields)) {
unsupported_gradle = 1
}
if (step_if_present != 0 && step_if != guarded_gradle_if) {
printf "%s: job %s has Gradle step with unsupported if condition: %s\n", workflow, job, step_if > "/dev/stderr"
invalid = 1
}
if (step_continue_on_error != 0) {
printf "%s: job %s has Gradle step with unsupported field: %s\n", workflow, job, step_field_raw["continue-on-error"] > "/dev/stderr"
invalid = 1
}
}
function finalize_step() {
if (step_active == 0) {
return
}
validate_wrapper_step()
validate_gradle_step()
}
function start_step() {
finalize_step()
reset_step()
step_active = 1
}
function validate_job() {
finalize_step()
if (job == "" || (gradle_line == 0 && unsupported_gradle == 0)) {
return
}
gradle_jobs++
if (unsupported_gradle != 0) {
printf "%s: job %s uses a Gradle invocation outside the canonical workflow structure\n", workflow, job > "/dev/stderr"
invalid = 1
}
if (gradle_line == 0) {
return
} else if (checkout_line == 0) {
printf "%s: job %s invokes Gradle without checkout\n", workflow, job > "/dev/stderr"
invalid = 1
} else if (validation_line == 0) {
printf "%s: job %s invokes Gradle without the exact pinned wrapper validation action\n", workflow, job > "/dev/stderr"
invalid = 1
} else if (!(checkout_line < validation_line && validation_line < gradle_line)) {
printf "%s: job %s must order checkout, exact wrapper validation, then Gradle\n", workflow, job > "/dev/stderr"
invalid = 1
}
}
BEGIN {
in_jobs = 0
invalid = 0
gradle_jobs = 0
single_quote = sprintf("%c", 39)
validation_reference = validation_action
sub(/[[:space:]]+#.*$/, "", validation_reference)
reset_job()
}
/^jobs:[[:space:]]*(#.*)?$/ {
in_jobs = 1
next
}
in_jobs && /^[^[:space:]#]/ {
validate_job()
reset_job()
in_jobs = 0
}
in_jobs && /^ [A-Za-z0-9_.-]+:[[:space:]]*(#.*)?$/ {
validate_job()
reset_job()
job = $0
sub(/^ /, "", job)
sub(/:.*/, "", job)
next
}
in_jobs && job != "" {
raw = $0
line_indent = indentation(raw)
if (run_block != 0) {
if (raw ~ /^ *$/) {
next
}
if (line_indent > 8) {
if (index(raw, "./gradlew") != 0) {
record_gradle(NR)
}
if (index(raw, "gradle/actions/dependency-submission@") != 0) {
step_unsupported_gradle = 1
}
next
}
run_block = 0
}
if (raw ~ /^ *#/) {
next
}
if (raw == " steps:") {
in_steps = 1
reset_step()
next
}
if (in_steps != 0 && line_indent == 4) {
finalize_step()
in_steps = 0
reset_step()
}
if (in_steps != 0 && raw ~ /^ - /) {
start_step()
content = substr(raw, 9)
record_step_field(content, NR)
next
}
if (in_steps != 0 && step_active != 0 && line_indent == 8) {
content = substr(raw, 9)
record_step_field(content, NR)
next
}
if (has_gradle_reference(raw)) {
unsupported_gradle = 1
}
}
END {
validate_job()
print gradle_jobs
if (invalid) {
exit 1
}
}
' "${workflow}"
); then
fail "workflow validation failed: ${workflow#"${REPOSITORY_ROOT}"/}"
fi
[[ "${jobs_in_workflow}" =~ ^[0-9]+$ ]] \
|| fail "workflow parser returned an invalid Gradle job count: ${workflow#"${REPOSITORY_ROOT}"/}"
((jobs_in_workflow > 0)) \
|| fail "Gradle-running workflow contains no detected Gradle job: ${workflow#"${REPOSITORY_ROOT}"/}"
((gradle_job_count += jobs_in_workflow))
done < <(find "${WORKFLOWS_DIRECTORY}" -type f \( -name '*.yml' -o -name '*.yaml' \) -print0)
((workflow_count > 0)) || fail 'no Gradle-running workflow was found'
((gradle_job_count > 0)) || fail 'no individual Gradle-running job was found'
((workflow_lock_valid != 0)) \
|| fail 'workflow lock mismatch: the workflow or composite-action set or bytes differ from the reviewed embedded manifest'
printf 'gradle-wrapper-contract: PASS\n'
+63
View File
@@ -0,0 +1,63 @@
name: reusable-gradle
# One place that knows how a Gradle job starts.
#
# Every job in this repository opened with the same preamble: checkout, a three-line pinned
# wrapper-validation step, then the JDK/cache action. The wrapper step is gone (setup-gradle
# validates wrappers itself), and this workflow removes the rest of the repetition for the jobs whose
# only variation is the Gradle command they run.
#
# Jobs that need service containers, a matrix, artifact uploads or per-job env stay written out with
# `./.github/actions/setup-gradle-java`, because expressing those through `workflow_call` inputs
# means encoding YAML inside strings — which is how a "shared" workflow becomes less readable than
# the duplication it replaced.
on:
workflow_call:
inputs:
tasks:
description: The Gradle task list, whitespace-separated.
required: true
type: string
gradle-args:
description: Flags appended after the task list.
required: false
type: string
default: "--no-daemon --stacktrace"
working-directory:
description: Directory the wrapper is invoked from.
required: false
type: string
default: src
timeout-minutes:
required: false
type: number
default: 30
continue-on-error:
description: Run the job as an advisory signal rather than a gate.
required: false
type: boolean
default: false
permissions:
contents: read
jobs:
gradle:
runs-on: ubuntu-latest
timeout-minutes: ${{ inputs.timeout-minutes }}
continue-on-error: ${{ inputs.continue-on-error }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- uses: ./.github/actions/setup-gradle-java
- name: Run ${{ inputs.tasks }}
working-directory: ${{ inputs.working-directory }}
env:
GRADLE_TASKS: ${{ inputs.tasks }}
GRADLE_ARGS: ${{ inputs.gradle-args }}
run: |
set -euo pipefail
# Word-split on purpose: both inputs are task/flag lists. They come from this repository's
# own workflow files, never from a pull request.
# shellcheck disable=SC2086
./gradlew ${GRADLE_TASKS} ${GRADLE_ARGS}
+54 -83
View File
@@ -1,5 +1,14 @@
name: ci-quality-gates
# The pull-request gate. Everything here blocks a merge.
#
# The job list used to include `gate-matrix-lint`, which ran .github/scripts/verify-gate-matrix.sh
# against .github/ci-gate-matrix.yml: a 1,025-line register of all 107 CI controls, checked for
# consistency against the Gradle task graph and this workflow by a 568-line shell script, which was
# itself checked by contract tests in :app-bootstrap. Adding one check meant editing Gradle, a
# workflow, the matrix, the verifier's expectations and a Java test. The information was already in
# the task graph and the job graph; the matrix was a third copy that had to be kept equal to both.
on:
pull_request:
push:
@@ -21,9 +30,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- name: Require the committed public-path security baseline
run: |
set -euo pipefail
@@ -37,21 +43,22 @@ jobs:
exit 1
fi
- uses: ./.github/actions/setup-gradle-java
- name: Check quality, public paths, and dependency locks
# `ci`, not `check`. A leaf's `check` is that leaf's — compile, its tests, Spotless, Checkstyle
# and Error Prone — and the repository-wide gates are named tasks of their own:
# ci = every leaf check + architectureCheck + qualityCheck + configContractCheck
# so CI runs strictly more than it used to while `./gradlew :domain-core:check` runs strictly
# less.
- name: Run the pull-request gate
working-directory: src
run: ./gradlew check verifyPublicPathSnapshot verifyDependencyLocks --warning-mode=fail --no-daemon --stacktrace
run: ./gradlew ci verifyPublicPathSnapshot verifyDependencyLocks --warning-mode=fail --no-daemon --stacktrace
# build-logic is an included build: its own suite is not reachable from the root project's
# `check`, so the convention plugins every leaf applies shipped untested in CI. Kept as its
# own step rather than folded into the aggregate invocation above, which
# ConditionalTransportQualificationContractTest asserts on byte-for-byte.
# `check`, so the convention plugins every leaf applies would otherwise ship untested.
- name: Test the build-logic convention plugins
working-directory: src
run: ./gradlew -p build-logic test --no-daemon --stacktrace
# Named as its own step because nothing else runs it: `check` does not depend on
# graphqlStableTest, so the lane's required-class guard — the check that its module-boundary
# suite has not silently stopped being discovered — protected nothing in CI. A separate step
# keeps the aggregate invocation below byte-identical, which ConditionalTransportQualification
# ContractTest asserts on, and the two tasks do not overlap.
# suite has not silently stopped being discovered — would protect nothing in CI.
- name: Qualify the GraphQL Stable lane
working-directory: src
run: ./gradlew :adapter:inbound:graphql:graphqlStableTest --no-daemon --stacktrace
@@ -60,55 +67,33 @@ jobs:
run: ./gradlew conditionalTransportQualification --no-daemon --stacktrace
sample-off:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Verify the application without the sample fixture
working-directory: src
run: ./gradlew :app-bootstrap:sampleOffTest verifyCleanArchitectureDependencies --no-daemon --stacktrace
gate-matrix-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Verify the gate matrix against the repository
run: bash .github/scripts/verify-gate-matrix.sh
uses: ./.github/workflows/_reusable-gradle.yml
with:
tasks: ":app-bootstrap:sampleOffTest verifyCleanArchitectureDependencies"
redis-sdk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
# Milestone A of the Redis wrapper/typed API plan: policy catalog, typed API parity,
# permit provenance, connection isolation, and the executor guard. There is no real-server
# lane yet — Tasks 10-17 add the contract suites that need one.
- name: Verify the Redis SDK policy, API parity, and guardrail contracts
working-directory: src
run: >-
./gradlew
:shared-contract:edgeRateLimitContractTest
:adapter:outbound:cache-redis:check
verifyCleanArchitectureDependencies
verifyEnvKeys
verifyPublicPathSnapshot
verifyConfigurationPropertiesProcessor
--no-daemon --stacktrace
# Milestone A of the Redis wrapper/typed API plan: policy catalog, typed API parity, permit
# provenance, connection isolation, and the executor guard. There is no real-server lane yet.
#
# `verifyConfigurationPropertiesProcessor` used to be in this list. It is deleted: the parity it
# enforced — a leaf declares Spring's configuration processor exactly when it owns
# @ConfigurationProperties — is now what applying `ca.spring-config` means.
# `verifyEnvKeys` is no longer named here either; it belongs to :app-bootstrap and runs through
# `configContractCheck`, which the quality-gates job covers.
uses: ./.github/workflows/_reusable-gradle.yml
with:
tasks: >-
:shared-contract:edgeRateLimitContractTest
:adapter:outbound:cache-redis:check
:app-bootstrap:verifyEnvKeys
verifyCleanArchitectureDependencies
verifyPublicPathSnapshot
jpa-candidate-evidence:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Produce zero-skip JPA candidate manifests
working-directory: src
@@ -125,25 +110,20 @@ jobs:
if-no-files-found: error
retention-days: 14
# Advisory only. Quarantine expiry/drift remains blocking through verifyQuarantineSunset in check.
# Advisory. The quarantine bucket runs so a flaky test is still executed and reported; it never
# blocks. The 14-day sunset registry that used to make an expired quarantine entry a build failure
# is gone — it was a 250-line YAML-and-Java parser guarding a registry with zero entries.
quarantine:
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run quarantined tests as an advisory signal
working-directory: src
run: ./gradlew quarantineTest --no-daemon
uses: ./.github/workflows/_reusable-gradle.yml
with:
tasks: quarantineTest
gradle-args: "--no-daemon"
continue-on-error: true
release-gate:
needs:
- quality-gates
- sample-off
- gate-matrix-lint
- redis-sdk
- jpa-candidate-evidence
if: always()
@@ -156,7 +136,6 @@ jobs:
env:
QUALITY_RESULT: ${{ needs.quality-gates.result }}
SAMPLE_OFF_RESULT: ${{ needs.sample-off.result }}
MATRIX_RESULT: ${{ needs.gate-matrix-lint.result }}
REDIS_RESULT: ${{ needs.redis-sdk.result }}
JPA_CANDIDATE_RESULT: ${{ needs.jpa-candidate-evidence.result }}
run: |
@@ -164,7 +143,6 @@ jobs:
for result in \
"${QUALITY_RESULT}" \
"${SAMPLE_OFF_RESULT}" \
"${MATRIX_RESULT}" \
"${REDIS_RESULT}" \
"${JPA_CANDIDATE_RESULT}"; do
if [[ "${result}" != "success" ]]; then
@@ -174,23 +152,16 @@ jobs:
done
echo "release-gate: all current blocking quality jobs succeeded."
# `needs` cannot reach another workflow, so every gate .github/ci-gate-matrix.yml marks
# release_blocking outside this file was invisible here: the field was read by nothing but an
# enum check in verify-gate-matrix.sh. filesystem-vulnerability-scan
# (dependency-vulnerability.yml::trivy-fs) is release_blocking: true and blocks on
# CRITICAL/HIGH and on the CISA KEV catalogue — it could be red while this job reported green
# and nothing in the repository joined the two.
# `needs` cannot reach another workflow, so a blocking check in another file has to be required
# by result. dependency-vulnerability.yml answers the same pull_request and push-to-main
# triggers as this workflow and trivy-fs carries no `if:` guard, so its check run always exists
# for this SHA — which is what makes it requirable rather than a matter of scheduling luck.
# Only `success` passes: a skipped or cancelled security scan is not a scan.
#
# dependency-vulnerability.yml answers the same pull_request and push-to-main triggers as this
# workflow and trivy-fs carries no `if:` guard, so its check run always exists for this SHA.
# That is what makes it requirable by result rather than by scheduling luck. Only `success`
# passes: a skipped or cancelled security scan is not a scan.
#
# The other release_blocking gates outside this file run on triggers this job does not share
# and so cannot be required here without changing when they run: release.yml, jpa-release.yml
# and fileserver-release.yml answer a release tag, and object-storage-qualification.yml and
# messaging-certification.yml answer a path filter or a schedule. That is left as a stated gap
# rather than a silently different one.
# The release-tag and path-filtered workflows (release.yml, jpa-release.yml,
# fileserver-certification.yml, object-storage-qualification.yml, messaging-certification.yml)
# run on triggers this job does not share, so they cannot be required here without changing
# when they run. That is a stated gap, not a hidden one.
- name: Require the cross-workflow release-blocking checks to have succeeded
env:
GH_TOKEN: ${{ github.token }}
@@ -35,9 +35,6 @@ jobs:
contents: write
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Submit the resolved Gradle dependency graph
uses: gradle/actions/dependency-submission@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # gradle/actions@v4.4.4
@@ -1,7 +1,13 @@
name: fileserver-release
name: fileserver-certification
# The gate a release must clear. Its job list is deliberately the same shape as the support matrix:
# nothing may be advertised at a support level whose evidence job is absent here.
# The certification a release must clear. Its job list is deliberately the same shape as the support
# matrix: nothing may be advertised at a support level whose evidence job is absent here.
#
# Named "certification", not "release", and the name is the point. This workflow proves a storage
# topology, a support matrix and a telemetry redaction claim. It deploys nothing and holds no cluster
# credential. Calling it `fileserver-release.yml` read as if GitHub Actions released the fileserver,
# which is the CI/CD boundary this repository has now fixed in docs/ci-cd/boundary.md: GitHub Actions
# tests, scans and publishes artifacts; Argo CD deploys.
#
# It used to be workflow_dispatch only, which made that sentence false: the four jobs below are the
# only place the fileserver support matrix, the PVC manifest and the telemetry redaction proof are
@@ -36,9 +42,6 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the architecture-wide dependency and module verification
working-directory: src
@@ -62,9 +65,6 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Prove every support claim maps to a job and every endpoint is documented
working-directory: src
@@ -79,9 +79,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
# This job checks the manifest, and only the manifest. It deliberately does not apply anything
# to a cluster.
#
@@ -96,8 +93,8 @@ jobs:
# The cluster result comes from an operator running infra/fileserver/kubernetes/
# pvc-certification-job.yaml against a real cluster and recording it in
# docs/fileserver/storage-certification.md. That is registered as
# fileserver-pvc-cluster-certification (delegated-pending) in .github/ci-gate-matrix.yml, so
# the absence is a tracked control rather than a green check.
# docs/fileserver/storage-certification.md, and the absence of a cluster result is stated
# there rather than hidden behind a green check.
- name: Check the certification manifest still says what the claim depends on
run: |
set -euo pipefail
@@ -115,9 +112,6 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Prove telemetry carries no filename, path, or raw identifier
working-directory: src
-12
View File
@@ -24,9 +24,6 @@ jobs:
FILESERVER_NFS_TESTS: "true"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Start the NFSv4 certification environment
run: docker compose -f infra/fileserver/nfs/compose.yml up -d --wait
@@ -46,9 +43,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the crash matrix and reconciliation suites
working-directory: src
@@ -65,9 +59,6 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the large-file and slow-client suites under a constrained heap
working-directory: src
@@ -86,9 +77,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Prove no run commits bytes from a stale lease
working-directory: src
-15
View File
@@ -44,9 +44,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the fileserver application and architecture suites
working-directory: src
@@ -62,9 +59,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Certify the local content store against the shared contract
working-directory: src
@@ -79,9 +73,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the servlet and reactive transport contracts
working-directory: src
@@ -96,9 +87,6 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the path, filename, range, and problem-detail hardening suite
working-directory: src
@@ -114,9 +102,6 @@ jobs:
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Prove transfer cost does not scale with file size
working-directory: src
+14 -49
View File
@@ -9,20 +9,13 @@ name: integration-main
#
# Two kinds of work live here.
#
# 1. The documentation-drift gates. They used to be `dependsOn` of the root `check`, so a README
# sentence about a renamed task failed a compile-and-test run and the fix was to edit a document
# before unrelated code could build. src/build.gradle now aggregates them as
# `verifyDocumentationContracts` and leaves them out of `check`. That demotion is only half a
# change: a gate nothing invokes has not been demoted, it has been deleted. This job is the other
# half, and it is the reason the four gates still run at all.
#
# 2. The lanes that need a machine that is not simultaneously compiling something else — load,
# 1. The lanes that need a machine that is not simultaneously compiling something else — load,
# abuse, graceful shutdown, TCP fault injection, resource bounds. They were web-nightly.yml and
# httpclient-nightly.yml, two module-shaped files whose only real difference was the cadence they
# shared. They now run on every push to main as well as nightly, which is strictly more often
# than before.
#
# 3. Lanes that were registered in Gradle and invoked by nothing. Ten Gradle tasks — six MongoDB
# 2. Lanes that were registered in Gradle and invoked by nothing. Ten Gradle tasks — six MongoDB
# container lanes, app-bootstrap's Testcontainers `integrationTest`, and the three messaging
# evidence tasks that `verifyMessagingContracts` reaches — existed, failed closed, and executed
# in no workflow. A lane nobody runs is not coverage; it is a file that looks like coverage. They
@@ -54,24 +47,17 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
# verifyReadmeCommands, verifyDocumentedLeafCount, verifyRunbookReferences and
# verifyTestSourceSetRegistry, as one task. Named as the aggregate rather than as four steps so
# that adding a fifth documentation gate is a build-file edit and not a workflow edit — and so
# that the demotion out of `check` has exactly one consumer to point at.
documentation-contracts:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Verify the documentation contracts
working-directory: src
run: ./gradlew verifyDocumentationContracts --no-daemon --stacktrace
# The documentation-drift gates that used to run here are gone rather than demoted.
#
# They were four hand-written parsers: README shell blocks compared against the Gradle task graph,
# runbook identifiers compared against every declared Java type, a leaf count written in prose
# compared against the registry, and a Markdown table compared against the declared source sets.
# Each was a custom parser for a file format nobody controls, and each made a documentation edit a
# precondition for a build. A stale sentence is a defect, but it is not one a build can be failed
# for, and link-check.yml already answers the one documentation question with a stable machine
# answer: does this link resolve.
jobs:
# Load, abuse and graceful shutdown measure behaviour that degrades gradually rather than breaking
# outright — which is exactly the kind of regression a per-PR gate never catches.
web-load-abuse-and-shutdown:
@@ -79,9 +65,6 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the load, abuse and shutdown lanes on every container
working-directory: src
@@ -107,9 +90,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Inject TCP faults against a real upstream
working-directory: src
@@ -128,9 +108,6 @@ jobs:
GRADLE_OPTS: -Dorg.gradle.project.performance.assertions.enabled=true
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Certify pool, streaming, retry, and rotation bounds
working-directory: src
@@ -144,14 +121,11 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 30
# Experimental by design (D-08): the result is reported, never used to block a merge. Registered
# in .github/ci-gate-matrix.yml as release_blocking: false so that "this job cannot fail the
# advisory so that "this job cannot fail the
# build" is written down rather than inferred from a field two hundred lines into a workflow.
continue-on-error: true
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Exercise the experimental HTTP/3 opt-in
working-directory: src
@@ -173,7 +147,7 @@ jobs:
# the pull-request budget is minutes for the whole gate.
#
# One single-line `./gradlew <task>` step per lane, not one folded command running six, because
# .github/scripts/verify-gate-matrix.sh reads these command lines to prove each registered lane is
# These command lines name each lane explicitly so that a lane which stops being invoked is
# actually executed — a folded command would leave six matrix rows unverifiable. It also means a
# red replica-set lane does not hide the compatibility lane behind it.
mongo-container-lanes:
@@ -184,9 +158,6 @@ jobs:
TESTCONTAINERS_REUSE_ENABLE: "false"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Single-node replica set contract lane
working-directory: src
@@ -236,9 +207,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Qualify the messaging contract, catalog, binding and schema evidence
working-directory: src
@@ -262,9 +230,6 @@ jobs:
TESTCONTAINERS_REUSE_ENABLE: "false"
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the real-PostgreSQL integration contracts
working-directory: src
@@ -21,9 +21,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Report Hibernate ORM 8 compatibility
id: compatibility-probe
-3
View File
@@ -21,9 +21,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Report Jakarta Persistence 4.0 compatibility
id: compatibility-probe
@@ -27,9 +27,6 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Report PostgreSQL 19 compatibility
id: compatibility-probe
-12
View File
@@ -30,9 +30,6 @@ jobs:
postgresql: ["16", "17", "18"]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Certify the platform against PostgreSQL ${{ matrix.postgresql }}
working-directory: src
@@ -48,9 +45,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Reproduce deadlock, serialization, and commit-ambiguity scenarios
working-directory: src
@@ -65,9 +59,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the query plan and database security suites
working-directory: src
@@ -83,9 +74,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Verify pool saturation and REQUIRES_NEW connection behaviour
working-directory: src
-3
View File
@@ -26,9 +26,6 @@ jobs:
JPA_EVIDENCE_TOPOLOGY: postgresql-16-testcontainers-tls-and-fault-matrix
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Verify the production-profile JPA R2 manifest DAG
working-directory: src
-6
View File
@@ -43,9 +43,6 @@ jobs:
postgresql: ["16", "17", "18"]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the full JPA release gate on PostgreSQL ${{ matrix.postgresql }}
working-directory: src
@@ -118,9 +115,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Verify architecture boundaries and the support matrix
working-directory: src
@@ -36,9 +36,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Certify the Kafka adapter against a real broker
working-directory: src
+1 -7
View File
@@ -55,9 +55,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Compile and format check
working-directory: src
@@ -89,7 +86,7 @@ jobs:
- name: Configuration surface
working-directory: src
run: |
./gradlew verifyEnvKeys verifyPublicPathSnapshot --console=plain
./gradlew :app-bootstrap:verifyEnvKeys verifyPublicPathSnapshot --console=plain
./gradlew verifyNotificationApiSurface verifyNotificationConfiguration --console=plain
# A support grade is a promise about production behaviour. This refuses one the pipeline
# cannot back — the check that would have caught five channels reading "Stable" while no
@@ -108,9 +105,6 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
# This job is named for ambiguity, restart recovery and callback burst. It used to run a
# unit-test filter and then `test` — neither of which restarts anything or bursts anything —
@@ -34,9 +34,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run non-skipping Poster image migration qualification
working-directory: src
@@ -46,9 +43,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run exact-release MinIO managed contract
working-directory: src
@@ -59,9 +53,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run digest-pinned MinIO and Toxiproxy fault contract
working-directory: src
@@ -82,9 +73,6 @@ jobs:
OBJECT_STORAGE_AWS_EXPECTED_OWNER: ${{ secrets.OBJECT_STORAGE_AWS_EXPECTED_OWNER }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run protected AWS common-subset qualification
working-directory: src
-24
View File
@@ -136,9 +136,6 @@ jobs:
timeout-minutes: 40
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Compare the wire contract across Tomcat, Jetty and Reactor Netty
working-directory: src
@@ -165,9 +162,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the proxy, prefix and spoofing contract behind a real Nginx
working-directory: src
@@ -184,9 +178,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the runtime contract on the second servlet container
working-directory: src
@@ -205,9 +196,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the upgrade and forwarded-header contract behind a real Nginx
working-directory: src
@@ -230,9 +218,6 @@ jobs:
transport: [apache, jdk, reactor]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Certify one transport against the shared contract
working-directory: src
@@ -254,9 +239,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the next-major Spring compatibility lane
working-directory: src
@@ -280,9 +262,6 @@ jobs:
postgresql: ["16", "18"]
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Certify the platform against PostgreSQL ${{ matrix.postgresql }}
working-directory: src
@@ -300,9 +279,6 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run the migration upgrade smoke scenarios
working-directory: src
-3
View File
@@ -116,9 +116,6 @@ jobs:
matrix: ${{ fromJson(needs.lanes.outputs.matrix) }}
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Start the topology
env:
+13 -30
View File
@@ -19,11 +19,13 @@ name: release
# design one:
# * jpa-release.yml — JpaReleaseRenderingTest reads that exact path and holds its PostgreSQL
# matrix and promotion list to src/config/jpa/release-registry.json.
# * fileserver-release.yml — FileserverDocumentationCoverageTest requires every job id named in
# docs/fileserver/support-matrix.md to be defined in a `.github/workflows/fileserver-*.yml`.
# * fileserver-certification.yml — FileserverDocumentationCoverageTest requires every job id named
# in docs/fileserver/support-matrix.md to be defined in a `.github/workflows/fileserver-*.yml`.
# It is named "certification" rather than "release" on purpose: it certifies a storage topology
# and a support matrix, it deploys nothing, and the CI/CD boundary in docs/ci-cd/boundary.md
# says GitHub Actions does not deploy.
# Folding either one in needs its src-side test (and, for fileserver, the support document) changed
# in the same commit. Until then the image job below cannot wait on them, which is what the
# `container-release-evidence-join` row in .github/ci-gate-matrix.yml records.
# in the same commit. Until then the image job below cannot wait on them — a stated gap.
#
# The image job DOES now wait on the evidence jobs in this file, which is new: while the image build
# lived in its own workflow it could publish while a sibling suite was still running or already red,
@@ -52,9 +54,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Verify architecture boundaries and the published surfaces
working-directory: src
@@ -62,7 +61,7 @@ jobs:
./gradlew
verifyCleanArchitectureDependencies
verifyPublicPathSnapshot
verifyEnvKeys
:app-bootstrap:verifyEnvKeys
:app-bootstrap:test --tests 'dev.caskeleton.bootstrap.architecture.*'
--no-daemon
--stacktrace
@@ -80,9 +79,6 @@ jobs:
timeout-minutes: 90
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run every web lane, Stable and Advanced
working-directory: src
@@ -108,9 +104,6 @@ jobs:
timeout-minutes: 60
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Run every websocket lane, Stable and Advanced
working-directory: src
@@ -151,9 +144,6 @@ jobs:
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: In-process contract lane
working-directory: src
@@ -172,11 +162,9 @@ jobs:
path: src/grpc/grpc-testkit/build/reports/tests/
if-no-files-found: warn
# Each declared gate runs as its own single-line `./gradlew <task>` step, because
# .github/scripts/verify-gate-matrix.sh reads these commands to prove the gate is actually
# executed — a folded or flag-laden command would make the declaration in
# .github/ci-gate-matrix.yml unverifiable. The architecture dependency gate that used to end this
# list is now architecture-and-surface above; it was the fourth copy of the same invocation.
# Each gate runs as its own single-line `./gradlew <task>` step so that a failure names the gate
# rather than a folded command. The architecture dependency gate that used to end this list is now
# architecture-and-surface above; it was the fourth copy of the same invocation.
httpclient-release-gate:
runs-on: ubuntu-latest
timeout-minutes: 60
@@ -189,9 +177,6 @@ jobs:
GRADLE_OPTS: -Dorg.gradle.project.performance.assertions.enabled=true
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # actions/checkout@v4.2.2
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
- uses: ./.github/actions/setup-gradle-java
- name: Focused module tests
run: ./gradlew :adapter:outbound:httpclient:test --no-daemon --stacktrace
@@ -241,9 +226,6 @@ jobs:
# The builder stage inside src/Dockerfile runs this repository's Gradle wrapper to produce the
# JAR that becomes the image. Validating the wrapper here checks the thing that is about to
# execute, before it executes, rather than after an image already exists.
- name: Validate Gradle wrapper
id: gradle-wrapper-validation
uses: gradle/actions/wrapper-validation@3f131e8634966bd73d06cc69884922b02e6faf92 # gradle/actions@v6
# The tag is the release identity; everything below derives from it. A tag that does not parse
# stops the release here, rather than producing an image named after whatever ref happened to
# be checked out.
@@ -393,8 +375,9 @@ jobs:
# a green trivy-fs has never been evidence about the artifact.
#
# --ignorefile is mandatory here as everywhere: .trivyignore.yaml is the single suppression
# source and verifyTrivyignore enforces that each entry carries a rationale and an expiry.
# An inline --skip or a second ignore file would be a suppression nobody reviews.
# source. Each entry carries a rationale and an expiry by policy, reviewed through CODEOWNERS
# (.github/dependency-vulnerability-policy.md); an inline --skip or a second ignore file would
# be a suppression nobody reviews.
#
# This step is the reason `docker push` is further down. A vulnerable image that was pushed and
# then reported is already pullable by everything that watches the tag.